版本記錄
版本號 | 時(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ù)~~~