零基础学习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

猜你喜欢

转载自blog.csdn.net/jfy307596479/article/details/84650530