光照-04.光照貼圖(Lighting maps)

前面的材質(zhì)系統(tǒng)對(duì)于除了最簡(jiǎn)單的模型以外都是不夠的,所以我們需要擴(kuò)展前面的系統(tǒng),我們要介紹diffuse和specular貼圖。它們?cè)试S你對(duì)一個(gè)物體的diffuse(而對(duì)于簡(jiǎn)潔的ambient成分來說,它們幾乎總是是一樣的)和specular成分能夠有更精確的影響。

漫反射貼圖(Diffuse maps)

使用一張圖片覆蓋住物體,以便我們?yōu)槊總€(gè)原始像素索引獨(dú)立顏色值。在光照?qǐng)鼍爸?,通過紋理來呈現(xiàn)一個(gè)物體的diffuse顏色,這個(gè)做法被稱做漫反射貼圖(Diffuse map)(因?yàn)?D建模師就是這么稱呼這個(gè)做法的)。注:它基本就是一個(gè)紋理。我們其實(shí)是使用同一個(gè)潛在原則下的不同名稱。

為了演示漫反射貼圖,我們將會(huì)使用下面的圖片,它是一個(gè)有一圈鋼邊的木箱:

在著色器中使用漫反射貼圖和紋理教程介紹的一樣。這次我們把紋理以sampler2D類型儲(chǔ)存在Material結(jié)構(gòu)體中。我們使用diffuse貼圖替代早期定義的vec3類型的diffuse顏色。

要記住的是sampler2D也叫做模糊類型,這意味著我們不能以某種類型對(duì)它實(shí)例化,只能用uniform定義它們。如果我們用結(jié)構(gòu)體而不是uniform實(shí)例化(就像函數(shù)的參數(shù)那樣),GLSL會(huì)拋出奇怪的錯(cuò)誤;這同樣也適用于其他模糊類型。

我們也要移除amibient材質(zhì)顏色向量,因?yàn)閍mbient顏色絕大多數(shù)情況等于diffuse顏色,所以不需要分別去儲(chǔ)存它:

struct Material     // 儲(chǔ)存物體的材質(zhì)屬性
{
    sampler2D diffuse;
    vec3 specular;
    float shininess;
};
in vec2 TexCoords;

如果你非把a(bǔ)mbient顏色設(shè)置為不同的值不可(不同于diffuse值),你可以繼續(xù)保留ambient的vec3,但是整個(gè)物體的ambient顏色會(huì)繼續(xù)保持不變。為了使每個(gè)原始像素得到不同ambient值,你需要對(duì)ambient值單獨(dú)使用另一個(gè)紋理。(???)

注意,在片段著色器中我們將會(huì)再次需要紋理坐標(biāo),所以我們聲明一個(gè)額外輸入變量。然后我們簡(jiǎn)單地從紋理采樣,來獲得原始像素的diffuse顏色值:

vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));   

同樣,不要忘記把a(bǔ)mbient材質(zhì)的顏色設(shè)置為diffuse材質(zhì)的顏色:

vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));

這就是diffuse貼圖的全部?jī)?nèi)容了。就像你看到的,這不是什么新的東西,但是它卻極大提升了視覺品質(zhì)。為了讓它工作,我們需要用到紋理坐標(biāo)更新頂點(diǎn)數(shù)據(jù),把它們作為頂點(diǎn)屬性傳遞到片段著色器,把紋理加載并綁定到合適的紋理單元。

更新的頂點(diǎn)數(shù)據(jù)可以從這里找到。頂點(diǎn)數(shù)據(jù)現(xiàn)在包括了頂點(diǎn)位置,法線向量和紋理坐標(biāo),每個(gè)立方體的頂點(diǎn)都有這些屬性。讓我們更新頂點(diǎn)著色器來接受紋理坐標(biāo)作為頂點(diǎn)屬性,然后發(fā)送到片段著色器:

