1. 程式人生 > >零基礎學習OpenGL(八)--立方體貼圖、天空盒、環境對映

零基礎學習OpenGL(八)--立方體貼圖、天空盒、環境對映

                                                                                    立方體貼圖

       將多個紋理組合起來對映到一張紋理上的一種紋理型別:立方體貼圖(Cube Map)。
       立方體貼圖:一個包含了6個2D紋理的紋理,每個2D紋理都組成了立方體的一個面:一個有紋理的立方體。之所以使用6個紋理合並在一張紋理而不使用6個單獨的紋理,是因為可以通過一個方向向量來進行索引或採樣。方向向量的原點在立方體的中心。方向向量的大小並不重要,只要提供了方向,OpenGL就會獲取方向向量(最終)所擊中的紋素,並返回對應的取樣紋理值。

        建立立方體貼圖:

         unsigned int textureID;

         glGenTextures(1, &textureID);

         glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);

        立方體貼圖包含有6個紋理,每個面一個,將紋理目標(target

)引數設定為立方體貼圖的一個特定的面,告訴OpenGL我們在對立方體貼圖的哪一個面建立紋理。和OpenGL的很多列舉(Enum)一樣,它們背後的int值是線性遞增的。如下圖:

          int width, height, nrChannels;

          unsigned char *data;

          for(unsigned int i = 0; i < textures_faces.size(); i++)

          {

                  data = stbi_load(textures_faces[i].c_str(), &width, &height, &nrChannels, 0);

                  glTexImage2D( GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data );

           }

         textures_faces的vector,它包含了立方體貼圖所需的所有紋理路徑,並以表中的順序排列。這將為當前繫結的立方體貼圖中的每個面生成一個紋理。

          也需要設定它的環繞和過濾方式:

          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_CLAMP_TO_EDGE,這是因為正好處於兩個面之間的紋理座標可能不能擊中一個面(由於一些硬體限制),所以通過使用GL_CLAMP_TO_EDGE,OpenGL將在我們對兩個面之間取樣的時候,永遠返回它們的邊界值。

         使用立方體貼圖的片段著色器:

         in vec3 textureDir; // 代表3D紋理座標的方向向量

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

         void main()

         {

               FragColor = texture(cubemap, textureDir);

         }

                                                                                                     天空盒

         天空盒是一個包含了整個場景的(大)立方體,它包含周圍環境的6個影象,讓玩家以為他處在一個比實際大得多的環境當中。將這六個面折成一個立方體,你就會得到一個完全貼圖的立方體,模擬一個巨大的場景。一些資源可能會提供了這樣格式的天空盒,你必須手動提取六個面的影象,但在大部分情況下它們都是6張單獨的紋理影象。

         載入天空盒:

         vector<std::string> faces;

          { "right.jpg", "left.jpg", "top.jpg", "bottom.jpg", "front.jpg", "back.jpg" };

         unsigned int cubemapTexture = loadCubemap(faces);

         unsigned int loadCubemap(vector<std::string> faces)

         {

                unsigned int textureID;

                glGenTextures(1, &textureID);

                glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);

                int width, height, nrChannels;

                 for (unsigned int i = 0; i < faces.size(); i++)

                {

                       unsigned char *data = stbi_load(faces[i].c_str(), &width, &height, &nrChannels, 0);

                      if (data)

                       {

                              glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data );

                               stbi_image_free(data);

                       }

                      else

                      {

                                std::cout << "Cubemap texture failed to load at path: " << faces[i] << std::endl;

                                stbi_image_free(data);

                       }

                }

               glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);  

               glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_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);

             return textureID;

           }

