在实时渲染领域,渲染管线的演进历程深刻影响了游戏引擎和图形应用的性能优化。从早期的固定功能管线,到如今的可编程管线时代,开发者们面对的挑战日益复杂。固定管线依赖硬件状态机处理基本几何变换,而现代可编程管线如 Vulkan 和 DirectX 12 则赋予开发者前所未有的控制力。然而,随着场景复杂度飙升——想想 Unreal Engine 或 Unity 中数百万三角形的开放世界——性能瓶颈迅速显现。GPU 的 Fill Rate 和内存带宽成为限制因素,过度绘制和无效 Draw Call 消耗宝贵资源。
剔除技术正是解决这些痛点的核心利器。剔除,即 Culling,指在渲染管线早期阶段丢弃不可见几何体,从而减少无谓的计算和绘制调用。根据行业基准数据,有效剔除可将 Draw Call 数量降低 50% 至 90%,显著提升 GPU 利用率。例如,在高密度城市场景中,未经优化的渲染可能产生数万 Draw Call,而剔除后仅剩数百。这不仅缓解 CPU 瓶颈,还优化了 GPU 的 Vertex Fetch 和 Raster 阶段。
本文聚焦 Vulkan 和 DirectX 12 时代的现代剔除技术,涵盖 CPU 侧和 GPU 侧方法,不涉及 OpenGL 等历史遗留实现。假设读者具备 Shader 编程基础,我们将从基础概念入手,逐步深入传统回顾、现代 CPU/GPU 技术、高级混合策略,直至性能评估和最佳实践。通过伪代码和 HLSL 示例剖析实现细节,并引用 Nanite 等前沿案例,帮助开发者构建高效渲染系统。
渲染剔除基础概念
渲染剔除的核心在于及早识别并丢弃对最终图像无贡献的几何体,从而避免下游管线阶段的无效工作。剔除按类型可分为视锥剔除,它在 CPU 侧测试物体是否落入相机视锥体外;遮挡剔除则判断物体是否被前景遮挡,可在 CPU 或 GPU 执行;后剔除针对背对相机的三角形,在 GPU 光栅化阶段丢弃;层次剔除利用 BVH 或 Octree 等加速结构,在 CPU 或 GPU 上实现高效遍历。这些技术嵌入渲染管线特定位置:视锥和遮挡剔除通常在 Vertex Fetch 之前执行,后剔除则在 Rasterizer 前介入,确保最小开销。
剔除的时机至关重要。在现代管线中,CPU 侧剔除发生在场景遍历阶段,生成可见物体列表;GPU 侧则通过 Compute Shader 预处理 Indirect Draw 参数,避免 CPU-GPU 同步开销。性能指标包括 Draw Call 数量、Overdraw 率和 Fill Rate,前者反映 CPU 负载,后两者暴露 GPU 像素填充瓶颈。优化目标是平衡剔除精度与计算成本,避免过度保守导致漏剔或遍历开销过高。
一个简单的 AABB 视锥剔除伪代码展示了基础算法。该代码首先将 AABB 的 8 个顶点变换到视锥空间,然后测试每个平面。以下是 C++ 风格实现:
bool FrustumCull(const AABB& box, const Frustum& frustum) {
Vec3 min = box.min;
Vec3 max = box.max;
Vec3 corners[8] = {
{min.x, min.y, min.z}, {min.x, min.y, max.z},
{min.x, max.y, min.z}, {min.x, max.y, max.z},
{max.x, min.y, min.z}, {max.x, min.y, max.z},
{max.x, max.y, min.z}, {max.x, max.y, max.z}
};
for (int i = 0; i < 6; ++i) {
const Plane& plane = frustum.planes[i];
bool inside = false;
for (int j = 0; j < 8; ++j) {
if (Dot(plane.normal, corners[j]) + plane.d <= 0) {
inside = true;
break;
}
}
if (!inside) return true; // 完全在平面外,剔除
}
return false; // 可能相交,不剔除
}
这段代码逐平面测试 AABB 分离轴定理:若所有顶点点积大于平面常数 d,则 AABB 在平面外侧,被剔除。Early-Out 机制确保快速拒绝,适用于静态场景。实际中,可用分离轴定理优化,仅测试 AABB 极值投影。
传统剔除技术回顾
视锥剔除是最基础的技术,其原理基于包围体如 AABB 或 Sphere 与相机 6 个视锥平面的相交测试。手动实现需计算世界矩阵变换后的极值点,投影到齐次空间判断。近裁剪平面需特殊处理以避免 Z-Fighting。引擎如 Unity 提供 Bounds.IntersectFrustum API 封装此逻辑,但局限明显:它仅考虑几何可见性,忽略深度遮挡,导致 Overdraw 激增。
后剔除则在 GPU Raster 阶段自动执行,通过检查三角形法线与视向点积剔除背面。在 OpenGL 中,仅需调用 glEnable(GL_CULL_FACE)设置状态机,指定 CW/CCW 绕序。现代实践转向 Shader 控制,如 Vertex Shader 中计算 gl_FrontFacing,或 Fragment Shader 用 discard 语句精确丢弃。但在 Mesh Shader 时代,此功能集成到 Topology 生成中,减少状态切换。
遮挡剔除的软件实现依赖 Occlusion Query:CPU 发出 Query,GPU 异步填充最小深度像素计数,延迟高达数帧导致 Stall。传统方案如 BSP 树在 Doom 中大放异彩,将场景分区为凸多面体,仅渲染 Portal 可见部分;Unreal Engine 4 的 Precomputed Visibility Volume 则预烘焙静态遮挡体积,运行时快速查询。这些方法适用于半静态室内场景,但动态物体需重构数据结构,成本高企。
现代 CPU 侧剔除技术
层次视锥剔除提升了效率,使用 BVH 或 Octree 构建场景层次。Top-Down 遍历从根节点开始:若父节点全在外,则子树 Early-Out;若全在内,直接标记可见;否则递归。Spatial Hash 则针对动态场景,将空间离散为 Voxel,快速定位邻域碰撞。多线程 Job System 如 Unity DOTS 并行遍历分支,线程数与核心数匹配。
层次遮挡剔除引入 Hi-Z Buffer,即深度纹理的 Mipmap 金字塔,粗层级加速测试。CHC++ 算法结合保守光栅化:从小节点向上测试,若像素被祖先遮挡则剪枝。该方法在 CPU 上保守渲染深度,测试子节点 Hi-Z 与父节点深度,避免 Query Stall。
Unity 的 Culling Groups 批量管理 LOD 组,Scriptable Render Pipeline 集成自定义 Culler;Unreal 的 Nanite 则间接剔除虚拟几何,每簇微三角共享 BVH 节点。性能优化融合 LOD:高 LOD 仅在视锥内生成,Cluster Culling 分区场景为 Tile。以下是 C# BVH 视锥剔除简化实现,适用于 Unity:
public class BVHNode {
public AABB bounds;
public BVHNode[] children;
public List<Mesh> meshes;
}
bool CullBVH(BVHNode node, Frustum frustum) {
if (FrustumCull(node.bounds, frustum)) return true;
if (node.children == null) {
foreach (var mesh in node.meshes) visibleMeshes.Add(mesh);
return false;
}
foreach (var child in node.children) {
if (!CullBVH(child, frustum)) return false;
}
return true;
}
此递归函数先测试节点 AABB,若剔除则返回 true 跳过子树;叶节点收集可见 Mesh。实际中,用栈迭代避免递归深度溢出,并行化子树遍历可将 10 万节点场景时间从 50ms 降至 5ms,多线程 Benchmark 显示 8 核提升 4x。
GPU 侧剔除技术
GPU 剔除利用 Compute Shader 的并行性,驱动 Indirect Drawing。流程为:CS 输入实例缓冲和视锥矩阵,输出 DrawArguments 结构数组,如 vkCmdDrawIndirect 接收 instanceCount、vertexOffset 等。CPU 仅上传 1 个 Dispatch 调用,GPU 自主生成数千 Draw Call,避免同步瓶颈。DirectX 12 的 ExecuteIndirect 进一步链式执行多 CS 阶段。
GPU 遮挡剔除常见于 Tiled/Cluster-based Deferred Rendering,将屏幕划分为 Tile,逐 Tile 测试深度。UE5 的 GPU Occlusion Queries 用 ROV 确保像素级精确,无需等待 Query 结果。Mesh Shader 作为 DX12/Vulkan 扩展,取代 VS+GS,在任务着色器中动态剔除并生成 LOD 拓扑,Nanite 即其典范。
Variable Rate Shading (VRS)允许每 Tile 稀疏着色率,降低 Overdraw;Rasterizer Order Views (ROV)记录光栅化顺序,实现精确遮挡。GPU 视锥剔除在 CS 中矩阵变换 AABB 中心和半径,测试 8 分离轴。Nanite 革命性在于虚拟微三角网格:GPU BVH 遍历簇节点,仅展开可见叶级,性能提升 10x,高细节场景 Draw Call 从百万降至千级。
以下 HLSL Compute Shader 生成 Indirect Args,处理 1M 实例:
cbuffer FrustumCB : register(b0) {
float4x4 viewProj;
// 6 planes
};
StructuredBuffer<AABB> instances : register(t0);
RWStructuredBuffer<DrawArgs> drawArgs : register(u0);
RWByteAddressBuffer counters : register(u1);
[numthreads(64,1,1)]
void CSMain(uint3 id : SV_DispatchThreadID) {
uint idx = id.x;
if (idx >= instanceCount) return;
AABB box = instances[idx];
float3 center = (box.min + box.max) * 0.5;
float3 extents = (box.max - box.min) * 0.5;
// Transform to clip space and test frustum
float4 corners[8];
// Generate 8 corners...
bool visible = true;
for (int p = 0; p < 6; ++p) {
float minProj = 1e9, maxProj = -1e9;
// Project extents on plane normal (SAT)
if (maxProj < 0) { visible = false; break; }
}
if (visible) {
uint outIdx = InterlockedAdd(counters[0], 1);
drawArgs[outIdx].instanceCount = 1;
drawArgs[outIdx].instanceOffset = idx;
// etc.
}
}
此 Shader 并行处理实例:用分离轴定理(SAT)测试 AABB,无需生成了 8 角点,仅投影 extents 到平面法线。可见实例原子递增 counter,填充 drawArgs。Dispatch(16384,1,1)覆盖 1M 实例,RTX 3090 下 <1ms,远胜 CPU。
高级主题与混合剔除
异步剔除针对 VR 高帧率,基于相机速度预测下一帧视锥,CPU 预热 GPU 缓冲。Ray Tracing 集成重用 BVH:DXR/VKRT 加速可见性查询,剔除 RT 不可见光线。移动端如 Metal/Vulkan Mobile 偏好轻量 CS,限制线程组大小避免寄存器压力。
混合策略最优:CPU 粗剔除生成候选列表,GPU 精剔除输出 Indirect Args,双缓冲异步同步。调试用 RenderDoc 捕获 Draw Call,NSight 分析 Overdraw 热图。常见陷阱包括保守测试漏剔动态物、浮点精度导致 T-Junction,以及 CS 同步 Barrier 开销。
性能评估与最佳实践
Benchmark 框架测试 10k 至 1M 物体场景,RTX 3090 硬件下,基础视锥剔除减少 Draw Call 70%,FPS 提升 2x,适合开放世界;Nanite 达 95% 减少、10x FPS,征服高细节。优先 GPU Indirect Drawing,动态场景选 BVH 胜 Octree;集成 LOD/Instancing 共享剔除参数。
未来 AI 加速剔除用 ML 预测可见性,WebGPU 渐支持 CS Culling。实践强调数据导向:Profile 驱动迭代,避免 Premature Optimization。
结尾
从视锥基础到 Nanite GPU BVH,剔除仍是现代渲染性能基石,桥接 CPU 智能与 GPU 马力。鼓励读者 fork Unity HDRP 或 Unreal Nanite Demo 实验,亲测 10x 提升。
参考论文如 Bittner 2012 CHC++ 和 Nanite SIGGRAPH;工具 Unity Profiler、NVIDIA Nsight;开源 bgfx、Falcor。
Q&A:Nanite 兼容 DX12 Ultimate,未来扩展 Vulkan。实验中遇 Stall?检查 Indirect 计数原子性。