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>