Cubemaps 立方体贴图
LearnOpenGL 顺序:Advanced OpenGL → Cubemaps · 原教程
先抓住唯一的新东西
普通 2D Texture:
vec2 UV → sampler2D → color
Cubemap:
vec3 direction → samplerCube → 自动命中六个面之一 → color
所以 Cubemap 最重要的变化不是“纹理变成六张”,而是索引方式从 2D 坐标变成了空间方向。方向向量的长度不重要,朝哪边才重要。
uniform samplerCube skybox;
vec3 color = texture(skybox, direction).rgb;六个面被放进同一个 GL_TEXTURE_CUBE_MAP 对象:
glBindTexture(GL_TEXTURE_CUBE_MAP, cubemapTexture);
for (unsigned int i = 0; i < 6; ++i)
{
glTexImage2D(
GL_TEXTURE_CUBE_MAP_POSITIVE_X + i,
0, GL_RGB, width, height, 0,
GL_RGB, GL_UNSIGNED_BYTE, data
);
}通常还会对 S / T / R 三个方向都设 GL_CLAMP_TO_EDGE,减少六个面边缘采样时的接缝。
Skybox 为什么能工作
Skybox 只是一个把相机包在里面的 Cube。它的顶点位置本身就是从原点指向 Cube 表面的方向,因此可以直接拿来当 cubemap sampling direction:
TexCoords = aPos;
FragColor = texture(skybox, TexCoords);但我们不希望“向前走几米,天空也靠近几米”。所以 Skybox 使用 Camera 的旋转,却去掉 View Matrix 的平移:
glm::mat4 view = glm::mat4(glm::mat3(camera.GetViewMatrix()));结果是:你可以转头看不同方向,但永远走不到天空盒边缘。
教程进一步把 Skybox 放到最后画,并在 Vertex Shader 中:
vec4 pos = projection * view * vec4(aPos, 1.0);
gl_Position = pos.xyww;透视除法后 z = w / w = 1.0,也就是最远深度;配合:
glDepthFunc(GL_LEQUAL);Skybox 只会填那些前面没有真实物体的像素。
Environment Mapping:同一个 Cubemap,不只是背景
反射真正做的是:
Camera → Fragment 的入射方向 I + Surface Normal N → reflect → R → samplerCube
vec3 I = normalize(Position - cameraPos);
vec3 R = reflect(I, normalize(Normal));
FragColor = vec4(texture(skybox, R).rgb, 1.0);所以镜面物体不是“把 Skybox 图片贴到表面”,而是每个 fragment 根据自己的法线和观察方向,算一个不同的环境采样方向。
折射也一样,只是把 reflect 换成 refract:
float ratio = 1.0 / 1.52; // air → glass
vec3 I = normalize(Position - cameraPos);
vec3 R = refract(I, normalize(Normal), ratio);
FragColor = vec4(texture(skybox, R).rgb, 1.0);它在 Pipeline Map 哪里
Cubemap 不是新的固定渲染阶段。它是一种 Texture Resource,通常在 Fragment Shader 中通过 samplerCube 被采样。
Geometry → Rasterization → Fragment Shader ↔ Cubemap Texture → Depth/Blend → Framebuffer
这也是为什么这节的 Pipeline Map 把 Cubemap 画成 Fragment Shader 的侧边资源,而不是硬塞进线性管线。
Interactive Lab
主 Lab 直接加载 LearnOpenGL 官方 resources/textures/skybox 六张图,并提供:
Reflection / Refraction / Normal Debug- Sphere / Cube 切换
- 折射率 IOR 调节
- 把 Camera Translation / Rotation 拆开,直接观察为什么 Skybox 必须移除 View Translation
Skybox Last + pos.xyww + LEQUAL的 Fragment 工作量示意- Direction Sampling Microscope:拖动
(x,y,z),看方向到底命中+X/-X/+Y/-Y/+Z/-Z哪一面
额外 Lab 专门做教材后面的两个练习:
- Reflection Map:用一张 2D Mask 控制同一个物体“哪里反射、反射多少”,避免整个模型都变成镜子。
- Dynamic Environment Map:在镜面球周围放动态物体;Static 模式下它们不会出现在环境反射里,Dynamic 模式则会从球心朝六个方向实时渲染进 Cubemap,再让球采样这张动态环境贴图。
和上一节 Framebuffer 串起来
静态 Cubemap 只包含预先拍好的环境,不包含场景里会动的物体。
如果想让镜面物体真的反射周围动态对象,可以把上一节 FBO 拿回来:从物体位置朝 +X/-X/+Y/-Y/+Z/-Z 把场景渲染六次,分别写入 Cubemap 六个面,再让物体采样这个动态 Cubemap。
这就是 Dynamic Environment Mapping。效果更真实,但代价也很直观:一个环境探针可能意味着额外 6 次场景渲染。