跳转至

Preprocessing 模块 API 参考

本文档描述了 src/preprocess 模块暴露的公共接口,重点关注渲染器用于多模型分发的具体 GaussianPreprocessor 类。

模块导出

// src/preprocess/index.ts
export interface IPreprocessor {
  initialize(device: GPUDevice, shDegree: number): Promise<void>;
  getBindGroupLayout(device: GPUDevice): GPUBindGroupLayout;
}
export interface PreprocessResults { /* 为将来使用保留 */ }
export { GaussianPreprocessor } from './gaussian_preprocessor';

渲染设置结构 (Render Settings Shape)

传递给 dispatchModelsettings 字段使用以下结构:

interface RenderSettings {
  gaussianScaling: number;
  maxSHDegree: number;
  showEnvMap: boolean;
  mipSplatting: boolean;
  kernelSize: number;
  walltime: number;
  sceneExtend: number;
  center: Float32Array;        // vec3
  clippingBoxMin: Float32Array; // vec3
  clippingBoxMax: Float32Array; // vec3
}

这些值严格按照上述顺序打包到 80 字节的渲染设置 Uniform 中(有关偏移量,请参阅架构文档)。

DispatchModelArgs

dispatchModel 接收以下形状的参数(与 GaussianRenderer.prepareMulti 调用它的方式一致):

interface DispatchModelArgs {
  camera: PerspectiveCamera;
  viewport: [number, number];
  pointCloud: PointCloud;
  sortStuff: PointCloudSortStuff; // 来自 GPURSSorter
  settings: RenderSettings;
  modelMatrix: Float32Array;      // 4×4 变换矩阵
  baseOffset: number;             // 全局 Splat 缓冲区内的切片偏移
  global: { splat2D: GPUBuffer }; // 共享输出缓冲区
  countBuffer?: GPUBuffer;        // 可选的 ONNX 生成的计数缓冲区
}

GaussianPreprocessor

class GaussianPreprocessor implements IPreprocessor {
  constructor();
  initialize(device: GPUDevice, shDegree: number, useRawColor?: boolean): Promise<void>;
  dispatchModel(args: DispatchModelArgs, encoder: GPUCommandEncoder): void;
  getBindGroupLayout(device: GPUDevice): GPUBindGroupLayout;
  debugCountValues(): Promise<void>;
}

initialize(device, shDegree, useRawColor = false)

  • 创建两个 UniformBuffer 实例(相机:272 B,设置:80 B)。
  • 构建架构文档中描述的管道布局(4 个绑定组)。
  • 将请求的 SH 阶数注入 preprocess.wgsl 并编译着色器模块。
  • useRawColortrue 时,设置 USE_RAW_COLOR 管道常量(绕过 SH 评估并将 SH 缓冲区解释为直接 RGBA)。

dispatchModel(args, encoder)

记录一个计算通道 (compute pass),该通道将一个点云写入由 baseOffset 定义的全局 Splat 缓冲区切片中。在内部,它:

  1. 从暂存缓冲区打包并刷新相机 + 设置 Uniform。
  2. 调用 PointCloud.updateModelParamsWithOffset(modelMatrix, baseOffset),如果存在,还会调用 pointCloud.setPrecisionForShader()
  3. 刷新点云的模型参数缓冲区,并可选地使用 encoder.copyBufferToBuffercountBuffer 覆盖 num_points(字节偏移量 68)。
  4. 为第 1 组(高斯/SH 缓冲区 + 全局 Splat 输出)和第 3 组(设置 + 模型参数)创建临时绑定组,并复用其他插槽的预构建绑定组。
  5. 调度 ceil(pointCloud.numPoints / 256) 个工作组。

该方法不返回值;结果写入 args 中引用的 GPU 缓冲区。

getBindGroupLayout(device)

返回相机 Uniform 绑定组布局(第 0 组)。这主要供需要将预处理器的相机 Uniform 嵌入其自己的管道布局的系统使用。其他绑定组布局是该类的内部布局。

debugCountValues()

如果 dispatchModel 使用动态 countBuffer 运行,预处理器会存储对源/目标缓冲区的引用。调用 debugCountValues() 使用 debugCountPipeline(参见 src/utils/debug-gpu-buffers.ts)打印 ONNX 计数和模型参数 Uniform,这在诊断间接绘制不匹配时非常方便。

Uniform 辅助函数

packCameraUniforms(camera, viewport)

  • 将视图矩阵、视图逆矩阵、投影矩阵(应用 VIEWPORT_Y_FLIP 后)、投影逆矩阵、视口大小和 camera.projection.focal(viewport) 写入暂存缓冲区。
  • 调用 UniformBuffer.setData(Float32Array)flush(device)

packSettingsUniforms(pointCloud, settings)

  • RenderSettings 结构序列化为具有小端写入的 DataView
  • 在写入场景中心之前填充到所需的 16 字节对齐。
  • 调用 UniformBuffer.setData(dataView)flush(device)

相关类型

  • PointCloud(参见 doc/modules/03-point_cloud)——拥有高斯/SH 缓冲区、绘制 Uniform 和模型参数 Uniform。
  • PointCloudSortStuff(来自 src/sort/radix_sort.ts)——提供 sorter_bg_presorter_unisorter_dis 以及预处理和渲染使用的乒乓缓冲区。
  • PerspectiveCamera ——提供 packCameraUniforms 使用的视图/投影矩阵和焦距计算。

错误处理

  • 如果视图或投影矩阵是奇异的(行列式 < 1e-6),packCameraUniforms 内的矩阵求逆将抛出异常。
  • initialize 传播 WebGPU 管道创建错误(例如,不支持的着色器功能)。
  • dispatchModel 假设提供的缓冲区足够大;调用者必须确保全局容量(Splat 缓冲区 + 排序器)超过点数总和。

用法快照

const preprocessorSH = new GaussianPreprocessor();
await preprocessorSH.initialize(device, 3, false);

const preprocessorRGB = new GaussianPreprocessor();
await preprocessorRGB.initialize(device, 0, true);

const encoder = device.createCommandEncoder();
let offset = 0;
for (const pc of pointClouds) {
  const pre = pc.colorMode === 'rgb' ? preprocessorRGB : preprocessorSH;
  pre.dispatchModel({
    camera,
    viewport: [width, height],
    pointCloud: pc,
    sortStuff: globalSortStuff,
    settings: buildRenderSettings(pc, renderArgs),
    modelMatrix: pc.transform,
    baseOffset: offset,
    global: { splat2D: globalSplatBuffer },
    countBuffer: 'countBuffer' in pc ? pc.countBuffer?.() : undefined,
  }, encoder);
  offset += pc.numPoints;
}