前面的材質(zhì)系統(tǒng)對(duì)于除了最簡(jiǎn)單的模型以外都是不夠的壤短,所以我們需要擴(kuò)展前面的系統(tǒng),我們要介紹diffuse和specular貼圖雁社。它們?cè)试S你對(duì)一個(gè)物體的diffuse(而對(duì)于簡(jiǎn)潔的ambient成分來(lái)說(shuō)浴井,它們幾乎總是是一樣的)和specular成分能夠有更精確的影響。
漫反射貼圖(Diffuse maps)
使用一張圖片覆蓋住物體霉撵,以便我們?yōu)槊總€(gè)原始像素索引獨(dú)立顏色值磺浙。在光照?qǐng)鼍爸校ㄟ^(guò)紋理來(lái)呈現(xiàn)一個(gè)物體的diffuse顏色徒坡,這個(gè)做法被稱(chēng)做漫反射貼圖(Diffuse map)(因?yàn)?D建模師就是這么稱(chēng)呼這個(gè)做法的)撕氧。注:它基本就是一個(gè)紋理。我們其實(shí)是使用同一個(gè)潛在原則下的不同名稱(chēng)喇完。
為了演示漫反射貼圖伦泥,我們將會(huì)使用下面的圖片,它是一個(gè)有一圈鋼邊的木箱:
在著色器中使用漫反射貼圖和紋理教程介紹的一樣锦溪。這次我們把紋理以sampler2D類(lèi)型儲(chǔ)存在Material結(jié)構(gòu)體中不脯。我們使用diffuse貼圖替代早期定義的vec3類(lèi)型的diffuse顏色。
要記住的是sampler2D也叫做模糊類(lèi)型刻诊,這意味著我們不能以某種類(lèi)型對(duì)它實(shí)例化防楷,只能用uniform定義它們。如果我們用結(jié)構(gòu)體而不是uniform實(shí)例化(就像函數(shù)的參數(shù)那樣)坏逢,GLSL會(huì)拋出奇怪的錯(cuò)誤域帐;這同樣也適用于其他模糊類(lèi)型赘被。
我們也要移除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)單地從紋理采樣供炼,來(lái)獲得原始像素的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)容了窘疮。就像你看到的袋哼,這不是什么新的東西,但是它卻極大提升了視覺(jué)品質(zhì)闸衫。為了讓它工作涛贯,我們需要用到紋理坐標(biāo)更新頂點(diǎn)數(shù)據(jù),把它們作為頂點(diǎn)屬性傳遞到片段著色器蔚出,把紋理加載并綁定到合適的紋理單元弟翘。
更新的頂點(diǎn)數(shù)據(jù)可以從這里找到。頂點(diǎn)數(shù)據(jù)現(xiàn)在包括了頂點(diǎn)位置身冬,法線(xiàn)向量和紋理坐標(biāo)衅胀,每個(gè)立方體的頂點(diǎn)都有這些屬性。讓我們更新頂點(diǎn)著色器來(lái)接受紋理坐標(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é)上再次獲得驚人的提升,這次添加到箱子上的光照開(kāi)始閃光了(名符其實(shí))步脓。你的箱子現(xiàn)在可能看起來(lái)像這樣:
你可以在這里得到應(yīng)用的全部代碼愿待。
鏡面貼圖
由于我們的物體是個(gè)箱子,大部分是木頭靴患,我們知道木頭是不應(yīng)該有鏡面高光的仍侥。我們通過(guò)把物體設(shè)置specular材質(zhì)設(shè)置為vec3(0.0f)來(lái)修正它。但是這樣意味著鐵邊會(huì)不再顯示鏡面高光鸳君,我們知道鋼鐵是會(huì)顯示一些鏡面高光的农渊。我們會(huì)想要控制物體部分地顯示鏡面高光,它帶有修改了的亮度或颊。這個(gè)問(wèn)題看起來(lái)和diffuse貼圖的討論一樣砸紊。這是巧合嗎传于?我想不是。
我們同樣用一個(gè)紋理貼圖醉顽,來(lái)獲得鏡面高光沼溜。這意味著我們需要生成一個(gè)黑白(或者你喜歡的顏色)紋理來(lái)定義specular亮度,把它應(yīng)用到物體的每個(gè)部分游添。下面是一個(gè)specular貼圖的例子:
盛末、
一個(gè)specular高光的亮度可以通過(guò)圖片中每個(gè)紋理的亮度來(lái)獲得。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ù)上來(lái)講续徽,木頭也有鏡面高光蚓曼,盡管這個(gè)閃亮值很小(更多的光被散射)钦扭,影響很小纫版,但是為了學(xué)習(xí)目的,我們可以假裝木頭不會(huì)有任何specular光反射客情。
使用Photoshop或Gimp之類(lèi)的工具其弊,通過(guò)將圖片進(jìn)行裁剪,將某部分調(diào)整成黑白圖樣膀斋,并調(diào)整亮度/對(duì)比度的做法梭伐,可以非常容易將一個(gè)diffuse紋理貼圖處理為specular貼圖。
鏡面貼圖采樣
一個(gè)specular貼圖和其他紋理一樣概页,所以代碼和diffuse貼圖的代碼也相似籽御。確保合理的加載了圖片练慕,生成一個(gè)紋理對(duì)象惰匙。由于我們?cè)谕瑯拥钠沃髦惺褂昧硪粋€(gè)紋理采樣器技掏,我們必須為specular貼圖使用一個(gè)不同的紋理單元(參見(jiàn)紋理),所以在渲染前讓我們把它綁定到合適的紋理單元
glActiveTexture (GL_TEXTURE1);
glBindTexture (GL_TEXTURE_2D, specularMap);
glUniform1i (glGetUniformLocation (lightingShader.Program, "material.specular"), 1);
然后更新片段著色器材質(zhì)屬性项鬼,接受一個(gè)sampler2D作為這個(gè)specular部分的類(lèi)型哑梳,而不是vec3:
struct Material
{
sampler2D diffuse;
sampler2D specular;
float shininess;
};
最后我們希望采樣這個(gè)specular貼圖,來(lái)獲取原始像素相應(yīng)的specular亮度:
vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));
通過(guò)使用一個(gè)specular貼圖我們可以定義極為精細(xì)的細(xì)節(jié)绘盟,物體的這個(gè)部分會(huì)獲得閃亮的屬性鸠真,我們可以設(shè)置它們相應(yīng)的亮度。specular貼圖給我們一個(gè)在diffuse貼圖上的控制層龄毡。
如果你不想成為主流吠卷,你可以在specular貼圖里使用顏色,不單單為每個(gè)原始像素設(shè)置specular亮度沦零,同時(shí)也設(shè)置specular高光的顏色祭隔。從真實(shí)角度來(lái)說(shuō),specular的顏色基本是由光源自身決定的路操,所以它不會(huì)生成真實(shí)的圖像(這就是為什么圖片通常是黑色和白色的:我們只關(guān)心亮度)疾渴。
如果你現(xiàn)在運(yùn)行應(yīng)用,你可以清晰地看到箱子的材質(zhì)現(xiàn)在非常類(lèi)似真實(shí)的鐵邊的木頭箱子了:
你可以在這里找到全部源碼屯仗。
使用diffuse和specular貼圖搞坝,我們可以給相關(guān)但簡(jiǎn)單物體添加一個(gè)極為明顯的細(xì)節(jié)。我們可以使用其他紋理貼圖魁袜,比如法線(xiàn)/bump貼圖或者反射貼圖桩撮,給物體添加更多的細(xì)節(jié)。但是這些在后面教程才會(huì)涉及峰弹。把你的箱子給你所有的朋友和家人看距境,有一天你會(huì)很滿(mǎn)足,我們的箱子會(huì)比現(xiàn)在更漂亮垮卓!
練習(xí)
調(diào)整光源的ambient垫桂,diffuse和specular向量值,看看它們?nèi)绾斡绊憣?shí)際輸出的箱子外觀粟按。
嘗試在片段著色器中反轉(zhuǎn)鏡面貼圖(Specular Map)的顏色值诬滩,然后木頭就會(huì)變得反光而邊框不會(huì)反光了(由于貼圖中鋼邊依然有一些殘余顏色,所以鋼邊依然會(huì)有一些高光灭将,不過(guò)反光明顯小了很多)疼鸟。
解答:
vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords)); // texture函數(shù)使用前面設(shè)置的紋理參數(shù)對(duì)相應(yīng)顏色值進(jìn)行采樣,返回vec4類(lèi)型
- 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è)帶顏色的鏡面貼圖空镜。
最終效果:
-添加一個(gè)叫做放射光貼圖(Emission Map)的東西,即記錄每個(gè)片段發(fā)光值(Emission Value)大小的貼圖,發(fā)光值是(模擬)物體自身發(fā)光(Emit)時(shí)可能產(chǎn)生的顏色吴攒。這樣的話(huà)物體就可以忽略環(huán)境光自身發(fā)光张抄。通常在你看到游戲里某個(gè)東西(比如 機(jī)器人的眼,或是箱子上的小燈)在發(fā)光時(shí),使用的就是放射光貼圖洼怔。使用這個(gè)貼圖(作者為 creativesam)作為放射光貼圖并使用在箱子上署惯,你就會(huì)看到箱子上有會(huì)發(fā)光的字了。
項(xiàng)目代碼镣隶。
實(shí)驗(yàn)結(jié)果如下: