Web Engine Docs
Preparing documentation
Use the search bar to quickly find any topic
Preparing documentation
Use the search bar to quickly find any topic
Modern GPU rendering with WebGPU support. Leverage compute shaders, storage buffers, and next-generation graphics features.
Web Engine supports both WebGL and WebGPU rendering backends. WebGPU provides modern GPU features including compute shaders, storage buffers, and improved performance. The engine automatically detects support and falls back to WebGL when needed.
Run general-purpose GPU compute for culling, LOD selection, and Hi-Z pyramids
Direct GPU buffer access without vertex attribute limitations
Improved shader compilation, better error messages, and reduced driver overhead
Lower CPU overhead, more efficient memory management, and explicit resource control
| Feature | WebGPU | WebGL 2 | Notes |
|---|---|---|---|
| Compute Shaders | ✓ | ✗ | WebGPU only feature |
| Storage Buffers | ✓ | ✗ | WebGL limited to textures |
| Indirect Draw | ✓ | ✓ | Both support indirect rendering |
| Instance Culling | GPU | CPU | WebGPU uses compute shaders |
| Hi-Z Occlusion | ✓ | ✗ | Requires compute + storage textures |
| MSAA | ✓ | ✓ | Both support antialiasing |
| Shadow Mapping | ✓ | ✓ | Feature parity |
| PBR Materials | ✓ | ✓ | Three.js provides both |
Renderer Selection
rendererType option.WebGPU is a modern API with growing browser support. Check compatibility before deploying WebGPU-dependent features:
| Browser | Version | Status | Notes |
|---|---|---|---|
| Chrome | 113+ | Stable | Full support since May 2023 |
| Edge | 113+ | Stable | Chromium-based, same as Chrome |
| Firefox | Nightly | Experimental | Behind flag, targeting 2024 |
| Safari | 18+ | Preview | Technology Preview only |
| Mobile Chrome | 113+ | Stable | Android support |
| Mobile Safari | — | None | iOS support pending |
// Check WebGPU availabilityconst hasWebGPU = 'gpu' in navigator;if (hasWebGPU) {try {const adapter = await navigator.gpu.requestAdapter();if (adapter) {console.log('WebGPU supported');console.log('Adapter:', adapter.info);// Request device to verify full supportconst device = await adapter.requestDevice();console.log('WebGPU device acquired');// Check for specific featuresconst features = Array.from(device.features);console.log('Supported features:', features);device.destroy();} else {console.log('WebGPU adapter unavailable');}} catch (error) {console.error('WebGPU initialization failed:', error);}} else {console.log('WebGPU not available, using WebGL fallback');}// Web Engine provides a helperimport { isWebGPURenderer } from '@web-engine-dev/core';const renderer = getRenderer();if (isWebGPURenderer(renderer)) {console.log('Using WebGPU renderer');} else {console.log('Using WebGL renderer');}
Web Engine supports WebGPU through Three.js's WebGPURenderer. Enable it by setting the renderer type during initialization:
import { createEngine, RendererType } from '@web-engine-dev/core';// Create engine with WebGPU rendererconst engine = await createEngine({rendererType: 'webgpu' as RendererType,quality: 'high',antialias: true,});// Engine will use WebGPU if available, fallback to WebGL otherwise// Check active rendererconst ctx = engine.getContext();if (ctx.renderer.isWebGPURenderer) {console.log('WebGPU active');// Access WebGPU-specific featuresconst device = ctx.renderer.backend.device;console.log('GPU Device:', device);}
import { RendererConfigRegistry } from '@web-engine-dev/core';// Resolve renderer profile with WebGPU preferenceconst profile = RendererConfigRegistry.resolve('runtime', {quality: 'high',rendererType: 'webgpu',init: {antialias: true,powerPreference: 'high-performance',},state: {toneMapping: THREE.ACESFilmicToneMapping,outputColorSpace: THREE.SRGBColorSpace,},});console.log('Renderer config:', profile);// Force WebGL fallback (for testing)const webglProfile = RendererConfigRegistry.resolve('runtime', {rendererType: 'webgl',});
Graceful Degradation
WebGPU's compute shaders enable GPU-accelerated operations beyond rendering. Web Engine uses compute for culling, LOD selection, and Hi-Z pyramid generation:
import { HiZPyramid } from '@web-engine-dev/core';import { WebGPURenderer } from 'three/webgpu';// Create Hi-Z pyramid for occlusion cullingconst hizPyramid = new HiZPyramid(0.5); // 0.5 = half resolutionfunction render(scene, camera, renderer: WebGPURenderer) {// Ensure pyramid matches viewporthizPyramid.ensureSize(renderer);// Render depth pre-passhizPyramid.renderDepthPass(scene, camera, renderer);// Build mip chain via compute shadershizPyramid.build(renderer);// Use pyramid for occlusion queriesconst hizTexture = hizPyramid.texture;const mipLevels = hizPyramid.mipLevels;console.log(`Hi-Z pyramid: ${mipLevels} mip levels`);// Main render passrenderer.render(scene, camera);}
import { Fn, storageTexture, textureStore } from 'three/tsl';import { StorageBufferAttribute } from 'three/webgpu';// Create storage buffer for entity dataconst entityCount = 100000;const positionsBuffer = new StorageBufferAttribute(entityCount * 3, 3, Float32Array);const radiiBuffer = new StorageBufferAttribute(entityCount, 1, Float32Array);const visibilityBuffer = new StorageBufferAttribute(entityCount, 1, Uint32Array);// Define compute shader using TSL (Three.js Shading Language)const frustumCullCompute = Fn(() => {// Compute node implementation// Read entity positions and radii from storage buffers// Test against frustum planes// Write visibility mask to output buffer})().compute(entityCount);// Execute compute shaderrenderer.compute(frustumCullCompute);// Read results back to CPU (if needed)const visibilityData = await renderer.backend.getArrayBufferAsync(visibilityBuffer);const visibility = new Uint32Array(visibilityData);console.log(`Visible entities: ${visibility.filter(v => v).length}`);
TSL Shading Language
HiZPyramid implementation for examples.Storage buffers provide direct GPU access to large data structures without vertex attribute limits:
import { StorageBufferAttribute, StorageInstancedBufferAttribute } from 'three/webgpu';// Create storage buffer for instance transformsconst instanceCount = 50000;const instanceMatrices = new StorageInstancedBufferAttribute(instanceCount * 16, // 16 floats per 4x4 matrix16, // itemSizeFloat32Array);// Populate buffer dataconst matrices = new Float32Array(instanceCount * 16);for (let i = 0; i < instanceCount; i++) {// Fill matrix dataconst offset = i * 16;matrices.set([1, 0, 0, 0, // column 00, 1, 0, 0, // column 10, 0, 1, 0, // column 2x, y, z, 1 // column 3 (translation)], offset);}// Upload to GPUinstanceMatrices.array.set(matrices);instanceMatrices.needsUpdate = true;// Use in instanced meshconst mesh = new THREE.InstancedMesh(geometry, material, instanceCount);mesh.instanceMatrix = instanceMatrices;// Storage buffers bypass the vertex attribute count limit// WebGL: max 16 attributes// WebGPU: unlimited via storage buffers
import { isWebGPURendererWithBackend } from '@web-engine-dev/core';// Read GPU buffer data back to CPUif (isWebGPURendererWithBackend(renderer)) {// Async readback (does not stall pipeline)const arrayBuffer = await renderer.backend.getArrayBufferAsync(storageBuffer);const data = new Float32Array(arrayBuffer);console.log('GPU data:', data);// Use for CPU-side logicprocessData(data);}// Warning: Frequent CPU-GPU sync is slow// Prefer keeping data on GPU when possible
import { IndirectDrawManager } from '@web-engine-dev/core';// Create indirect draw managerconst indirectDrawManager = new IndirectDrawManager({maxDrawCalls: 10000,enableGPUCulling: true, // WebGPU only});// Register draw callsindirectDrawManager.addDrawCall({geometry,material,instanceCount: 5000,baseInstance: 0,});// Execute all draws with single GPU commandindirectDrawManager.execute(renderer, scene, camera);// WebGPU: Compute shader culls invisible instances// WebGL: CPU-side culling + indirect draw
import { StorageTexture } from 'three/webgpu';// Create storage texture for compute shader outputconst storageTexture = new StorageTexture(1024, 1024);storageTexture.name = 'ComputeOutput';// Use in compute shaderimport { storageTexture as storageTex, textureStore } from 'three/tsl';const computeNode = Fn(() => {const texNode = storageTex(storageTexture);const coords = uvec2(instanceIndex.modInt(1024), instanceIndex.div(1024));const color = vec4(1.0, 0.0, 0.0, 1.0);textureStore(texNode, coords, color).toWriteOnly();})().compute(1024 * 1024);renderer.compute(computeNode);// Storage textures support read/write access in compute shaders// Regular textures are read-only in shaders
// WebGPU caches pipeline states automatically// Minimize state changes for better performance// ❌ Bad: Change state per objectobjects.forEach(obj => {material.transparent = obj.transparent;renderer.render(scene, camera);});// ✅ Good: Batch by stateconst opaque = objects.filter(o => !o.transparent);const transparent = objects.filter(o => o.transparent);renderBatch(opaque, material, false);renderBatch(transparent, material, true);function renderBatch(objects, material, transparent) {material.transparent = transparent;objects.forEach(obj => obj.visible = true);renderer.render(scene, camera);objects.forEach(obj => obj.visible = false);}
// WebGPU compiles shaders asynchronously// Warm up pipelines during loading screenasync function warmupPipelines(materials: Material[]) {const dummyScene = new THREE.Scene();const dummyCamera = new THREE.PerspectiveCamera();for (const material of materials) {const mesh = new THREE.Mesh(new THREE.BoxGeometry(),material);dummyScene.add(mesh);// Trigger pipeline compilationrenderer.compile(dummyScene, dummyCamera);await new Promise(resolve => setTimeout(resolve, 0));dummyScene.remove(mesh);}console.log('Pipeline warmup complete');}// Call during loading screenawait warmupPipelines(allMaterials);
Always provide WebGL fallbacks for WebGPU-only features:
import { isWebGPURenderer } from '@web-engine-dev/core';class CullingSystem {private gpuCulling: GPUCullingPipeline | null = null;private cpuCulling: CPUCullingFallback;constructor(renderer: CompatibleRenderer) {if (isWebGPURenderer(renderer)) {// Use GPU compute shadersthis.gpuCulling = new GPUCullingPipeline(renderer);console.log('Using GPU culling');} else {// Fallback to CPU cullingthis.cpuCulling = new CPUCullingFallback();console.log('Using CPU culling fallback');}}execute(entities: Entity[], camera: Camera) {if (this.gpuCulling) {return this.gpuCulling.cull(entities, camera);} else {return this.cpuCulling.cull(entities, camera);}}}// Feature detection patternfunction getOptimalImplementation() {const renderer = getRenderer();if (isWebGPURenderer(renderer)) {return {culling: 'gpu',lodSelection: 'gpu',occlusion: 'hiz',};} else {return {culling: 'cpu',lodSelection: 'cpu',occlusion: 'none',};}}
Progressive Enhancement
// Enable WebGPU validation in developmentconst adapter = await navigator.gpu.requestAdapter();const device = await adapter.requestDevice({// Enable validation featuresrequiredFeatures: [],requiredLimits: {},});// Set up error handlerdevice.addEventListener('uncapturederror', (event) => {console.error('WebGPU error:', event.error);});// Chrome DevTools provides WebGPU inspection// 1. Open DevTools// 2. Go to "Rendering" tab// 3. Enable "WebGPU"// 4. View pipeline states, buffers, and textures
If you're upgrading from WebGL-only to WebGPU support:
// 1. Update renderer creation// Old:const renderer = new THREE.WebGLRenderer({ antialias: true });// New:import { createRenderer } from '@web-engine-dev/core';const renderer = await createRenderer({rendererType: 'webgpu', // Auto-fallback to WebGLantialias: true,});// 2. Check renderer type before using WebGPU featuresif (isWebGPURenderer(renderer)) {// Use compute shadersenableGPUCulling();} else {// Use CPU fallbackenableCPUCulling();}// 3. Replace vertex attributes with storage buffers (optional)// Old (WebGL):const geometry = new THREE.BufferGeometry();geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));// New (WebGPU):import { StorageBufferAttribute } from 'three/webgpu';const storageAttr = new StorageBufferAttribute(positions.length, 3, Float32Array);storageAttr.array.set(positions);// 4. Update custom shaders to TSL (if using)// Old (GLSL):const material = new THREE.ShaderMaterial({vertexShader: glslVertex,fragmentShader: glslFragment,});// New (TSL):import { MeshStandardNodeMaterial } from 'three/webgpu';const material = new MeshStandardNodeMaterial();material.colorNode = customColorNode; // TSL node