SparkJS 完整技术文档
Spark --- 基于 THREE.js 和 WebGL2 的高级 3D Gaussian Splatting 渲染器
源文档: https://sparkjs.dev/docs/
整理日期: 2026-05-08
目录
- 快速开始
- 概述
- [2.0 新特性](#2.0 新特性)
- [0.1 → 2.0 迁移指南](#0.1 → 2.0 迁移指南)
- 系统设计
- SparkRenderer
- SplatMesh
- PackedSplats
- ExtSplats
- [加载 Splat 文件](#加载 Splat 文件)
- [程序化生成 Splats](#程序化生成 Splats)
- [Splat 编辑器](#Splat 编辑器)
- [Level-of-Detail 入门](#Level-of-Detail 入门)
- [LoD 深度解析](#LoD 深度解析)
- [Dyno 着色器图系统](#Dyno 着色器图系统)
- [Dyno 标准库](#Dyno 标准库)
- 控制
- 性能调优
- 社区资源
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实例 - 即时 LoD :
new 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 |
sort32、sort360 |
已废弃,32位排序和360度排序自动启用 |
| 随机无排序渲染 | 已废弃 |
| Splat 纹理 | 已废弃 |
4.3 临时回退
如有需要可使用 OldSparkRenderer(原 Spark 0.1 的 SparkRenderer)。
5. 系统设计
5.1 渲染数据流
SparkRenderer 是 Spark 中的关键组件,管理排序过程:
- 遍历可见的 THREE.js 场景图
- 编译场景中所有 splats 的完整列表
- 通过
SparkViewpoint从 GPU 读回距离 - 在后台 worker 线程(
SplatWorker)中使用桶排序 - 在下一次
render()调用时执行单次实例化几何绘制
排序顺序落后渲染至少一帧,但通常不可察觉。
typescript
const spark = new SparkRenderer({ renderer: webGlRenderer });
scene.add(spark);
5.2 可编程 Splats
标准化管线提供:
- 刚性变换
- RGB/透明度调整
- 球谐函数
- 颜色编辑和扰动(通过
SplatEdit) - 双四元数骨骼动画(
SplatSkinning) - 可通过
dyno注入任意代码
SplatMesh 派生自 SplatGenerator → THREE.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(包含几何体 + 材质)。派生自 SplatGenerator → THREE.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 形状。可通过更新 SplatEdit 和 SplatEditSdf 对象创建实时动画和特效(如风中波纹、火焰、水面效果等)。
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)
基于体素八叉树:
- 将每个 splat 放入其"自然"体素
- 从最细层级开始,合并同体素内多个 splat
- 逐层上升,每个体素一个 splat
- 直到所有 splat 在 2×2×2 体素内
- 最终合并为根节点
默认使用 1.5(非 2.0)作为指数基底,产生更平滑的 LoD 过渡。
14.4 Splat 树遍历
从根开始,优先队列按 pixel_scale(视点角大小)排序:
- 队列顶部 splat 的
pixel_scale< 屏幕像素大小 → 停止 - 无子节点 → 加入输出集合
- 替换为子节点会超过最大 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:
- 从
SplatGenerator/SplatMesh动态生成 splat - 计算 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 内部使用 FpsMovement 和 PointerControls。
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
React 示例
spark-react-basic:基本 canvas + THREE.js + Sparkspark-react-r3f:React Three Fiber 声明式集成spark-react-router:React Router v7 + SSRspark-react-nextjs:Next.js App Router 集成
GitHub
https://github.com/sparkjsdev/spark