天天看點

OpenGL學習總結(八)

進階OpenGL總結(三)

六、立方體貼圖

1、建立立方體貼圖

a、立方體貼圖和其他紋理一樣,是以要建立一個立方體貼圖,在進行任何紋理操作之前,需要生成一個紋理,激活相應紋理單元然後綁定到合适的紋理目标上。這次要綁定到 GL_TEXTURE_CUBE_MAP紋理類型:

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

由于立方體貼圖包含6個紋理,立方體的每個面一個紋理,我們必須調用glTexImage2D函數6次,函數的參數和前面教程講的相似。然而這次我們必須把紋理目标(target)參數設定為立方體貼圖特定的面,這是告訴OpenGL我們建立的紋理是對應立方體哪個面的。是以我們便需要為立方體貼圖的每個面調用一次 glTexImage2D。

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

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  前           

c、和很多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
    );
}           

d、這兒我們有個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);           

e、在片段着色器中,我們必須使用一個不同的采樣器——samplerCube,用它來從texture函數中采樣,但是這次使用的是一個vec3方向向量,取代vec2。下面是一個片段着色器使用了立方體貼圖的例子:

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

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

void main()
{
    color = texture(cubemap, textureDir);
}

           

2、具體應用——天空盒

a、加載天空盒

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

b、顯示天空盒

頂點着色器:

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

片斷着色器:

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

uniform samplerCube skybox;

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

主程式:

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           

3、環境映射

我們現在有了一個把整個環境映射到為一個單獨紋理的對象,我們利用這個資訊能做的不僅是天空盒。使用帶有場景環境的立方體貼圖,我們還可以讓物體有一個反射或折射屬性。像這樣使用了環境立方體貼圖的技術叫做環境貼圖技術,其中最重要的兩個是反射(reflection)和折射(refraction)。

a、反射

凡是是一個物體(或物體的某部分)反射(Reflect)他周圍的環境的屬性,比如物體的顔色多少有些等于它周圍的環境,這要基于觀察者的角度。例如一個鏡子是一個反射物體:它會基于觀察者的角度泛着它周圍的環境。

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

OpenGL學習總結(八)

我們基于觀察方向向量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));
}           

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

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

b、折射

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

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

OpenGL學習總結(八)

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

折射可以通過GLSL的内建函數refract來實作,除此之外還需要一個法線向量,一個觀察方向和一個兩種材質之間的折射指數。

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

空氣  1.00
水   1.33
冰   1.309
玻璃  1.52
寶石  2.42           

我們使用這些折射指數來計算光線通過兩個材質的比率。在我們的例子中,光線/視線從空氣進入玻璃(如果我們假設箱子是玻璃做的)是以比率是1.00/1.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);
}           

繼續閱讀