OpenGL:Cube Map
前面绘制天空盒的时候有提到过 Cube Map,但是 Cube Map 又是什么呢?
Cube Map 其实是就是一个包含了6个2D纹理的纹理,每个2D纹理都组成了立方体的一个面:一个有纹理的立方体。与2D纹理坐标 (s, t) 不同的是,我们使用3个维度 (s, t, r) 来对 Cube Map 进行索引定位,并且 Cube Map 有个特性,其纹理坐标等于它的方向坐标。就是我们使用单位立方体,中心为原点,某个点的坐标就等于它的纹理坐标。
Cube Map 不只是能够用来创建天空盒,还能够用来制作环境反射和折射等效果。
6个纹理面:
每个面对应OpenGL特定的目标:
| 枚举常量 | 方向 |
|---|---|
| GL_TEXTURE_CUBE_MAP_POSITIVE_X | +X (右) |
| GL_TEXTURE_CUBE_MAP_NEGATIVE_X | -X (左) |
| GL_TEXTURE_CUBE_MAP_POSITIVE_Y | +Y (上) |
| GL_TEXTURE_CUBE_MAP_NEGATIVE_Y | -Y (下) |
| GL_TEXTURE_CUBE_MAP_POSITIVE_Z | +Z (后/远) |
| GL_TEXTURE_CUBE_MAP_NEGATIVE_Z | -Z (前/近) |
和2D纹理创建类似,我们需要读取纹理数据,然后将其绑定到纹理对象上面去。不同的是,我们创建的是 GL_TEXTURE_CUBE_MAP 为目标的纹理对象,并且需要给6个不同的目标设置不同的纹理。下面是创建 Cube Map 的伪代码:
GLuint textureID;
glGenTexture(1, &textureID);
glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);const GLenum targets[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
};for(int i = 0; i < 6; ++i) {// 读取纹理数据data = load_image(faces[i]); // faces 是纹理文件的名字// 设置纹理数据glTexImage2D(targets[i], 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
}// 设置纹理参数
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);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
根据上面的流程我们就创建好了一个 Cube Map 对象,下面我们将使用这个对象来进行天空盒的制作。
和skybox一样,我们需要制作shader进行天空盒的绘制:
// skybox.vert
#version 440
layout (location = 0) in vec3 aPostion;out vec3 TexCoord;uniform mat4 projection;
uniform mat4 view;void main()
{TexCoord = aPostion;gl_Position = projection * view * vec4(aPostion, 1.0f);gl_Position = gl_Position.xyww;
}
在这个 vertex shader 中,我们将原先外部传入的纹理坐标换成了使用立方体的位置坐标作为纹理坐标(这里需要注意的是,我使用的是单位大小的立方体,如果你使用不是单位大小的,你需要进行标准化)。
// skybox.frag
#version 440
out vec4 fragColor;in vec3 TexCoord;layout (binding = 0) uniform samplerCube skybox;void main()
{fragColor = texture(skybox, TexCoord);
}
在 fragment shader 中,注意到使用的sampler是专门对cubemap进行采样的 samplerCube,其使用方法和sampler2D 一样,就不赘述了。
和之前同样的绘制流程:渲染其他物体 -> 禁止深度写入 -> 渲染天空盒 -> 重置深度状态。
// 1. 首先生效的物体
renderScene(window, mainShader, currentTime, true); // 2. 天空盒配置(在最后渲染)
glDepthMask(GL_FALSE); // 禁用深度写入
glDepthFunc(GL_LEQUAL); // 启用小于等于深度测试skybox.use();
skybox.setMat4("projection", pMat);// 消位移,只保留旋转(防止近裁剪面穿帮)
glm::mat4 view = glm::mat4(glm::mat3(vMat));
skybox.setMat4("view", view);// 3. 绘制天空盒
glBindVertexArray(VAO);
cubemap.bind();
glDrawArrays(GL_TRIANGLES, 0, 36); // 4. 重置深度状态
glDepthMask(GL_TRUE);
glDepthFunc(GL_LESS);

