← ALL ARTICLES

可见性剔除

全库目录221
本文目录7

一个物体在视野边缘突然消失,剔除可能把本该保留的区域排掉了。背面、视锥和遮挡测试都以减少无贡献工作为目标,但使用的数据和保守条件不同。先用可见对象对照基线,再逐层查看包围体与深度,才能定位误删来自哪一步。

背面剔除

封闭网格通常只需要看到朝外的一面。GPU 根据屏幕空间绕序判断三角形正反面,再按 Rasterizer State 剔除 Front 或 Back。

常见问题:

  • 负缩放翻转绕序;
  • DCC、引擎和 API 的坐标手性不同;
  • 法线方向正确不代表三角形绕序正确;
  • 双面材质关闭剔除后,片元成本和过度绘制会增加。

视锥剔除

CPU 或 GPU 用对象 Bounds 与六个视锥平面测试。完全在任一平面外的对象不可见。

通常先用 Bounding Sphere 或 AABB 做便宜粗测。Bounds 太大会保留很多不可见对象;Bounds 太小会错误剔除动画、粒子或 Shader 位移后的几何。

视锥可以表示成六个朝内的平面。平面使用单位法线 n\mathbf n 和常数 dd:

distance(p)=n⋅p+ddistance(\mathbf p)=\mathbf n\cdot\mathbf p+d

Bounding Sphere 的球心到任一平面的距离小于 −r-r 时,整个球位于平面外:

cpp
bool IsSphereVisible(const Frustum& frustum,
                     Vector3 center, float radius)
{
    for (const Plane& plane : frustum.planes) {
        float distance = Dot(plane.normal, center) + plane.d;
        if (distance < -radius)
            return false;
    }
    return true;
}

AABB 可以使用投影半径测试。设半尺寸为 e\mathbf e,平面法线为 n\mathbf n,AABB 在法线方向的投影半径为:

r=ex∣nx∣+ey∣ny∣+ez∣nz∣r=e_x|n_x|+e_y|n_y|+e_z|n_z|

再使用与 Bounding Sphere 相同的 distance < -r 判断。球测试直接使用世界半径时要求平面法线为单位向量;未归一化时比较阈值应为 −r∥n∥-r\|\mathbf n\|。AABB的投影半径已经包含法线尺度,法线和常数d一起缩放即可保持判定,无需额外归一化。提取投影矩阵平面时还要核对矩阵行列主序、NDC 深度和反向 Z。

保守边界的数值检查

平面 x=0x=0 朝向 x≥0x\ge0,盒子中心为 (−2,0,0)(-2,0,0)、半尺寸为 (1,1,1)(1,1,1)。中心投影为-2,投影半径为1,最靠近平面的角也只有-1,因此可以剔除。把中心移到-1,盒子刚好接触平面,严格小于判定会保留它。动画或顶点位移必须扩展包围体;这个判定只证明包围体完全在外,不能修复包围体本身漏包几何的问题。

遮挡剔除

视锥内的物体仍可能被墙体和近处物体挡住。遮挡剔除需要根据已有遮挡信息判断 Bounds 是否可能可见。

常见方案:

  • 离线 PVS/Portal;
  • Hardware Occlusion Query;
  • Software Rasterizer;
  • 基于上一帧或当前帧 Hi-Z 的 GPU Occlusion Culling。

遮挡判断本身有成本,也会引入延迟和保守误差。小物体、动态遮挡和相机快速移动需要特别处理。

遮挡测试的完整区域摘要与包围体查询见 基于深度的可见性优化。其保守方向与视锥测试相同:能够证明完全不可见才删除,数据缺失时保留。离线PVS或门户需要自己的场景分区输入,不能仅用一个运行时深度矩形替代。

验证方法

  • 用反面、负缩放和不同绕序网格检查背面规则。
  • 在视锥边界移动包围体,检查空间与矩阵约定。
  • 让遮挡物快速移开,检查历史遮挡结果是否误删新露出的对象。

相关主题

实现与验证:raster_models.py 与 test_raster.py把AABB投影半径与八角点枚举对照,并检查平面缩放和贴边保留,运行 python -B 知识库/examples/revision/test_raster.py。球测试是C++宿主片段;引擎剔除、遮挡历史与GPU捕获未运行。

参考资料