美文网首页
OpenGL 中正背面剔除、深度测试、ZFighting的理解

OpenGL 中正背面剔除、深度测试、ZFighting的理解

作者: 远方竹叶 | 来源:发表于2020-07-14 18:26 被阅读0次

    在绘制3D场景时,我们所能看到的总是物体的一部分,对于看不到的那部分,实际上是不需要渲染的,全部渲染是很耗费性能的。对于不可⻅的部分,应该及早丢弃,这种做法叫做隐藏⾯消除。

    解决方案

    1. 油画算法

    先绘制场景中的离观察者较远的物体,再绘制较近的物体

    举个例子,我们开发时,会遇到视图叠加,如下图

    我们正常的做法就是先绘制红色部分,再绘制黄色部分,最后绘制灰色部分,就可以解决隐藏面消除的问题。

    弊端

    当我们遇到下面相互重叠这种情况的时候,油画算法就不能处理了

    2. 正背面剔除

    我们平常观察一个物体,无论是 2D 的还是 3D 的,都只能观察到一部分,并不能看到物体的全部。在 OpenGL 中,通过分析顶点数据的顺序,可以做到检查所有正面朝向观察者的面,并渲染它们,背向观察者的那一面就会被丢弃。这样做既能让观察者看到物体的轮廓,又可以提高 OpenGL 在渲染时的性能。

    对于观察者来说,能看到的就是正面,看不到的就是背面。那么对于 OpenGL 怎么来判断呢?

    • 正面:按照逆时针顶点连接顺序的三⻆形⾯
    • 背面:按照顺时针顶点连接顺序的三角形⾯

    这里需要提醒下:正⾯和背⾯不是绝对的,是三角形的顶点定义顺序和观察者方向共同决定的。若观察者的观察⽅向发生改变,正⾯和背面也会发生相应的改变。

    正背面剔除的代码使用:

    //开启表面剔除(默认背面剔除)
    glEnable(GL_CULL_FACE);
    
    //关闭表面剔除(默认背面剔除)
    glDisable(GL_CULL_FACE);
    
    //选择剔除那个面(正面/背面)
    // mode参数为: GL_FRONT, GL_BACK, GL_FRONT_AND_BACK,默认GL_BACK
    glCullFace(GLenum mode);
    

    弊端

    当出现两个面都是正/反面的时候,这时候 OpenGL 无法区分哪个面在前,哪个面在后,就会出现问题。例如案例中的甜甜圈:

    3. 深度测试

    3.1深度

    就是像素点的 Z 坐标轴距离观察者的距离。如果观察者在 Z 轴的正方向,Z 值越大则越靠近观察者;如果观察者在 Z 轴的负方向,Z 值越小则越靠近观察者

    3.2 深度缓冲区(DepthBuffer)

    深度缓存区,存储在显存中,专门存储每个像素点距离观察者平面(近裁剪面)的深度值(一对一关联存储)。

    3.3 深度测试

    深度缓冲区和颜⾊缓存区是对应的。颜⾊缓存区存储像素的颜⾊信息,而深度缓冲区存储像素的深度信息。OpenGL 进行绘制时,首先会将对应的像素的深度值与深度缓冲区中的深度值进行比较,如果大于深度缓冲区中的值,则会丢弃这一部分,不进行绘制;反之,则会获取像素对应的颜色信息和深度值,分别更新颜色缓冲区和深度缓冲区。

    3.4 深度值的计算

    深度值,⼀般由16位、24位或者32位值表示,通常是24位。

    • 位数越高,深度的精确度越好。
    • 深度值的范围在[0,1]之间,值越⼩表示越靠近观察者,值越大表示远离观察者。
    3.5 使用
    • 开启深度测试
    glEnable(GL_DEPTH_TEST);
    
    • 在绘制场景前,如果使用到了深度缓冲,需要清除深度缓冲。清除深度缓冲区默认值为1.0。
    glClear(GL_DEPTH_BUFFER_BIT);
    
    • 指定深度测试判断模式
    /**
     GL_ALWAYS  总是通过测试
     GL_NEVER   总是不通过测试
     GL_LESS    在当前深度值 < 存储的深度值时通过
     GL_GREATER 在当前深度值 > 存储的深度值时通过
     GL_EQUAL   在当前深度值 == 存储的深度值时通过
     GL_LEQUAL  在当前深度值 <= 存储的深度值时通过
     GL_GEQUAL  在当前深度值 >= 存储的深度值时通过
     GL_NOTEQUAL在当前深度值 != 存储的深度值时通过
     */
    void glDepthFunc(GLEnum mode);
    
    • 深度缓冲区写入开关
    //value: GL_TURE,开启深度缓冲区写入; GL_FALSE,关闭深度缓冲区写⼊
    void glDepthMask(GLBool value);
    

    4. ZFighting闪烁问题

    4.1 原因

    由于精度的限制,对于相差非常小的深度值(比如在同一个深度进行2次渲染),就可能出现不能正确区分两个深度值的问题,导致测试的结果随机出现。所以,显示时2个画⾯交错出现,就会出现闪烁问题。

    4.2 解决办法
    1. 启用Polygon Offset

    增大重叠或深度值接近的2个图形的深度值差距,使得OpenGL可以区分两个深度值。

    /**
         GL_POLYGON_OFFSET_FILL   对应的光栅化模式GL_FILL
         GL_POLYGON_OFFSET_LINE   对应的光栅化模式GL_LINE
         GL_POLYGON_OFFSET_POINT  对应的光栅化模式GL_POINT
         */
    glEnable(GL_POLYGON_OFFSET_FILL);
    
    1. 指定偏移量

    通过 glPolygonOffset 来指定2个参数 factor 和 units。offset为负值,将使得z值距离摄像机更近;⽽正值,将使得z值距离摄像机更远。 一般而言,我们设置factor和units设置为-1.0和-1.0。

    //应⽤到⽚段上总偏移计算⽅程式
    //Depth Offset = (DZ * factor) + (r * units);
    //DZ:深度值(Z值)
    //r:使得深度缓冲区产⽣变化的最⼩值,是由具体OpenGL平台指定的⼀个常量
    void glPolygonOffset(Glfloat factor, Glfloat units);
    
    1. 关闭Polygon Offset
    // 参数和开启的参数相同
    glDisable(GL_POLYGON_OFFSET_FILL);
    
    1. ZFighting 预防
    • 不要将两个物体靠的太近。
    • 尽可能将近裁剪面设置得离观察者远一些。
    • 使用更高位数的深度缓冲区。

    完整代码见 Github - OpenGL_doughnut

    相关文章

      网友评论

          本文标题:OpenGL 中正背面剔除、深度测试、ZFighting的理解

          本文链接:https://www.haomeiwen.com/subject/ieefhktx.html