OpenGL 核心技術之立方體貼圖


筆者介紹:姜雪偉,IT公司技術合伙人,IT高級講師,CSDN社區專家。特邀編輯,暢銷書作者,國家專利發明人;已出版書籍:《手把手教你架構3D游戲引擎》電子工業出版社和《Unity3D實戰核心技術具體解釋》電子工業出版社等。

CSDN視頻網址:http://edu.csdn.net/lecturer/144

在這里介紹立方體貼圖主要是告訴讀者,利用立方體貼圖原理。我們能夠做非常多事情:比方天空盒,環境映射中的反射和折射效果等等。當然環境映射也能夠使用一張紋理貼圖實現。這個會在博文的最后給讀者介紹,以下開始介紹立方體貼圖實現原理。

我們在游戲開發中通常的做法是將2D紋理映射到物體的一個面上,本篇博文介紹的是將多個紋理組合起來映射到一個單一紋理,這就稱為立方體貼圖。在介紹立方體貼圖前。先解釋一下紋理採樣,假設我們有一個單位立方體。有個以原點為起點的方向向量在它的中心。

從立方體貼圖上使用橘黃色向量採樣一個紋理值看起來下圖:


注意,方向向量的大小無關緊要。一旦提供了方向,OpenGL就會獲取方向向量碰觸到立方體表面上的對應的紋理像素。這樣就返回了正確的紋理採樣值。

方向向量觸碰到立方體表面的一點也就是立方體貼圖的紋理位置。這意味着僅僅要立方體的中心位於原點上。我們就能夠使用立方體的位置向量來對立方體貼圖進行採樣。然后我們就能夠獲取全部頂點的紋理坐標。就和立方體上的頂點位置一樣。所獲得的結果是一個紋理坐標,通過這個紋理坐標就能獲取到立方體貼圖上正確的紋理。

以下開始介紹創建立方體貼圖,立方體貼圖和其它紋理一樣。所以要創建一個立方體貼圖,在進行不論什么紋理操作之前,須要生成一個紋理。激活對應紋理單元然后綁定到合適的紋理目標上。這次要綁定到 GL_TEXTURE_CUBE_MAP紋理類型:

GLuint textureID;
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);

由於立方體貼圖包括6個紋理,立方體的每一個面一個紋理,我們必須調用glTexImage2D函數6次,函數的參數和前面教程講的類似。然而這次我們必須把紋理目標(target)參數設置為立方體貼圖特定的面。這是告訴OpenGL我們創建的紋理是對應立方體哪個面的。

因此我們便須要為立方體貼圖的每一個面調用一次 glTexImage2D

由於立方體貼圖有6個面,OpenGL就提供了6個不同的紋理目標,來應對立方體貼圖的各個面。

紋理目標(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其它枚舉一樣,對應的int值都是連續添加的,所以我們有一個紋理位置的數組或vector。就能以 GL_TEXTURE_CUBE_MAP_POSITIVE_X 為起始來對它們進行遍歷,每次迭代枚舉值加  1 ,這樣循環全部的紋理目標效率較高:

int width,height;
unsigned char* image;  
for(GLuint i = 0; i < textures_faces.size(); i++)
{
    image = SOIL_load_image(textures_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
    );
}

這兒我們有個vector叫textures_faces。它包括立方體貼圖所各個紋理的文件路徑,而且以上表所列的順序排列。它將為每一個當前綁定的cubemp的每一個面生成一個紋理。

由於立方體貼圖和其它紋理沒什么不同,我們也要定義它的圍繞方式和過濾方式:

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。它的含義僅僅是簡單的設置了紋理的R坐標。R坐標對應於紋理的第三個維度(就像位置的z一樣)。

我們把放置方式設置為 GL_CLAMP_TO_EDGE ,由於紋理坐標在兩個面之間,所以可能並不能觸及哪個面(由於硬件限制),因此使用 GL_CLAMP_TO_EDGE 后OpenGL會返回它們的邊界的值。雖然我們可能在兩個兩個面中間進行的採樣。

在繪制物體之前。將使用立方體貼圖,而在渲染前我們要激活對應的紋理單元並綁定到立方體貼圖上。這和普通的2D紋理沒什么差別。

在片段着色器中。我們也必須使用一個不同的採樣器——samplerCube,用它來從texture函數中採樣。可是這次使用的是一個vec3方向向量,代替vec2。以下是一個片段着色器使用了立方體貼圖的樣例:

in vec3 textureDir; // 用一個三維方向向量來表示立方體貼圖紋理的坐標

uniform samplerCube cubemap;  // 立方體貼圖紋理採樣器

void main()
{
    color = texture(cubemap, textureDir);
}
立方體貼圖的技術實現了后,我們利用該技術實現天空盒:

天空盒是一個立方體,它由六個面組成,每一個面須要一個貼圖,網上有非常多這種天空盒的資源。當然美術也能夠制作,這些天空盒通常有以下的樣式:



假設你把這6個面折疊到一個立方體中,你機會獲得模擬了一個巨大的風景的立方體。原理清楚了。接下來使用程序創建天空盒:

由於天空盒實際上就是一個立方體貼圖,載入天空盒和之前我們載入立方體貼圖的沒什么大的不同。為了載入天空盒我們將使用以下的函數。它接收一個包括6個紋理文件路徑的vector:

GLuint loadCubemap(vector<const GLchar*> faces)
{
    GLuint textureID;
    glGenTextures(1, &textureID);
    glActiveTexture(GL_TEXTURE0);

    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
        );
    }
    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;
}
在我們調用這個函數之前,我們將把合適的紋理路徑載入到一個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);

