顏色
在前面的教程中我們已經(jīng)簡要提到過該如何在OpenGL中使用顏色(Color),但是我們至今所接觸到的都是很淺層的知識(shí)。本節(jié)我們將會(huì)更廣泛地討論顏色,并且還會(huì)為接下來的光照(Lighting)教程創(chuàng)建一個(gè)場(chǎng)景。
顏色可以數(shù)字化的由紅色(Red)、綠色(Green)和藍(lán)色(Blue)三個(gè)分量組成,它們通常被縮寫為RGB。這三個(gè)不同的分量組合在一起幾乎可以表示存在的任何一種顏色。例如,要獲取一個(gè)珊瑚紅(Coral)顏色我們可以這樣定義一個(gè)顏色向量:
glm::vec3 color(1.0f, 0.5f, 0.31f);
我們?cè)诂F(xiàn)實(shí)生活中看到某一物體的顏色并不是這個(gè)物體的真實(shí)顏色,而是它所反射(Reflected)的顏色。換句話說,那些不能被物體吸收(Absorb)的顏色(被反射的顏色)就是我們能夠感知到的物體的顏色。下圖顯示的是一個(gè)珊瑚紅的玩具,它以不同強(qiáng)度的方式反射了幾種不同的顏色。

正如你所見,白色的陽光是一種所有可見顏色的集合,上面的物體吸收了其中的大部分顏色,它僅反射了那些代表這個(gè)物體顏色的部分,這些被反射顏色的組合就是我們感知到的顏色(此例中為珊瑚紅)。
這些顏色反射的規(guī)律被直接地運(yùn)用在圖形領(lǐng)域。我們?cè)贠penGL中創(chuàng)建一個(gè)光源時(shí)都會(huì)為它定義一個(gè)顏色。在前面的段落中所提到光源的顏色都是白色的,那我們就繼續(xù)來創(chuàng)建一個(gè)白色的光源吧。當(dāng)我們把光源的顏色與物體的顏色相乘,所得到的就是這個(gè)物體所反射該光源的顏色(也就是我們感知到的顏色)。
讓我們?cè)俅螌徱曃覀兊耐婢?這一次它還是珊瑚紅)并看看如何計(jì)算出他的反射顏色。我們通過檢索結(jié)果顏色的每一個(gè)分量來看一下光源色和物體顏色的反射運(yùn)算:
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);
我們可以看到玩具在進(jìn)行反射時(shí)吸收了白色光源顏色中的大部分顏色,但它對(duì)紅、綠、藍(lán)三個(gè)分量都有一定的反射,反射量是由物體本身的顏色所決定的。這也代表著現(xiàn)實(shí)中的光線原理。由此,我們可以定義物體的顏色為這個(gè)物體從一個(gè)光源反射各個(gè)顏色分量的多少。現(xiàn)在,如果我們使用一束綠色的光又會(huì)發(fā)生什么呢?
glm::vec3 lightColor(0.0f, 1.0f, 0.0f);
glm::vec3 toyColor(1.0f, 0.5f, 0.31f);
glm::vec3 result = lightColor * toyColor; // = (0.0f, 0.5f, 0.0f);
可以看到,我們的玩具沒有紅色和藍(lán)色的光讓它來吸收或反射,這個(gè)玩具也吸收了光線中一半的綠色,當(dāng)然它仍然反射了光的一半綠色。它現(xiàn)在看上去是深綠色(Dark-greenish)的。我們可以看到,如果我們用一束綠色的光線照來照射玩具,那么只有綠色能被反射和感知到,沒有紅色和藍(lán)色能被反射和感知。這樣做的結(jié)果是,一個(gè)珊瑚紅的玩具突然變成了深綠色物體?,F(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);
如你所見,我們可以通過物體對(duì)不同顏色光的反射來的得到意想不到的不到的顏色,從此創(chuàng)作顏色已經(jīng)變得非常簡單。
創(chuàng)建一個(gè)光照?qǐng)鼍?/h2>
在接下來的教程中,我們將通過模擬真實(shí)世界中廣泛存在的光照和顏色現(xiàn)象來創(chuàng)建有趣的視覺效果?,F(xiàn)在我們將在場(chǎng)景中創(chuàng)建一個(gè)看得到的物體來代表光源,并且在場(chǎng)景中至少添加一個(gè)物體來模擬光照。
首先我們需要一個(gè)物體來接收投光(Cast the light on),我們將無恥地使用前面教程中的立方體箱子。我們還需要一個(gè)物體來代表光源,它代表光源在這個(gè)3D空間中的確切位置。簡單起見,我們依然使用一個(gè)立方體來代表光源(我們已擁有立方體的頂點(diǎn)數(shù)據(jù)是吧?)。
用來接收投光的物體的containerVAO設(shè)置:
GLuint containerVAO, VBO;
glGenVertexArrays (1, &containerVAO);
glGenBuffers (1, &VBO);
glBindVertexArray (containerVAO);
glBindBuffer (GL_ARRAY_BUFFER, VBO);
glBufferData (GL_ARRAY_BUFFER, sizeof (vertices), vertices, GL_STATIC_DRAW); // 試一下GL_DYNAMIC_DRAW,和GL_STREAM_DRAW
glVertexAttribPointer (0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof (GLfloat), (GLvoid*) 0);
glEnableVertexAttribArray (0);
glBindBuffer (GL_ARRAY_BUFFER, 0);
glBindVertexArray (0);
然后,我們首先需要一個(gè)頂點(diǎn)著色器來繪制箱子。與上一個(gè)教程的頂點(diǎn)著色器相比,容器的頂點(diǎn)位置保持不變(雖然這一次我們不需要紋理坐標(biāo)),因此頂點(diǎn)著色器中沒有新的代碼。我們將會(huì)使用上一篇教程頂點(diǎn)著色器的精簡版:
#version 330 core
layout (location = 0) in vec3 position;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main ()
{
gl_Position = projection * view * model * vec4(position, 1.0);
}
請(qǐng)確認(rèn)更新你的頂點(diǎn)數(shù)據(jù)和屬性對(duì)應(yīng)的指針與新的頂點(diǎn)著色器一致(當(dāng)然你可以繼續(xù)保留紋理數(shù)據(jù)并保持屬性對(duì)應(yīng)的指針有效。在這一節(jié)中我們不使用紋理,但如果你想要一個(gè)全新的開始那也不是什么壞主意)。
因?yàn)槲覀冞€要?jiǎng)?chuàng)建一個(gè)表示燈(光源)的立方體,所以我們還要為這個(gè)燈創(chuàng)建一個(gè)特殊的VAO。當(dāng)然我們也可以讓這個(gè)燈和其他物體使用同一個(gè)VAO然后對(duì)他的model(模型)矩陣做一些變換,然而接下來的教程中我們會(huì)頻繁地對(duì)頂點(diǎn)數(shù)據(jù)做一些改變并且需要改變屬性對(duì)應(yīng)指針設(shè)置,我們并不想因此影響到燈(我們只在乎燈的位置),因此我們有必要為燈創(chuàng)建一個(gè)新的lightVAO。
// 為燈(光源)創(chuàng)建一個(gè)新的VAO
GLuint lightVAO;
glGenVertexArrays (1, &lightVAO);
glBindVertexArray (lightVAO);
// 只需要綁定VBO不用再次設(shè)置VBO的數(shù)據(jù),因?yàn)槿萜?物體)的VBO數(shù)據(jù)中已經(jīng)包含了正確的立方體頂點(diǎn)數(shù)據(jù)
glBindBuffer (GL_ARRAY_BUFFER, VBO);
// 設(shè)置燈立方體的頂點(diǎn)屬性指針(僅設(shè)置燈的頂點(diǎn)數(shù)據(jù))
glVertexAttribPointer (0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof (GLfloat), (GLvoid*) 0);
glEnableVertexAttribArray (0);
glBindBuffer (GL_ARRAY_BUFFER, 0);
glBindVertexArray (0);
既然我們已經(jīng)創(chuàng)建了表示燈和被照物體的立方體,我們只需要再定義一個(gè)東西就行了了,那就是片段著色器
#version 330 core
out vec4 color;
uniform vec3 objectColor;
uniform vec3 lightColor;
void main ()
{
color = vec4(lightColor * objectColor, 1.0f);
}
正如本篇教程一開始所討論的一樣,我們將光源的顏色與物體(能反射)的顏色相乘。接下來讓我們把物體的顏色設(shè)置為上一節(jié)中所提到的珊瑚紅并把光源設(shè)置為白色:
// 把物體的顏色設(shè)置為上一節(jié)中所提到的珊瑚紅并把光源設(shè)置為白色:
GLint objectColorLoc = glGetUniformLocation (lightingShader.Program, "objectColor");
GLint lightColorLoc = glGetUniformLocation (lightingShader.Program, "ligthColor");
glUniform3f (objectColorLoc, 1.0f, 0.5f, 0.31f); // 珊瑚紅
glUniform3f (lightColorLoc, 1.0f, 1.0f, 1.0f); // 白色
要注意的是,當(dāng)我們修改頂點(diǎn)或者片段著色器后,燈的位置或顏色也會(huì)隨之改變,這并不是我們想要的效果。所以我們需要為燈創(chuàng)建另外的一套著色器程序,從而能保證它能夠在其他光照著色器變化的時(shí)候保持不變。
頂點(diǎn)著色器和我們當(dāng)前的頂點(diǎn)著色器是一樣的,所以你可以直接把燈的頂點(diǎn)著色器復(fù)制過來。片段著色器保證了燈的顏色一直是亮的,我們通過給燈定義一個(gè)常量的白色來實(shí)現(xiàn):
#version 330 core
out vec4 color;
void main ()
{
color = vec4(1.0f); // 設(shè)置四維向量的所有元素為1.0f
}
當(dāng)我們想要繪制我們的物體的時(shí)候,我們需要使用剛剛定義的光照著色器繪制箱子(或者可能是其它的一些物體),讓我們想要繪制燈的時(shí)候,我們會(huì)使用燈的著色器。在之后的教程里我們會(huì)逐步升級(jí)這個(gè)光照著色器從而能夠緩慢的實(shí)現(xiàn)更真實(shí)的效果。
使用這個(gè)燈立方體的主要目的是為了讓我們知道光源在場(chǎng)景中的具體位置。我們通常在場(chǎng)景中定義一個(gè)光源的位置,但這只是一個(gè)位置,它并沒有視覺意義。為了顯示真正的燈,我們將表示光源的燈立方體繪制在與光源同樣的位置。我們將使用我們?yōu)樗陆ǖ钠沃髯屗3炙恢碧幱诎咨珷顟B(tài),不受場(chǎng)景中的光照影響。
我們聲明一個(gè)全局vec3變量來表示光源在場(chǎng)景的世界空間坐標(biāo)中的位置:
glm::vec3 lightPos(1.2f, 1.0f, 2.0f);
然后我們把燈平移到這兒,當(dāng)然我們需要對(duì)它進(jìn)行縮放,讓它不那么明顯:
model = glm::mat4 ();
model = glm::translate (model, lightPos); // 平移光源
model = glm::scale (model, glm::vec3 (0.2f)); // 縮放光源
繪制燈立方體的代碼應(yīng)該與下面的類似:
lampShader.Use ();
// 設(shè)置模型、視圖和投影矩陣uniform
model = glm::mat4 ();
model = glm::translate (model, lightPos); // 平移光源
model = glm::scale (model, glm::vec3 (0.2f)); // 縮放光源
view = glm::mat4();
view = camera.GetViewMatrix ();
projection = glm::mat4();
projection = glm::perspective (camera.Zoom, (GLfloat) screenWidth / (GLfloat) screenHeight, 0.1f, 100.0f);
modelLoc = glGetUniformLocation (lampShader.Program, "model");
viewLoc = glGetUniformLocation (lampShader.Program, "view");
projectionLoc = glGetUniformLocation (lampShader.Program, "projection");
glUniformMatrix4fv (modelLoc, 1, GL_FALSE, glm::value_ptr (model));
glUniformMatrix4fv (viewLoc, 1, GL_FALSE, glm::value_ptr (view));
glUniformMatrix4fv (projectionLoc, 1, GL_FALSE, glm::value_ptr (projection));
// Draw the light object (using light's vertex attributes)
glBindVertexArray (lampVAO);
glDrawArrays (GL_TRIANGLES, 0, 36);
glBindVertexArray (0);
請(qǐng)把上述的所有代碼片段放在你程序中合適的位置,這樣我們就能有一個(gè)干凈的光照實(shí)驗(yàn)場(chǎng)地了。如果一切順利,運(yùn)行效果將會(huì)如下圖所示:

如果你在把上述代碼片段放到一起編譯遇到困難,可以去認(rèn)真地看看我的項(xiàng)目代碼。