立方體貼圖(Cubemap)
我們之前一直使用的是2D紋理,還有更多的紋理類型我們沒(méi)有探索過(guò),本教程中我們討論的紋理類型是將多個(gè)紋理組合起來(lái)映射到一個(gè)單一紋理,它就是cubemap。
基本上說(shuō)cubemap它包含6個(gè)2D紋理,這每個(gè)2D紋理是一個(gè)立方體(cube)的一個(gè)面,也就是說(shuō)它是一個(gè)有貼圖的立方體。為什么費(fèi)事地把6個(gè)獨(dú)立紋理結(jié)合為一個(gè)單獨(dú)的紋理,只使用6個(gè)各自獨(dú)立的不行嗎?這是因?yàn)閏ubemap有自己特有的屬性,可以使用方向向量對(duì)它們索引和采樣。想象一下,我們有一個(gè)1×1×1的單位立方體,有個(gè)以原點(diǎn)為起點(diǎn)的方向向量在它的中心。
從cubemap上使用橘黃色向量采樣一個(gè)紋理值看起來(lái)和下圖有點(diǎn)像:

方向向量的大小無(wú)關(guān)緊要。一旦提供了方向,OpenGL就會(huì)獲取方向向量觸碰到立方體表面上的相應(yīng)的紋理像素(texel),這樣就返回了正確的紋理采樣值。
方向向量觸碰到立方體表面的一點(diǎn)也就是cubemap的紋理位置,這意味著只要立方體的中心位于原點(diǎn)上(此時(shí)位置向量和方向向量相同),我們就可以使用立方體的位置向量來(lái)對(duì)cubemap進(jìn)行采樣。然后我們就可以獲取所有頂點(diǎn)的紋理坐標(biāo),就和立方體上的頂點(diǎn)位置一樣。所獲得的結(jié)果是一個(gè)紋理坐標(biāo),通過(guò)這個(gè)紋理坐標(biāo)就能獲取到cubemap上正確的紋理。
創(chuàng)建一個(gè)Cubemap
Cubemap和其他紋理一樣,所以要?jiǎng)?chuàng)建一個(gè)cubemap,在進(jìn)行任何紋理操作之前,需要生成一個(gè)紋理,激活相應(yīng)紋理單元然后綁定到合適的紋理目標(biāo)上。這次要綁定到 GL_TEXTURE_CUBE_MAP紋理類型:
GLuint textureID;
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);
由于立方體貼圖包含6個(gè)紋理,立方體的每個(gè)面一個(gè)紋理,我們必須調(diào)用glTexImage2D函數(shù)6次,函數(shù)的參數(shù)和前面教程講的相似。然而這次我們必須把紋理目標(biāo)(target)參數(shù)設(shè)置為立方體貼圖特定的面,這是告訴OpenGL我們創(chuàng)建的紋理是對(duì)應(yīng)立方體哪個(gè)面的。因此我們便需要為立方體貼圖的每個(gè)面調(diào)用一次 glTexImage2D。
由于立方體貼圖有6個(gè)面,OpenGL就提供了6個(gè)不同的紋理目標(biāo),來(lái)應(yīng)對(duì)立方體貼圖的各個(gè)面。
| 紋理目標(biāo)(Texture target) | 方位 |
|---|---|
| GL_TEXTURE_CUBE_MAP_POSITIVE_X | 右 |
| GL_TEXTURE_CUBE_MAP_NEGATIVE_X | 左 |
| GL_TEXTURE_CUBE_MAP_POSITIVE_Y | 上 |
| GL_TEXTURE_CUBE_MAP_NEGATIVE_Y | 下 |
| GL_TEXTURE_CUBE_MAP_POSITIVE_Z | 后 |
| GL_TEXTURE_CUBE_MAP_NEGATIVE_Z | 前 |
和很多OpenGL其他枚舉一樣,對(duì)應(yīng)的int值都是連續(xù)增加的,所以我們有一個(gè)紋理位置的數(shù)組或vector,就能以GL_TEXTURE_CUBE_MAP_POSITIVE_X為起始來(lái)對(duì)它們進(jìn)行遍歷,每次迭代枚舉值加 1,這樣循環(huán)所有的紋理目標(biāo)效率較高:
int width, height;
unsigned char* image;
glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);
for(GLuint i = 0; i < faces.size(); i++)
{
image = SOIL_load_image(faces[i], &width, &height, 0, SOIL_LOAD_RGB);
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
SOIL_free_image_data(image);
}
這兒我們有個(gè)vector叫textures_faces,它包含立方體貼圖所需各個(gè)紋理的文件路徑,并且以上表所列的順序排列。它將為每個(gè)當(dāng)前綁定的cubemap的每個(gè)面生成一個(gè)紋理。
由于立方體貼圖和其他紋理沒(méi)什么不同,我們也要定義它的環(huán)繞方式和過(guò)濾方式:
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
別被 GL_TEXTURE_WRAP_R嚇到,它只是簡(jiǎn)單的設(shè)置了紋理的R坐標(biāo),R坐標(biāo)對(duì)應(yīng)于紋理的第三個(gè)維度(就像位置的z一樣)。我們把放置方式設(shè)置為 GL_CLAMP_TO_EDGE ,由于紋理坐標(biāo)在兩個(gè)面之間,所以可能并不能觸及哪個(gè)面(由于硬件限制),因此使用 GL_CLAMP_TO_EDGE后OpenGL會(huì)返回它們的邊界的值,盡管我們可能在兩個(gè)兩個(gè)面中間進(jìn)行的采樣。
在繪制物體之前,將使用立方體貼圖,而在渲染前我們要激活相應(yīng)的紋理單元并綁定到立方體貼圖上,這和普通的2D紋理沒(méi)什么區(qū)別。
在片段著色器中,我們也必須使用一個(gè)不同的采樣器——samplerCube,用它來(lái)從texture函數(shù)中采樣,但是這次使用的是一個(gè)vec3方向向量,取代vec2。下面是一個(gè)片段著色器使用了立方體貼圖的例子:
in vec3 textureDir; // 用一個(gè)三維方向向量來(lái)表示立方體貼圖紋理的坐標(biāo)
uniform samplerCube cubemap; // 立方體貼圖紋理采樣器
void main()
{
color = texture(cubemap, textureDir);
}
使用立方體貼圖可以簡(jiǎn)單的實(shí)現(xiàn)很多有意思的技術(shù)。其中之一便是著名的天空盒(Skybox)。
天空盒
天空盒(Skybox)是一個(gè)包裹整個(gè)場(chǎng)景的立方體,它由6個(gè)圖像構(gòu)成一個(gè)環(huán)繞的環(huán)境,給玩家一種他所在的場(chǎng)景比實(shí)際的要大得多的幻覺(jué)。比如有些在視頻游戲中使用的天空盒的圖像是群山、白云或者滿天繁星。比如下面的夜空繁星的圖像就來(lái)自《上古卷軸》:

