光照-01.顏色

顏色

在前面的教程中我們已經(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)目代碼

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 版本記錄 前言 OpenGL 圖形庫項(xiàng)目中一直也沒用過,最近也想學(xué)著使用這個(gè)圖形庫,感覺還是很有意思,也就自然想著...
    刀客傳奇閱讀 6,454評(píng)論 0 2
  • 版本記錄 前言 OpenGL ES圖形庫項(xiàng)目中一直也沒用過,最近也想學(xué)著使用這個(gè)圖形庫,感覺還是很有意思,也就自然...
    刀客傳奇閱讀 3,028評(píng)論 0 2
  • 現(xiàn)實(shí)世界的光照是極其復(fù)雜的,而且會(huì)受到諸多因素的影響,這是以目前我們所擁有的處理能力無法模擬的。因此OpenGL的...
    IceMJ閱讀 2,130評(píng)論 1 6
  • 國慶 我想為祖國 獻(xiàn)上一首歌 不是 貝九吟唱的歡歌 不是 那薩伊女神走過 也不只是 夢(mèng)中的二泉映月 也不只是 生命...
    茶舍花開閱讀 250評(píng)論 0 0
  • 《連載風(fēng)云錄》《接龍客棧房號(hào)故事》《客棧棧規(guī)》《滴血的玫瑰目錄》 上一章 文丨薔薇下的陽光 “老貓,你說,薔薇的傷...
    薔薇下的陽光閱讀 641評(píng)論 18 18

友情鏈接更多精彩內(nèi)容