- 先看效果
- 思路
- 实践
- 准备一个 Cube
- 再准备好 CubeMap(立方体贴图)
- 天空盒子的 Shader
- 效果1
- 在应用层设置传入的视图变化矩阵前,删除移动的量
- 在GLSL shader层移动视图变化矩阵的移动量
- 效果3
- 添加其他几何体看看
- 深度问题
- 效果4
- 效果5
- 天空盒边界接缝处瑕疵问题
- 边界缝隙解决
- 优化天空盒的渲染队列
- References
LearnGL - 学习笔记目录
前些篇:
- LearnGL - 11.1 - 实现简单的Gouraud-Phong光照模型
- LearnGL - 11.2 - 实现简单的Phong光照模型
- LearnGL - 11.3 - 实现简单的Blinn-Phong光照模型
- LearnGL - 11.4 - 实现简单的Flat BlinnPhong光照模型
- LearnGL - 13 - PointLight - 点光源
- LearnGL - 13.1 - SpotLight - 聚光灯
- LearnGL - 14 - MultiLight - 多光源
这些演示光照计算先告一段落。
这一篇:实现 Sky Box (天空盒)
其实参考的学习资料已经学习到后面的大部分了,只不过写文章的速度比较慢,当作是给自己复习。
前几天用 Unity 也做了一个小游戏给家里人玩玩,哈哈,顺便熟悉一下 Unity。
本人才疏学浅,如有什么错误,望不吝指出。
先看效果- 先准备一个 Cube(立方体),作为渲染 Skybox 天空盒的网格
- 再准备好 CubeMap(立方体贴图)
- 在 Shader 中使用 Cube 的 顶点坐标作为方向(从 原点 到 顶点坐标 的方向) 作为对 CubeMap 采样用的方向
使用之前我们自己自定义的网格文件格式:Testing_Skybox.m
#vertices:8
-0.5, 0.5, -0.5
0.5, 0.5, -0.5
0.5, -0.5, -0.5
-0.5, -0.5, -0.5
-0.5, 0.5, 0.5
0.5, 0.5, 0.5
0.5, -0.5, 0.5
-0.5, -0.5, 0.5
#indices:36
0, 1, 2
0, 2, 3
4, 7, 6
4, 6, 5
4, 0, 3
4, 3, 7
5, 6, 2
5, 2, 1
0, 4, 5
0, 5, 1
7, 3, 6
3, 2, 6
#colors:0
#uv:0
#normals:0
#tangents:0
可以看到,vertices
的顶点数量就只有 8 个顶点,还有索引,就没有其他数据了
这些索引可以对应下图的内容:
OK,Mesh 网格数据都准备好了,下一步是纹理
再准备好 CubeMap(立方体贴图)如果用到旧版本的 API 的话,我们需要使用到一些枚举:
纹理目标方位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前 这些枚举值是连续的,所以我们可以遍历的形式去使用,如下旧版 API 代码构建纹理:
int width, height, nrChannels;
unsigned char *data;
for(unsigned int i = 0; i wire_frame = false; // 默认就是非线框模式,不用设置
//mat->enabledCullFace = true; // 默认启用面向剔除 : true,不用设置
mat->cullFace = DrawState_FaceCullingType::Front; // 剔除面向为:正面
//mat->enabledDepthTest = true; // 默认启用深度测试 : true,不用设置
//mat->depthCompare = DrawState_DepthTestingType::Less; // 默认为 Less 不用设置
mat->enabledDepthWrite = false; // 不用写深度,因为本身为最大深度了
主要看两句:
mat->cullFace = DrawState_FaceCullingType::Front; // 剔除面向为:正面
mat->enabledDepthWrite = false; // 不用写深度,因为本身为最大深度了
剔除正面
也不需要些深度,因为天空和只会被挡,而不会挡住其他东西(除非你要制作一些比天空盒还要远的东西,太空?那也只能动态过度不同的天空盒了)
添加上其他的几何体后,发现效果不对了?
天空盒都挡住了我们的几何体了?怎么办呢?
然后我们将相机往前移动里球体和气球猫网格几何体都近一些,会显示部分球体与气球猫的网格
这是因为深度的问题
还记得我们之前看的 sky box 的 cube 的顶点数据吗?
#vertices:8
-0.5, 0.5, -0.5
0.5, 0.5, -0.5
0.5, -0.5, -0.5
-0.5, -0.5, -0.5
-0.5, 0.5, 0.5
0.5, 0.5, 0.5
0.5, -0.5, 0.5
-0.5, -0.5, 0.5
所以明显天空盒渲染出来的深度有些比球体和气球猫几何体的网格的深度要还小导致的
那么我们要想办法将天空盒的深度都渲染到最大的深度
之前说过的,深度的范围:0.0~1.0,深度越小,就越靠近镜头,越大,就越远离镜头
所以我们要想办法将天空盒渲染到最远的深度:1.0的值
shader 如下:
// jave.lin - testing_skybox.vert
#version 450 compatibility
uniform mat4 mMat;
uniform mat4 vMat;
uniform mat4 pMat;
in vec3 vPos;
out vec3 fSkybox_sample_vec;
void main() {
/*
X_x X_y X_z X_o
Y_x Y_y Y_z Y_o
Z_x Z_y Z_z Z_o
0 0 0 1
将 X_o = Y_o = Z_o = 0
矩阵变成
X_x X_y X_z 0
Y_x Y_y Y_z 0
Z_x Z_y Z_z 0
0 0 0 1
*/
// cube 的采样方向
fSkybox_sample_vec = vPos;
// 复制
mat4 new_vMat = vMat;
//将 X_o = Y_o = Z_o = 0
new_vMat[3][0] = new_vMat[3][1] = new_vMat[3][2] = 0;
vec4 outPos = pMat * new_vMat * mMat * vec4(vPos, 1.0);
// 注意我们将 z 改为了 w,即:每个顶点都设置深度最大,因为天空盒都假设是最远的背景内容
// 注意我们的深度范围是:0~1 (最小~最大)
// 为何用w深度就可以为最大呢(1)
// 因为顶点着色器之后,会执行透视除法
// 透视除法:假设 pos(x,y,z,w) 是顶点变化后的坐标点
// 透视除法将之前的坐标除以他第四个分量 w pos /= pos.w; // 相当于:pos.xyzw = pos.xyzw / pos.wwww;
// 为何是第四分量,因为我们故意在 Projection 矩阵的[2][3] 设置为 -1,这样即可获取原本透视前的 z 值,即可:view space 下的 z
// 然后结合齐次坐标的表示:(1,2,3,1)=(2,4,6,2)
// 即可:(x,y,z,w)/w=(x/w,y/w,z/w,w/w)=(x/w,y/w,z/w,1)
// 这就是 齐次坐标 透视除法 的由来
// 那么回想刚刚我们前面设置的:pos = pos.xyww; // 注意后面两个分量是 ww
// 相当于: pos.xyzw = pos.xyww
// 如果:pos.xyzw / pos.wwww = 后面两个参数肯定都是1,因为非零的数除以本身等于1
gl_Position = outPos.xyww;
}
主要查看:
gl_Position = outPos.xyww;
然后详细的说明可以看注释,这里不在重复说明
效果4上面将天空盒的深度设置为 1.0 的越大值后,发现啥都不显示了,如下图:
这是因为我们的默认的深度比较是 Less
的方式导致的
Less
的方式意味着,必须比深度缓存中的值要小,才能通过,而我们的深度缓存值默认每帧渲染前都先清理深度缓存值为 1.0 导致的,这样天空盒渲染的也是深度为 1.0 ,所以天空盒的深度沒比緩存的值要小,而是相等,所以我们可以将天空盒的渲染状态的:深度比较调整为:LEqual
即可,LEqual
是小于、等于的意思(LEqual == Less or Equal)。
那么我们再调整好深度比较方式为:LEqual
(不要用 Equal
,因为会有精度问题,造成类似 z-fighting 的问题),再看看效果:
//mat->wire_frame = false; // 默认就是非线框模式,不用设置
//mat->enabledCullFace = true; // 默认启用面向剔除 : true,不用设置
mat->cullFace = DrawState_FaceCullingType::Front; // 剔除面向为:正面
//mat->enabledDepthTest = true; // 默认启用深度测试 : true,不用设置
//mat->depthCompare = DrawState_DepthTestingType::Less; // 默认为 Less 不用设置
mat->depthCompare = DrawState_DepthTestingType::LEqual;
mat->enabledDepthWrite = false; // 不用写深度,因为本身为最大深度了
效果5
将 mat->depthCompare = DrawState_DepthTestingType::LEqual;
后,运行效果为:
嗯,这看起来还不错的效果!
天空盒边界接缝处瑕疵问题看起来上面的运行情况还想没啥问题了,其实,如果仔细看天空盒的每个面向的边界接缝处,都有一些缝隙,看起来很不舒服,如下图:
这是 底部 的边界缝隙的问题: 这是 顶部 的边界缝隙的问题:
OpenGL 还专门提供了一个 API:
glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
只要在相机渲染对应的 Renderer 之前对其按渲染队列值排序一下即可实现。
我还特地给天空盒添加了一个专用的队列值
一般我们可能会将天空盒在最早的时候就渲染了。
然后其他的几何体可以后面再渲染来挡住原来天空盒渲染过的片元内容。(覆盖深度)
但是这样会浪费效率,所以我们决定将天空和放在了渲染完所有的不透明几何体之后在渲染它。
这样就可以利用之前不透明物体的深度值来剔除天空盒不必要的片段,从而提升性能。
但如果你的不透明几何体需要制作折射效果,那么注意天空盒或是你的环境反射、折射贴图还是得设置会比 Geometry 更早渲染的队列,所以我将 Skybox 该会了 500,比 background 还早一些
References- Cubemap Texture
- 立方体贴图