#version 330 core
layout (location = 0) in vec3 position;
layout (location = 1) in vec3 normal;
layout (location = 2) in vec2 texCoords;
...
out vec2 TexCoords;

void main()
{
...
   TexCoords = texCoords;
}

要保證更新的頂點(diǎn)屬性指針,不僅是VAO匹配新的頂點(diǎn)數(shù)據(jù),也要把箱子圖片加載為紋理。在繪制箱子之前,我們希望首選紋理單元被賦為material.diffuse這個(gè)uniform采樣器,并綁定箱子的紋理到這個(gè)紋理單元:

glActiveTexture (GL_TEXTURE0);      // 激活紋理單元
glBindTexture (GL_TEXTURE_2D, diffuseMap);  // 綁定紋理diffuseMap到當(dāng)前激活的紋理單元
glUniform1i (glGetUniformLocation (lightingShader.Program, "material.diffuse"), 0); // 定義采樣器material.diffuse對(duì)應(yīng)這個(gè)紋理單元

現(xiàn)在,使用一個(gè)diffuse貼圖,我們?cè)诩?xì)節(jié)上再次獲得驚人的提升,這次添加到箱子上的光照開始閃光了(名符其實(shí))。你的箱子現(xiàn)在可能看起來像這樣:

Image 070.png

你可以在這里得到應(yīng)用的全部代碼。

鏡面貼圖

由于我們的物體是個(gè)箱子,大部分是木頭,我們知道木頭是不應(yīng)該有鏡面高光的。我們通過把物體設(shè)置specular材質(zhì)設(shè)置為vec3(0.0f)來修正它。但是這樣意味著鐵邊會(huì)不再顯示鏡面高光,我們知道鋼鐵是會(huì)顯示一些鏡面高光的。我們會(huì)想要控制物體部分地顯示鏡面高光,它帶有修改了的亮度。這個(gè)問題看起來和diffuse貼圖的討論一樣。這是巧合嗎?我想不是。

我們同樣用一個(gè)紋理貼圖,來獲得鏡面高光。這意味著我們需要生成一個(gè)黑白(或者你喜歡的顏色)紋理來定義specular亮度,把它應(yīng)用到物體的每個(gè)部分。下面是一個(gè)specular貼圖的例子:


、

一個(gè)specular高光的亮度可以通過圖片中每個(gè)紋理的亮度來獲得。specular貼圖的每個(gè)像素可以顯示為一個(gè)顏色向量,比如:在那里黑色代表顏色向量vec3(0.0f),灰色是vec3(0.5f)。在片段著色器中,我們采樣相應(yīng)的顏色值,把它乘以光的specular亮度。像素越“白”,乘積的結(jié)果越大,物體的specualr部分越亮。

由于箱子幾乎是由木頭組成,木頭作為一個(gè)材質(zhì)不會(huì)有鏡面高光,整個(gè)木頭部分的diffuse紋理被用黑色覆蓋:黑色部分不會(huì)包含任何specular高光。箱子的鐵邊有一個(gè)修改的specular亮度,它自身更容易受到鏡面高光影響,木紋部分則不會(huì)。

從技術(shù)上來講,木頭也有鏡面高光,盡管這個(gè)閃亮值很?。ǜ嗟墓獗簧⑸洌?,影響很小,但是為了學(xué)習(xí)目的,我們可以假裝木頭不會(huì)有任何specular光反射。

使用Photoshop或Gimp之類的工具,通過將圖片進(jìn)行裁剪,將某部分調(diào)整成黑白圖樣,并調(diào)整亮度/對(duì)比度的做法,可以非常容易將一個(gè)diffuse紋理貼圖處理為specular貼圖。

鏡面貼圖采樣

一個(gè)specular貼圖和其他紋理一樣,所以代碼和diffuse貼圖的代碼也相似。確保合理的加載了圖片,生成一個(gè)紋理對(duì)象。由于我們?cè)谕瑯拥钠沃髦惺褂昧硪粋€(gè)紋理采樣器,我們必須為specular貼圖使用一個(gè)不同的紋理單元(參見紋理),所以在渲染前讓我們把它綁定到合適的紋理單元