上圖中使用了幾個(gè)夜空的圖片給予玩家一種置身廣袤宇宙的感覺(jué),可實(shí)際上,他還是在一個(gè)小盒子之中。
網(wǎng)上有很多這樣的天空盒的資源。這個(gè)網(wǎng)站就提供了很多。這些天空盒圖像通常有下面的樣式:

這個(gè)細(xì)致(高精度)的天空盒就是我們將在場(chǎng)景中使用的那個(gè),你可以在這里下載。
加載天空盒
由于天空盒實(shí)際上就是一個(gè)立方體貼圖,加載天空盒和之前我們加載立方體貼圖的沒(méi)什么大的不同。為了加載天空盒我們將使用下面的函數(shù),它接收一個(gè)包含6個(gè)紋理文件路徑的vector:
GLuint loadCubemap(vector<const GLchar*> faces)
{
GLuint textureID;
glGenTextures(1, &textureID);
int width, height;
unsigned char* image;
glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);
for(GLuint i = 0; i < faces.size(); i++)
{
image = SOIL_load_image(faces[i], &width, &height, 0, SOIL_LOAD_RGB);
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
SOIL_free_image_data(image); // // 釋放源圖像內(nèi)存
}
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
return textureID;
}
在我們調(diào)用這個(gè)函數(shù)之前,我們將把合適的紋理路徑加載到一個(gè)vector之中,順序還是按照立方體貼圖枚舉的特定順序:
vector<const GLchar*> faces;
faces.push_back("right.jpg");
faces.push_back("left.jpg");
faces.push_back("top.jpg");
faces.push_back("bottom.jpg");
faces.push_back("back.jpg");
faces.push_back("front.jpg");
GLuint cubemapTexture = loadCubemap(faces);
現(xiàn)在我們已經(jīng)用cubemapTexture作為id把天空盒加載為立方體貼圖。我們現(xiàn)在可以把它綁定到一個(gè)立方體來(lái)替換不完美的clear color,在前面的所有教程中這個(gè)東西做背景已經(jīng)很久了。
顯示天空盒
因?yàn)樘炜蘸欣L制在了一個(gè)立方體上,我們還需要另一個(gè)VAO、VBO以及一組全新的頂點(diǎn),和任何其他物體一樣。你可以從這里獲得頂點(diǎn)數(shù)據(jù)。
立方體貼圖用于給3D立方體帖上紋理,可以用立方體的位置作為紋理坐標(biāo)進(jìn)行采樣。當(dāng)一個(gè)立方體的中心位于原點(diǎn)(0,0,0)的時(shí)候,它的每一個(gè)位置向量也就是以原點(diǎn)為起點(diǎn)的方向向量。這個(gè)方向向量就是我們要得到的立方體某個(gè)位置的相應(yīng)紋理值。出于這個(gè)理由,我們只需要提供位置向量,而無(wú)需紋理坐標(biāo)。為了渲染天空盒,我們需要一組新著色器,它們不會(huì)太復(fù)雜。因?yàn)槲覀冎挥幸粋€(gè)頂點(diǎn)屬性,頂點(diǎn)著色器非常簡(jiǎn)單:
#version 330 core
layout (location = 0) in vec3 position;
out vec3 TexCoords;
uniform mat4 projection;
uniform mat4 view;
void main()
{
gl_Position = projection * view * vec4(position, 1.0);
TexCoords = position;
}
注意,頂點(diǎn)著色器有意思的地方在于我們把輸入的位置向量作為輸出給片段著色器的紋理坐標(biāo)。片段著色器就會(huì)把它們作為輸入去采樣samplerCube:
#version 330 core
in vec3 TexCoords;
out vec4 color;
uniform samplerCube skybox;
void main()
{
color = texture(skybox, TexCoords);
}
為了繪制天空盒,我們將把它作為場(chǎng)景中第一個(gè)繪制的物體并且關(guān)閉深度寫入。這樣天空盒才能成為所有其他物體的背景來(lái)繪制出來(lái)。
glDepthMask(GL_FALSE);
skyboxShader.Use();
// ... Set view and projection matrix
glBindVertexArray(skyboxVAO);
glBindTexture(GL_TEXTURE_CUBE_MAP, cubemapTexture);
glDrawArrays(GL_TRIANGLES, 0, 36);
glBindVertexArray(0);
glDepthMask(GL_TRUE);
// ... Draw rest of the scene
當(dāng)前的視圖矩陣對(duì)所有天空盒的位置進(jìn)行了轉(zhuǎn)轉(zhuǎn)縮放和平移變換,所以玩家移動(dòng),立方體貼圖也會(huì)跟著移動(dòng)!我們打算移除視圖矩陣的平移部分,這樣移動(dòng)就影響不到天空盒的位置向量了。在基礎(chǔ)光照教程里我們提到過(guò)我們可以只用4X4矩陣的3×3部分去除平移。我們可以簡(jiǎn)單地將矩陣轉(zhuǎn)為33矩陣再轉(zhuǎn)回來(lái),就能達(dá)到目標(biāo):
glm::mat4 view = glm::mat4(glm::mat3(camera.GetViewMatrix()));
這會(huì)移除所有平移,但保留所有旋轉(zhuǎn),因此用戶仍然能夠向四面八方看。由于有了天空盒,場(chǎng)景即可變得巨大了。如果你添加些物體然后自由在其中游蕩一會(huì)兒你會(huì)發(fā)現(xiàn)場(chǎng)景的真實(shí)度有了極大提升。最后的效果看起來(lái)像這樣:

這里有全部源碼,你可以對(duì)比一下你寫的。
嘗試用不同的天空盒實(shí)驗(yàn),看看它們對(duì)場(chǎng)景有多大影響。
優(yōu)化
如果我們先渲染了天空盒,那么我們就是在為每一個(gè)屏幕上的像素運(yùn)行片段著色器,即使天空盒只有部分在顯示著;fragment可以使用前置深度測(cè)試(early depth testing)簡(jiǎn)單地被丟棄,這樣就節(jié)省了我們寶貴的帶寬。所以最后渲染天空盒就能夠給我們帶來(lái)輕微的性能提升。采用這種方式,深度緩沖被全部物體的深度值完全填充,所以我們只需要渲染通過(guò)前置深度測(cè)試的那部分天空的片段就行了,而且能顯著減少片段著色器的調(diào)用。
問(wèn)題是天空盒是個(gè)1×1×1的立方體,極有可能會(huì)渲染失敗,因?yàn)闃O有可能通不過(guò)深度測(cè)試。簡(jiǎn)單地不用深度測(cè)試渲染它也不是解決方案,這是因?yàn)樘炜蘸袝?huì)在之后覆蓋所有的場(chǎng)景中其他物體。我們需要耍個(gè)花招讓深度緩沖相信天空盒的深度緩沖有著最大深度值1.0,如此只要有個(gè)物體存在深度測(cè)試就會(huì)失敗,看似物體就在它前面了。
在坐標(biāo)系教程中我們說(shuō)過(guò),透視除法(perspective division)是在頂點(diǎn)著色器運(yùn)行之后執(zhí)行的,把gl_Position的xyz坐標(biāo)除以w元素。我們從深度測(cè)試教程了解到除法結(jié)果的z元素等于頂點(diǎn)的深度值。利用這個(gè)信息,我們可以把輸出位置的z元素設(shè)置為它的w元素,這樣就會(huì)導(dǎo)致z元素等于1.0了,因?yàn)?,?dāng)透視除法應(yīng)用后,它的z元素轉(zhuǎn)換為w/w = 1.0:
void main()
{
vec4 pos = projection * view * vec4(position, 1.0);
gl_Position = pos.xyww;
TexCoords = position;
}
最終,標(biāo)準(zhǔn)化設(shè)備坐標(biāo)就總會(huì)有個(gè)與1.0相等的z值了,1.0就是深度值的最大值。只有在沒(méi)有任何物體可見(jiàn)的情況下天空盒才會(huì)被渲染(只有通過(guò)深度測(cè)試才渲染,否則假如有任何物體存在,就不會(huì)被渲染,只去渲染物體)。
我們必須改變一下深度方程,把它設(shè)置為GL_LEQUAL,原來(lái)默認(rèn)的是GL_LESS。深度緩沖會(huì)為天空盒用1.0這個(gè)值填充深度緩沖,所以我們需要保證天空盒是使用小于等于深度緩沖來(lái)通過(guò)深度測(cè)試的,而不是小于。
你可以在這里找到優(yōu)化過(guò)的版本的源碼。
環(huán)境映射
我們現(xiàn)在有了一個(gè)把整個(gè)環(huán)境映射到為一個(gè)單獨(dú)紋理的對(duì)象,我們利用這個(gè)信息能做的不僅是天空盒。使用帶有場(chǎng)景環(huán)境的立方體貼圖,我們還可以讓物體有一個(gè)反射或折射屬性。像這樣使用了環(huán)境立方體貼圖的技術(shù)叫做環(huán)境貼圖技術(shù),其中最重要的兩個(gè)是反射(reflection)和折射(refraction)。
反射
凡是是一個(gè)物體(或物體的某部分)反射(Reflect)他周圍的環(huán)境的屬性,比如物體的顏色多少有些等于它周圍的環(huán)境,這要基于觀察者的角度。例如一個(gè)鏡子是一個(gè)反射物體:它會(huì)基于觀察者的角度泛著它周圍的環(huán)境。
下圖展示了我們?nèi)绾斡?jì)算反射向量,然后使用這個(gè)向量去從一個(gè)立方體貼圖中采樣:

我們基于觀察方向向量I和物體的法線向量N計(jì)算出反射向量R。我們可以使用GLSL的內(nèi)建函數(shù)reflect來(lái)計(jì)算這個(gè)反射向量。最后向量R作為一個(gè)方向向量對(duì)立方體貼圖進(jìn)行索引/采樣,返回一個(gè)環(huán)境的顏色值。最后的效果看起來(lái)就像物體反射了天空盒。
因?yàn)槲覀冊(cè)趫?chǎng)景中已經(jīng)設(shè)置了一個(gè)天空盒,創(chuàng)建反射就不難了。我們改變一下箱子使用的那個(gè)片段著色器,給箱子一個(gè)反射屬性:
#version 330 core
in vec3 Normal;
in vec3 Position;
out vec4 color;
uniform vec3 cameraPos;
uniform samplerCube skybox;
void main()
{
vec3 I = normalize(Position - cameraPos);
vec3 R = reflect(I, normalize(Normal));
color = texture(skybox, R);
}
我們先來(lái)計(jì)算觀察/攝像機(jī)方向向量I,然后使用它來(lái)計(jì)算反射向量R,接著我們用R從天空盒立方體貼圖采樣。要注意的是,我們有了片段的插值Normal和Position變量,所以我們需要修正頂點(diǎn)著色器適應(yīng)它。
#version 330 core
layout (location = 0) in vec3 position;
layout (location = 1) in vec3 normal;
out vec3 Normal;
out vec3 Position;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main()
{
gl_Position = projection * view * model * vec4(position, 1.0f);
Normal = mat3(transpose(inverse(model))) * normal;
Position = vec3(model * vec4(position, 1.0f));
}
我們用了法線向量,所以我們打算使用一個(gè)法線矩陣(normal matrix)變換它們。Position輸出的向量是一個(gè)世界空間位置向量。頂點(diǎn)著色器輸出的Position用來(lái)在片段著色器計(jì)算觀察方向向量。
因?yàn)槲覀兪褂梅ň€,你還得更新頂點(diǎn)數(shù)據(jù),更新屬性指針。還要確保設(shè)置cameraPos的uniform。
然后在渲染箱子前我們還得綁定立方體貼圖紋理:
glBindVertexArray(cubeVAO);
glBindTexture(GL_TEXTURE_CUBE_MAP, skyboxTexture);
glDrawArrays(GL_TRIANGLES, 0, 36);
glBindVertexArray(0);
編譯運(yùn)行你的代碼,你等得到一個(gè)鏡子一樣的箱子。箱子完美地反射了周圍的天空盒:

當(dāng)反射應(yīng)用于整個(gè)物體之上的時(shí)候,物體看上去就像有一個(gè)像鋼和鉻這種高反射材質(zhì)。如果我們加載模型教程中的納米鎧甲模型,我們就會(huì)獲得一個(gè)鉻金屬制鎧甲:

看起來(lái)挺驚艷,但是現(xiàn)實(shí)中大多數(shù)模型都不是完全反射的。我們可以引進(jìn)反射貼圖(reflection map)來(lái)使模型有另一層細(xì)節(jié)。和diffuse、specular貼圖一樣,我們可以從反射貼圖上采樣來(lái)決定fragment的反射率。使用反射貼圖我們還可以決定模型的哪個(gè)部分有反射能力,以及強(qiáng)度是多少。本節(jié)的練習(xí)中,要由你來(lái)在我們?cè)缙趧?chuàng)建的模型加載器引入反射貼圖,這會(huì)極大的提升納米服模型的細(xì)節(jié)。
折射
環(huán)境映射的另一個(gè)形式叫做折射(Refraction),它和反射差不多。折射是光線通過(guò)特定材質(zhì)對(duì)光線方向的改變。我們通??吹较袼粯拥谋砻?,光線并不是直接通過(guò)的,而是讓光線彎曲了一點(diǎn)。它看起來(lái)像你把半只手伸進(jìn)水里的效果。
折射遵守斯涅爾定律,使用環(huán)境貼圖看起來(lái)就像這樣:

我們有個(gè)觀察向量I,一個(gè)法線向量N,這次折射向量是R。就像你所看到的那樣,觀察向量的方向有輕微彎曲。彎曲的向量R隨后用來(lái)從立方體貼圖上采樣。
折射可以通過(guò)GLSL的內(nèi)建函數(shù)refract來(lái)實(shí)現(xiàn),除此之外還需要一個(gè)法線向量,一個(gè)觀察方向和一個(gè)兩種材質(zhì)之間的折射指數(shù)。
折射指數(shù)決定了一個(gè)材質(zhì)上光線扭曲的數(shù)量,每個(gè)材質(zhì)都有自己的折射指數(shù)。下表是常見(jiàn)的折射指數(shù):
| 材質(zhì) | 折射指數(shù) |
|---|---|
| 空氣 | 1.00 |
| 水 | 1.33 |
| 冰 | 1.309 |
| 玻璃 | 1.52 |
| 寶石 | 2.42 |
我們使用這些折射指數(shù)來(lái)計(jì)算光線通過(guò)兩個(gè)材質(zhì)的比率。在我們的例子中,光線/視線從空氣進(jìn)入玻璃(如果我們假設(shè)箱子是玻璃做的)所以比率是1.001.52 = 0.658。
我們已經(jīng)綁定了立方體貼圖,提供了定點(diǎn)數(shù)據(jù),設(shè)置了攝像機(jī)位置的uniform?,F(xiàn)在只需要改變片段著色器:
void main()
{
float ratio = 1.00 / 1.52;
vec3 I = normalize(Position - cameraPos);
vec3 R = refract(I, normalize(Normal), ratio);
color = texture(skybox, R);
}
通過(guò)改變折射指數(shù)你可以創(chuàng)建出完全不同的視覺(jué)效果。編譯運(yùn)行應(yīng)用,結(jié)果也不是太有趣,因?yàn)槲覀冎皇怯昧艘粋€(gè)普通箱子,這不能顯示出折射的效果,看起來(lái)像個(gè)放大鏡。使用同一個(gè)著色器,納米服模型卻可以展示出我們期待的效果:玻璃制物體。