由於天空盒繪制在了一個立方體上。我們還須要還有一個VAO、VBO以及一組全新的頂點,將天空盒顯示出來。

立方體貼圖用於給3D立方體帖上紋理,能夠用立方體的位置作為紋理坐標進行採樣。當一個立方體的中心位於原點(0,0。0)的時候。它的每一個位置向量也就是以原點為起點的方向向量。

這個方向向量就是我們要得到的立方體某個位置的對應紋理值。

出於這個理由,我們僅僅須要提供位置向量。而無需紋理坐標。為了渲染天空盒,我們須要一組新着色器,它們不會太復雜。由於我們僅僅有一個頂點屬性。頂點着色器非常easy:

#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;
}
注意。頂點着色器有意思的地方在於我們把輸入的位置向量作為輸出給片段着色器的紋理坐標。

片段着色器就會把它們作為輸入去採樣samplerCube:

#version 330 core
in vec3 TexCoords;
out vec4 color;

uniform samplerCube skybox;

void main()
{
    color = texture(skybox, TexCoords);
}

片段着色器比較明了。我們把頂點屬性中的位置向量作為紋理的方向向量,使用它們從立方體貼圖採樣紋理值。渲染天空盒如今非常easy,我們有了一個立方體貼圖紋理。我們簡單綁定立方體貼圖紋理。天空盒就自己主動地用天空盒的立方體貼圖填充了。為了繪制天空盒,我們將把它作為場景中第一個繪制的物體而且關閉深度寫入。

這樣天空盒才干成為全部其它物體的背景來繪制出來。

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
我們希望天空盒以玩家為中心。這樣不管玩家移動了多遠。天空盒都不會變近,這樣就產生一種四周的環境真的非常大的印象。當前的視圖矩陣對全部天空盒的位置進行了轉轉縮放和平移變換。所以玩家移動,立方體貼圖也會跟着移動。我們打算移除視圖矩陣的平移部分,這樣移動就影響不到天空盒的位置向量了。在基礎光照教程里我們提到過我們能夠僅僅用4X4矩陣的3×3部分去除平移。我們能夠簡單地將矩陣轉為33矩陣再轉回來。就能達到目標。

glm::mat4 view = glm::mat4(glm::mat3(camera.GetViewMatrix()));
這會移除全部平移。但保留全部旋轉。因此用戶仍然能夠向四面八方看。由於有了天空盒。場景即可變得巨大了。

假設你加入些物體然后自由在當中游盪一會兒你會發現場景的真實度有了極大提升。最后的效果看起來像這樣:



