WebGPU 开发指南
本文介绍如何在项目中使用 WebGPU 渲染 3DGS 场景,以及与 WebGL 接入方式的差异。
引擎支持
WebGPU 管线仅支持 Three.js 引擎。 CesiumJS 引擎不支持 WebGPU 渲染。
版本要求
SDK版本:SDK v0.6.4+
数据格式限制
WebGPU 管线目前仅支持 .lcc2 格式。.lcc 格式暂时不支持 WebGPU 渲染,后续会逐步支持。
如果传入 .lcc 数据路径,应降级到 WebGL 渲染器。示例代码中通过 forceWebGL 参数实现了这一逻辑:
const forceWebGL = new URL(dataPath, location.origin).pathname.endsWith('.lcc');
const renderer = await createRenderer(canvas, forceWebGL);
WebXR限制
目前SDK仅WebGL管线支持WebXR功能。WebGPU管线暂不支持WebXR。
移动端限制
不建议移动端使用WebGPU管线,当前SDK的WebGL管线性能更优。
Three.js 的三条渲染路径
Three.js 引擎源码中存在三条渲染路径,理解它们的区别对正确接入 SDK 至关重要。
路径 1:经典 WebGLRenderer(广泛使用)
WebGLRenderer 是 Three.js 自诞生以来的核心渲染器,至今仍是使用最广泛的选择。它直接基于 WebGL 2 API 构建,材质系统使用 GLSL shader。
引用方式:
import * as THREE from 'three';
const renderer = new THREE.WebGLRenderer();
SDK 支持此路径,同时支持 .lcc 和 .lcc2 格式。
路径 2:新架构 WebGPURenderer + WebGPU 后端
WebGPURenderer 是 Three.js 的新一代渲染器,采用全新的抽象后端架构。它引入了基于节点的材质系统(Node Material)和 TSL(Three.js Shading Language),shader 代码通过 TSL 编写后可按后端自动编译为 WGSL 或 GLSL。当浏览器支持 WebGPU 时,WebGPURenderer 使用原生 WebGPU 后端。
引用方式:
import * as THREE from 'three';
import * as THREE_WEBGPU from 'three/webgpu';
import * as THREE_TSL from 'three/tsl';
const renderer = new THREE_WEBGPU.WebGPURenderer({ canvas });
await renderer.init();
SDK 支持此路径,仅支持 .lcc2 格式。
路径 3:新架构 WebGPURenderer + WebGL 2 后端
WebGPURenderer 内置了 WebGL 2 后端作为降级方案。当浏览器不支持 WebGPU 时,WebGPURenderer 会自动切换到 WebGL 2 后端运行。这意味着虽然仍在使用 WebGL 2,但材质系统、shader 管线与路径 2 相同(Node Material + TSL),只是最终编译目标为 GLSL。
SDK 不支持此路径。
路径对比
| 路径 | 渲染器 | 实际图形 API | 材质 / Shader 系统 | SDK 支持 | 支持格式 |
|---|---|---|---|---|---|
| 1. 经典 WebGL | WebGLRenderer | WebGL 2 | GLSL(ShaderMaterial) | ✅ | .lcc .lcc2 |
| 2. 新架构 WebGPU | WebGPURenderer | WebGPU | Node Material / TSL / WGSL | ✅ | .lcc2 |
| 3. 新架构 WebGL 降级 | WebGPURenderer | WebGL 2(后端降级) | Node Material / TSL / GLSL | ❌ | - |
WebGPU 与 WebGL 接入差异
与 WebGL 接入相比,WebGPU 接入改动集中在以下几点,其余业务代码(相机控制、性能参数、渲染循环等)完全一致。
1. 额外引入 three/webgpu 和 three/tsl
WebGL 只需引入 three,WebGPU 需要额外引入两个模块:
<!-- importmap -->
<script type="importmap">
{
"imports": {
"three": "./engine/three/three.module.js",
"three/webgpu": "./engine/three/three.webgpu.js",
"three/tsl": "./engine/three/three.tsl.js",
"three/addons/": "./engine/three/jsm/"
}
}
</script>
import * as THREE from 'three';
import * as THREE_WEBGPU from 'three/webgpu';
import * as THREE_TSL from 'three/tsl';
2. 异步创建渲染器
WebGL 渲染器是同步创建的,WebGPU 渲染器需要异步初始化:
// WebGL (synchronous)
const renderer = new THREE.WebGLRenderer({ canvas, antialias: false });
// WebGPU (asynchronous)
const renderer = new THREE_WEBGPU.WebGPURenderer({ canvas, antialias: false });
await renderer.init();
推荐使用 SDK 示例中提供的 createRenderer 工具函数,它实现了完整的降级链:
- 浏览器不支持
navigator.gpu→ 回退WebGLRenderer - 获取不到 GPU adapter → 回退
WebGLRenderer WebGPURenderer初始化后静默降级到 WebGL 2 后端(无GPUDevice)→ 销毁并回退WebGLRenderer- 以上均通过 → 返回真正的 WebGPU 渲染器
import { createRenderer } from './engine/three/util.js';
const renderer = await createRenderer(canvas, forceWebGL);
const isWebGPU = renderer.isWebGPURenderer === true;
3. 关闭 sRGB 输出和色调映射
WebGPU 模式下必须关闭 Three.js 默认的 sRGB 输出转换和色调映射。默认的 sRGB 输出会额外添加一道 linear → sRGB 转换 pass,与 3DGS 的 premultiplied alpha 混合冲突,导致颜色偏差:
if (isWebGPU) {
renderer.outputColorSpace = THREE.LinearSRGBColorSpace;
renderer.toneMapping = THREE.NoToneMapping;
}
WebGL 模式不需要此处理。
4. LCCRender.load 参数
WebGPU 需要额外传入 renderLibWebgpu 和 renderLibTSL:
// WebGL
LCCRender.load({
camera, scene, renderer,
dataPath,
renderLib: THREE,
canvas: renderer.domElement,
// ...
});
// WebGPU
LCCRender.load({
camera, scene, renderer,
dataPath,
renderLib: THREE,
renderLibWebgpu: THREE_WEBGPU, // new
renderLibTSL: THREE_TSL, // new
canvas: renderer.domElement,
// ...
});
GPU 设备 Limits 配置
创建 WebGPURenderer 时,建议查询 GPU adapter 的实际 limits 并传入 requiredLimits,以充分利用设备能力:
const adapter = await navigator.gpu.requestAdapter();
const requiredLimits = {
maxBufferSize: adapter.limits.maxBufferSize,
maxStorageBufferBindingSize: adapter.limits.maxStorageBufferBindingSize,
maxTextureDimension2D: adapter.limits.maxTextureDimension2D
};
const renderer = new THREE_WEBGPU.WebGPURenderer({
canvas,
antialias: false,
requiredLimits
});
await renderer.init();
完整 WebGPU 示例
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>LCC Three.js WebGPU Demo</title>
<style>
html, body { margin: 0; padding: 0; height: 100%; overflow: hidden; }
canvas { display: block; }
</style>
</head>
<body>
<script type="importmap">
{
"imports": {
"three": "./engine/three/three.module.js",
"three/webgpu": "./engine/three/three.webgpu.js",
"three/tsl": "./engine/three/three.tsl.js",
"three/addons/": "./engine/three/jsm/"
}
}
</script>
<script type="module">
import * as THREE from 'three';
import * as THREE_WEBGPU from 'three/webgpu';
import * as THREE_TSL from 'three/tsl';
import { createRenderer } from './engine/three/util.js';
import { LCCRender } from '../sdk/lcc-sdk-three.js';
const dataPath = 'https://your-server.com/assets/scene/meta.lcc2';
// WebGPU only supports .lcc2; force WebGL for .lcc assets
const forceWebGL = new URL(dataPath, location.origin).pathname.endsWith('.lcc');
const scene = new THREE.Scene();
const canvas = document.createElement('canvas');
document.body.appendChild(canvas);
// Create renderer with automatic WebGPU → WebGL fallback
const renderer = await createRenderer(canvas, forceWebGL);
const isWebGPU = renderer.isWebGPURenderer === true;
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setClearColor(0x000000);
// Disable sRGB output for WebGPU to avoid color space conflicts with 3DGS
if (isWebGPU) {
renderer.outputColorSpace = THREE.LinearSRGBColorSpace;
renderer.toneMapping = THREE.NoToneMapping;
}
const camera = new THREE.PerspectiveCamera(
45, window.innerWidth / window.innerHeight, 0.1, 150000
);
camera.position.set(0, 2, 0);
// Load 3DGS scene
const lccObj = LCCRender.load({
camera, scene, renderer,
dataPath,
renderLib: THREE,
renderLibWebgpu: THREE_WEBGPU,
renderLibTSL: THREE_TSL,
canvas: renderer.domElement,
useEnv: true,
useIndexDB: true,
useLoadingEffect: true,
appKey: null // replace with your key
},
(mesh) => { console.log('Loaded', mesh); },
(percent) => { console.log('Loading ' + (percent * 100).toFixed(1) + '%'); },
() => { console.log('Loading failed'); }
);
// Configure performance parameters
lccObj.setStartLod(0);
lccObj.setMaxSplats(8000000);
lccObj.setMaxNodeSplats(4000000);
lccObj.setMaxDistance(200);
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
renderer.setAnimationLoop(() => {
LCCRender.update();
renderer.render(scene, camera);
});
</script>
</body>
</html>