美文网首页
OpenGL 渲染技巧:深度测试

OpenGL 渲染技巧:深度测试

作者: 打碟的DJ | 来源:发表于2020-07-23 21:14 被阅读0次

    深度

    深度就是在 openGL 坐标系中,像素点的 Z 坐标距离观察者的距离;

    • 观察者可以放在任意位置。所以不能简单的说 Z 数值越大或越小,观察者就越靠近物体;
    • 如果观察者在 Z 轴正方向,则 Z 值越大越靠近观察者
    • 如果观察者在 Z 轴负方向,则 Z 值越小越靠近观察者

    深度缓冲区

    深度缓冲区,其实就是一块内存区域(显存),专门存储着每个像素点(绘制在屏幕上)的深度值。

    为什么要使用深度缓冲区

    在不适用深度缓冲区的时候,我们先绘制一个距离比较近的视图,在绘制距离比较远的视图,距离较远的视图会覆盖距离较近的视图。有了深度缓冲区后,绘制视图的顺序就不那么重要了。

    实际上,只要存在深度缓冲区,OpenGL 都会把像素的深度值写入到缓冲区中,除非调用 glDepthMask(GL_FALSE) 来禁止写入。

    深度缓冲区原理

    把距离观察者平面(近裁剪面)的深度值与窗口中每个像素点1对1进行关联以及存储;eg:如果一个视图是 120 * 120,则会存储 14400 个深度值。

    深度测试

    深度缓冲区颜色缓冲区是对应的。颜色缓冲区存储像素的颜色信息,而深度缓冲区存储像素的深度信息。在决定是否绘制一个物体表面时,首先要将对应的像素的深度值与当前深度缓冲区的深度值进行比较。如果大于深度缓冲区中的值,则丢弃这部分,否则利用这个像素对应的深度值和颜色值,分别更新深度缓冲区和颜色缓冲区。这个过程称之为深度测试

    深度值的计算

    • 深度值一般由16位、24位、32位值表示,通常是24位。位数越高的话,深度的精确度越高。深度值的范围在 [0, 1] 之间,值越小表示越靠近观察者,值越大表示远离观察者。
    • 深度缓冲区主要是通过计算深度值来比较大小。从观察者看到其内容与场景中的所有对象的 z 值进行比较。这些视图空间中的 z 值可以是投影平头截体的近平面和远平面之间的任何值。我们因此需要一些方法来转换这些视图空间 z 值到 [0 ,1] 的范围内。


    far 和 near 是提供到投影矩阵设置可见视图的远近值。

    out vec4 color;
    float LinearizeDepth(float depth) {
        float near = 0.1;
        float far = 100.0;
        float z = depth * 2.0 - 1.0; // Back to NDC
        return (2.0 * near) / (far + near - z * (far - near));
    }
    void main() {
        float depth = LinearizeDepth(gl_FragCoord.z);
        color = vec4(vec3(depth), 1.0f);
    }
    

    深度测试的使用

    • 开启深度测试
    glEnable(GL_DEPTH_TEST);
    
    • 在绘制场景前,清除颜色缓冲区,深度缓冲区
    glClearColor(0.0f,0.0f,0.0f,1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
    • 关闭深度测试
    glDisable(GL_DEPTH_TEST);
    
    • 指定深度测试的判断模式


      深度测试判断模式
    • 打开/阻断 深度缓冲区写入

    // 开启深度缓冲区写入
    glDepthMask(GL_TRUE);
    // 关闭深度缓冲区写入
    glDepthMask(GL_FALSE);
    

    ZFighting 闪烁问题

    ZFighting 闪烁问题出现原因

    因为开启深度测试后,OpenGL 就不会再去绘制模型被遮挡的部分。但是由于深度缓冲区精度的限制,对于深度相差较小的情况。OpenGL 就可能出现不能正确判断两者的深度值,会导致深度测试的结果不可预测,显示出来的视图会交错闪烁。

    解决方式

    通过 PolygonOffset

    • 第一步:启用 Polygon Offset 方式解决

    让深度值之间产生间隔。可以理解为在执行深度测试之前将立方体的深度值做一些细微的增加,这样就能将重叠的两个图形深度值与之前的有所区分。

    /**
     * 参数列列表:  
     * GL_POLYGON_OFFSET_POINT 对应光栅化模式: GL_POINT 
     * GL_POLYGON_OFFSET_LINE 对应光栅化模式: GL_LINE
     * GL_POLYGON_OFFSET_FILL 对应光栅化模式: GL_FILL
    */
    glEnable(GL_POLYGON_OFFSET_FILL);
    
    • 第二步:指定偏移量

    通过 glPolygonOffset 来指定;
    每个 Fragment 的深度值都会增加如下所示的偏移量:

    /**
     * DZ:深度值(Z值)
     * r:使得深度缓冲区产⽣生变化的最⼩小值
    */
    Depth Offset = (DZ * factor) + (r * units); 
    

    一个大于 0 的 Offset 会把模型推离距观察者更远的位置,小于 0 的则会把模型拉近
    一般而言,只需要将 -1.0 和 1 这样简单赋值给 glPolygonOffset 基本就可以满足需求

    • 第三步:关闭 Polygon Offset
    glDisable(GL_POLYGON_OFFSET_FILL);
    

    ZFighting 闪烁问题的预防

    • 不要将两个物体靠的太近
    • 尽可能将近裁面设置得离观察者远一些;但是这种方式会使得距离观察者较近的物体被裁剪掉,因为需要调整好裁剪面参数。
    • 使用更高位数的深度缓冲区

    裁剪

    在 OpenGL 中有一种提高渲染的方式;只刷新屏幕上发生变化的部分。OpenGL 允许将要进行渲染的窗口只去指定一个裁剪框。

    基本原理:

    用于渲染时限制绘制区域;通过此技术可以在屏幕(帧缓冲)指定一个矩形区域。启用裁剪测试之后,不再此区域内的片源将被丢弃,只有再次矩形区域内的片元才有可能进入帧缓冲。因此实际达到的效果就是在屏幕上开辟一个小窗口,可以再其中指定内容的绘制。

    // 开启裁剪测试
    glEnable(GL_SCISSOR_TEST);
    //2.关闭裁剪测试 
    glDisable(GL_SCISSOR_TEST);
    //3.指定裁剪窗⼝口
    // x,y:指定裁剪框左下角的位置
    // width,height:指定裁剪尺寸
    void glScissor(Glint x,Glint y,GLSize width,GLSize height);
    

    窗口、视口、裁剪区域

    • 窗口:就是显示的界面
    • 视口:窗口中用来显示图形的一块矩形区域,它可以和窗口等大,也可以比窗口大或者小。只有绘制在视口区域中的图形才能被显示,如果图形有一部分超出了视口区域,那么那一部分是看不到的。通过 glViewport() 函数设置。
    • 裁剪区域:视口矩形区域的最小最大 x 坐标和最小最大 y 坐标,而不是窗口的最小最大x y 坐标。通过 glOrtho() 函数设置,这个函数还需指定最远
      z 坐标,形成一个立体的裁剪区域。

    相关文章

      网友评论

          本文标题:OpenGL 渲染技巧:深度测试

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