跳转至

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())

  1. 排序器创建GPURSSorter.create(device, queue) - 带有适配器测试的异步初始化。
  2. 预处理器设置:创建两个实例:
  3. preprocessorSH:使用 shDegree(默认 3)初始化。
  4. preprocessorRGB:使用阶数 0 初始化,用于直接 RGB 模型。
  5. 管线布局:结合 PointCloud.renderBindGroupLayoutGPURSSorter.createRenderBindGroupLayout
  6. 渲染管线:创建两个变体:
  7. 标准管线:无深度测试。
  8. 深度管线:使用初始 depth24plus 格式创建(格式更改时重新创建)。
  9. 间接绘制缓冲:16 字节缓冲,初始化为 {vertexCount: 4, instanceCount: 0, firstVertex: 0, firstInstance: 0}
  10. 全局缓冲:分配初始 100 万 Splat 容量(splat2D 缓冲 + PointCloudSortStuff)。

帧准备 (prepareMulti)

  1. 容量检查:汇总所有 pointCloud.numPoints,如果需要则增长全局缓冲(1.25 倍增长因子)。
  2. 排序器重置recordResetIndirectBuffer 清除 keys_sizedispatch_x 计数器。
  3. 逐模型调度
  4. 计算每个模型的 baseOffset(前几个模型点数的累积和)。
  5. 检测颜色模式:pointCloud.colorMode → 选择 preprocessorSHpreprocessorRGB
  6. 构建渲染设置:将 RenderArgs 与每云元数据(bbox、center、kernelSize 等)合并。
  7. 调用 preprocessor.dispatchModel(),传入:
    • 全局 splat2D 缓冲和 sortStuff
    • 特定于模型的 baseOffset 和变换矩阵。
    • 用于动态模型的可选 ONNX countBuffer
  8. 全局排序:单次 recordSortIndirect 调用一起处理所有模型。
  9. 间接绘制更新:将 sorter_uni.keys_size (4 字节) 复制到 drawIndirectBuffer[4:8] 以设置实例计数。

帧渲染 (renderMulti)

  1. 绑定组
  2. @group(0): 全局 renderBG(绑定全局 splat2D 缓冲)。
  3. @group(1): 全局 sorter_render_bg(绑定 sorter_unipayload_a)。
  4. 管线选择:如果 useDepth && pipelineDepth 存在,则使用深度管线,否则使用标准管线。
  5. 间接绘制:单次 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) 实现动态缓冲增长:

  1. 计算所需容量:Math.max(1, total)
  2. 如果当前 globalCapacity >= needed,则提前返回。
  3. 以 1.25 倍因子增长:Math.ceil(needed * 1.25)
  4. 销毁旧缓冲(如果有):
  5. globalBuffers.splat2D.destroy()
  6. sortStuff 缓冲由排序器拥有(未使用时被 GC)。
  7. 分配新资源:
  8. sorter.createSortStuff(device, newCapacity) → 新的 PointCloudSortStuff
  9. device.createBuffer() 用于 splat2D(大小 = newCapacity * BUFFER_CONFIG.SPLAT_STRIDE)。
  10. 使用 PointCloud.renderBindGroupLayout 创建新的 renderBG 绑定组。
  11. 更新 globalCapacityglobalBuffers 引用。

缓冲布局

全局 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_sizesorter_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。
  • 减少开销:消除每模型的管线切换和绘制调用。

内存占用

全局缓冲随总点数扩展: - splat2Dcapacity * 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 利用率。