我的OpenGL学习进阶之旅关于OpenGL ES 开启深度测试,直接黑屏的问题的解决方法

Posted 字节卷动

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了我的OpenGL学习进阶之旅关于OpenGL ES 开启深度测试,直接黑屏的问题的解决方法相关的知识,希望对你有一定的参考价值。

一、问题描述

今日,写了个OpenGL的小程序,发现场景直接黑屏。

二、排查问题

2.1 怀疑createProgram链接着色器出了问题

看日志

  1. 成功加载了顶点着色器和片段着色器
  2. 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);

但是为什么呢? 我们接下来分析。

四、知其然知其所以然

本节内容参考自下面两篇博客:

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_TESTglEnable来启用深度测试。因为深度测试默认是关闭的,所以必须得调用下面的方法才能启动深度测试

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)是指两个平面(或三角形)相互平行且靠近的过于紧密,模板缓冲区不具有足够的精度确定哪一个平面靠前,导致这两个平面的内容不断交替显示,看上去像平面内容争夺顶靠前的位置。

防止深度冲突的方法:

  1. 不要让物体之间靠得过近,以免它们的三角形面片发生重叠;
  2. 把近平面设置得远一些(越靠近近平面的位置精度越高);
  3. 牺牲一些性能,使用更高精度的深度值。

4.3 开启深度测试的注意事项

  1. 使用深度测试,最常见的错误是没有使用glEnable(GL_DEPTH_TEST);
  2. 开启深度测试,或者没有使用glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);清除深度缓冲区。
  3. 与深度缓冲区相关的另一个函数是glDepthMask,它的参数是布尔类型,GL_FALSE将关闭缓冲区写入,默认是GL_TRUE,开启了深度缓冲区写入。
  4. 注意防止深度冲突

五、总结

我们一开始启用深度测试,直接黑屏的原因就是因为没有使用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 绘制纹理,因为加载纹理坐标设置错误,导致纹理无法渲染的问题

我的OpenGL学习进阶之旅OpenGL ES 着色语言 (下)

我的OpenGL学习进阶之旅OpenGL ES 着色语言 (下)