可见性剔除
全库目录221
本文目录7
一个物体在视野边缘突然消失,剔除可能把本该保留的区域排掉了。背面、视锥和遮挡测试都以减少无贡献工作为目标,但使用的数据和保守条件不同。先用可见对象对照基线,再逐层查看包围体与深度,才能定位误删来自哪一步。
背面剔除
封闭网格通常只需要看到朝外的一面。GPU 根据屏幕空间绕序判断三角形正反面,再按 Rasterizer State 剔除 Front 或 Back。
常见问题:
- 负缩放翻转绕序;
- DCC、引擎和 API 的坐标手性不同;
- 法线方向正确不代表三角形绕序正确;
- 双面材质关闭剔除后,片元成本和过度绘制会增加。
视锥剔除
CPU 或 GPU 用对象 Bounds 与六个视锥平面测试。完全在任一平面外的对象不可见。
通常先用 Bounding Sphere 或 AABB 做便宜粗测。Bounds 太大会保留很多不可见对象;Bounds 太小会错误剔除动画、粒子或 Shader 位移后的几何。
视锥可以表示成六个朝内的平面。平面使用单位法线 和常数 :
Bounding Sphere 的球心到任一平面的距离小于 时,整个球位于平面外:
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 可以使用投影半径测试。设半尺寸为 ,平面法线为 ,AABB 在法线方向的投影半径为:
再使用与 Bounding Sphere 相同的 distance < -r 判断。球测试直接使用世界半径时要求平面法线为单位向量;未归一化时比较阈值应为 。AABB的投影半径已经包含法线尺度,法线和常数d一起缩放即可保持判定,无需额外归一化。提取投影矩阵平面时还要核对矩阵行列主序、NDC 深度和反向 Z。
保守边界的数值检查
平面 朝向 ,盒子中心为 、半尺寸为 。中心投影为-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捕获未运行。
参考资料
- Microsoft Rasterization Rules:覆盖规则与屏幕空间判定的API背景。
- AABB支持半径由线性函数在长方体上的最大值推导,八角点CPU枚举作为独立检查。遮挡层级的实现与来源见 基于深度的可见性优化。