立方体贴图

立方体贴图其实很好理解,就是将二维的贴图变成立方体的,注意,立方体贴图其实并不是三维贴图,或者说立方体贴图只是三维贴图的特殊形式,因为它的形状恰好是一个正立方体,而一般的三维贴图形状可能并不是固定的。

和二维贴图一样,立方体贴图也是需要用glGenTextures()来创建,但是不再是绑定到GL_TEXTURE2D而是GL_TEXTURE_CUBE_MAP。立方体贴图由于有六个面,所以对于每个面都需要使用glTexImage2D来绑定一张二维贴图,而且六个面都有唯一的标识:

而且和TEXTURE0纹理槽一样,这些面的标识也是连续的,所以就意味着可以用GL_TEXTURE_CUBE_MAP_POSITIVE_X+i来遍历每一个面。

和二维贴图一样,立方体贴图也需要设置纹理的属性,这里特别的是由于有第三个维度,所以在设置环绕的时候,对于z轴也需要设置glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE)。对于二维纹理而言,我们使用的是二维向量来对其取样,所以对于三维我们就需要用三维向量。对于立方贴图而言,中心是在原点坐标上的,然后以任意方向的向量做射线,击中立方体面上的某个像素,从而便完成了贴图的取样。

天空盒

很怀疑立方体贴图就是为设计天空盒而发明的。天空盒顾名思义就是模仿天空的盒子,通过六个面无缝的衔接,让身处其中的玩家感受到被空间包围的感觉。如果只是一个带着贴图的盒子那么直接让立方体贴上纹理就完事了,但是这里要求是六个面无缝衔接,这点二维纹理还真无法实现,所以感觉立方贴图就是为天空盒诞生的。

首先要有一个中心在原点,然后范围是(-1,1)的立方体,这就是天空盒的本体了,然后我们要做的就是为这个“盒子”蒙上一层皮。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#version 330 core

layout(location=0) in vec3 aPos;

out vec3 Texcoord_3d;

uniform mat4 View;
uniform mat4 Projection;


void main(){

vec4 finalpos=Projection*View*vec4(aPos,1.f);
gl_Position=finalpos.xyww;
Texcoord_3d=aPos;

}

这里的顶点着色器很有讲究,首先不需要Model矩阵了,因为天空盒是不能一直动来动去的,然后就是这里并没有输入纹理坐标,因为天空盒的中心也是在坐标原点,所以直接用顶点坐标就可以直接在纹理上采样,所以直接把Pos传给片段着色器作为纹理坐标。

片段着色器就简单很多了,和二维纹理一样,立方体贴图也是用texture()函数来进行取样,只不过纹理坐标变成了三维而已。

1
2
3
4
5
6
7
8
9
10
11
12
13
#version 330 core

in vec3 Texcoord_3d;

uniform samplerCube skybox;

out vec4 FragColor;

void main(){

FragColor=texture(skybox,Texcoord_3d);

}

这里的samplerCube和之前的sampler2D其实都是一样的,都是一个整数,而且在着色器里面一般有很多个,并且之间也都是连续的。至此我们其实已经可以渲染出天空盒了,但是还存在一个问题,我们希望天空盒一直在坐标原点,这样无论玩家如何移动,都会在天空盒里面。但是由于我们的View矩阵是存在位移,也即是第四列,因此我们其实只需要取View矩阵的3X3矩阵即可。所以可以这样处理:

1
View=mat4(mat3(camera.getLooAt()));

这样我们只会得到一个旋转的View矩阵而不会随着视角进行位移了。最后我们渲染出来的天空盒就会是这个样子,就好像我们被包围在了星空之中一样。

天空盒优化

之前我们渲染的渲染顺序是先渲染天空盒,并关闭深度测试,然后打开深度测试,渲染其他物体。这样天空盒永远都是在物体背后充当背景。这样确实可以完成天空盒的任务,但是性能却浪费了很多。试想一个四合院的场景,只有院子中间可以看到天空,其他地方都是墙壁或建筑,如果先渲染天空盒的话,会有很多原本看不到的天空盒也跟着渲染了,所以有什么办法可以避免这种性能的浪费。

我们知道,NDC坐标系里面深度的范围是(-1,1),那么如果我们将天空盒的深度永远都设置为1,那么只要有物体渲染,那么天空盒就不会被渲染,这样我们就可以把天空盒放到最后一个渲染。

那么该如何设置天空盒的深度,也即是z轴坐标呢?我们知道在顶点着色器中完成变化后,openGL还会自动做一次透视除法,即除以w值,那么如果我们强制将z设置成w值,那么做完透视除法后z轴将一直保持为1,这样也就做到了将z天空盒z轴设置为1了。

1
2
vec4 finalpos=Projection*View*vec4(aPos,1.f);
gl_Position=finalpos.xyww;

这样一来,天空盒只有在没有物体的地方才会被渲染出来,在之前那个四合院场景中天空盒便只会在中间的天窗处渲染出来。当然这里我们最后还要设置一个东西,就是深度测试函数,要改为glDepthFunc(GL_LEQUAL)即小于等于深度值时通过,因为默认深度值就是1,如果不加这个,天空盒就无法通过深度测试了。最后添加上光照,一个在星空下的箱子就出现了。