你可以向想象一下,如果將光線、反射、折射和頂點(diǎn)的移動(dòng)合理的結(jié)合起來(lái)就能創(chuàng)造出漂亮的水的圖像。一定要注意,出于物理精確的考慮當(dāng)光線離開(kāi)物體的時(shí)候還要再次進(jìn)行折射;現(xiàn)在我們簡(jiǎn)單的使用了單邊(一次)折射,大多數(shù)目的都可以得到滿足。
動(dòng)態(tài)環(huán)境貼圖
現(xiàn)在,我們已經(jīng)使用了靜態(tài)圖像組合的天空盒,看起來(lái)不錯(cuò),但是沒(méi)有考慮到物體可能移動(dòng)的實(shí)際場(chǎng)景。我們到現(xiàn)在還沒(méi)注意到這點(diǎn),是因?yàn)槲覀兡壳斑€只使用了一個(gè)物體。如果我們有個(gè)鏡子一樣的物體,它周圍有多個(gè)物體,只有天空盒在鏡子中可見(jiàn),就好像場(chǎng)景中只有一個(gè)物體一樣。
使用幀緩沖可以為提到的物體的所有6個(gè)不同角度創(chuàng)建一個(gè)場(chǎng)景的紋理,把它們每次渲染迭代儲(chǔ)存為一個(gè)立方體貼圖。之后我們可以使用這個(gè)(動(dòng)態(tài)生成的)立方體貼圖來(lái)創(chuàng)建真實(shí)的反射和折射表面,這樣就能包含所有其他物體了。這種方法叫做動(dòng)態(tài)環(huán)境映射(Dynamic Environment Mapping),因?yàn)槲覀儎?dòng)態(tài)地創(chuàng)建了一個(gè)物體的以其四周為參考的立方體貼圖,并把它用作環(huán)境貼圖。
它看起效果很好,但是有一個(gè)劣勢(shì):使用環(huán)境貼圖我們必須為每個(gè)物體渲染場(chǎng)景6次,這需要非常大的開(kāi)銷?,F(xiàn)代應(yīng)用嘗試盡量使用天空盒子,凡可能預(yù)編譯立方體貼圖就創(chuàng)建少量動(dòng)態(tài)環(huán)境貼圖。動(dòng)態(tài)環(huán)境映射是個(gè)非常棒的技術(shù),要想在不降低執(zhí)行效率的情況下實(shí)現(xiàn)它就需要很多巧妙的技巧。
練習(xí)
嘗試在模型加載中引進(jìn)反射貼圖,你將再次得到很大視覺(jué)效果的提升。這其中有幾點(diǎn)需要注意:Assimp并不支持反射貼圖,我們可以使用環(huán)境貼圖的方式將反射貼圖從aiTextureType_AMBIENT
類型中來(lái)加載反射貼圖的材質(zhì)。
我匆忙地使用反射貼圖來(lái)作為鏡面反射的貼圖,而反射貼圖并沒(méi)有很好的映射在模型上:)。
由于加載模型已經(jīng)占用了3個(gè)紋理單元,因此你要綁定天空盒到第4個(gè)紋理單元上,這樣才能在同一個(gè)著色器內(nèi)從天空盒紋理中取樣。
你可以在此獲取解決方案的源代碼,這其中還包括升級(jí)過(guò)的Model和Mesh類,還有用來(lái)繪制反射貼圖的頂點(diǎn)著色器和片段著色器。
如果你一切都做對(duì)了,那你應(yīng)該看到和下圖類似的效果:

我沒(méi)有做出來(lái),和所給代碼相同,我運(yùn)行出的結(jié)果是七彩的,待解決?。?!
