WebGPU Development Guide
This guide explains how to use WebGPU to render 3DGS scenes in your project and the differences from WebGL integration.
Engine Support
The WebGPU pipeline only supports the Three.js engine. CesiumJS does not support WebGPU rendering.
Version Requirements
SDK version: SDK v0.6.4+
Data Format Limitations
The WebGPU pipeline currently only supports the .lcc2 format. The .lcc format does not yet support WebGPU rendering; support will be added in future releases.
If an .lcc data path is provided, you should fall back to the WebGL renderer. The sample code implements this logic via the forceWebGL parameter:
const forceWebGL = new URL(dataPath, location.origin).pathname.endsWith('.lcc');
const renderer = await createRenderer(canvas, forceWebGL);
WebXR Limitations
Currently, the SDK only supports WebXR in the WebGL pipeline. The WebGPU pipeline does not support WebXR.
Mobile Limitations
WebGPU is not recommended for mobile devices. The SDK's WebGL pipeline currently offers better mobile performance.
Three.js Rendering Paths
Three.js has three rendering paths. Understanding their differences is essential for correct SDK integration.
Path 1: Classic WebGLRenderer (widely used)
WebGLRenderer has been the core renderer since Three.js was created and remains the most widely used choice. It is built directly on the WebGL 2 API, and its material system uses GLSL shaders.
Import:
import * as THREE from 'three';
const renderer = new THREE.WebGLRenderer();
The SDK supports this path, with both .lcc and .lcc2 formats.
Path 2: New Architecture WebGPURenderer + WebGPU Backend
WebGPURenderer is the next-generation renderer for Three.js, featuring a new abstract backend architecture. It introduces a node-based material system (Node Material) and TSL (Three.js Shading Language). Shaders written in TSL are automatically compiled to WGSL or GLSL depending on the backend. When the browser supports WebGPU, WebGPURenderer uses the native WebGPU backend.
Import:
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();
The SDK supports this path, with .lcc2 format only.
Path 3: New Architecture WebGPURenderer + WebGL 2 Backend
WebGPURenderer includes a built-in WebGL 2 backend as a fallback. When the browser does not support WebGPU, WebGPURenderer automatically switches to the WebGL 2 backend. Although it still uses WebGL 2, the material system and shader pipeline are the same as Path 2 (Node Material + TSL), with GLSL as the final compilation target.
The SDK does not support this path.
Path Comparison
| Path | Renderer | Graphics API | Material / Shader System | SDK Support | Supported Formats |
|---|---|---|---|---|---|
| 1. Classic WebGL | WebGLRenderer | WebGL 2 | GLSL (ShaderMaterial) | ✅ | .lcc .lcc2 |
| 2. New Arch WebGPU | WebGPURenderer | WebGPU | Node Material / TSL / WGSL | ✅ | .lcc2 |
| 3. New Arch WebGL Fallback | WebGPURenderer | WebGL 2 (backend fallback) | Node Material / TSL / GLSL | ❌ | - |
WebGPU vs WebGL Integration Differences
Compared to WebGL integration, WebGPU changes are concentrated in the following areas. All other business code (camera controls, performance parameters, render loop, etc.) remains identical.
1. Additional Imports: three/webgpu and three/tsl
WebGL only requires three. WebGPU requires two additional modules:
<!-- 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. Async Renderer Creation
The WebGL renderer is created synchronously. The WebGPU renderer requires async initialization:
// WebGL (synchronous)
const renderer = new THREE.WebGLRenderer({ canvas, antialias: false });
// WebGPU (asynchronous)
const renderer = new THREE_WEBGPU.WebGPURenderer({ canvas, antialias: false });
await renderer.init();
We recommend using the createRenderer utility function provided in the SDK examples, which implements a complete fallback chain:
- Browser does not support
navigator.gpu→ fall back toWebGLRenderer - Cannot get GPU adapter → fall back to
WebGLRenderer WebGPURenderersilently falls back to WebGL 2 backend after init (noGPUDevice) → destroy and fall back toWebGLRenderer- All checks pass → return a true WebGPU renderer
import { createRenderer } from './engine/three/util.js';
const renderer = await createRenderer(canvas, forceWebGL);
const isWebGPU = renderer.isWebGPURenderer === true;
3. Disable sRGB Output and Tone Mapping
In WebGPU mode, you must disable Three.js default sRGB output conversion and tone mapping. The default sRGB output adds an extra linear → sRGB conversion pass that conflicts with 3DGS premultiplied alpha blending, causing color inaccuracy:
if (isWebGPU) {
renderer.outputColorSpace = THREE.LinearSRGBColorSpace;
renderer.toneMapping = THREE.NoToneMapping;
}
WebGL mode does not need this.
4. LCCRender.load Parameters
WebGPU requires additional renderLibWebgpu and renderLibTSL parameters:
// 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 Device Limits Configuration
When creating a WebGPURenderer, it is recommended to query the GPU adapter's actual limits and pass requiredLimits to fully utilize device capabilities:
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();
Complete WebGPU Example
<!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>