實現上述效果的核心代碼例如以下所看到的:

 // Clear buffers
        glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);


        // Draw skybox first
        glDepthMask(GL_FALSE);// Remember to turn depth writing off
        skyboxShader.Use();		
        glm::mat4 view = glm::mat4(glm::mat3(camera.GetViewMatrix()));	// Remove any translation component of the view matrix
        glm::mat4 projection = glm::perspective(camera.Zoom, (float)screenWidth/(float)screenHeight, 0.1f, 100.0f);
        glUniformMatrix4fv(glGetUniformLocation(skyboxShader.Program, "view"), 1, GL_FALSE, glm::value_ptr(view));
        glUniformMatrix4fv(glGetUniformLocation(skyboxShader.Program, "projection"), 1, GL_FALSE, glm::value_ptr(projection));
        // skybox cube
        glBindVertexArray(skyboxVAO);
        glActiveTexture(GL_TEXTURE0);
        glUniform1i(glGetUniformLocation(shader.Program, "skybox"), 0);
        glBindTexture(GL_TEXTURE_CUBE_MAP, cubemapTexture);
        glDrawArrays(GL_TRIANGLES, 0, 36);
        glBindVertexArray(0);
        glDepthMask(GL_TRUE);
        
        // Then draw scene as normal
        shader.Use();
        glm::mat4 model;
        view = camera.GetViewMatrix();	
       
        glUniformMatrix4fv(glGetUniformLocation(shader.Program, "model"), 1, GL_FALSE, glm::value_ptr(model));
        glUniformMatrix4fv(glGetUniformLocation(shader.Program, "view"), 1, GL_FALSE, glm::value_ptr(view));
        glUniformMatrix4fv(glGetUniformLocation(shader.Program, "projection"), 1, GL_FALSE, glm::value_ptr(projection));
        // Cubes
        glBindVertexArray(cubeVAO);
        glActiveTexture(GL_TEXTURE0);
        glUniform1i(glGetUniformLocation(shader.Program, "texture_diffuse1"), 0);
        glBindTexture(GL_TEXTURE_2D, cubeTexture);  		
        glDrawArrays(GL_TRIANGLES, 0, 36);
        glBindVertexArray(0);			


        // Swap the buffers
        glfwSwapBuffers(window);

以上僅僅是生成了一個天空盒。還沒有去考慮效率問題,接下來我們就分析一下怎樣利用深度測試去優化,如今我們在渲染場景中的其它物體之前渲染了天空盒。這么做沒錯。可是不怎么高效。假設我們先渲染了天空盒,那么我們就是在為每一個屏幕上的像素執行片段着色器,即使天空盒僅僅有部分在顯示着。fragment能夠使用前置深度測試(early depth testing)簡單地被丟棄,這樣就節省了我們寶貴的帶寬。

所以最后渲染天空盒就能夠給我們帶來輕微的性能提升。

採用這種方式,深度緩沖被全部物體的深度值全然填充,所以我們僅僅須要渲染通過前置深度測試的那部分天空的片段即可了,而且能顯著降低片段着色器的調用。問題是天空盒是個立方體,極有可能會渲染失敗。由於極有可能通只是深度測試。簡單地不用深度測試渲染它也不是解決方式,這是由於天空盒會在之后覆蓋全部的場景中其它物體。我們須要耍個花招讓深度緩沖相信天空盒的深度緩沖有着最大深度值1.0。如此僅僅要有個物體存在深度測試就會失敗,看似物體就在它前面了。

 透視除法(perspective division)是在頂點着色器執行之后執行的。把gl_Position的xyz坐標除以w元素。我們從深度測試教程了解到除法結果的z元素等於頂點的深度值。利用這個信息。我們能夠把輸出位置的z元素設置為它的w元素,這樣就會導致z元素等於1.0了,由於,當透視除法應用后。它的z元素轉換為w/w = 1.0:

void main()
{
    vec4 pos = projection * view * vec4(position, 1.0);
    gl_Position = pos.xyww;
    TexCoords = position;
}

終於。標准化設備坐標就總會有個與1.0相等的z值了。1.0就是深度值的最大值。僅僅有在沒有不論什么物體可見的情況下天空盒才會被渲染(僅僅有通過深度測試才渲染。否則假如有不論什么物體存在,就不會被渲染,僅僅去渲染物體)。

我們必須改變一下深度方程,把它設置為GL_LEQUAL,原來默認的是GL_LESS。深度緩沖會為天空盒用1.0這個值填充深度緩沖。所以我們須要保證天空盒是使用小於等於深度緩沖來通過深度測試的,而不是小於。

優化改動后的代碼例如以下所看到的:

