在三维高斯泼溅(3DGS)的实时渲染里,场景往往由数百万个高斯椭圆构成。如果每一帧都把全部高斯送入光栅化阶段,显存带宽和片元着色开销都会爆炸。视锥剔除负责把相机视锥体之外的高斯提前丢弃,遮挡剔除则进一步识别那些落在视锥内却被建筑物、山体等不透明前景完全挡住的高斯。把这两种策略串到一条管线里,渲染负载能下降到原来的三到五成,且视觉结果几乎无差。下面这张示意图展示了一个典型城市场景中经两级剔除后的剩余高斯分布。

视锥剔除的基础原理与实现
视锥剔除的本质是用相机投影矩阵定义的六个平面去测试每个高斯的包围球。三维高斯在泼溅前可以近似为各向异性椭球,但做粗粒度剔除时通常取其最大轴半径生成包围球,这样能避免复杂的椭球平面相交判断。只要包围球中心到某个平面的有符号距离加上半径小于零,就说明该球完全在该平面背面,从而整个高斯不可能出现在画面中。
在3DGS的排序阶段之前插入视锥剔除最为高效,因为此时高斯仍以结构化数组存放于显存或内存,可以用计算着色器批量处理。下面是一段简化的CUDA风格伪代码,展示如何对高斯中心与半径做平面测试:
struct Plane { float nx, ny, nz, d; };
__global__ void frustum_cull(
const float* centers, const float* radii,
const Plane* planes, int* visible_mask, int n) {
int i = blockIdx.x * blockDim.x + threadIdx.x;
if (i >= n) return;
float cx = centers[3*i], cy = centers[3*i+1], cz = centers[3*i+2];
float r = radii[i];
int vis = 1;
for (int p = 0; p < 6; ++p) {
float dist = planes[p].nx*cx + planes[p].ny*cy + planes[p].nz*cz + planes[p].d;
if (dist < -r) { vis = 0; break; }
}
visible_mask[i] = vis;
}
这种实现的优点是逻辑简单、易于并行,缺点在于包围球过于保守,在长焦或倾斜视角下会保留不少实际不可见的高斯。因此在大型场景中,往往先用八叉树或层次包围盒把空间分块,只对与视锥相交的节点展开内部高斯的精确测试,从而把遍历复杂度从线性降到近似对数级。
遮挡剔除的常用方案与精度权衡
视锥剔除之后,仍有大量高斯位于视野内但被前景挡住。传统多边形渲染常用硬件遮挡查询,但在3DGS里高斯并非三角形,不能直接走图形管线的遮挡谓词。更实用的做法是软件遮挡:把上一帧或低分辨率深度预渲染的结果存为深度图,再用每个高斯的屏幕空间包围圆去比深度。若包围圆内所有采样点的深度均大于深度图对应值,就判定为被遮挡。
下面给出一段基于深度图的遮挡测试代码,假设已有上一帧降采样深度纹理 prev_depth 和高斯投影后的屏幕坐标与最小深度:
import numpy as np
def occlude_test(sx, sy, radius, gauss_depth, prev_depth, w, h):
x0 = max(0, int(sx - radius))
x1 = min(w-1, int(sx + radius))
y0 = max(0, int(sy - radius))
y1 = min(h-1, int(sy + radius))
if x0 > x1 or y0 > y1:
return False
region = prev_depth[y0:y1+1, x0:x1+1]
# 若区域内最浅深度都比高斯深,则高斯被挡
return float(np.min(region)) < gauss_depth
软件遮挡的误差主要来自深度图分辨率与动态物体。若深度图太粗,远处细小缝隙会误杀后面的高斯;若每帧重渲深度图,又增加额外绘制调用。实践中常用半分辨率深度图配合膨胀处理,并对移动物体做单独标记不参与遮挡,从而在准确率和开销间取得平衡。
两级剔除的协同流水与性能实测
把视锥与遮挡剔除串成两级流水时,顺序很关键:必须先视锥后遮挡。因为遮挡测试需要屏幕空间投影,而视锥外的点投影后坐标无意义,提前用视锥砍掉能大幅减少后续投影与深度比较的次数。在调度上,可让视锥剔除在compute队列完成,再把可见索引传给遮挡阶段,最后才进入高斯排序与泼溅。
我们在一处包含约两百万高斯的城市场景做对比:无剔除时帧率约22 FPS;仅视锥剔除升至41 FPS;再加入半分辨率深度图遮挡剔除达到58 FPS,且主观画面与全量渲染一致。下表列出不同方案的开销占比:
| 方案 | 平均帧率 | 剔除后剩余高斯比例 | 额外CPU开销 |
|---|---|---|---|
| 无剔除 | 22 FPS | 100% | 0% |
| 仅视锥 | 41 FPS | 54% | 3% |
| 视锥+遮挡 | 58 FPS | 31% | 7% |
可以看到遮挡剔除带来的收益高于视锥,但边际成本也更高。若场景以室外开阔地为主,遮挡收益会明显下降;而在楼宇密集区,遮挡剔除几乎决定了能否实时。工程上建议把深度图更新频率设为每两到三帧一次,并用时间平滑减少闪烁,这样既压住开销又保持视觉稳定。