顯示天空盒:

          頂點著色器:

           #version 330 core

           layout (location = 0) in vec3 aPos;

           out vec3 TexCoords;

           uniform mat4 projection;  

           uniform mat4 view;

           void main()

            {

                   TexCoords = aPos;

                   gl_Position = projection * view * vec4(aPos, 1.0);

            }

         將輸入的位置向量作為輸出給片段著色器的紋理座標。片段著色器會將它作為輸入來取樣Cube。

         #version 330 core

         out vec4 FragColor;

          in vec3 TexCoords;

          uniform samplerCube skybox;

           void main()

           {

                  FragColor = texture(skybox, TexCoords);

            }

       繪製天空盒時,我們需要將它變為場景中的第一個渲染的物體,並且禁用深度寫入。這樣子天空盒就會永遠被繪製在其它物體的背後了。

         glDepthMask(GL_FALSE);

         skyboxShader.use();

          // ... 設定觀察和投影矩陣

          glBindVertexArray(skyboxVAO);

          glBindTexture(GL_TEXTURE_CUBE_MAP, cubemapTexture);

          glDrawArrays(GL_TRIANGLES, 0, 36);

           glDepthMask(GL_TRUE);

           // ... 繪製剩下的場景

          希望移除觀察矩陣中的位移部分,讓移動不會影響天空盒的位置向量。通過取4x4矩陣左上角的3x3矩陣來移除變換矩陣的位移部分。我們可以將觀察矩陣轉換為3x3矩陣(移除位移),再將其轉換回4x4矩陣,來達到類似的效果。

          glm::mat4 view = glm::mat4(glm::mat3(camera.GetViewMatrix()));

優化:

         我們現在是首先渲染天空盒,之後再渲染場景中的其它物體。如果我們先渲染天空盒,我們就會對螢幕上的每一個畫素執行一遍片段著色器,即便只有一小部分的天空盒最終是可見的。可以使用提前深度測試(Early Depth Testing)輕鬆丟棄掉的片段能夠節省我們很多寶貴的頻寬。

        天空盒只是一個1x1x1的立方體,它很可能會不通過大部分的深度測試,導致渲染失敗。我們需要欺騙深度緩衝,讓它認為天空盒有著最大的深度值1.0,只要它前面有一個物體,深度測試就會失敗。z分量等於頂點的深度值。

        TexCoords = aPos;

         vec4 pos = projection * view * vec4(aPos, 1.0);

         gl_Position = pos.xyww;

      這樣天空盒就只會在沒有物體的地方渲染了。

                                                                                             環境對映

      通過使用環境的立方體貼圖,我們可以給物體反射和折射的屬性。

      根據觀察方向向量I和物體的法向量N,來計算反射向量R。我們可以使用GLSL內建的reflect函式來計算這個反射向量。最終的R向量將會作為索引/取樣立方體貼圖的方向向量,返回環境的顏色值。最終的結果是物體看起來反射了天空盒。

       物體的頂點著色器:   

        layout (location = 0) in vec3 aPos;

         layout (location = 1) in vec3 aNormal;

         out vec3 Normal;

          out vec3 Position;

          uniform mat4 model;

          uniform mat4 view;

          uniform mat4 projection;

          void main()

          {

                   Normal = mat3(transpose(inverse(model))) * aNormal;

                   Position = vec3(model * vec4(aPos, 1.0));

                  gl_Position = projection * view * model * vec4(aPos, 1.0);

          }

       改變物體的片段著色器就可以讓物體有反射性:

        #version 330 core

        out vec4 FragColor;

         in vec3 Normal;

         in vec3 Position;

          uniform vec3 cameraPos;

          uniform samplerCube skybox;

           void main()

           {

                 vec3 I = normalize(Position - cameraPos);

                 vec3 R = reflect(I, normalize(Normal));

                  FragColor = vec4(texture(skybox, R).rgb, 1.0);

           }

         還可以使用反射貼圖,它也是可以取樣的紋理影象,它決定這片段的反射性。

         使用折射可以創建出類玻璃的效果,reflect換為refract就好。

動態環境貼圖:

        通過使用幀緩衝,我們能夠為物體的6個不同角度創建出場景的紋理,並在每個渲染迭代中將它們儲存到一個立方體貼圖中。之後我們就可以使用這個(動態生成的)立方體貼圖來創建出更真實的,包含其它物體的,反射和折射表面了。這就叫做動態環境對映(Dynamic Environment Mapping),因為我們動態建立了物體周圍的立方體貼圖,並將其用作環境貼圖。

 

參考自https://learnopengl-cn.github.io