OpenGL 圖形庫的使用(十一)—— 光照之顏色

版本記錄

版本號 時(shí)間
V1.0 2017.10.29

前言

OpenGL ES圖形庫項(xiàng)目中一直也沒用過,最近也想學(xué)著使用這個(gè)圖形庫,感覺還是很有意思,也就自然想著好好的總結(jié)一下,希望對大家能有所幫助。
1. OpenGL 圖形庫使用(一) —— 概念基礎(chǔ)
2. OpenGL 圖形庫使用(二) —— 渲染模式、對象、擴(kuò)展和狀態(tài)機(jī)
3. OpenGL 圖形庫使用(三) —— 著色器、數(shù)據(jù)類型與輸入輸出
4. OpenGL 圖形庫使用(四) —— Uniform及更多屬性
5. OpenGL 圖形庫使用(五) —— 紋理
6. OpenGL 圖形庫使用(六) —— 變換
7. OpenGL 圖形庫的使用(七)—— 坐標(biāo)系統(tǒng)之五種不同的坐標(biāo)系統(tǒng)(一)
8. OpenGL 圖形庫的使用(八)—— 坐標(biāo)系統(tǒng)之3D效果(二)
9. OpenGL 圖形庫的使用(九)—— 攝像機(jī)(一)
10. OpenGL 圖形庫的使用(十)—— 攝像機(jī)(二)

顏色

現(xiàn)實(shí)世界中有無數(shù)種顏色,每一個(gè)物體都有它們自己的顏色。我們需要使用(有限的)數(shù)值來模擬真實(shí)世界中(無限)的顏色,所以并不是所有現(xiàn)實(shí)世界中的顏色都可以用數(shù)值來表示的。然而我們?nèi)阅芡ㄟ^數(shù)值來表現(xiàn)出非常多的顏色,甚至你可能都不會(huì)注意到與現(xiàn)實(shí)的顏色有任何的差異。顏色可以數(shù)字化的由紅色(Red)、綠色(Green)和藍(lán)色(Blue)三個(gè)分量組成,它們通常被縮寫為RGB。僅僅用這三個(gè)值就可以組合出任意一種顏色。例如,要獲取一個(gè)珊瑚紅(Coral)色的話,我們可以定義這樣的一個(gè)顏色向量:

glm::vec3 coral(1.0f, 0.5f, 0.31f);

我們在現(xiàn)實(shí)生活中看到某一物體的顏色并不是這個(gè)物體真正擁有的顏色,而是它所反射的(Reflected)顏色。換句話說,那些不能被物體所吸收(Absorb)的顏色(被拒絕的顏色)就是我們能夠感知到的物體的顏色。例如,太陽光能被看見的白光其實(shí)是由許多不同的顏色組合而成的(如下圖所示)。如果我們將白光照在一個(gè)藍(lán)色的玩具上,這個(gè)藍(lán)色的玩具會(huì)吸收白光中除了藍(lán)色以外的所有子顏色,不被吸收的藍(lán)色光被反射到我們的眼中,讓這個(gè)玩具看起來是藍(lán)色的。下圖顯示的是一個(gè)珊瑚紅的玩具,它以不同強(qiáng)度反射了多個(gè)顏色。

你可以看到,白色的陽光實(shí)際上是所有可見顏色的集合,物體吸收了其中的大部分顏色。它僅反射了代表物體顏色的部分,被反射顏色的組合就是我們所感知到的顏色(此例中為珊瑚紅)。

這些顏色反射的定律被直接地運(yùn)用在圖形領(lǐng)域。當(dāng)我們在OpenGL中創(chuàng)建一個(gè)光源時(shí),我們希望給光源一個(gè)顏色。在上一段中我們有一個(gè)白色的太陽,所以我們也將光源設(shè)置為白色。當(dāng)我們把光源的顏色與物體的顏色值相乘,所得到的就是這個(gè)物體所反射的顏色(也就是我們所感知到的顏色)。讓我們再次審視我們的玩具(這一次它還是珊瑚紅),看看如何在圖形學(xué)中計(jì)算出它的反射顏色。我們將這兩個(gè)顏色向量作分量相乘,結(jié)果就是最終的顏色向量了:

glm::vec3 lightColor(1.0f, 1.0f, 1.0f);
glm::vec3 toyColor(1.0f, 0.5f, 0.31f);
glm::vec3 result = lightColor * toyColor; // = (1.0f, 0.5f, 0.31f);

可以看到,并沒有紅色和藍(lán)色的光讓我們的玩具來吸收或反射。這個(gè)玩具吸收了光線中一半的綠色值,但仍然也反射了一半的綠色值。玩具現(xiàn)在看上去是深綠色(Dark-greenish)的。我們可以看到,如果我們用綠色光源來照射玩具,那么只有綠色分量能被反射和感知到,紅色和藍(lán)色都不能被我們所感知到。這樣做的結(jié)果是,一個(gè)珊瑚紅的玩具突然變成了深綠色物體。現(xiàn)在我們來看另一個(gè)例子,使用深橄欖綠色(Dark olive-green)的光源:

glm::vec3 lightColor(0.33f, 0.42f, 0.18f);
glm::vec3 toyColor(1.0f, 0.5f, 0.31f);
glm::vec3 result = lightColor * toyColor; // = (0.33f, 0.21f, 0.06f);

可以看到,我們可以使用不同的光源顏色來讓物體顯現(xiàn)出意想不到的顏色。有創(chuàng)意地利用顏色其實(shí)并不難。

這些顏色的理論已經(jīng)足夠了,下面我們來構(gòu)造一個(gè)實(shí)驗(yàn)用的場景吧。


創(chuàng)建光照場景

在接下來的教程中,我們將會(huì)廣泛地使用顏色來模擬現(xiàn)實(shí)世界中的光照效果,創(chuàng)造出一些有趣的視覺效果。由于我們現(xiàn)在將會(huì)使用光源了,我們希望將它們顯示為可見的物體,并在場景中至少加入一個(gè)物體來測試模擬光照的效果。

首先我們需要一個(gè)物體來作為被投光(Cast the light)的對象,我們將使用前面教程中的那個(gè)著名的立方體箱子。我們還需要一個(gè)物體來代表光源在3D場景中的位置。簡單起見,我們依然使用一個(gè)立方體來代表光源(我們已擁有立方體的頂點(diǎn)數(shù)據(jù)是吧?)。

填一個(gè)頂點(diǎn)緩沖對象(VBO),設(shè)定一下頂點(diǎn)屬性指針和其它一些亂七八糟的東西現(xiàn)在對你來說應(yīng)該很容易了,所以我們就不再贅述那些步驟了。如果你仍然覺得這很困難,我建議你復(fù)習(xí)之前的教程,并且在繼續(xù)學(xué)習(xí)之前先把練習(xí)過一遍。

我們首先需要一個(gè)頂點(diǎn)著色器來繪制箱子。與之前的頂點(diǎn)著色器相比,容器的頂點(diǎn)位置是保持不變的(雖然這一次我們不需要紋理坐標(biāo)了),因此頂點(diǎn)著色器中沒有新的代碼。我們將會(huì)使用之前教程頂點(diǎn)著色器的精簡版:

#version 330 core
layout (location = 0) in vec3 aPos;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

void main()
{
    gl_Position = projection * view * model * vec4(aPos, 1.0);
}

記得更新你的頂點(diǎn)數(shù)據(jù)和屬性指針使其與新的頂點(diǎn)著色器保持一致(當(dāng)然你可以繼續(xù)留著紋理數(shù)據(jù)和屬性指針。在這一節(jié)中我們將不會(huì)用到它們,但有一個(gè)全新的開始也不是什么壞主意)。

因?yàn)槲覀冞€要?jiǎng)?chuàng)建一個(gè)表示燈(光源)的立方體,所以我們還要為這個(gè)燈創(chuàng)建一個(gè)專門的VAO。當(dāng)然我們也可以讓這個(gè)燈和其它物體使用同一個(gè)VAO,簡單地對它的model(模型)矩陣做一些變換就好了,然而接下來的教程中我們會(huì)頻繁地對頂點(diǎn)數(shù)據(jù)和屬性指針做出修改,我們并不想讓這些修改影響到燈(我們只關(guān)心燈的頂點(diǎn)位置),因此我們有必要為燈創(chuàng)建一個(gè)新的VAO。

unsigned int lightVAO;
glGenVertexArrays(1, &lightVAO);
glBindVertexArray(lightVAO);
// 只需要綁定VBO不用再次設(shè)置VBO的數(shù)據(jù),因?yàn)橄渥拥腣BO數(shù)據(jù)中已經(jīng)包含了正確的立方體頂點(diǎn)數(shù)據(jù)
glBindBuffer(GL_ARRAY_BUFFER, VBO);
// 設(shè)置燈立方體的頂點(diǎn)屬性(對我們的燈來說僅僅只有位置數(shù)據(jù))
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);

這段代碼對你來說應(yīng)該非常直觀。現(xiàn)在我們已經(jīng)創(chuàng)建了表示燈和被照物體箱子,我們只需要再定義一個(gè)片段著色器就行了:

#version 330 core
out vec4 FragColor;

uniform vec3 objectColor;
uniform vec3 lightColor;

void main()
{
    FragColor = vec4(lightColor * objectColor, 1.0);
}

這個(gè)片段著色器從uniform變量中接受物體的顏色和光源的顏色。正如本節(jié)一開始所討論的那樣,我們將光源的顏色和物體(反射的)顏色相乘。這個(gè)著色器理解起來應(yīng)該很容易。我們把物體的顏色設(shè)置為之前提到的珊瑚紅色,并把光源設(shè)置為白色。

// 在此之前不要忘記首先 use 對應(yīng)的著色器程序(來設(shè)定uniform)
lightingShader.use();
lightingShader.setVec3("objectColor", 1.0f, 0.5f, 0.31f);
lightingShader.setVec3("lightColor",  1.0f, 1.0f, 1.0f);

要注意的是,當(dāng)我們修改頂點(diǎn)或者片段著色器后,燈的位置或顏色也會(huì)隨之改變,這并不是我們想要的效果。我們不希望燈的顏色在接下來的教程中因光照計(jì)算的結(jié)果而受到影響,而是希望它能夠與其它的計(jì)算分離。我們希望燈一直保持明亮,不受其它顏色變化的影響(這樣它才更像是一個(gè)真實(shí)的光源)。

為了實(shí)現(xiàn)這個(gè)目標(biāo),我們需要為燈的繪制創(chuàng)建另外的一套著色器,從而能保證它能夠在其它光照著色器發(fā)生改變的時(shí)候不受影響。頂點(diǎn)著色器與我們當(dāng)前的頂點(diǎn)著色器是一樣的,所以你可以直接把現(xiàn)在的頂點(diǎn)著色器用在燈上。燈的片段著色器給燈定義了一個(gè)不變的常量白色,保證了燈的顏色一直是亮的:

#version 330 core
out vec4 FragColor;

void main()
{
    FragColor = vec4(1.0); // 將向量的四個(gè)分量全部設(shè)置為1.0
}

當(dāng)我們想要繪制我們的物體的時(shí)候,我們需要使用剛剛定義的光照著色器來繪制箱子(或者可能是其它的物體)。當(dāng)我們想要繪制燈的時(shí)候,我們會(huì)使用燈的著色器。在之后的教程里我們會(huì)逐步更新這個(gè)光照著色器,從而能夠慢慢地實(shí)現(xiàn)更真實(shí)的效果。

使用這個(gè)燈立方體的主要目的是為了讓我們知道光源在場景中的具體位置。我們通常在場景中定義一個(gè)光源的位置,但這只是一個(gè)位置,它并沒有視覺意義。為了顯示真正的燈,我們將表示光源的立方體繪制在與光源相同的位置。我們將使用我們?yōu)樗陆ǖ钠沃鱽砝L制它,讓它一直處于白色的狀態(tài),不受場景中的光照影響。

我們聲明一個(gè)全局vec3變量來表示光源在場景的世界空間坐標(biāo)中的位置:

glm::vec3 lightPos(1.2f, 1.0f, 2.0f);

然后我們把燈位移到這里,然后將它縮小一點(diǎn),讓它不那么明顯:

model = glm::mat4();
model = glm::translate(model, lightPos);
model = glm::scale(model, glm::vec3(0.2f));

繪制燈立方體的代碼應(yīng)該與下面的類似:

lampShader.use();
// 設(shè)置模型、視圖和投影矩陣uniform
...
// 繪制燈立方體對象
glBindVertexArray(lightVAO);
glDrawArrays(GL_TRIANGLES, 0, 36);

請把上述的所有代碼片段放在你程序中合適的位置,這樣我們就能有一個(gè)干凈的光照實(shí)驗(yàn)場地了。如果一切順利,運(yùn)行效果將會(huì)如下圖所示:

具體源碼可以來這里看一下源代碼,并仔細(xì)研究我的代碼和注釋。

后記

未完,待續(xù)~~~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,533評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,055評論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,365評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,561評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,346評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,889評論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,978評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,118評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,637評論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,558評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,739評論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,246評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 43,980評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,619評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,347評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,702評論 2 370

推薦閱讀更多精彩內(nèi)容