// Clear buffers
        glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        
        
        // Draw scene as normal
        shader.Use();
        glm::mat4 model;
        glm::mat4 view = camera.GetViewMatrix();	
        glm::mat4 projection = glm::perspective(camera.Zoom, (float)screenWidth/(float)screenHeight, 0.1f, 100.0f);
        glUniformMatrix4fv(glGetUniformLocation(shader.Program, "model"), 1, GL_FALSE, glm::value_ptr(model));
        glUniformMatrix4fv(glGetUniformLocation(shader.Program, "view"), 1, GL_FALSE, glm::value_ptr(view));
        glUniformMatrix4fv(glGetUniformLocation(shader.Program, "projection"), 1, GL_FALSE, glm::value_ptr(projection));
        // Cubes
        glBindVertexArray(cubeVAO);
        glActiveTexture(GL_TEXTURE0);
        glUniform1i(glGetUniformLocation(shader.Program, "texture_diffuse1"), 0);
        glBindTexture(GL_TEXTURE_2D, cubeTexture);  		
        glDrawArrays(GL_TRIANGLES, 0, 36);
        glBindVertexArray(0);			

        // Draw skybox as last
        glDepthFunc(GL_LEQUAL);  // Change depth function so depth test passes when values are equal to depth buffer's content
        skyboxShader.Use();		
        view = glm::mat4(glm::mat3(camera.GetViewMatrix()));	// Remove any translation component of the view matrix
        glUniformMatrix4fv(glGetUniformLocation(skyboxShader.Program, "view"), 1, GL_FALSE, glm::value_ptr(view));
        glUniformMatrix4fv(glGetUniformLocation(skyboxShader.Program, "projection"), 1, GL_FALSE, glm::value_ptr(projection));
        // skybox cube
        glBindVertexArray(skyboxVAO);
        glActiveTexture(GL_TEXTURE0);
        glUniform1i(glGetUniformLocation(shader.Program, "skybox"), 0);
        glBindTexture(GL_TEXTURE_CUBE_MAP, cubemapTexture);
        glDrawArrays(GL_TRIANGLES, 0, 36);
        glBindVertexArray(0);
        glDepthFunc(GL_LESS); // Set depth function back to default
        

        // Swap the buffers
        glfwSwapBuffers(window);

利用立方體貼圖還能夠實現環境映射中的反射和折射效果。我們如今有了一個把整個環境映射到為一個單獨紋理的對象,我們利用這個信息能做的不僅是天空盒。

使用帶有場景環境的立方體貼圖,我們還能夠讓物體有一個反射或折射屬性。像這樣使用了環境立方體貼圖的技術叫做環境貼圖技術,當中最重要的兩個是反射(reflection)折射(refraction)

凡是是一個物體(或物體的某部分)反射(Reflect)他周圍的環境的屬性,比方物體的顏色多少有些等於它周圍的環境,這要基於觀察者的角度。

比如一個鏡子是一個反射物體:它會基於觀察者的角度泛着它周圍的環境。

反射的基本思路不難。下圖展示了我們怎樣計算反射向量,然后使用這個向量去從一個立方體貼圖中採樣:


我們基於觀察方向向量I和物體的法線向量N計算出反射向量R。我們能夠使用GLSL的內建函數reflect來計算這個反射向量。

最后向量R作為一個方向向量對立方體貼圖進行索引/採樣。返回一個環境的顏色值。

最后的效果看起來就像物體反射了天空盒。

由於我們在場景中已經設置了一個天空盒,創建反射就不難了。

我們改變一下箱子使用的那個片段着色器。給箱子一個反射屬性:

#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);
}
我們先來計算觀察/攝像機方向向量I,然后使用它來計算反射向量R,接着我們用R從天空盒立方體貼圖採樣。要注意的是,我們有了片段的插值Normal和Position變量,所以我們須要修正頂點着色器適應它。


#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));
}

我們用了法線向量,所以我們打算使用一個法線矩陣(normal matrix)變換它們。Position輸出的向量是一個世界空間位置向量。

頂點着色器輸出的Position用來在片段着色器計算觀察方向向量。

由於我們使使用方法線。你還得更新頂點數據,更新屬性指針。還要確保設置cameraPos的uniform。

然后在渲染箱子前我們還得綁定立方體貼圖紋理:

glBindVertexArray(cubeVAO);
glBindTexture(GL_TEXTURE_CUBE_MAP, skyboxTexture);
glDrawArrays(GL_TRIANGLES, 0, 36);
glBindVertexArray(0);

編譯執行你的代碼,你等得到一個鏡子一樣的箱子。箱子完美地反射了周圍的天空盒:



