本文主要解決一個(gè)問(wèn)題:
如何使用光照貼圖給材質(zhì)添加更多的靈活性?
引言
在上一篇文章中中,我們?yōu)檎麄€(gè)物體定義了一個(gè)整體的材質(zhì),但是現(xiàn)實(shí)世界中的對(duì)象通常不只一種材質(zhì),而是有多種材質(zhì)組成。 想象一輛汽車(chē):車(chē)框架是鋼制的,還噴了漆,看上去閃亮閃亮的,窗戶(hù)的部分能照出周?chē)木拔?,輪胎是橡膠不那么閃,里面的骨架是鋼就亮很多(前提是你洗了車(chē)) 。由此可見(jiàn),物體有很大可能是由不同材質(zhì)組成的一個(gè)整體。難道我們還對(duì)物體的每個(gè)部分都設(shè)置一個(gè)材質(zhì)嗎?
當(dāng)然不是,我們有光照貼圖!嚴(yán)格來(lái)說(shuō),有三種光照貼圖:環(huán)境光貼圖、漫反射光貼圖、鏡面高光貼圖。但是環(huán)境光和漫反射光的顏色相似,只是稍微暗淡點(diǎn),所以我們可以把漫反射光的貼圖用到環(huán)境光上。剩下的就只有兩種貼圖了:漫反射光貼圖和鏡面高光貼圖。
漫反射光貼圖
還記得我們講紋理的章節(jié)嗎?在紋理章節(jié)里,我們直接把片元的顏色設(shè)置成從紋理種采樣的顏色值,而在這章中,我們會(huì)對(duì)采樣后的顏色值再進(jìn)行一系列的計(jì)算,這就是光照貼圖(不管是漫反射還是鏡面高光)的原理。
由于是對(duì)漫反射顏色產(chǎn)生影響,所以我們稱(chēng)之為漫反射光貼圖。但是,使用的方法還是類(lèi)似的。本次我們使用下面的圖來(lái)進(jìn)行操作:
這是一個(gè)帶金屬邊的木盒子,至于為什么要金屬邊,你往下看就知道了。
要使用這張圖,我們需要把材質(zhì)結(jié)構(gòu)中的環(huán)境光和漫反射屬性去掉,替換成2D紋理圖。
struct Material {
sampler2D diffuse;
vec3 specular;
float shininess;
};
...
in vec2 TexCoords;
記?。簊ampler2D是OpenGL中的隱含類(lèi)型,我們不能去設(shè)置它,只能將它暴露出來(lái)讓OpenGL自己去設(shè)置。如果你強(qiáng)行設(shè)置,OpenGL會(huì)爆出一大堆亂七八糟的Error,煩都煩死了。
改了材質(zhì)結(jié)構(gòu)之后,引用的方式自然也得改,從單純的一個(gè)變量引用,現(xiàn)在需要對(duì)紋理進(jìn)行采樣了。
vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));
vec3 diffuse = light.diffuse * (diff * vec3(texture(material.diffuse, TexCoords)));
環(huán)境光也用一樣的紋理(如果你非得要用別的紋理,也沒(méi)什么問(wèn)題,用同樣的方法搞一張就行了。),替換掉material.diffuse和material.ambient之后,就是這樣了。
當(dāng)然,如果你現(xiàn)在就編譯運(yùn)行,是絕對(duì)看不到什么效果滴,為啥?因?yàn)槲覀冞€沒(méi)有把紋理坐標(biāo)傳遞給片元著色器??!使用紋理當(dāng)然要為頂點(diǎn)指定紋理坐標(biāo),然后將紋理坐標(biāo)傳遞給頂點(diǎn)著色器,讓頂點(diǎn)著色器將紋理坐標(biāo)傳遞給片元著色器。頂點(diǎn)屬性已經(jīng)準(zhǔn)備好了,就是這樣:
float vertices[] = {
// 位置 // 法線 // 紋理坐標(biāo)
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f,
0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f,
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
-0.5f, 0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
-0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f,
0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f,
0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f,
0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f
};
替換之后,在頂點(diǎn)著色器中添加屬性的輸入:
layout (location = 2) in vec2 aTexCoords;
...
out vec2 TexCoords;
void main()
{
...
TexCoords = aTexCoords;
}
別忘了添加頂點(diǎn)的紋理屬性,然后將一眾的屬性跨度改成8*sizeof(float)。
// 紋理屬性
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
glEnableVertexAttribArray(2);
因?yàn)槲覀兊捻?xiàng)目是開(kāi)始了光照之后的新鮮貨,之前紋理章節(jié)中加載圖片的代碼已經(jīng)沒(méi)了,正好我們把這部分的功能封裝成一個(gè)函數(shù),用起來(lái)就簡(jiǎn)單了。代碼已經(jīng)封裝好了,請(qǐng)看:
//加載紋理
unsigned int loadTexture(char const * path){
unsigned int textureID;
glGenTextures(1, &textureID);
int width, height, nrComponents;
unsigned char * data = stbi_load(path, &width, &height, &nrComponents, 0);
if (data) {
GLenum format;
if (nrComponents == 1)
format = GL_RED;
else if (nrComponents == 3)
format = GL_RGB;
else if (nrComponents == 4)
format = GL_RGBA;
glBindTexture(GL_TEXTURE_2D, textureID);
glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
stbi_image_free(data);
}
else {
std::cout << "紋理加載失敗,路徑是:" << path << std::endl;
stbi_image_free(data);
}
return textureID;
}
最后,我們需要加載之前的圖片做紋理,設(shè)置漫反射紋理圖,啟用這張紋理圖:
unsigned int diffuseMap = loadTexture("container2.png");
...
lightingShader.setInt("material.diffuse", 0);
...
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, diffuseMap);
編譯運(yùn)行,不出意外的話,效果應(yīng)該像這樣:
如果效果有差,參考這里的完整代碼。
鏡面高光貼圖
乍一看效果倒是不錯(cuò),可是總感覺(jué)怪怪,為啥一個(gè)木頭箱子會(huì)有這么亮的反光呢?我們來(lái)修復(fù)這個(gè)問(wèn)題,將木頭的部分反光效果去除,邊框金屬部分的反光效果保留。這個(gè)過(guò)程看上去和漫反射貼圖一樣,巧合?我想不是。
用一張紋理圖來(lái)充當(dāng)鏡面高光的效果圖。我們需要生成一張黑白的紋理圖(當(dāng)然你想用彩色的也沒(méi)問(wèn)題),這張圖已經(jīng)準(zhǔn)備好了,就是下面這張:
木頭的部分沒(méi)有鏡面高光效果,所以是黑色的,外面的金屬框有鏡面高光效果,所以其顏色為灰色。
嚴(yán)格來(lái)說(shuō),木頭也是有高光效果的,只是非常微弱,大部分都被散射掉了。我們出于學(xué)習(xí)的目的,將高光效果設(shè)置成了0。
用神器PS或者其他的軟件就能做出一張合格的紋理圖,所以,是不是考慮一下學(xué)個(gè)PS:)?
用法和之前幾乎沒(méi)有區(qū)別。先來(lái)把片元著色器中的代碼改一改:
struct Material{
sampler2D diffuse;
sampler2D specular;
float shininess;
};
vec3 specular = light.specular * (spec * vec3(texture(material.specular, TexCoords)));
將材質(zhì)中的鏡面高光改成紋理采樣,計(jì)算鏡面高光的時(shí)候也改成紋理采樣。
緊接著,在主函數(shù)中加載紋理圖,設(shè)置鏡面高光紋理圖,將紋理圖綁定到紋理單元1上。
unsigned int specularMap = loadTexture("container2_specular.png");
lightingShader.setInt("material.specular", 1);
...
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, specularMap);
改完之后,編譯運(yùn)行,你會(huì)看到類(lèi)似下面的效果:
如果你看到的場(chǎng)景不正確,請(qǐng)查看這里的代碼比對(duì)。
總結(jié)
在本文中,我們學(xué)習(xí)了如何用光照貼圖代替材質(zhì)的單一反射屬性,在同一個(gè)物體的不同部分應(yīng)用不同的材質(zhì)。使用的方式很簡(jiǎn)單,對(duì)貼圖進(jìn)行采樣,然后和光照進(jìn)行計(jì)算。這種方式,和之前紋理章節(jié)介紹的內(nèi)容十分相似,對(duì)比之前紋理章節(jié)的片元著色器代碼,你會(huì)有更深的理解。
參考資料
www.learningopengl.com(非常好的網(wǎng)站,建議學(xué)習(xí))