SparkJS-完整技术文档

SparkJS 完整技术文档

Spark --- 基于 THREE.js 和 WebGL2 的高级 3D Gaussian Splatting 渲染器

源文档: https://sparkjs.dev/docs/

整理日期: 2026-05-08


目录

  1. 快速开始
  2. 概述
  3. [2.0 新特性](#2.0 新特性)
  4. [0.1 → 2.0 迁移指南](#0.1 → 2.0 迁移指南)
  5. 系统设计
  6. SparkRenderer
  7. SplatMesh
  8. PackedSplats
  9. ExtSplats
  10. [加载 Splat 文件](#加载 Splat 文件)
  11. [程序化生成 Splats](#程序化生成 Splats)
  12. [Splat 编辑器](#Splat 编辑器)
  13. [Level-of-Detail 入门](#Level-of-Detail 入门)
  14. [LoD 深度解析](#LoD 深度解析)
  15. [Dyno 着色器图系统](#Dyno 着色器图系统)
  16. [Dyno 标准库](#Dyno 标准库)
  17. 控制
  18. 性能调优
  19. 社区资源

1. 快速开始

1.1 Quick Start

复制以下代码到 index.html 文件,或直接在 Web Playground 中运行:

html 复制代码
<style> body {margin: 0;} </style>
<script type="importmap">
  {
    "imports": {
      "three": "https://cdnjs.cloudflare.com/ajax/libs/three.js/0.180.0/three.module.js",
      "@sparkjsdev/spark": "https://sparkjs.dev/releases/spark/2.0.0/spark.module.js"
    }
  }
</script>
<script type="module">
  import * as THREE from "three";
  import { SparkRenderer, SplatMesh } from "@sparkjsdev/spark";

  const scene = new THREE.Scene();
  const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.01, 1000);
  const renderer = new THREE.WebGLRenderer();
  renderer.setSize(window.innerWidth, window.innerHeight);
  document.body.appendChild(renderer.domElement);

  const spark = new SparkRenderer({ renderer });
  scene.add(spark);

  const splatURL = "https://sparkjs.dev/assets/splats/butterfly.spz";
  const butterfly = new SplatMesh({ url: splatURL });
  butterfly.quaternion.set(1, 0, 0, 0);
  butterfly.position.set(0, 0, -3);
  scene.add(butterfly);

  renderer.setAnimationLoop(function animate(time) {
    renderer.render(scene, camera);
    butterfly.rotation.y += 0.01;
  });
</script>

1.2 NPM 安装

shell 复制代码
npm install @sparkjsdev/spark

1.3 开发构建

构建 Spark(需要安装 Rust):

shell 复制代码
npm install
npm run build:wasm
npm run dev

这将在 http://localhost:8080/ 启动一个 Web 服务器,包含示例。

1.4 Spark 2.0 快速开始

Spark 2.0 使用 NewSparkRenderer(现已合并为 SparkRenderer)并添加到场景中:

javascript 复制代码
const spark = new SparkRenderer({ renderer: THREE.WebGLRenderer });
scene.add(spark);

启用 LoD 渲染:

javascript 复制代码
const splats = new SplatMesh({ url: "./my-splats.spz", lod: true });
scene.add(splats);

推荐预构建 LoD 树:

shell 复制代码
npm run build-lod -- my-splats.ply more-splats.spz --quality

输出 .RAD 文件可直接加载,支持分页流式传输:

javascript 复制代码
const splats = new SplatMesh({ url: "./my-splats-lod.rad", paged: true });
scene.add(splats);

2. 概述

3D Gaussian Splatting 已成为生成式 AI 和 3D 重建领域的前沿技术。通过将 3D 场景和对象表示为微小的高斯形状"斑点"(splats)集合,机器学习技术可以创建细节丰富、照片级逼真的 3D 内容,并实现实时渲染。

Spark 是一个为 THREE.js 和 WebGL2 构建的动态 3DGS 渲染器,在任何 Web 浏览器(桌面、移动、WebXR)中运行。只需数行代码,任何使用 THREE.js 的用户都可以轻松地将 3DGS 添加到场景中。

核心特性

  • 与 THREE.js 渲染管线集成:融合 splat 和网格对象渲染
  • 跨平台:支持 98%+ WebGL2 设备
  • 高性能:即使在低功耗移动设备上也能快速渲染
  • 多对象正确排序:多个 splat 对象同时渲染时保证正确的遮挡关系
  • 多格式支持:.PLY(含压缩)、.SPZ、.SPLAT、.KSPLAT
  • 多视点渲染:同时渲染多个视点
  • 完全动态:每个 splat 可被变换和编辑以支持动画
  • 实时编辑:splat 颜色编辑、位移、骨骼动画
  • 着色器图系统:在 GPU 上动态创建/编辑 splat
  • 高精度编码ExtSplats 提供 32 字节/splat 的 float32 精度

3. 2.0 新特性

Spark 2.0 主要驱动力是支持海量动态 3D Gaussian Splat 世界。

3.1 2.0 核心特性

稳定的可调帧率

  • 计算视点相关的"切片"通过 LoD 树,选择最佳 N 个 splats
  • 固定 N(桌面 2.5M,移动 500K-1.5M)
  • 时间复杂度 O(N log N)

复合 LoD 世界

  • 支持多 splat 对象同时 LoD 渲染
  • 联合遍历多棵树,最大化最小屏幕空间 splat 尺寸

Splat 降采样

  • tiny-lod:快速紧凑算法(按需运行)
  • bhatt-lod:高质量算法(预处理推荐)

可流式传输的 LoD 文件格式

  • .RAD(RADiance field)新格式
  • 支持 HTTP Range 请求流式传输
  • 从粗糙到精细的渐进式加载

共享分页/流式 splat 缓冲区

  • LRU "splat 页表"管理 GPU 内存
  • 默认 16M splats 池,跨所有对象共享
  • 支持 100M+ splats 场景在移动设备上实时渲染

高精度 splat 编码

  • ExtSplats:32字节/splat,float32 中心坐标

3.2 其他特性

  • 多独立视点和渲染器 :多个 SparkRenderer 实例
  • 即时 LoDnew SplatMesh({ url: "./my-splats.spz", lod: true })
  • LoD/非LoD 版本切换nonLod: true + SplatMesh.enableLod
  • 按需创建 LoD.createLodSplats()
  • 命令行 LoD 构建工具build-lod
  • 可调 LoD 细节级别:全局和每个 SplatMesh 级别
  • 大文件支持ReadableStream 加载 GB 级文件
  • 可链式 splat 修改器
  • 可自定义 splat 着色器代码
  • SparkXr:简易 AR/VR 包装器
  • 协方差 Splats(实验性):非均匀缩放
  • 线性混合蒙皮(实验性):骨骼动画的 LBS
  • Splat 传送门(实验性):动态 splat 传送门

4. 0.1 → 2.0 迁移指南

4.1 更新依赖

json 复制代码
{
  "dependencies": {
    "three": "0.180.0",
    "@sparkjsdev/spark": "^2.0.0"
  }
}

CDN 直接链接:

html 复制代码
<script type="importmap">
  {
    "imports": {
      "three": "https://cdnjs.cloudflare.com/ajax/libs/three.js/0.180.0/three.module.js",
      "@sparkjsdev/spark": "https://sparkjs.dev/releases/spark/2.0.0/spark.module.js"
    }
  }
</script>

4.2 关键变更

Spark 0.1 Spark 2.0
自动注入 SparkRenderer 必须手动创建 new SparkRenderer({ renderer })scene.add(spark)
spark.newViewpoint() 创建独立 new SparkRenderer()
sortRadial 按视点配置 直接在 SparkRenderer 上配置
VRButton 替换为 SparkXr
sort32sort360 已废弃,32位排序和360度排序自动启用
随机无排序渲染 已废弃
Splat 纹理 已废弃

4.3 临时回退

如有需要可使用 OldSparkRenderer(原 Spark 0.1 的 SparkRenderer)。


5. 系统设计

5.1 渲染数据流

SparkRenderer 是 Spark 中的关键组件,管理排序过程:

  1. 遍历可见的 THREE.js 场景图
  2. 编译场景中所有 splats 的完整列表
  3. 通过 SparkViewpoint 从 GPU 读回距离
  4. 在后台 worker 线程(SplatWorker)中使用桶排序
  5. 在下一次 render() 调用时执行单次实例化几何绘制

排序顺序落后渲染至少一帧,但通常不可察觉。

typescript 复制代码
const spark = new SparkRenderer({ renderer: webGlRenderer });
scene.add(spark);

5.2 可编程 Splats

标准化管线提供:

  • 刚性变换
  • RGB/透明度调整
  • 球谐函数
  • 颜色编辑和扰动(通过 SplatEdit
  • 双四元数骨骼动画(SplatSkinning
  • 可通过 dyno 注入任意代码

SplatMesh 派生自 SplatGeneratorTHREE.Object3D,可放置在场景层次结构的任何位置。


6. SparkRenderer

6.1 创建

typescript 复制代码
const spark = new SparkRenderer({
  renderer: THREE.WebGLRenderer;
});
const scene = new THREE.Scene();
scene.add(spark);

6.2 构造函数参数

必需参数

参数 描述
renderer THREE.WebGLRenderer 实例。应使用 antialias: false(默认)

重要可选参数

参数 默认值 描述
maxStdDev Math.sqrt(8) 渲染标准差范围
sortRadial true true=径向距离排序,false=Z深度排序
enableLod true 启用 LoD 渲染
lodSplatCount 自动 目标 splat 数量
lodSplatScale 1.0 LoD splat 数量缩放因子
pagedExtSplats false 分页 splat 使用扩展编码
covSplats false 启用协方差编码(非均匀缩放)
enable2DGS false 启用 2D 高斯渲染
premultipliedAlpha true 预乘 alpha
vertexShader undefined 自定义顶点着色器
fragmentShader undefined 自定义片段着色器
target undefined 离线渲染目标配置

6.3 主要方法

方法 描述
dispose() 释放所有资源
update({ scene, camera }) 手动更新 splat
render(scene, camera) 渲染到画布
renderTarget({ scene, camera }) 渲染到渲染目标
readTarget() 读回渲染目标像素
renderReadTarget({ scene, camera }) 渲染并读回
renderCubeMap({ scene, worldCenter, ... }) 渲染立方体贴图
renderEnvMap({ scene, worldCenter, ... }) 渲染环境贴图

7. SplatMesh

SplatMesh 是显示和操作 splat 集合的高级接口,类似于传统的三角形 THREE.Mesh(包含几何体 + 材质)。派生自 SplatGeneratorTHREE.Object3D

7.1 创建

typescript 复制代码
const splats = new SplatMesh({
  url?: string;                    // 文件 URL (.ply/.spz/.splat/.ksplat/.sog/.zip/.rad)
  fileBytes?: Uint8Array;         // 原始文件字节
  stream?: ReadableStream;        // 流式加载
  streamLength?: number;          // 流长度
  packedSplats?: PackedSplats;    // 已有 PackedSplats
  maxSplats?: number;             // 预留 splat 数量
  constructSplats?: (splats) => Promise<void>;  // 构造函数
  onProgress?: (event) => void;   // 进度回调
  onLoad?: (mesh) => Promise<void>;  // 加载完成回调
  editable?: boolean;             // 是否可编辑 (默认 true)
  raycastable?: boolean;          // 是否参与射线检测 (默认 true)
  onFrame?: ({ mesh, time, deltaTime }) => void;  // 每帧回调
  objectModifiers?: GsplatModifier[];  // 对象空间修改器
  worldModifiers?: GsplatModifier[];   // 世界空间修改器
  splatEncoding?: SplatEncoding;  // 编码范围覆盖
  extSplats?: boolean | ExtSplats; // 扩展 splat 编码
  lod?: boolean | number;         // 启用 LoD
  nonLod?: boolean;               // 保留非 LoD 版本
  enableLod?: boolean;            // 强制使用/不使用 LoD
  lodScale?: number;              // 每网格 LoD 细节缩放
  paged?: boolean;                // 启用分页流式传输
});
scene.add(splats);

7.2 实例属性

属性 描述
initialized Promise,可等待初始化完成
isInitialized 是否初始化完成
recolor THREE.Color,着色所有 splats
opacity 全局透明度倍数
skinning SplatSkinning 骨骼动画实例
edits SplatEdit 列表
splatRgba RgbaArray,覆盖 RGBA 值
maxSh 最大球谐函数级别 (默认 3)
enableViewToObject 每帧更新 viewToObject
enableViewToWorld 每帧更新 viewToWorld
enableWorldToView 每帧更新 worldToView

7.3 方法

方法 描述
dispose() 释放缓冲区
pushSplat(center, scales, quaternion, opacity, color) 添加 splat
forEachSplat(callback) 遍历所有 splat
getBoundingBox(centers_only) 获取包围盒
updateGenerator() 更新处理管线
raycast(raycaster, intersects) 射线检测

7.4 射线检测示例

javascript 复制代码
const raycaster = new THREE.Raycaster();
canvas.addEventListener("click", (event) => {
  raycaster.setFromCamera(new THREE.Vector2(
    (event.clientX / canvas.width) * 2 - 1,
    -(event.clientY / canvas.height) * 2 + 1,
  ), camera);
  const intersects = raycaster.intersectObjects(scene.children);
  const splatIndex = intersects.findIndex((i) => i.object instanceof SplatMesh);
});

8. PackedSplats

PackedSplats 是 Gaussian splat 的集合,以精确 16 字节/splat 的格式打包,最大化内存和缓存效率。

8.1 字节布局

每个 PackedSplat 占用 16 字节(4 × uint32):

偏移 (字节) 字段 大小 描述
0 R 1 红色通道 (uint8 → 0.0--1.0)
1 G 1 绿色通道
2 B 1 蓝色通道
3 A 1 Alpha/透明度通道
4--5 center.x 2 X 坐标 (float16)
6--7 center.y 2 Y 坐标 (float16)
8--9 center.z 2 Z 坐标 (float16)
10 quat oct.U 1 八面体四元数 U 分量 (uint8)
11 quat oct.V 1 八面体四元数 V 分量 (uint8)
12 scale.x 1 X 缩放 (log编码到 uint8)
13 scale.y 1 Y 缩放
14 scale.z 1 Z 缩放
15 quat angle (θ) 1 编码四元数旋转角度 (θ/π·255)

8.2 编码细节

  • RGBA:uint8 sRGB 值,0...255 → 0...1
  • 中心坐标:float16,10位尾数(约 0.1% 精度),范围达 32K
  • 缩放 :对数编码 ln(scale),范围 e-12...e9,约 7% 步长
  • 旋转:八面体编码轴方向 (U/V 各 8 位) + 旋转角度 (0...π, 8 位)

8.3 球谐函数 (SH) 布局

缓冲区 每 splat 字数 存储值 量化
sh1 2 (8 字节) 9 值 (3 系数 × RGB) Sint7
sh2 4 (16 字节) 15 值 (5 系数 × RGB) Sint8
sh3 4 (16 字节) 21 值 (7 系数 × RGB) Sint6

8.4 JavaScript API

javascript 复制代码
// 通过接口设置
packedSplats.setSplat(index, center, scales, quaternion, opacity, color);

// 直接设置 Uint32 数组
import { utils } from "@sparkjsdev/spark";
utils.setPackedSplat(packedSplats.packedArray, index, x, y, z, scaleX, scaleY, ...);
utils.setPackedSplatQuat(packedSplats.packedArray, index, quatX, quatY, quatZ, quatW);

// 解包
const { center, scales, quaternion, color, opacity } = utils.unpackSplat(packedSplats.packedArray, index);

// 遍历
packedSplats.forEachSplat((index, center, scales, quaternion, opacity, color) => {
    utils.setPackedSplatScales(packedSplat.packedArray, index, 0.005, 0.01, 0.015);
    packedSplat.setSplat(index, center, scales, quaternion, opacity, color);
});

8.5 GLSL 工具函数

glsl 复制代码
// 打包 splat 为 uvec4
uvec4 packSplat(vec3 center, vec3 scales, vec4 quaternion, vec4 rgba);

// 从 uvec4 解包 splat
void unpackSplat(uvec4 packed, out vec3 center, out vec3 scales, out vec4 quaternion, out vec4 rgba);

8.6 实例方法

方法 描述
dispose() 释放渲染目标和纹理
ensureSplats(numSplats) 确保数组容量
getSplat(index) 解包 splat 为 THREE.js 组件
setSplat(index, center, scales, quaternion, opacity, color) 设置所有组件
pushSplat(center, scales, quaternion, opacity, color) 追加 splat
forEachSplat(callback) 遍历
getTexture() 返回 DataArrayTexture
getEmpty() 返回未初始化纹理
createLodSplats() 创建 LoD

9. ExtSplats

ExtSplats 使用双倍宽度编码 32 字节/splat(vs PackedSplats 的 16 字节),以保留更多精度同时保持 GPU 友好布局。

9.1 32 字节布局

extArrays[0] 包含字节 0...15,extArrays[1] 包含字节 16...31:

偏移 字段 大小 描述
0-3 center.x 4 float32
4-7 center.y 4 float32
8-11 center.z 4 float32
12-13 opacity 2 float16
14-15 reserved 2 保留
16-17 color.r 2 float16
18-19 color.g 2 float16
20-21 color.b 2 float16
22-23 ln(scale.x) 2 float16
24-25 ln(scale.y) 2 float16
26-27 ln(scale.z) 2 float16
28-31 quaternion 4 打包八面体+角度 (10/10/12 位)

9.2 API

typescript 复制代码
import { utils } from "@sparkjsdev/spark";

utils.encodeExtSplat(extSplats.extArrays, index, x, y, z, sx, sy, sz, qx, qy, qz, qw, opacity, r, g, b);
const { center, scales, quaternion, color, opacity } = utils.decodeExtSplat(extSplats.extArrays, index);

Dyno 中使用:

typescript 复制代码
const gsplat = dyno.readExtSplat(extSplats.dyno, index);

10. 加载 Splat 文件

Spark 支持大多数流行的 splat 文件格式。

10.1 可自动检测的格式 (.ply, .spz, .sog/.zip, .rad)

javascript 复制代码
// 简单加载
const splats = new SplatMesh({ url: "./butterfly.ply" });
scene.add(splats);

// 无扩展名但可从内容自动检测
scene.add(new SplatMesh({ url: "plyBin/0123456789abcdef" }));

10.2 通过 PackedSplats/ExtSplats 加载

javascript 复制代码
const packedSplats = new PackedSplats({ url: "./clone.ply" });

const splats1 = new SplatMesh({ packedSplats });
scene.add(splats1);

const splats2 = new SplatMesh({ packedSplats });
scene.add(splats2);

10.3 通过 SplatLoader

javascript 复制代码
const loader = new SplatLoader();
loader.loadAsync(url, (event) => {
  if (event.type === "progress") {
    const progress = event.lengthComputable
      ? `${((event.loaded / event.total) * 100).toFixed(2)}%`
      : `${event.loaded} bytes`;
    console.log(`下载进度: ${progress}`);
  }
})
.then((packedSplats) => {
  const splatMesh = new SplatMesh({ packedSplats });
  splatMesh.quaternion.set(1, 0, 0, 0);  // OpenCV → OpenGL
  splatMesh.position.set(0, 0, -1);
  splatMesh.scale.setScalar(0.5);
  scene.add(splatMesh);
})
.catch((error) => console.warn(error));

10.4 额外格式 (.splat, .ksplat)

这些格式无法从内容自动检测,需通过 URL 扩展名或显式设置 fileType

javascript 复制代码
// URL 扩展名推断
const splats = new SplatMesh({ url: "./butterfly.splat" });
scene.add(splats);

// 显式设置
scene.add(new SplatMesh({
  url: "splatBin/0123456789abcdef",
  fileType: SplatFileType.SPLAT,
}));

11. 程序化生成 Splats

11.1 基础方法

javascript 复制代码
const splats = new PackedSplats();
const center = new THREE.Vector3(0, 0, 0);
const scales = new THREE.Vector3(0.1, 0.1, 0.1);
const quaternion = new THREE.Quaternion();
const opacity = 1.0;
const color = new THREE.Color();
splats.pushSplat(center, scales, quaternion, opacity, color);

使用构造函数回调:

javascript 复制代码
const splats = new PackedSplats({
  construct: (splats) => {
    for (let i = 0; i < NUM_SPLATS; ++i) {
      // 计算 splat #i
      splats.pushSplat(center, scales, quaternion, opacity, color);
    }
  },
});

const mesh = new SplatMesh({ packedSplats: splats });
scene.add(mesh);

11.2 内置构造函数

Grid 网格

javascript 复制代码
import { constructGrid } from "@sparkjsdev/spark";

const grid = new SplatMesh({
  constructSplats: (splats) => constructGrid({
    splats,
    extents: new THREE.Box3(
      new THREE.Vector3(-10, -10, -10),
      new THREE.Vector3(10, 10, 10),
    ),
  }),
});

XYZ 坐标轴

javascript 复制代码
import { constructAxes } from "@sparkjsdev/spark";

const axes = new SplatMesh({
  constructSplats: (splats) => constructAxes({ splats }),
});

球体

javascript 复制代码
import { constructSpherePoints } from "@sparkjsdev/spark";

const sphere = new SplatMesh({
  constructSplats: (splats) => constructSpherePoints({
    splats,
    maxDepth: 4,  // 递归深度(splat 数量指数增长)
  }),
});

文本光栅化

typescript 复制代码
const splats = textSplats({
  text: string;
  font?: string;       // 默认 "Arial"
  fontSize?: number;   // 默认 32
  color?: THREE.Color;
  dotRadius?: number;  // 默认 0.8
  textAlign?: "left" | "center" | "right";
  lineHeight?: number; // 默认 1.0
});

图像转 Splats

typescript 复制代码
const image = imageSplats({
  url: string;
  dotRadius?: number;
  subXY?: number;  // 子采样因子
  forEachSplat?: (width, height, index, center, scales, quaternion, opacity, color) => number | null;
});

粒子效果

staticBox

在 3D 包围盒内生成随机"静态"splats。

snowBox

生成随时间确定性移动的粒子轨迹,支持雪/雨等效果:

typescript 复制代码
const snowControls = generators.snowBox({
  box: THREE.Box3,                      // 包围盒
  numSplats: number,                    // splat 数量
  density: number,                      // 密度 (默认 100)
  anisoScale: THREE.Vector3,           // 各向异性缩放
  minScale: number,                     // 最小缩放 (默认 0.001)
  maxScale: number,                     // 最大缩放 (默认 0.005)
  fallDirection: THREE.Vector3,        // 下落方向
  fallVelocity: number,                // 下落速度
  wanderScale: number,                  // 漂移缩放
  wanderVariance: number,               // 漂移方差
  color1: THREE.Color,                  // 颜色1
  color2: THREE.Color,                  // 颜色2
  opacity: number,                      // 透明度 (默认 1)
});

// 预设
const mySnow = { ...DEFAULT_SNOW, density: 500 };
const myRain = { ...DEFAULT_RAIN };

12. Splat 编辑器

Spark 提供通过 SDF(有符号距离场)编辑 splat 的能力。

12.1 创建编辑操作

typescript 复制代码
const edit = new SplatEdit({
  name?: string;
  rgbaBlendMode?: SplatEditRgbaBlendMode;  // 默认 MULTIPLY
  sdfSmooth?: number;                       // SDF 形状之间混合 (默认 0.0)
  softEdge?: number;                        // 软边缘 (默认 0.0)
  invert?: boolean;                         // 反转 (默认 false)
  sdfs?: SplatEditSdf[];                   // SDF 形状数组
});
scene.add(edit);

12.2 RGBA 混合模式

模式 描述
MULTIPLY RGBA 分量乘法混合(默认)
SET_RGB 忽略 Alpha,直接设置 RGB
ADD_RGBA 将 SDF 的 RGBA 值加到 splat 上(可产生超饱和效果)

12.3 SDF 形状

typescript 复制代码
const shape = new SplatEditSdf({
  type?: SplatEditSdfType;  // 默认 SPHERE
  invert?: boolean;
  opacity?: number;          // 默认 1.0
  color?: THREE.Color;       // 默认 Color(1, 1, 1)
  displace?: THREE.Vector3;  // 默认 Vector3(0,0,0)
  radius?: number;           // 默认 0.0
});
edit.add(shape);

SDF 形状类型

类型 描述 参数
ALL 影响空间所有点
PLANE 无限平面 position, rotation
SPHERE 球体 position, radius
BOX 盒子(可选圆角) position, rotation, sizes, radius
ELLIPSOID 椭球体 position, rotation, sizes
CYLINDER 圆柱体 position, rotation, size_y
CAPSULE 胶囊体 position, rotation, size_y
INFINITE_CONE 无限圆锥 position, rotation, radius=angle

12.4 多 SDF 形状混合

RGBA-XYZ 值使用指数"softmax"函数混合所有 SDF 形状。可通过更新 SplatEditSplatEditSdf 对象创建实时动画和特效(如风中波纹、火焰、水面效果等)。

13. Level-of-Detail 入门

13.1 两种简单方法

方法一:使用 lod 标志

javascript 复制代码
const splats = new SplatMesh({ url: "./my-splats.spz", lod: true });
scene.add(splats);

在后台 WebWorker 中创建 LoD 版本(每 1M splats 约 1-3 秒),最多支持约 30M 输入 splats。

方法二:预构建 LoD 树(推荐)

shell 复制代码
npm run build-lod -- my-splats.ply more-splats.spz --quality

输出 my-splats-lod.rad,可直接加载:

javascript 复制代码
const splats = new SplatMesh({ url: "./my-splats-lod.rad" });

即时加载(流式传输):

javascript 复制代码
const splats = new SplatMesh({ url: "./my-splats-lod.rad", paged: true });

13.2 处理大坐标

启用 ExtSplats 扩展 splat 编码(32字节/splat,float32 中心坐标):

javascript 复制代码
// 加载 splat 时
new SplatMesh({ url: "./my-splats.spz", extSplats: true });

// 流式/池化 splats
const spark = new SparkRenderer({ renderer, pagedExtSplats: true });

13.3 调优 LoD 细节和性能

调整 splat 数量预算

  • SparkRenderer.lodSplatCount:基础 splat 数量(自动根据平台设置)
  • SparkRenderer.lodSplatScale:splat 数量倍数(默认 1.0)

形状 LoD splat 选择(中央凹渲染)

  • SparkRenderer.behindFoveate:背后 foveation 缩放(默认 0.2)
  • SparkRenderer.coneFov0:全分辨率锥角(默认 90°)
  • SparkRenderer.coneFov:降低分辨率锥角(默认 120°)
  • SparkRenderer.coneFoveate:锥边缘 foveation 缩放(默认 0.4)

单个 SplatMesh 预算偏置

  • SplatMesh.lodScale:每个对象细节缩放(2.0 = 2x 细节)
  • SplatMesh.behindFoveate / coneFov0 / coneFov / coneFoveate:覆盖全局参数

13.4 build-lod 命令行工具

shell 复制代码
npm run build-lod -- my-splats.ply more-splats.spz [..options]

主要选项:

  • --quick:快速紧凑的 tiny-lod 方法(默认)
  • --quality:高质量 bhatt-lod 方法(推荐离线使用)
  • --max-sh=#:限制最大球谐函数编码 (0...3)
  • --rad-chunked:输出分块 RAD 文件用于流式传输

使用 --rad-chunked 后会生成小头文件 .rad 和块文件 .radc,Spark 自动获取所需块。

shell 复制代码
# 直接调用 Rust
cd rust/build-lod
cargo build --release
cargo run --release -- my-splats.ply more-splats.spz [..options]

14. LoD 深度解析

14.1 LoD 高斯 Splat 树

"高斯 splat 树"概念上类似 MipMap:高细节 splat 被降采样为更粗糙的 splat,表示为树节点和子节点。通过合并所有 splat 到单个"根 splat",形成树/层次结构。

Spark LoD 渲染器在后台 worker 线程中计算树的"切割",找到满足以下条件的前沿节点集合:

  • 屏幕空间大小相似
  • 不小于一个屏幕像素
  • 最大 splat 数量为 N

算法 O(N log N) 时间复杂度。

14.2 Splat 降采样

合并 splat 时:

  • 按照 Kerbl 等人的文献计算中心和 3D 协方差
  • 权重 = 透明度 × 表面积(椭球模型)
  • 颜色 = 加权和

Spark 透明度函数

传统高斯透明度 A 最多为 1.0,合并大量 splat 后曲线非高斯。Spark 使用替代函数:

  • D ≤ 1.0:opacity(x) = D * e^(-0.5 * x²)
  • D > 1.0:opacity(x) = e^(-0.5 * D * (|x| - (D-1))²)

D 从存储的 uint8 解码:0...1 → 0...2,若 > 1.0 则映射到 1...5。

14.3 Quick LoD 算法 (tiny-lod)

基于体素八叉树:

  1. 将每个 splat 放入其"自然"体素
  2. 从最细层级开始,合并同体素内多个 splat
  3. 逐层上升,每个体素一个 splat
  4. 直到所有 splat 在 2×2×2 体素内
  5. 最终合并为根节点

默认使用 1.5(非 2.0)作为指数基底,产生更平滑的 LoD 过渡。

14.4 Splat 树遍历

从根开始,优先队列按 pixel_scale(视点角大小)排序:

  1. 队列顶部 splat 的 pixel_scale < 屏幕像素大小 → 停止
  2. 无子节点 → 加入输出集合
  3. 替换为子节点会超过最大 splat 预算 → 停止

14.5 多 Splat 树遍历

扩展算法支持多棵 splat 树,每个根加入初始优先队列,全局平衡 splat 数量。

14.6 LoD Worker

树遍历在后台 WebWorker 中运行,Rust 实现编译为 WebAssembly。单个专用 lodWorker 实例执行序列化 RPC 遍历。

14.7 预构建 SPZ LoD Splat 树

使用 build-lod 工具输出 .RAD 文件。RAD 文件支持:

  • 流式传输
  • 可配置编码
  • 从粗糙到精细的渐进加载

15. Dyno 着色器图系统

dyno 着色器图系统是 Spark 的架构支柱之一,允许使用 JavaScript(和可选的 GLSL)创建自定义计算图,编译为 GLSL 在 GPU 上运行。

15.1 核心概念

  • Dyno:具有多个类型化输入和输出的函数块
  • DynoVal<T>:值在 GPU 计算图中的类型化引用
  • DynoType :GPU 类型(内置如 "int", "float", "vec4" 或自定义如 Gsplat

15.2 主要用途

Spark 在两个地方使用 Dyno:

  1. SplatGenerator/SplatMesh 动态生成 splat
  2. 计算 splat 距离度量用于 CPU 回读和排序

15.3 DynoBlock 示例

typescript 复制代码
const myGsplatGenerator = dyno.dynoBlock(
  { index: "int" },           // 输入
  { gsplat: Gsplat },          // 输出
  ({ index }) => {             // 闭包:输入 → 输出
    let gsplat = dyno.readPackedSplat(myPackedSplats, index);
    const opacity = dyno.dynoConst("float", 1.0);
    gsplat = dyno.combineGsplat({ gsplat, opacity });
    return { gsplat };
  },
);

15.4 自定义类型

typescript 复制代码
export const defineGsplat = unindent(`
  struct Gsplat {
    vec3 center;
    uint flags;
    vec3 scales;
    int index;
    vec4 quaternion;
    vec4 rgba;
  };
  const uint GSPLAT_FLAG_ACTIVE = 1u << 0u;
`);

15.5 值类型

描述
DynoLiteral 常量 GLSL 字面量
DynoConst JavaScript 值常量
DynoUniform 可每帧更新的统一变量
DynoOutput Dyno 块的输出

15.6 Dyno 构造函数选项

选项 描述
inTypes 输入名 → DynoType 的映射
outTypes 输出名 → DynoType 的映射
inputs 输入值映射
update 每次执行前调用的更新函数
globals 全局 GLSL 定义
statements GLSL 语句
generate 生成 globals + statements + uniforms

16. Dyno 标准库

Spark dyno 系统提供覆盖 GLSL ES 3.0 大部分内置函数的标准库。

约定:PascalCase 类名,camelCase 辅助函数。例如 new dyno.Add({ a, b }) 等价于 dyno.add(a, b)

16.1 数据类型转换

函数 描述
bool(value) 转布尔
int(value) / uint(value) 转整数
float(value) 转浮点
bvec<N>(value) / ivec<N>(value) / uvec<N>(value) / vec<N>(value) 转向量
mat<N>(value) 转矩阵
floatBitsToInt(value) / floatBitsToUint(value) float 位重解释
intBitsToFloat(value) / uintBitsToFloat(value) int 位重解释
pack/unpack 系列 Snorm/Unorm/Half 打包

16.2 逻辑运算

and, or, xor, not, lessThan, lessThanEqual, greaterThan, greaterThanEqual, equal, notEqual, any, all, select, compXor

16.3 数学运算

add, sub, mul, div, mod, modf, neg, abs, sign, floor, ceil, trunc, round, fract, pow, exp, exp2, log, log2, sqr, sqrt, inversesqrt, min, max, clamp, mix, step, smoothstep, isNan, isInf

16.4 三角函数

radians, degrees, sin, cos, tan, asin, acos, atan, atan2, sinh, cosh, tanh, asinh, acosh, atanh

16.5 线性代数

length, distance, dot, cross, normalize, faceforward, reflectVec, refractVec, split, combine, projectH, extendVec, swizzle, compMult, outer, transpose, determinant, inverse

16.6 纹理查找

textureSize, texture, texelFetch

16.7 变换

函数 描述
transformPos(position, { scale?, scales?, rotate?, translate? }) 位置变换
transformDir(dir, { scale?, scales?, rotate? }) 方向变换
transformQuat(quat, { rotate? }) 四元数旋转

16.8 统一变量

typescript 复制代码
dynoBool(value)      // 布尔
dynoFloat(value)     // 浮点
dynoInt(value)       // 整数
dynoVec<N>(value)    // 浮点向量
dynoMat<N>(value)    // 矩阵
dynoSampler2D(texture)  // 采样器

16.9 哈希和随机数

使用 PCG 随机数生成器:

函数 描述
pcgMix(value) 混合值到 uint32 种子
pcgNext(state) 推进 PCG
pcgHash(state) 哈希为 uint32
hash(value) / hash2/3/4(value) 哈希为 uint/uvec
hashFloat(value) / hashVec2/3/4(value) 哈希为 float/vec

16.10 Splat 数据操作

函数 描述
numPackedSplats(packedSplats) 获取 splat 数量
readPackedSplat(packedSplats, index) 按索引读取
readPackedSplatRange(packedSplats, index, base, count) 范围读取
splitGsplat(gsplat) 拆分为组件
combineGsplat(gsplat) 从组件创建
gsplatNormal(gsplat) 获取法线
transformGsplat(gsplat, { scale?, rotate?, translate?, recolor? }) 变换

Gsplat 结构体字段

字段 类型 描述
center vec3 中心位置
flags uint 标志 (0x1 = active)
scales vec3 缩放
index int 数组索引
quaternion vec4 四元数旋转
rgba vec4 颜色

17. 控制

17.1 SparkControls

typescript 复制代码
const controls = new SparkControls({
  canvas: HTMLCanvasElement;
});

renderer.setAnimationLoop((time) => {
  renderer.render(scene, camera);
  controls.update(camera);
});

SparkControls 内部使用 FpsMovementPointerControls

17.2 FpsMovement

FPS 风格移动控制(键盘+鼠标或手柄):

typescript 复制代码
const fpsMovement = new FpsMovement({
  moveSpeed?: number;          // 默认 1.0
  rollSpeed?: number;          // 默认 2.0
  stickThreshold?: number;    // 默认 0.1
  rotateSpeed?: number;        // 默认 2.0
  capsMultiplier?: number;     // 默认 10.0
  shiftMultiplier?: number;   // 默认 5.0
  ctrlMultiplier?: number;    // 默认 0.2
  xr?: THREE.WebXRManager;    // XR 手柄支持
});

17.3 PointerControls

指针/鼠标/触摸控制:

typescript 复制代码
const pointerControls = new PointerControls({
  canvas: HTMLCanvasElement;
  rotateSpeed?: number;        // 默认 0.002
  slideSpeed?: number;         // 默认 0.006
  scrollSpeed?: number;        // 默认 0.0015
  reverseRotate?: boolean;     // 反转旋转
  reverseSlide?: boolean;      // 反转滑动
  moveInertia?: number;        // 默认 0.15
  rotateInertia?: number;      // 默认 0.15
  doublePress?: ({ position, intervalMs }) => void;
});

17.4 GUI 配置

typescript 复制代码
import GUI from "lil-gui";

const gui = new GUI({ title: "Settings + Controls" }).close();
const controlOptions = { reversePointerFps: false, reversePointerPan: false };
gui.add(controlOptions, "reversePointerFps")
   .name("Reverse Pointer FPS")
   .onChange((value) => { pointerControls.reverseRotate = value; });

18. 性能调优

18.1 Splat 预算建议

平台 推荐 splat 数量
Quest 3 ≤ 1M
Android 手机 1-2M
iPhone 1-3M
桌面电脑 1-5M(高端可达 10-20M+)

18.2 关键参数调优

maxStdDev

javascript 复制代码
// 减小值提升性能,如 VR 推荐 Math.sqrt(5)
spark.maxStdDev = Math.sqrt(5);

关闭抗锯齿

javascript 复制代码
const renderer = new THREE.WebGLRenderer({ antialias: false });  // 默认值

MSAA 对 splat 渲染无益且显著增加开销。

像素比

javascript 复制代码
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));

高 DPI 渲染可能不必要,考虑是否有足够 splats 来支持。


19. 社区资源

Discord

加入 Spark Discord

React 示例

GitHub

https://github.com/sparkjsdev/spark

在线工具

相关推荐
MATLAB代码顾问2 小时前
【3D重建】NeRF:神经辐射场详解与实践
3d
AIminminHu2 小时前
((AI升级篇)OpenGL渲染与几何内核那点事-(二-1-(14):你的3D查看器,是怎么一步步先试着造个数据工厂,向学会“教”机器人看世界的而努力)
人工智能·3d·机器人
2401_8638014619 小时前
osgb怎么直接导入3dmax,加载打开osgb格式,模型优化,一键高模生成低模建筑插件
3d·3dsmax·3dmax·3dtiles·osgb
小三金2 天前
免费的国外模型资源网站整理
3d
AI_Auto3 天前
【智能制造】- 工业制造中的3D视觉四大核应用场景
3d·制造
ZC跨境爬虫3 天前
跟着 MDN 学 HTML day_16:(音频与视频处理——从画布滤镜到3D沉浸音频的进阶指南)
前端·javascript·ui·3d·html·音视频
爱看书的小沐5 天前
【小沐杂货铺】基于Three.js绘制三维艺术画廊3DArtGallery (Three.js,WebGL)
javascript·3d·webgl·three.js·babylon.js·三维画廊
格林威5 天前
3D相机视觉检测:环境光太强,结构光点云全是噪点怎么办?
开发语言·人工智能·数码相机·计算机视觉·3d·视觉检测·工业相机
threelab5 天前
Three.js 3D 饼图效果 | 三维可视化 / AI 提示词
javascript·人工智能·3d