Renderer 模块架构
Renderer 模块协调高斯泼溅 (Gaussian splatting) 的完整渲染管线,协调预处理、GPU 基数排序和间接绘制执行。GaussianRenderer 作为高级协调器,管理静态 GPU 资源、双预处理器(SH 和 RGB)、共享排序器实例以及用于多模型批处理的全局缓冲。
高层管线
┌────────────────────────────────────────────────────────────┐
│ 应用层 (Application Layer) │
│ RenderLoop / App → prepareMulti() / renderMulti() │
└───────────────▲────────────────────────────────────────────┘
│
┌───────────────┴────────────────────────────────────────────┐
│ 渲染层 (Renderer Layer) │
│ GaussianRenderer │
│ • 静态管线 (渲染, 深度) │
│ • 双预处理器 (SH, RGB) │
│ • 单个 GPURSSorter 实例 │
│ • 全局缓冲 (splat2D, 排序器负载) │
└───────────────┬────────────────────────────────────────────┘
│
┌───────────────┴────────────────────────────────────────────┐
│ 准备阶段 (prepareMulti) │
│ 1. ensureGlobalCapacity(总点数) │
│ 2. 重置排序器间接缓冲 │
│ 3. 对每个 PointCloud: │
│ • 选择预处理器 (SH vs RGB) │
│ • dispatchModel(baseOffset, countBuffer?) │
│ 4. recordSortIndirect(全局排序) │
│ 5. 复制 keys_size → drawIndirect.instanceCount │
└───────────────┬────────────────────────────────────────────┘
│
┌───────────────┴────────────────────────────────────────────┐
│ 渲染阶段 (renderMulti) │
│ 1. 绑定全局 renderBG (@group(0)) │
│ 2. 绑定全局 sorter_render_bg (@group(1)) │
│ 3. 设置管线 (如果启用则为深度感知) │
│ 4. drawIndirect(所有模型一次性绘制) │
└────────────────────────────────────────────────────────────┘
关键设计原则
- 静态资源缓存:管线、布局和间接缓冲在
initialize()期间创建一次,并在帧之间重用。 - 双预处理器支持:分别为 SH(球谐函数)和 RGB 模型提供独立的
GaussianPreprocessor实例,以实现高效的颜色模式切换。 - 全局排序:单个共享排序器实例与全局缓冲允许将多个模型批处理到单个间接绘制中。
- 逐云缓存:
WeakMap<PointCloud, PointCloudSortStuff>缓存每个点云的排序资源,仅在点数更改时重建。 - 容量管理:全局缓冲以 1.25 倍的因子增长,以分摊总点数增加时的重新分配成本。
- 深度管线变体:当调用
setDepthFormat()时按需创建可选的启用深度的管线。
资源生命周期
初始化 (initialize())
- 排序器创建:
GPURSSorter.create(device, queue)- 带有适配器测试的异步初始化。 - 预处理器设置:创建两个实例:
preprocessorSH:使用shDegree(默认 3)初始化。preprocessorRGB:使用阶数 0 初始化,用于直接 RGB 模型。- 管线布局:结合
PointCloud.renderBindGroupLayout和GPURSSorter.createRenderBindGroupLayout。 - 渲染管线:创建两个变体:
- 标准管线:无深度测试。
- 深度管线:使用初始
depth24plus格式创建(格式更改时重新创建)。 - 间接绘制缓冲:16 字节缓冲,初始化为
{vertexCount: 4, instanceCount: 0, firstVertex: 0, firstInstance: 0}。 - 全局缓冲:分配初始 100 万 Splat 容量(
splat2D缓冲 +PointCloudSortStuff)。
帧准备 (prepareMulti)
- 容量检查:汇总所有
pointCloud.numPoints,如果需要则增长全局缓冲(1.25 倍增长因子)。 - 排序器重置:
recordResetIndirectBuffer清除keys_size和dispatch_x计数器。 - 逐模型调度:
- 计算每个模型的
baseOffset(前几个模型点数的累积和)。 - 检测颜色模式:
pointCloud.colorMode→ 选择preprocessorSH或preprocessorRGB。 - 构建渲染设置:将
RenderArgs与每云元数据(bbox、center、kernelSize 等)合并。 - 调用
preprocessor.dispatchModel(),传入:- 全局
splat2D缓冲和sortStuff。 - 特定于模型的
baseOffset和变换矩阵。 - 用于动态模型的可选 ONNX
countBuffer。
- 全局
- 全局排序:单次
recordSortIndirect调用一起处理所有模型。 - 间接绘制更新:将
sorter_uni.keys_size(4 字节) 复制到drawIndirectBuffer[4:8]以设置实例计数。
帧渲染 (renderMulti)
- 绑定组:
@group(0): 全局renderBG(绑定全局splat2D缓冲)。@group(1): 全局sorter_render_bg(绑定sorter_uni和payload_a)。- 管线选择:如果
useDepth && pipelineDepth存在,则使用深度管线,否则使用标准管线。 - 间接绘制:单次
drawIndirect(drawIndirectBuffer, 0)调用渲染所有可见 Splat。
绑定组布局 (Bind Group Layouts)
渲染管线布局
渲染器使用两个绑定组:
- @group(0):
PointCloud.renderBindGroupLayout(device) -
binding 2: 对投影后的splat2D缓冲(顶点属性)的只读访问。 -
@group(1):
GPURSSorter.createRenderBindGroupLayout(device) binding 0:sorter_uni(只读GeneralInfo结构体,包含keys_size)。binding 4:payload_a(用于间接绘制的排序后索引缓冲)。
全局 vs 逐云资源
全局路径 (多模型):
- 单个 splat2D 缓冲,大小为总容量。
- 单个 PointCloudSortStuff,包含全局排序缓冲。
- 单个 renderBG 绑定组,指向全局 splat2D。
- 所有模型一次间接绘制。
逐云路径 (旧版单模型):
- 每个 PointCloud 有自己的 splat2DBuffer(由 PointCloud 管理)。
- 每个点云缓存 PointCloudSortStuff(通过 WeakMap)。
- pointCloud.renderBindGroup() 绑定每云 splat2DBuffer。
- 每个模型单独绘制(尽管仍然建议使用 prepareMulti)。
预处理器选择
渲染器根据 PointCloud.colorMode 自动选择合适的预处理器:
'sh'模式:使用preprocessorSH(用shDegree初始化)。- 处理球谐系数(4、12、27 或 48 个通道)。
-
在片元着色器中评估 SH 基函数。
-
'rgb'模式:使用preprocessorRGB(用阶数 0 初始化)。 - 直接 RGB 颜色通道(3 或 4 个通道)。
- 不需要 SH 评估。
选择发生在 getColorMode() 中,它读取 pointCloud.colorMode。两个预处理器都写入相同的全局 splat2D 缓冲格式,确保与共享渲染管线的兼容性。
全局缓冲管理
容量增长
ensureGlobalCapacity(total) 实现动态缓冲增长:
- 计算所需容量:
Math.max(1, total)。 - 如果当前
globalCapacity >= needed,则提前返回。 - 以 1.25 倍因子增长:
Math.ceil(needed * 1.25)。 - 销毁旧缓冲(如果有):
globalBuffers.splat2D.destroy()。sortStuff缓冲由排序器拥有(未使用时被 GC)。- 分配新资源:
sorter.createSortStuff(device, newCapacity)→ 新的PointCloudSortStuff。device.createBuffer()用于splat2D(大小 =newCapacity * BUFFER_CONFIG.SPLAT_STRIDE)。- 使用
PointCloud.renderBindGroupLayout创建新的renderBG绑定组。 - 更新
globalCapacity和globalBuffers引用。
缓冲布局
全局 splat2D 缓冲:
- 大小:globalCapacity * BUFFER_CONFIG.SPLAT_STRIDE。
- 用法:STORAGE | COPY_DST | COPY_SRC。
- 布局:由预处理器写入的每个 Splat 属性(位置、颜色、协方差等)。
全局排序缓冲 (通过 PointCloudSortStuff):
- key_a, key_b:乒乓深度键缓冲(填充到工作组倍数)。
- payload_a, payload_b:乒乓索引缓冲(payload_a 中的最终排序顺序)。
- sorter_uni:包含 keys_size(可见 Splat 计数)的 GeneralInfo 结构体。
- sorter_dis:带有工作组计数的间接调度缓冲。
深度管线
渲染器通过单独的管线变体支持可选的深度测试:
创建
createDepthPipeline() 创建一个具有以下特性的深度感知管线:
- 与标准管线相同的着色器模块和入口点。
- depthStencil 配置:
- 格式:可配置(默认 depth24plus,可通过 setDepthFormat() 更改)。
- depthWriteEnabled: false(只读深度测试)。
- depthCompare: 'less'(标准 Z 缓冲比较)。
运行时控制
setDepthEnabled(enabled):切换useDepth标志。setDepthFormat(format):更新depthFormat并重新创建深度管线。
当 useDepth && pipelineDepth 为真时,renderMulti() 使用深度管线;否则,它使用标准管线。这允许在纯粹从后到前排序(无深度)和深度辅助渲染之间切换。
渲染设置合并
buildRenderSettings() 将 RenderArgs 与每云元数据合并:
| 设置 | 来源优先级 |
|---|---|
maxSHDegree |
min(args.maxSHDegree ?? pointCloud.shDeg, renderer.shDegree) |
showEnvMap |
args.showEnvMap ?? true |
mipSplatting |
args.mipSplatting ?? pointCloud.mipSplatting ?? false |
kernelSize |
args.kernelSize ?? pointCloud.kernelSize ?? DEFAULT_KERNEL_SIZE |
walltime |
args.walltime ?? 1.0 |
sceneExtend |
args.sceneExtend ?? computed sceneSize |
center |
args.sceneCenter ?? pointCloud.center |
clippingBoxMin/Max |
args.clippingBox ?? pointCloud.bbox |
这些设置传递给 preprocessor.dispatchModel() 并写入预处理器的 Uniform 缓冲供着色器使用。
集成点
Point Cloud 模块
- 绑定组布局:
PointCloud.renderBindGroupLayout(device)提供@group(0)布局。 - 每云资源:
pointCloud.renderBindGroup()返回每云渲染的绑定组。 - 变换矩阵:
pointCloud.transform(4×4 矩阵)传递给预处理器用于模型空间投影。 - 元数据:
bbox,center,shDeg,colorMode,kernelSize,mipSplatting用于渲染设置。 - ONNX 支持:
DynamicPointCloud.countBuffer()为间接管线提供可选的绘制计数。
Preprocess 模块
- 双预处理器:两个
GaussianPreprocessor实例处理 SH 和 RGB 模型。 - 调度接口:
dispatchModel()将 Splat 写入指定baseOffset处的全局splat2D缓冲。 - 计数器更新:预处理器原子地递增
sorter_uni.keys_size和sorter_dis.dispatch_x。 - 设置注入:渲染设置(内核大小、SH 阶数、裁剪框等)写入预处理器 Uniform。
Sorting 模块
- 单个排序器:所有模型共享一个
GPURSSorter实例。 - 布局:
GPURSSorter.createRenderBindGroupLayout(device)提供@group(1)布局。 - 排序资源:
sorter.createSortStuff(device, capacity)分配全局排序缓冲。 - 间接排序:
recordSortIndirect()使用预处理的计数器一次性处理所有模型。 - 负载访问:排序后的
payload_a缓冲为间接绘制提供索引。
Shader 模块
- 高斯着色器:
gaussianShader(来自src/shaders/index)实现顶点和片元阶段。 - 存储访问:顶点着色器从
@group(0)存储缓冲读取所有属性。 - 混合:片元着色器使用预乘 Alpha 混合(
src: one, dst: one-minus-src-alpha)。 - 图元:三角带拓扑(每个 Splat 4 个顶点)。
调试与诊断
统计信息
getRenderStats(pointCloud) 返回:
- gaussianCount:点云中的总点数。
- visibleSplats:来自排序器的最新 keys_size(如果可用,则为缓存的 num_points)。
- memoryUsage:粗略估计(高斯 + SH 缓冲 + 排序缓冲)。
调试辅助工具
readInstanceCountDebug():GPU→CPU 回读drawIndirectBuffer[4:8](实例计数)。readPayloadSampleDebug(n):从全局payload_a缓冲转储前n个负载索引。debugONNXCount():链接到预处理器调试流以跟踪 ONNX 驱动的计数缓冲。
调试日志
通过 (globalThis as any).GS_DEBUG_LOGS = true 启用详细日志记录。渲染器记录:
- 容量增长事件
- 逐模型调度偏移量
- 全局排序完成
- 实例计数更新
性能考量
资源重用
- 静态资源:管线、布局和间接缓冲创建一次,永久重用。
- 逐云缓存:排序资源缓存在
WeakMap中,仅在点数更改时重建。 - 全局缓冲:以 1.25 倍因子增长以减少重新分配频率。
多模型批处理
- 单次排序:一个基数排序 Pass 一起处理所有模型。
- 单次绘制:一个间接绘制调用渲染所有可见 Splat。
- 减少开销:消除每模型的管线切换和绘制调用。
内存占用
全局缓冲随总点数扩展:
- splat2D:capacity * SPLAT_STRIDE 字节。
- 排序缓冲:capacity * (key_size + payload_size) * 2 (乒乓) + 直方图暂存区。
对于包含许多小模型的场景,全局缓冲可能会超过每模型的内存,但这会被批处理的优势所抵消。
常见模式
多模型帧
await renderer.initialize();
renderer.prepareMulti(encoder, queue, pointClouds, {
camera,
viewport: [width, height],
maxSHDegree: 3,
});
const pass = encoder.beginRenderPass(passDesc);
renderer.renderMulti(pass, pointClouds);
pass.end();
启用深度的渲染
renderer.setDepthFormat('depth32float');
renderer.setDepthEnabled(true);
// 后续的 renderMulti() 调用使用深度管线
单模型渲染(旧路径/逐模型渲染)
旧路径指的是在引入多模型批处理(prepareMulti/renderMulti)之前使用的逐模型渲染方式。虽然现在仍然支持,但推荐使用批处理方式,即使是单个模型。
旧路径特点:
- 使用 render(pass, pointCloud) 方法,每个模型单独调用
- 使用每个点云自己的 splat2DBuffer(由 PointCloud 模块管理)
- 使用缓存的每云排序资源(WeakMap<PointCloud, PointCloudSortStuff>)
- 每个模型单独执行绘制调用
// 仍然使用 prepareMulti 进行预处理(推荐)
renderer.prepareMulti(encoder, queue, [pointCloud], args);
// 旧路径:使用 render() 而不是 renderMulti()
renderer.render(pass, pointCloud); // 使用每云缓存,单独绘制
注意:即使是单个模型,也推荐使用 renderMulti(),因为它使用全局缓冲区,性能更好。
故障排除
- 容量超限:如果总点数超过
globalCapacity,则重新分配缓冲。预计会出现短暂的帧峰值,但不会崩溃。 - 混合颜色模式:确保
PointCloud.colorMode设置正确('sh'或'rgb'),以便渲染器选择正确的预处理器。 - 深度伪影:为 Z 缓冲测试启用深度管线,或为纯粹的从后到前排序禁用它。
- 零实例计数:务必在
renderMulti之前调用prepareMulti;预处理会填充间接缓冲。 - 陈旧的排序结果:确保每帧运行
prepareMulti;排序资源在每个准备阶段开始时重置。
渲染器架构提供了一个高性能、资源高效的管线,用于渲染多个高斯泼溅模型,具有最小的 CPU 开销和最佳的 GPU 利用率。