glActiveTexture (GL_TEXTURE1);
glBindTexture (GL_TEXTURE_2D, specularMap);
glUniform1i (glGetUniformLocation (lightingShader.Program, "material.specular"), 1);

然后更新片段著色器材質(zhì)屬性,接受一個(gè)sampler2D作為這個(gè)specular部分的類型,而不是vec3:

struct Material     
{
    sampler2D diffuse;
    sampler2D specular;
    float shininess;
};

最后我們希望采樣這個(gè)specular貼圖,來獲取原始像素相應(yīng)的specular亮度:

vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));

通過使用一個(gè)specular貼圖我們可以定義極為精細(xì)的細(xì)節(jié),物體的這個(gè)部分會(huì)獲得閃亮的屬性,我們可以設(shè)置它們相應(yīng)的亮度。specular貼圖給我們一個(gè)在diffuse貼圖上的控制層。

如果你不想成為主流,你可以在specular貼圖里使用顏色,不單單為每個(gè)原始像素設(shè)置specular亮度,同時(shí)也設(shè)置specular高光的顏色。從真實(shí)角度來說,specular的顏色基本是由光源自身決定的,所以它不會(huì)生成真實(shí)的圖像(這就是為什么圖片通常是黑色和白色的:我們只關(guān)心亮度)。

如果你現(xiàn)在運(yùn)行應(yīng)用,你可以清晰地看到箱子的材質(zhì)現(xiàn)在非常類似真實(shí)的鐵邊的木頭箱子了:

你可以在這里找到全部源碼

使用diffuse和specular貼圖,我們可以給相關(guān)但簡(jiǎn)單物體添加一個(gè)極為明顯的細(xì)節(jié)。我們可以使用其他紋理貼圖,比如法線/bump貼圖或者反射貼圖,給物體添加更多的細(xì)節(jié)。但是這些在后面教程才會(huì)涉及。把你的箱子給你所有的朋友和家人看,有一天你會(huì)很滿足,我們的箱子會(huì)比現(xiàn)在更漂亮!

練習(xí)

  • 調(diào)整光源的ambient,diffuse和specular向量值,看看它們?nèi)绾斡绊憣?shí)際輸出的箱子外觀。

  • 嘗試在片段著色器中反轉(zhuǎn)鏡面貼圖(Specular Map)的顏色值,然后木頭就會(huì)變得反光而邊框不會(huì)反光了(由于貼圖中鋼邊依然有一些殘余顏色,所以鋼邊依然會(huì)有一些高光,不過反光明顯小了很多)。

解答:

vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));       // texture函數(shù)使用前面設(shè)置的紋理參數(shù)對(duì)相應(yīng)顏色值進(jìn)行采樣,返回vec4類型
  • Try creating a specular map from the diffuse texture that uses actual colors instead of black and white and see that the result doesn't look too realistic. 如果你不會(huì)處理圖片,你可以使用這個(gè)帶顏色的鏡面貼圖。

最終效果:

Image 072.png

-添加一個(gè)叫做放射光貼圖(Emission Map)的東西,即記錄每個(gè)片段發(fā)光值(Emission Value)大小的貼圖,發(fā)光值是(模擬)物體自身發(fā)光(Emit)時(shí)可能產(chǎn)生的顏色。這樣的話物體就可以忽略環(huán)境光自身發(fā)光。通常在你看到游戲里某個(gè)東西(比如 機(jī)器人的眼,或是箱子上的小燈)在發(fā)光時(shí),使用的就是放射光貼圖。使用這個(gè)貼圖(作者為 creativesam)作為放射光貼圖并使用在箱子上,你就會(huì)看到箱子上有會(huì)發(fā)光的字了。

機(jī)器人的眼

項(xiàng)目代碼
實(shí)驗(yàn)結(jié)果如下:

最后編輯于
?著作權(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),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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