可以想象一下,如果我们有一张可以将摄像机整个包住的大纹理,那么不管摄像机怎么转都能够看到对应的像素。很容易联想到,我们可以用它来实现不会发生变化的场景,例如远处的山,填上的太阳,不会动的云等等。
但是,这个纹理应该是怎样的呢?OpenGL里有立方体贴图,他可以实现这个功能。为什么不用球,我认为应该是立方体足够使用了,并且切图更加方便,使用起来也方便。虽然可能造成面中心的点与边上的点到球心的距离不一样导致的显示效果不一致,但是只要分辨率足够,我们是很难看出那种差距的。
创建立方体贴图
和创建普通的纹理一样,我们创建,然后绑定立方体贴图。
unsigned int textureID;
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);
不同的是,立方体贴图有六个面,我们需要为六个面分别加载纹理数据。这六个面分别是
纹理目标 | 方位 |
---|---|
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 | 前 |
幸运的是,GL_TEXTURE_CUBE_MAP_POSITIVE_X 下面的值都与上一个偏移1,我们可以使用循环访问。
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
不要被GL_TEXTURE_WRAP_R吓到,它仅仅是为纹理的R坐标设置了环绕方式,它对应的是纹理的第三个维度(和位置的z一样)。我们将环绕方式设置为GL_CLAMP_TO_EDGE,这是因为正好处于两个面之间的纹理坐标可能不能击中一个面(由于一些硬件限制),所以通过使用GL_CLAMP_TO_EDGE,OpenGL将在我们对两个面之间采样的时候,永远返回它们的边界值。
-
在shader里 ,立方体贴图的采样器是samplerCube,采样时的第二个参数为点的方向。
-
在绘制立方体贴图时,八个顶点的位置也就是它在摄像机坐标系的的方向。
-
因为天空盒是一直以摄像机为原点,所以不需要模型矩阵。
-
需要注意的一点是,我们假设天空盒是无限大的,所以摄像机的运动对天空盒的场景不应该有位移变化,所以,传入的摄像机矩阵不应该包含摄像机的位移。
#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);
}
#version 330 core
out vec4 FragColor;
in vec3 TexCoords;
uniform samplerCube skybox;
void main()
{
FragColor = texture(skybox, TexCoords);
}
优化
天空盒非常大,每个绘制循环都把天空盒所有面都画到难免浪费性能,所以我们可以使用提前深度测试丢弃不需要绘制的天空盒。如果我们先绘制天空盒再绘制物体,就会绘制天空盒的所有片元。
-
提前深度测试是指在片元着色器之前执行的深度测试,一般我们说的深度测试是指在片 元着色器—模板测试—深度测试-Blend 中的深度测试。也就是说,只要我们先绘制了其他物体,接下来绘制天空盒的时候就会自动使用提前深度测试。
-
由于天空盒无限大,也即无限远,所以我们需要将天空盒像素的点的Z值置为1.0表示无限远,gl_Position = pos.xyww;(w等于1.0).
-
由于先绘制了物体,这时候深度缓冲中物体之外的地方都是无限远(1.0),而天空盒的z值又都是无限远1.0,所以需要调整深度函数为GL_LEQUAL(小与等于),这样天空盒就会绘制上了。
环境映射
这里的环境映射包括反射和折射,实现方法是:假设摄像机在物体某个像素处看到的颜色,那么方向由反射和折射角确定,反射和折射角则由摄像机坐标和物体表面的像素坐标,法线,反射(折射)率确定。
动态环境贴图
目的:如果场景中有多个物体,这些多个物体都是镜面的,基于之前的环境映射,那么只能将天空盒的纹理映射到镜面上,那么镜面上肯定不会有其他的物体。为了解决这个问题。
策略:在绘制那个镜面前,使用帧缓冲,先将其他物体和天空盒绘制出来,将绘制在帧缓冲中的纹理当做天空盒的纹理。
缺点:当有多个镜面物体时仍然难以在镜面中显示镜面物体。需要为使用环境贴图的物体渲染场景6次,性能消耗大。