WebGL 深度测试详解

WebGL 深度测试详解

预计阅读时间: 7 分钟

1. 深度测试基础

1.1 深度测试是什么?

深度测试

深度测试是 WebGL 中的一种渲染技术,用于确定片段的可见性。

可以简单理解为,深度测试实际就是一个开关,通过与否决定是否把片段写入颜色缓冲

至于是否更新深度缓冲,则取决于是否开启深度缓冲写入

深度缓冲概述

深度缓冲(Depth Buffer)是由窗口系统自动创建的,用于存储每个坐标的片段深度信息:

  • 存储精度:16、24 或 32 位浮点数,大多数系统采用 24 位精度
  • 运行环境:在屏幕空间中进行,使用屏幕空间坐标
  • 基本原理:存储每个片段的深度值,并与相同坐标的其他片段进行深度比较
  • 大小:与屏幕分辨率有关,通常为屏幕宽度和高度(实际上是窗口大小,就是canvas的宽高)
  • 作用:存储每个片段的深度值,并与相同坐标的其他片段进行深度比较

1.2 深度测试机制

核心概念

深度测试的主要目的是确定片段的可见性:

  1. 比较当前片段与深度缓冲中已存储的深度值
  2. 根据比较结果决定是否更新深度值
  3. 决定是否丢弃当前片段(即该片段是否被其他片段遮挡),亦即决定是否写入颜色缓冲

1.2 深度缓冲控制

可以通过以下方式控制深度缓冲的行为:

  1. 只读深度缓冲

    • 对所有片段执行深度测试
    • 但不更新深度缓冲
    • 通过设置深度掩码(Depth Mask)为 GL_FALSE 实现
  2. 提前深度测试

硬件特性
  • 允许在片段着色器之前进行深度测试
  • 可以提前丢弃被遮挡的片段,避免不必要的片段着色器运算
  • 限制:使用时不能在片段着色器中写入深度值

2. 深度精度

2.1 精度特性

深度精度分布
  • 近处区域需要更高的精度
  • 远处区域精度要求相对较低
  • 深度值在屏幕空间中呈非线性分布
  • 在观察空间(透视矩阵应用前)中是线性的

实际上在应用投影矩阵后,会把深度值做一个非线性变换,与距离成反比

Fdepth=1/z1/near1/far1/nearF_{\text{depth}} = \frac{1/z - 1/\text{near}}{1/\text{far} - 1/\text{near}}

深度缓冲中0.5的值并不代表着物体的z值是位于平截头体的中间了,这个顶点的z值实际上非常接近近平面,z值分布图如下

z值分布图

可以看到,深度值很大一部分是由很小的z值所决定的,这给了近处的物体很大的深度精度

2.2 深度冲突(Z-fighting)

深度冲突问题

当两个面非常接近时,由于深度缓冲精度不足,无法准确区分前后关系,导致渲染结果在两个面之间不断切换,产生闪烁效果。

解决方案

  1. 物体间距控制

    • 避免将面过于靠近
    • 设置细微的间距差异(人眼无法察觉)
  2. 近平面调整

    • 在不影响视觉效果的前提下,尽可能远离观察者
    • 利用深度缓冲在近平面附近精度较高的特性
  3. 提升深度精度

    • 可以从默认的 24 位提升到 32 位
    • 注意:会影响性能

3. 渲染流程中的深度处理

关键步骤

渲染过程中涉及三个重要的深度相关步骤:

  1. 深度测试

    • 决定片段是否可以写入颜色缓冲
    • 所有片段都会写入颜色缓冲,所以最终的渲染结果,取决于渲染顺序
  2. 深度缓冲写入

    • 更新深度缓冲中的深度值
    • 即使深度测试通过,未开启深度缓冲写入时也不会更新深度值
  3. 颜色缓冲写入

    • 在深度测试通过或未开启深度测试时执行
    • 不受深度缓冲写入状态影响

深度测试与深度缓冲写入

深度测试通过与否,以及深度缓冲写入与否,是两个不同的概念,容易混淆,这里需要澄清一下:

  1. 深度测试:决定片段是否可以写入颜色缓冲
  2. 深度缓冲写入:决定是否更新深度缓冲

深度测试流程

关闭深度缓冲写入的场景

  1. 3D场景中,如果需要绘制大量不透明物体,关闭深度缓冲写入可以提高渲染效率
  2. 在立方体贴图渲染中,关闭深度缓冲写入可以提高渲染效率 立方体贴图渲染