我的OpenGL学习进阶之旅关于OpenGL ES 开启深度测试,直接黑屏的问题的解决方法
Posted 字节卷动
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了我的OpenGL学习进阶之旅关于OpenGL ES 开启深度测试,直接黑屏的问题的解决方法相关的知识,希望对你有一定的参考价值。
一、问题描述
今日,写了个OpenGL的小程序,发现场景直接黑屏。
二、排查问题
2.1 怀疑createProgram链接着色器出了问题
看日志
- 成功加载了顶点着色器和片段着色器
- createProgram也是正常的,没有异常打印
正常创建了Program,也没有异常打印,所以应该不是这里的问题
2.2 怀疑是不是没有draw()?
加上日志打印,排查draw() 方法有没有正常执行,如何
日志在正常打印,说明在不停的绘制,但是为啥不停的绘制还是黑屏?还得继续排查
2.3 对比以前写的可以正常显示的其他Demo代码
我对比了之前可以正常显示的其他Demo的代码,发现我这个Demo使用了
glEnable(GL_DEPTH_TEST);
开启了深度测试,而其他的代码是没有开启深度测试的
有异常的代码如下所示:
void BasicLightingSample::draw()
LOGD("BasicLightingSample::Draw()")
if (mProgram == GL_NONE || m_TextureId == GL_NONE)
LOGD("BasicLightingSample::Draw() mProgram == GL_NONE || m_TextureId == GL_NONE")
return;
// Use the program object
glUseProgram(mProgram);
glEnable(GL_DEPTH_TEST);
// ... 其他绘制逻辑
三、解决问题
3.1 去掉glEnable(GL_DEPTH_TEST);
我们直接将代码glEnable(GL_DEPTH_TEST);
去掉,如下所示:
void BasicLightingSample::draw()
LOGD("BasicLightingSample::Draw()")
if (mProgram == GL_NONE || m_TextureId == GL_NONE)
LOGD("BasicLightingSample::Draw() mProgram == GL_NONE || m_TextureId == GL_NONE")
return;
// Use the program object
glUseProgram(mProgram);
// glEnable(GL_DEPTH_TEST);
// ... 其他绘制逻辑
发现此时有画面,但是画面会有异常,如下所示:
我们可以发现,随着手指触摸它,让它不停旋转,之前每一次旋转绘制的帧都没有清除掉, 很难看!
但是至少画面出来了,看来是因为开启深度测试导致的黑屏,我们继续排查为啥开启深度测试会导致黑屏呢?
3.3 加上 glClear(GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
我们在上一步去掉 glEnable(GL_DEPTH_TEST);
的情况下,加上代码
glClear(GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
完整代码如下:
void BasicLightingSample::draw()
LOGD("BasicLightingSample::Draw()")
if (mProgram == GL_NONE || m_TextureId == GL_NONE)
LOGD("BasicLightingSample::Draw() mProgram == GL_NONE || m_TextureId == GL_NONE")
return;
// Use the program object
glUseProgram(mProgram);
glClear(GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// glEnable(GL_DEPTH_TEST);
// ... 其他绘制逻辑
运行的效果也有异常,如下所示:
这一次比上一次好了,没有那么多历史的帧残留在绘制。但是光照效果还是有异常,我们加上深度测试看一看。
这是因为:如果关闭深度测试,OpenGL只根据绘制的先后顺序决定显示结果。那么后绘制的平面遮挡了一部分先绘制的本应该显示出来的立方体,这种效果是不符合实际的。
3.4 加上 glClear(GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
和 glEnable(GL_DEPTH_TEST);
这一次我们不光加上 glClear(GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
,我们还加上 glEnable(GL_DEPTH_TEST);
如下所示:
void BasicLightingSample::draw()
LOGD("BasicLightingSample::Draw()")
if (mProgram == GL_NONE || m_TextureId == GL_NONE)
LOGD("BasicLightingSample::Draw() mProgram == GL_NONE || m_TextureId == GL_NONE")
return;
// Use the program object
glUseProgram(mProgram);
glClear(GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnable(GL_DEPTH_TEST);
// ... 其他绘制逻辑
这一次,我们可爱的超越妹妹终于正常显示了
不停旋转,画面也正常,如下所示:
看来,下面的两行代码必须搭配使用才可以正常。
glClear(GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnable(GL_DEPTH_TEST);
但是为什么呢? 我们接下来分析。
四、知其然知其所以然
本节内容参考自下面两篇博客:
- OpenGL-深度测试 https://www.it610.com/article/1409069347401465856.htm
- LEARN OPENGL 深度测试 https://zhuanlan.zhihu.com/p/369781131
- NDK OpenGL ES 3.0 开发(十):深度测试 https://blog.csdn.net/Kennethdroid/article/details/101709694
4.1 什么是深度缓冲区?
深度缓冲区(Detph buffer
)同颜色缓冲区(color buffer
)是对应的,
颜色缓冲区存储的像素的颜色信息,
而深度缓冲区存储像素的深度信息。
4.1.1 了解深度
深度其实就是该像素点在3D世界中距离摄像机的距离,z值
4.1.2 思考下,为什么要使用深度缓冲区?
-
在不使用深度测试的时候
如果我们先绘制一个距离比较近的物理再绘制距离较远的物理,则距离远的位图因为后绘制,会把距离近的物体覆盖掉 -
有了深度缓冲区后
绘制物体的顺序就不那么重要的。实际上,只要存在深度缓冲区OpenGL都会把像素的深度值写入到缓冲区中除非调用glDepthMask(GL FALSE).来禁止写入。
4.1.3 究竟什么是深度缓冲区?
其实深度缓冲区,就是一块内存区域,专门存储着每个像素点(绘制在屏幕上的)深度值,深度值(Z值)越大,则离摄像机就越远。
深度缓冲区一般由窗口管理系统来创建,深度值一般由16位,24位或者32位值表示,通常是24位。当然位数越高的话,深度的精确度越好
4.2 什么是深度测试?
在决定是否绘制一个物体的表面时,首先将表面对应像素的深度值
与当前深度缓冲区中的值
进行比较
,
如果大于等于深度缓冲区中值,则丢弃这部分;
否则利用这个像素对应的深度值和颜色值,分别更新深度缓冲区和颜色缓冲区。
这一过程称之为深度测试(Depth Testing)
。
深度缓冲区通常用于隐藏表面的消除。
传统上,它保存渲染表面上每个像素与视点最近物体的距离值,对于每个新的输入片段,将其与视点的具体和存储值比较。
默认情况下,如果输入片段的深度值小于深度缓冲区中保存的值(意味着它离观看者更近),则 输入片段的深度值代替保存在深度缓冲区中的值,然后其颜色值代替颜色缓冲区中的颜色值。
4.2.1 启用深度测试
这是深度缓冲区的标准方法,如果这就是你想做的,那么调用带GL_DEPTH_TEST
的glEnable
来启用深度测试。因为深度测试默认是关闭的,所以必须得调用下面的方法才能启动深度测试
glEnable(GL_DEPTH_TEST);
当它启用的时候,如果一个片段通过了深度测试的话,OpenGL会在深度缓冲中储存该片段的z值;如果没有通过深度缓冲,则会丢弃该片段。
如果你启用了深度缓冲,你还应该在每个渲染迭代之前使用GL_DEPTH_BUFFER_BIT
来清除深度缓冲
,否则你会仍在使用上一次渲染迭代中的写入的深度值: 这也就是我们为什么一开始黑屏的原因。
所以我们 需要在绘制场景前,清除颜色缓冲区时,清除深度缓冲区:
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
4.2.2 深度缓冲区的深度值
清除深度缓冲区的默认值是1.0,表示最大的深度值,深度值的范围在[0,1]之间,值越小表示越靠近观察者,值越大表示远离观察者。
在进行深度测试时,当前深度值和深度缓冲区中的深度值,进行比较的函数,可以由用户通过glDepthFunc
指定,这个函数包括一个参数
void glDepthFunc (GLenum func);
深度测试启用后,默认情况下深度测试函数使用 GL_LESS
,这将丢弃深度值高于或等于当前深度缓冲区的值的片段。
例如我们可以使用GL_AWALYS
参数,这与默认不开启深度测试效果是一样的:
glDepthFunc(GL_ALWAYS);
4.3 深度测试中的深度冲突现象
深度测试中,深度冲突现象需要值得注意。深度冲突(Z-fighting)是指两个平面(或三角形)相互平行且靠近的过于紧密,模板缓冲区不具有足够的精度确定哪一个平面靠前,导致这两个平面的内容不断交替显示,看上去像平面内容争夺顶靠前的位置。
防止深度冲突的方法:
- 不要让物体之间靠得过近,以免它们的三角形面片发生重叠;
- 把近平面设置得远一些(越靠近近平面的位置精度越高);
- 牺牲一些性能,使用更高精度的深度值。
4.3 开启深度测试的注意事项
- 使用深度测试,最常见的错误是没有使用
glEnable(GL_DEPTH_TEST);
- 开启深度测试,或者没有使用
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
清除深度缓冲区。 - 与深度缓冲区相关的另一个函数是
glDepthMask
,它的参数是布尔类型,GL_FALSE
将关闭缓冲区写入,默认是GL_TRUE
,开启了深度缓冲区写入。 - 注意防止深度冲突
五、总结
我们一开始启用深度测试,直接黑屏的原因就是因为没有使用glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
清除深度缓冲区。
因为如果不使用glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
清除深度缓冲区,那么就会仍在使用上一次渲染迭代中的写入的深度值。
而深度测试(Depth Testing)
过程,在决定是否绘制一个物体的表面时,首先将表面对应像素的深度值
与当前深度缓冲区中的值
进行比较
,
如果大于等于深度缓冲区中值,则丢弃这部分;
否则利用这个像素对应的深度值和颜色值,分别更新深度缓冲区和颜色缓冲区。
所以,这样值不变,就会直接被丢弃,所以就黑屏了!
因此,我们使用深度测试的时候,最好如下面所示一样搭配使用:
glClear(GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnable(GL_DEPTH_TEST);
以上是关于我的OpenGL学习进阶之旅关于OpenGL ES 开启深度测试,直接黑屏的问题的解决方法的主要内容,如果未能解决你的问题,请参考以下文章
我的OpenGL学习进阶之旅关于OpenGL ES 开启深度测试,直接黑屏的问题的解决方法
我的OpenGL学习进阶之旅关于OpenGL ES 开启深度测试,直接黑屏的问题的解决方法
我的OpenGL学习进阶之旅关于OpenGL ES 绘制纹理,因为加载纹理坐标设置错误,导致纹理无法渲染的问题
我的OpenGL学习进阶之旅关于OpenGL ES 绘制纹理,因为加载纹理坐标设置错误,导致纹理无法渲染的问题