C++程序實現的核心代碼例如以下所看到的:

 // Clear buffers
        glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        
        // Draw scene as normal
        shader.Use();
        glm::mat4 model;
        glm::mat4 view = camera.GetViewMatrix();	
        glm::mat4 projection = glm::perspective(camera.Zoom, (float)screenWidth/(float)screenHeight, 0.1f, 100.0f);
        glUniformMatrix4fv(glGetUniformLocation(shader.Program, "model"), 1, GL_FALSE, glm::value_ptr(model));
        glUniformMatrix4fv(glGetUniformLocation(shader.Program, "view"), 1, GL_FALSE, glm::value_ptr(view));
        glUniformMatrix4fv(glGetUniformLocation(shader.Program, "projection"), 1, GL_FALSE, glm::value_ptr(projection));
        glUniform3f(glGetUniformLocation(shader.Program, "cameraPos"), camera.Position.x, camera.Position.y, camera.Position.z);
        // Cubes
        glBindVertexArray(cubeVAO);
        glBindTexture(GL_TEXTURE_CUBE_MAP, skyboxTexture);  		
        glDrawArrays(GL_TRIANGLES, 0, 36);
        glBindVertexArray(0);			

        // Draw skybox as last
        glDepthFunc(GL_LEQUAL);  // Change depth function so depth test passes when values are equal to depth buffer's content
        skyboxShader.Use();		
        glm::mat4 view = glm::mat4(glm::mat3(camera.GetViewMatrix()));	// Remove any translation component of the view matrix
        glUniformMatrix4fv(glGetUniformLocation(skyboxShader.Program, "view"), 1, GL_FALSE, glm::value_ptr(view));
        glUniformMatrix4fv(glGetUniformLocation(skyboxShader.Program, "projection"), 1, GL_FALSE, glm::value_ptr(projection));
        // skybox cube
        glBindVertexArray(skyboxVAO);
        glBindTexture(GL_TEXTURE_CUBE_MAP, skyboxTexture);
        glDrawArrays(GL_TRIANGLES, 0, 36);
        glBindVertexArray(0);
        glDepthFunc(GL_LESS); // Set depth function back to default
        

        // Swap the buffers
        glfwSwapBuffers(window);
另外環境映射作用於角色身上的效果例如以下所看到的:



以下介紹反射技術,

環境映射的還有一個形式叫做折射(Refraction),它和反射差點兒相同。折射是光線通過特定材質對光線方向的改變。我們通常看到像水一樣的表面。光線並非直接通過的,而是讓光線彎曲了一點。它看起來像你把半僅僅手伸進水里的效果。

折射遵守斯涅爾定律,使用環境貼圖看起來就像這樣:


我們有個觀察向量I,一個法線向量N,這次折射向量是R。就像你所看到的那樣。觀察向量的方向有輕微彎曲。彎曲的向量R隨后用來從立方體貼圖上採樣。

折射能夠通過GLSL的內建函數refract來實現。除此之外還須要一個法線向量,一個觀察方向和一個兩種材質之間的折射指數。

折射指數決定了一個材質上光線扭曲的數量,每一個材質都有自己的折射指數。下表是常見的折射指數:

材質 折射指數
空氣 1.00
1.33
1.309
玻璃 1.52
寶石 2.42

我們使用這些折射指數來計算光線通過兩個材質的比率。在我們的樣例中,光線/視線從空氣進入玻璃(假設我們假設箱子是玻璃做的)所以比率是1.001.52 = 0.658。

我們已經綁定了立方體貼圖,提供了定點數據,設置了攝像機位置的uniform。如今僅僅須要改變片段着色器:

void main()
{
    float ratio = 1.00 / 1.52;
    vec3 I = normalize(Position - cameraPos);
    vec3 R = refract(I, normalize(Normal), ratio);
    color = texture(skybox, R);
}
通過改變折射指數你能夠創建出全然不同的視覺效果。編譯執行應用,結果也不是太有趣,由於我們僅僅是用了一個普通箱子,這不能顯示出折射的效果。看起來像個放大鏡。

使用同一個着色器,納米服模型卻能夠展示出我們期待的效果:玻璃制物體。



以上都是利用立方體貼圖實現的技術,事實上實現環境映射並不一定必須使用立方體貼圖,也能夠使用一張貼圖實現效果,詳情查看筆者已經介紹過的案例:

Cocos2d-x 3.x 圖形學渲染系列二十二  關於使用一張貼圖實現的環境映射效果,效果例如以下所看到的:





免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM