303 lines
7.0 KiB
Markdown
303 lines
7.0 KiB
Markdown
# WebGPU Rendering
|
|
|
|
Modern GPU API for next-generation graphics.
|
|
|
|
## WebGPU Renderer
|
|
|
|
Next-generation rendering backend:
|
|
|
|
```javascript
|
|
import WebGPU from 'three/addons/capabilities/WebGPU.js';
|
|
import WebGPURenderer from 'three/addons/renderers/webgpu/WebGPURenderer.js';
|
|
|
|
// Check support
|
|
if (WebGPU.isAvailable()) {
|
|
const renderer = new WebGPURenderer();
|
|
renderer.setSize(window.innerWidth, window.innerHeight);
|
|
document.body.appendChild(renderer.domElement);
|
|
|
|
// Use setAnimationLoop (not requestAnimationFrame)
|
|
renderer.setAnimationLoop(() => {
|
|
renderer.render(scene, camera);
|
|
});
|
|
} else {
|
|
const warning = WebGPU.getErrorMessage();
|
|
document.body.appendChild(warning);
|
|
}
|
|
```
|
|
|
|
## Benefits of WebGPU
|
|
|
|
- Better performance (lower CPU overhead)
|
|
- Compute shaders
|
|
- Modern GPU features
|
|
- Unified shading language (WGSL)
|
|
- Better multi-threading support
|
|
- More predictable behavior
|
|
|
|
## Compute Shaders
|
|
|
|
GPU-accelerated computation:
|
|
|
|
```javascript
|
|
import { storageBuffer, uniform, Fn } from 'three/nodes';
|
|
import { StorageBufferAttribute } from 'three/addons/renderers/common/StorageBufferAttribute.js';
|
|
|
|
// Create storage buffer
|
|
const particleCount = 10000;
|
|
const positionBuffer = new StorageBufferAttribute(particleCount * 3, 3);
|
|
|
|
// Fill initial positions
|
|
for (let i = 0; i < particleCount; i++) {
|
|
positionBuffer.setXYZ(
|
|
i,
|
|
Math.random() * 10 - 5,
|
|
Math.random() * 10 - 5,
|
|
Math.random() * 10 - 5
|
|
);
|
|
}
|
|
|
|
// Create compute shader
|
|
const computeParticles = Fn(() => {
|
|
const position = storageBuffer(positionBuffer);
|
|
const time = uniform('time', 0);
|
|
const index = instanceIndex;
|
|
|
|
// Update position
|
|
const pos = position.element(index);
|
|
pos.y.addAssign(sin(time.add(index)).mul(0.01));
|
|
|
|
// Wrap around
|
|
If(pos.y.greaterThan(5), () => {
|
|
pos.y.assign(-5);
|
|
});
|
|
})();
|
|
|
|
// Create compute node
|
|
const computeNode = computeParticles.compute(particleCount);
|
|
|
|
// Execute in render loop
|
|
renderer.setAnimationLoop(() => {
|
|
renderer.compute(computeNode);
|
|
renderer.render(scene, camera);
|
|
});
|
|
```
|
|
|
|
## Storage Buffers
|
|
|
|
GPU-accessible memory:
|
|
|
|
```javascript
|
|
import { storage, Fn, vec3, float } from 'three/nodes';
|
|
|
|
// Define storage buffer structure
|
|
const particleData = storage(
|
|
new THREE.StorageBufferAttribute(count * 7, 7), // 7 floats per particle
|
|
'vec3', // position
|
|
'vec3', // velocity
|
|
'float' // life
|
|
);
|
|
|
|
// Access in compute shader
|
|
const updateParticle = Fn(() => {
|
|
const data = particleData.element(instanceIndex);
|
|
const position = data.xyz;
|
|
const velocity = data.toVec3(3); // offset 3
|
|
const life = data.element(6);
|
|
|
|
// Update
|
|
position.addAssign(velocity.mul(deltaTime));
|
|
life.subAssign(deltaTime);
|
|
})();
|
|
```
|
|
|
|
## WebGPU Node Materials
|
|
|
|
Use TSL (Three Shading Language) with WebGPU:
|
|
|
|
```javascript
|
|
import { MeshStandardNodeMaterial, texture, normalMap } from 'three/nodes';
|
|
|
|
const material = new MeshStandardNodeMaterial();
|
|
|
|
// Node-based material definition
|
|
material.colorNode = texture(diffuseTexture);
|
|
material.normalNode = normalMap(normalTexture);
|
|
material.roughnessNode = float(0.5);
|
|
material.metalnessNode = float(0.8);
|
|
|
|
// Works with both WebGL and WebGPU automatically
|
|
```
|
|
|
|
## Indirect Drawing
|
|
|
|
Efficient rendering with compute-generated draw calls:
|
|
|
|
```javascript
|
|
import { IndirectStorageBufferAttribute } from 'three/addons/renderers/common/IndirectStorageBufferAttribute.js';
|
|
|
|
// Create indirect buffer
|
|
const indirectBuffer = new IndirectStorageBufferAttribute(count, 5);
|
|
// 5 elements: count, instanceCount, first, baseInstance, (padding)
|
|
|
|
// Update with compute shader
|
|
const updateIndirect = Fn(() => {
|
|
const indirect = storage(indirectBuffer);
|
|
// Compute visibility and update instance count
|
|
const visible = computeVisibility();
|
|
If(visible, () => {
|
|
indirect.element(1).addAssign(1); // increment instanceCount
|
|
});
|
|
})();
|
|
|
|
// Render using indirect buffer
|
|
renderer.drawIndirect(mesh, indirectBuffer);
|
|
```
|
|
|
|
## Multi-Render-Target (MRT)
|
|
|
|
Render to multiple textures simultaneously:
|
|
|
|
```javascript
|
|
import { WebGPURenderTarget } from 'three/addons/renderers/webgpu/WebGPURenderTarget.js';
|
|
|
|
const renderTarget = new WebGPURenderTarget(width, height, {
|
|
count: 3, // number of render targets
|
|
format: THREE.RGBAFormat
|
|
});
|
|
|
|
// Access individual textures
|
|
const albedoTexture = renderTarget.textures[0];
|
|
const normalTexture = renderTarget.textures[1];
|
|
const depthTexture = renderTarget.textures[2];
|
|
|
|
// Use in deferred rendering pipeline
|
|
renderer.setRenderTarget(renderTarget);
|
|
renderer.render(scene, camera);
|
|
```
|
|
|
|
## Async Shader Compilation
|
|
|
|
Avoid frame drops:
|
|
|
|
```javascript
|
|
// Compile materials ahead of time
|
|
await renderer.compileAsync(scene, camera);
|
|
|
|
// Start rendering after compilation
|
|
renderer.setAnimationLoop(() => {
|
|
renderer.render(scene, camera);
|
|
});
|
|
```
|
|
|
|
## Performance Monitoring
|
|
|
|
GPU timestamp queries:
|
|
|
|
```javascript
|
|
// Query GPU timing
|
|
const timestampQuery = renderer.getTimestampQuery();
|
|
|
|
timestampQuery.begin();
|
|
renderer.render(scene, camera);
|
|
timestampQuery.end();
|
|
|
|
timestampQuery.getResult().then((duration) => {
|
|
console.log(`GPU time: ${duration}ms`);
|
|
});
|
|
```
|
|
|
|
## WebGPU-Specific Features
|
|
|
|
### Texture Compression
|
|
|
|
```javascript
|
|
// BC7 compression (higher quality)
|
|
const texture = new THREE.CompressedTexture(
|
|
mipmaps,
|
|
width,
|
|
height,
|
|
THREE.RGBA_BPTC_Format
|
|
);
|
|
```
|
|
|
|
### Depth Textures
|
|
|
|
```javascript
|
|
const depthTexture = new THREE.DepthTexture(width, height);
|
|
depthTexture.type = THREE.FloatType; // 32-bit depth
|
|
depthTexture.format = THREE.DepthFormat;
|
|
```
|
|
|
|
### Storage Textures
|
|
|
|
```javascript
|
|
import { storageTexture } from 'three/nodes';
|
|
|
|
// Read-write texture in compute shader
|
|
const writeableTexture = storageTexture(texture);
|
|
|
|
const computeShader = Fn(() => {
|
|
const coord = vec2(instanceIndex % width, instanceIndex / width);
|
|
const color = vec4(1, 0, 0, 1);
|
|
writeableTexture.store(coord, color);
|
|
})();
|
|
```
|
|
|
|
## Migration from WebGL
|
|
|
|
Most Three.js code works with both:
|
|
|
|
```javascript
|
|
// WebGL
|
|
const renderer = new THREE.WebGLRenderer();
|
|
|
|
// WebGPU (drop-in replacement for most cases)
|
|
const renderer = new WebGPURenderer();
|
|
|
|
// Exceptions:
|
|
// - Custom shaders: need to use Node materials or WGSL
|
|
// - Some extensions not available
|
|
// - Compute shaders only in WebGPU
|
|
```
|
|
|
|
## WGSL (WebGPU Shading Language)
|
|
|
|
Native shader language for WebGPU:
|
|
|
|
```wgsl
|
|
@group(0) @binding(0) var<storage, read_write> positions: array<vec3f>;
|
|
@group(0) @binding(1) var<uniform> time: f32;
|
|
|
|
@compute @workgroup_size(64)
|
|
fn main(@builtin(global_invocation_id) global_id: vec3u) {
|
|
let index = global_id.x;
|
|
if (index >= arrayLength(&positions)) {
|
|
return;
|
|
}
|
|
|
|
var pos = positions[index];
|
|
pos.y += sin(time + f32(index)) * 0.01;
|
|
positions[index] = pos;
|
|
}
|
|
```
|
|
|
|
## Browser Support
|
|
|
|
As of 2025:
|
|
- ✅ Chrome 113+
|
|
- ✅ Edge 113+
|
|
- ✅ Safari 18+ (macOS/iOS)
|
|
- ❌ Firefox (in development)
|
|
|
|
Check support: `WebGPU.isAvailable()`
|
|
|
|
## Best Practices
|
|
|
|
- Use compute shaders for particle systems, physics
|
|
- Leverage storage buffers for large datasets
|
|
- Async compile before rendering
|
|
- Use Node materials instead of custom GLSL
|
|
- Test on both WebGL and WebGPU
|
|
- Provide WebGL fallback for unsupported browsers
|