移动skill位置

This commit is contained in:
2026-03-28 13:57:54 +08:00
parent c0ca5b51c4
commit 5aa3ca5ac9
27 changed files with 10127 additions and 2 deletions

View File

@@ -0,0 +1,404 @@
# TSL Compute Shaders
Compute shaders run on the GPU for parallel processing of data. TSL makes them accessible through JavaScript.
## Basic Setup
```javascript
import * as THREE from 'three/webgpu';
import { Fn, instancedArray, instanceIndex, vec3 } from 'three/tsl';
// Create storage buffer
const count = 100000;
const positions = instancedArray(count, 'vec3');
// Create compute shader
const computeShader = Fn(() => {
const position = positions.element(instanceIndex);
position.x.addAssign(0.01);
})().compute(count);
// Execute
renderer.compute(computeShader);
```
## Storage Buffers
### Instanced Arrays
```javascript
import { instancedArray } from 'three/tsl';
// Create typed storage buffers
const positions = instancedArray(count, 'vec3');
const velocities = instancedArray(count, 'vec3');
const colors = instancedArray(count, 'vec4');
const indices = instancedArray(count, 'uint');
const values = instancedArray(count, 'float');
```
### Accessing Elements
```javascript
const computeShader = Fn(() => {
// Get element at current index
const position = positions.element(instanceIndex);
const velocity = velocities.element(instanceIndex);
// Read values
const x = position.x;
const speed = velocity.length();
// Write values
position.assign(vec3(0, 0, 0));
position.x.assign(1.0);
position.addAssign(velocity);
})().compute(count);
```
### Accessing Other Elements
```javascript
const computeShader = Fn(() => {
const myIndex = instanceIndex;
const neighborIndex = myIndex.add(1).mod(count);
const myPos = positions.element(myIndex);
const neighborPos = positions.element(neighborIndex);
// Calculate distance to neighbor
const dist = myPos.distance(neighborPos);
})().compute(count);
```
## Compute Shader Patterns
### Initialize Particles
```javascript
const computeInit = Fn(() => {
const position = positions.element(instanceIndex);
const velocity = velocities.element(instanceIndex);
// Random positions using hash
position.x.assign(hash(instanceIndex).mul(10).sub(5));
position.y.assign(hash(instanceIndex.add(1)).mul(10).sub(5));
position.z.assign(hash(instanceIndex.add(2)).mul(10).sub(5));
// Zero velocity
velocity.assign(vec3(0));
})().compute(count);
// Run once at startup
await renderer.computeAsync(computeInit);
```
### Physics Update
```javascript
const gravity = uniform(-9.8);
const deltaTimeUniform = uniform(0);
const groundY = uniform(0);
const computeUpdate = Fn(() => {
const position = positions.element(instanceIndex);
const velocity = velocities.element(instanceIndex);
const dt = deltaTimeUniform;
// Apply gravity
velocity.y.addAssign(gravity.mul(dt));
// Update position
position.addAssign(velocity.mul(dt));
// Ground collision
If(position.y.lessThan(groundY), () => {
position.y.assign(groundY);
velocity.y.assign(velocity.y.negate().mul(0.8)); // Bounce
velocity.xz.mulAssign(0.95); // Friction
});
})().compute(count);
// In animation loop
function animate() {
deltaTimeUniform.value = clock.getDelta();
renderer.compute(computeUpdate);
renderer.render(scene, camera);
}
```
### Attraction to Point
```javascript
const attractorPos = uniform(new THREE.Vector3(0, 0, 0));
const attractorStrength = uniform(1.0);
const computeAttract = Fn(() => {
const position = positions.element(instanceIndex);
const velocity = velocities.element(instanceIndex);
// Direction to attractor
const toAttractor = attractorPos.sub(position);
const distance = toAttractor.length();
const direction = toAttractor.normalize();
// Apply force (inverse square falloff)
const force = direction.mul(attractorStrength).div(distance.mul(distance).add(0.1));
velocity.addAssign(force.mul(deltaTimeUniform));
})().compute(count);
```
### Neighbor Interaction (Boids-like)
```javascript
const computeBoids = Fn(() => {
const myPos = positions.element(instanceIndex);
const myVel = velocities.element(instanceIndex);
const separation = vec3(0).toVar();
const alignment = vec3(0).toVar();
const cohesion = vec3(0).toVar();
const neighborCount = int(0).toVar();
// Check nearby particles
Loop(count, ({ i }) => {
If(i.notEqual(instanceIndex), () => {
const otherPos = positions.element(i);
const otherVel = velocities.element(i);
const dist = myPos.distance(otherPos);
If(dist.lessThan(2.0), () => {
// Separation
const diff = myPos.sub(otherPos).normalize().div(dist);
separation.addAssign(diff);
// Alignment
alignment.addAssign(otherVel);
// Cohesion
cohesion.addAssign(otherPos);
neighborCount.addAssign(1);
});
});
});
If(neighborCount.greaterThan(0), () => {
const n = neighborCount.toFloat();
alignment.divAssign(n);
cohesion.divAssign(n);
cohesion.assign(cohesion.sub(myPos));
myVel.addAssign(separation.mul(0.05));
myVel.addAssign(alignment.sub(myVel).mul(0.05));
myVel.addAssign(cohesion.mul(0.05));
});
// Limit speed
const speed = myVel.length();
If(speed.greaterThan(2.0), () => {
myVel.assign(myVel.normalize().mul(2.0));
});
myPos.addAssign(myVel.mul(deltaTimeUniform));
})().compute(count);
```
## Workgroups and Synchronization
### Workgroup Size
```javascript
// Default workgroup size is typically 64 or 256
const computeShader = Fn(() => {
// shader code
})().compute(count, { workgroupSize: 64 });
```
### Barriers
```javascript
import { workgroupBarrier, storageBarrier, textureBarrier } from 'three/tsl';
const computeShader = Fn(() => {
// Write data
sharedData.element(localIndex).assign(value);
// Ensure all workgroup threads reach this point
workgroupBarrier();
// Now safe to read data written by other threads
const neighborValue = sharedData.element(localIndex.add(1));
})().compute(count);
```
## Atomic Operations
For thread-safe read-modify-write operations:
```javascript
import { atomicAdd, atomicSub, atomicMax, atomicMin, atomicAnd, atomicOr, atomicXor } from 'three/tsl';
const counter = instancedArray(1, 'uint');
const computeShader = Fn(() => {
// Atomically increment counter
atomicAdd(counter.element(0), 1);
// Atomic max
atomicMax(maxValue.element(0), localValue);
})().compute(count);
```
## Using Compute Results in Materials
### Instanced Mesh with Computed Positions
```javascript
// Create instanced mesh
const geometry = new THREE.SphereGeometry(0.1, 16, 16);
const material = new THREE.MeshStandardNodeMaterial();
// Use computed positions
material.positionNode = positions.element(instanceIndex);
// Optionally use computed colors
material.colorNode = colors.element(instanceIndex);
const mesh = new THREE.InstancedMesh(geometry, material, count);
scene.add(mesh);
```
### Points with Computed Positions
```javascript
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.Float32BufferAttribute(new Float32Array(count * 3), 3));
const material = new THREE.PointsNodeMaterial();
material.positionNode = positions.element(instanceIndex);
material.colorNode = colors.element(instanceIndex);
material.sizeNode = float(5.0);
const points = new THREE.Points(geometry, material);
scene.add(points);
```
## Execution Methods
```javascript
// Synchronous compute (blocks until complete)
renderer.compute(computeShader);
// Asynchronous compute (returns promise)
await renderer.computeAsync(computeShader);
// Multiple computes
renderer.compute(computeInit);
renderer.compute(computePhysics);
renderer.compute(computeCollisions);
```
## Reading Back Data (GPU to CPU)
```javascript
// Create buffer for readback
const readBuffer = new Float32Array(count * 3);
// Read data back from GPU
await renderer.readRenderTargetPixelsAsync(
computeTexture,
0, 0, width, height,
readBuffer
);
```
## Complete Example: Particle System
```javascript
import * as THREE from 'three/webgpu';
import {
Fn, If, instancedArray, instanceIndex, uniform,
vec3, float, hash, time
} from 'three/tsl';
// Setup
const count = 50000;
const positions = instancedArray(count, 'vec3');
const velocities = instancedArray(count, 'vec3');
const lifetimes = instancedArray(count, 'float');
// Uniforms
const emitterPos = uniform(new THREE.Vector3(0, 0, 0));
const gravity = uniform(-2.0);
const dt = uniform(0);
// Initialize
const computeInit = Fn(() => {
const pos = positions.element(instanceIndex);
const vel = velocities.element(instanceIndex);
const life = lifetimes.element(instanceIndex);
pos.assign(emitterPos);
// Random velocity in cone
const angle = hash(instanceIndex).mul(Math.PI * 2);
const speed = hash(instanceIndex.add(1)).mul(2).add(1);
vel.x.assign(angle.cos().mul(speed).mul(0.3));
vel.y.assign(speed);
vel.z.assign(angle.sin().mul(speed).mul(0.3));
// Random lifetime
life.assign(hash(instanceIndex.add(2)).mul(2).add(1));
})().compute(count);
// Update
const computeUpdate = Fn(() => {
const pos = positions.element(instanceIndex);
const vel = velocities.element(instanceIndex);
const life = lifetimes.element(instanceIndex);
// Apply gravity
vel.y.addAssign(gravity.mul(dt));
// Update position
pos.addAssign(vel.mul(dt));
// Decrease lifetime
life.subAssign(dt);
// Respawn dead particles
If(life.lessThan(0), () => {
pos.assign(emitterPos);
const angle = hash(instanceIndex.add(time.mul(1000))).mul(Math.PI * 2);
const speed = hash(instanceIndex.add(time.mul(1000)).add(1)).mul(2).add(1);
vel.x.assign(angle.cos().mul(speed).mul(0.3));
vel.y.assign(speed);
vel.z.assign(angle.sin().mul(speed).mul(0.3));
life.assign(hash(instanceIndex.add(time.mul(1000)).add(2)).mul(2).add(1));
});
})().compute(count);
// Material
const material = new THREE.PointsNodeMaterial();
material.positionNode = positions.element(instanceIndex);
material.sizeNode = float(3.0);
material.colorNode = vec3(1, 0.5, 0.2);
// Geometry (dummy positions)
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.Float32BufferAttribute(new Float32Array(count * 3), 3));
const points = new THREE.Points(geometry, material);
scene.add(points);
// Init
await renderer.computeAsync(computeInit);
// Animation loop
function animate() {
dt.value = Math.min(clock.getDelta(), 0.1);
renderer.compute(computeUpdate);
renderer.render(scene, camera);
}
```

View File

@@ -0,0 +1,453 @@
# TSL Core Concepts
## Types and Constructors
### Scalar Types
```javascript
import { float, int, uint, bool } from 'three/tsl';
const f = float(1.0);
const i = int(42);
const u = uint(100);
const b = bool(true);
```
### Vector Types
```javascript
import { vec2, vec3, vec4, color } from 'three/tsl';
const v2 = vec2(1.0, 2.0);
const v3 = vec3(1.0, 2.0, 3.0);
const v4 = vec4(1.0, 2.0, 3.0, 1.0);
// Color (RGB, accepts hex or components)
const c = color(0xff0000); // Red
const c2 = color(1, 0.5, 0); // Orange
```
### Matrix Types
```javascript
import { mat2, mat3, mat4 } from 'three/tsl';
const m3 = mat3();
const m4 = mat4();
```
### Type Conversion
```javascript
const v = vec3(1, 2, 3);
const v4 = v.toVec4(1.0); // vec4(1, 2, 3, 1)
const f = int(42).toFloat(); // 42.0
const c = v.toColor(); // Convert to color
```
## Vector Swizzling
Access and reorder vector components using standard notation:
```javascript
const v = vec3(1.0, 2.0, 3.0);
// Single component access
v.x // 1.0
v.y // 2.0
v.z // 3.0
// Multiple components
v.xy // vec2(1.0, 2.0)
v.xyz // vec3(1.0, 2.0, 3.0)
// Reorder components
v.zyx // vec3(3.0, 2.0, 1.0)
v.xxy // vec3(1.0, 1.0, 2.0)
v.rrr // vec3(1.0, 1.0, 1.0) - same as xxx
// Alternative accessors (all equivalent)
v.xyz // position
v.rgb // color
v.stp // texture coordinates
```
## Uniforms
Uniforms pass values from JavaScript to shaders:
```javascript
import { uniform } from 'three/tsl';
import * as THREE from 'three/webgpu';
// Create uniforms
const myColor = uniform(new THREE.Color(0x0066ff));
const myFloat = uniform(0.5);
const myVec3 = uniform(new THREE.Vector3(1, 2, 3));
// Update at runtime
myColor.value.set(0xff0000);
myFloat.value = 0.8;
myVec3.value.set(4, 5, 6);
// Use in material
material.colorNode = myColor;
```
### Auto-Updating Uniforms
```javascript
// Update every frame
const animatedValue = uniform(0).onFrameUpdate((frame) => {
return Math.sin(frame.time);
});
// Update per object render
const perObjectValue = uniform(0).onObjectUpdate((object) => {
return object.userData.customValue;
});
// Update once per render cycle
const renderValue = uniform(0).onRenderUpdate((state) => {
return state.delta;
});
```
## Operators
### Arithmetic
```javascript
// Method chaining (preferred)
const result = a.add(b).mul(c).sub(d).div(e);
// Individual operations
a.add(b) // a + b
a.sub(b) // a - b
a.mul(b) // a * b
a.div(b) // a / b
a.mod(b) // a % b
a.negate() // -a
```
### Comparison
```javascript
a.equal(b) // a == b
a.notEqual(b) // a != b
a.lessThan(b) // a < b
a.greaterThan(b) // a > b
a.lessThanEqual(b) // a <= b
a.greaterThanEqual(b) // a >= b
```
### Logical
```javascript
a.and(b) // a && b
a.or(b) // a || b
a.not() // !a
a.xor(b) // a ^ b
```
### Bitwise
```javascript
a.bitAnd(b) // a & b
a.bitOr(b) // a | b
a.bitXor(b) // a ^ b
a.bitNot() // ~a
a.shiftLeft(n) // a << n
a.shiftRight(n) // a >> n
```
### Assignment (for variables)
```javascript
const v = vec3(0).toVar(); // Create mutable variable
v.assign(vec3(1, 2, 3)); // v = vec3(1, 2, 3)
v.addAssign(vec3(1)); // v += vec3(1)
v.subAssign(vec3(1)); // v -= vec3(1)
v.mulAssign(2.0); // v *= 2.0
v.divAssign(2.0); // v /= 2.0
```
## Variables
### Mutable Variables
```javascript
// Create mutable variable with toVar()
const myVar = vec3(1, 0, 0).toVar();
myVar.assign(vec3(0, 1, 0));
myVar.addAssign(vec3(0, 0, 1));
// Name the variable (useful for debugging)
const named = vec3(0).toVar('myPosition');
```
### Constants
```javascript
// Create compile-time constant
const PI_HALF = float(Math.PI / 2).toConst();
```
### Properties (named values for shader stages)
```javascript
import { property } from 'three/tsl';
// Create named property
const myProp = property('vec3', 'customColor');
myProp.assign(vec3(1, 0, 0));
```
## Control Flow
### Conditionals
```javascript
import { If, select } from 'three/tsl';
// If-ElseIf-Else
const result = vec3(0).toVar();
If(value.greaterThan(0.5), () => {
result.assign(vec3(1, 0, 0)); // Red
}).ElseIf(value.greaterThan(0.25), () => {
result.assign(vec3(0, 1, 0)); // Green
}).Else(() => {
result.assign(vec3(0, 0, 1)); // Blue
});
// Ternary operator (select)
const color = select(
condition, // if true
vec3(1, 0, 0), // return this
vec3(0, 0, 1) // else return this
);
```
### Switch-Case
```javascript
import { Switch } from 'three/tsl';
const col = vec3(0).toVar();
Switch(intValue)
.Case(0, () => { col.assign(color(1, 0, 0)); })
.Case(1, () => { col.assign(color(0, 1, 0)); })
.Case(2, () => { col.assign(color(0, 0, 1)); })
.Default(() => { col.assign(color(1, 1, 1)); });
```
### Loops
```javascript
import { Loop, Break, Continue } from 'three/tsl';
// Simple loop (0 to 9)
const sum = float(0).toVar();
Loop(10, ({ i }) => {
sum.addAssign(float(i));
});
// Ranged loop with options
Loop({ start: int(0), end: int(count), type: 'int' }, ({ i }) => {
// Loop body
});
// Nested loops
Loop(width, height, ({ i, j }) => {
// i = outer loop index
// j = inner loop index
});
// Loop control
Loop(100, ({ i }) => {
If(shouldStop, () => {
Break(); // Exit loop
});
If(shouldSkip, () => {
Continue(); // Skip to next iteration
});
});
```
### Flow Control
```javascript
import { Discard, Return } from 'three/tsl';
// Discard fragment (make transparent)
If(alpha.lessThan(0.5), () => {
Discard();
});
// Return from function
const myFn = Fn(() => {
If(condition, () => {
Return(vec3(1, 0, 0));
});
return vec3(0, 0, 1);
});
```
## Custom Functions with Fn()
### Basic Function
```javascript
import { Fn } from 'three/tsl';
const addVectors = Fn(([a, b]) => {
return a.add(b);
});
// Usage
const result = addVectors(vec3(1, 0, 0), vec3(0, 1, 0));
```
### Default Parameters
```javascript
const oscillate = Fn(([frequency = 1.0, amplitude = 1.0]) => {
return time.mul(frequency).sin().mul(amplitude);
});
// Call variations
oscillate(); // Uses defaults
oscillate(2.0); // frequency = 2.0
oscillate(2.0, 0.5); // frequency = 2.0, amplitude = 0.5
```
### Named Parameters (Object Style)
```javascript
const createGradient = Fn(({ colorA = vec3(0), colorB = vec3(1), t = 0.5 }) => {
return mix(colorA, colorB, t);
});
// Call with named parameters
createGradient({ colorA: vec3(1, 0, 0), t: uv().x });
```
### Function with Context
```javascript
// Access shader context
const customShader = Fn(({ material, geometry, object }) => {
if (material.userData.customColor) {
return uniform(material.userData.customColor);
}
return vec3(1);
});
```
## Time and Animation
```javascript
import { time, deltaTime } from 'three/tsl';
// time - seconds since start
const rotation = time.mul(0.5); // Half rotation per second
// deltaTime - time since last frame
const velocity = speed.mul(deltaTime);
```
### Oscillators
```javascript
import { oscSine, oscSquare, oscTriangle, oscSawtooth } from 'three/tsl';
// All oscillators return 0-1 range
oscSine(time) // Smooth sine wave
oscSquare(time) // Square wave (0 or 1)
oscTriangle(time) // Triangle wave
oscSawtooth(time) // Sawtooth wave
// Custom frequency
oscSine(time.mul(2.0)) // 2Hz oscillation
```
## Math Functions
### Basic Math
```javascript
import { abs, sign, floor, ceil, fract, mod, min, max, clamp } from 'three/tsl';
abs(x) // Absolute value
sign(x) // -1, 0, or 1
floor(x) // Round down
ceil(x) // Round up
fract(x) // Fractional part (x - floor(x))
mod(x, y) // Modulo
min(x, y) // Minimum
max(x, y) // Maximum
clamp(x, 0, 1) // Clamp to range
```
### Trigonometry
```javascript
import { sin, cos, tan, asin, acos, atan, atan2 } from 'three/tsl';
sin(x)
cos(x)
tan(x)
asin(x)
acos(x)
atan(x)
atan2(y, x)
```
### Exponential
```javascript
import { pow, exp, log, sqrt, inverseSqrt } from 'three/tsl';
pow(x, 2.0) // x^2
exp(x) // e^x
log(x) // Natural log
sqrt(x) // Square root
inverseSqrt(x) // 1 / sqrt(x)
```
### Interpolation
```javascript
import { mix, step, smoothstep } from 'three/tsl';
mix(a, b, 0.5) // Linear interpolation
step(0.5, x) // 0 if x < 0.5, else 1
smoothstep(0.0, 1.0, x) // Smooth 0-1 transition
```
### Vector Math
```javascript
import { length, distance, dot, cross, normalize, reflect, refract } from 'three/tsl';
length(v) // Vector length
distance(a, b) // Distance between points
dot(a, b) // Dot product
cross(a, b) // Cross product (vec3 only)
normalize(v) // Unit vector
reflect(incident, normal)
refract(incident, normal, eta)
```
### Constants
```javascript
import { PI, TWO_PI, HALF_PI, EPSILON } from 'three/tsl';
PI // 3.14159...
TWO_PI // 6.28318...
HALF_PI // 1.57079...
EPSILON // Very small number
```
## Utility Functions
```javascript
import { hash, checker, remap, range, rotate } from 'three/tsl';
// Pseudo-random hash
hash(seed) // Returns 0-1
// Checkerboard pattern
checker(uv()) // Returns 0 or 1
// Remap value from one range to another
remap(x, 0, 1, -1, 1) // Map 0-1 to -1 to 1
// Generate value in range
range(min, max) // Random in range (per instance)
// Rotate 2D vector
rotate(vec2(1, 0), angle)
```

View File

@@ -0,0 +1,359 @@
# WebGPU Device Loss Handling
## What Is Device Loss?
Device loss occurs when the GPU driver cannot continue processing commands. Causes include:
- Driver crashes
- Extreme resource pressure
- Long-running shaders (GPU watchdog triggers after ~10 seconds)
- Driver updates
- Significant device configuration changes
When a device is lost, the `GPUDevice` object and **all objects created with it become unusable**. All buffers, textures, pipelines, and GPU memory are discarded.
## Listening for Device Loss
Detect loss by attaching a callback to the device's `lost` promise:
```javascript
const adapter = await navigator.gpu.requestAdapter();
if (!adapter) { return; }
const device = await adapter.requestDevice();
device.lost.then((info) => {
console.error('WebGPU device lost:', info.message);
// Handle recovery
});
```
**Important:** Don't `await` this promise directly - it will block indefinitely if loss never occurs.
### Device Loss Information
The `GPUDeviceLostInfo` object provides:
| Property | Description |
|----------|-------------|
| `reason` | `'destroyed'` (intentional via `destroy()`) or `'unknown'` (unexpected) |
| `message` | Human-readable debugging info (don't parse programmatically) |
```javascript
device.lost.then((info) => {
if (info.reason === 'unknown') {
// Unexpected loss - attempt recovery
handleUnexpectedDeviceLoss();
} else {
// Intentional destruction - expected behavior
}
});
```
### Devices Starting Lost
`adapter.requestDevice()` always returns a `GPUDevice`, but it may already be lost if creation failed. This occurs when the adapter was "consumed" (used previously) or "expired."
**Best practice:** Always get a new adapter right before requesting a device.
## Recovery Strategies
### Minimal Recovery (Page Reload)
For simple applications:
```javascript
device.lost.then((info) => {
if (info.reason === 'unknown') {
// Warn user before reload
alert('Graphics error occurred. The page will reload.');
location.reload();
}
});
```
### Restart GPU Content Only (Recommended for Three.js)
Recreate the device and reconfigure the canvas without full page reload:
```javascript
import * as THREE from 'three/webgpu';
let renderer;
let scene, camera;
async function initWebGPU() {
renderer = new THREE.WebGPURenderer();
await renderer.init();
// Access the underlying WebGPU device
const device = renderer.backend.device;
device.lost.then((info) => {
console.error('Device lost:', info.message);
if (info.reason === 'unknown') {
// Dispose current renderer
renderer.dispose();
// Reinitialize
initWebGPU();
}
});
// Configure canvas
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// Recreate scene content
setupScene();
}
function setupScene() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
// ... add meshes, lights, etc.
}
initWebGPU();
```
### Restore with Application State
For applications with user progress or configuration:
```javascript
let appState = {
cameraPosition: { x: 0, y: 5, z: 10 },
settings: {},
// Don't save transient data like particle positions
};
// Save state periodically
function saveState() {
appState.cameraPosition = {
x: camera.position.x,
y: camera.position.y,
z: camera.position.z
};
localStorage.setItem('appState', JSON.stringify(appState));
}
// Restore on recovery
async function initWebGPU() {
renderer = new THREE.WebGPURenderer();
await renderer.init();
const savedState = localStorage.getItem('appState');
if (savedState) {
appState = JSON.parse(savedState);
}
setupScene();
// Restore camera position
camera.position.set(
appState.cameraPosition.x,
appState.cameraPosition.y,
appState.cameraPosition.z
);
renderer.backend.device.lost.then((info) => {
if (info.reason === 'unknown') {
saveState();
renderer.dispose();
initWebGPU();
}
});
}
```
## When Recovery Fails
If `requestAdapter()` returns `null` after device loss, the OS or browser has blocked GPU access:
```javascript
async function initWebGPU() {
const adapter = await navigator.gpu.requestAdapter();
if (!adapter) {
// Check if this is initial failure or post-loss failure
if (hadPreviousDevice) {
showMessage('GPU access lost. Please restart your browser.');
} else {
showMessage('WebGPU is not supported on this device.');
}
return;
}
// Continue with device creation...
}
```
## Testing Device Loss
### Using destroy()
Call `device.destroy()` to simulate loss:
```javascript
let simulatedLoss = false;
function simulateDeviceLoss() {
simulatedLoss = true;
renderer.backend.device.destroy();
}
// In your device.lost handler:
device.lost.then((info) => {
if (info.reason === 'unknown' || simulatedLoss) {
simulatedLoss = false;
// Treat as unexpected loss for testing
handleDeviceLoss();
}
});
// Add debug keybinding
window.addEventListener('keydown', (e) => {
if (e.key === 'L' && e.ctrlKey && e.shiftKey) {
simulateDeviceLoss();
}
});
```
**Limitations of destroy():**
- Unmaps buffers immediately (real loss doesn't)
- Always allows device recovery (real loss may not)
### Chrome GPU Process Crash Testing
Navigate to `about:gpucrash` in a **separate tab** to crash the GPU process.
Chrome enforces escalating restrictions:
| Crash | Effect |
|-------|--------|
| 1st | New adapters allowed |
| 2nd within 2 min | Adapter requests fail (resets on page refresh) |
| 3rd within 2 min | All pages blocked (reset after 2 min or browser restart) |
| 3-6 within 5 min | GPU process stops restarting; browser restart required |
### Chrome Testing Flags
Bypass crash limits for development:
```bash
# macOS
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome \
--disable-domain-blocking-for-3d-apis \
--disable-gpu-process-crash-limit
# Windows
chrome.exe --disable-domain-blocking-for-3d-apis --disable-gpu-process-crash-limit
# Linux
google-chrome --disable-domain-blocking-for-3d-apis --disable-gpu-process-crash-limit
```
## Complete Example
```javascript
import * as THREE from 'three/webgpu';
import { color, time, oscSine } from 'three/tsl';
let renderer, scene, camera, mesh;
let hadPreviousDevice = false;
async function init() {
// Check WebGPU support
if (!navigator.gpu) {
showError('WebGPU not supported');
return;
}
// Create renderer
renderer = new THREE.WebGPURenderer({ antialias: true });
try {
await renderer.init();
} catch (e) {
if (hadPreviousDevice) {
showError('GPU recovery failed. Please restart browser.');
} else {
showError('Failed to initialize WebGPU.');
}
return;
}
hadPreviousDevice = true;
// Setup device loss handler
const device = renderer.backend.device;
device.lost.then(handleDeviceLoss);
// Setup scene
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = 5;
const geometry = new THREE.BoxGeometry();
const material = new THREE.MeshStandardNodeMaterial();
material.colorNode = color(0x00ff00).mul(oscSine(time));
mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(5, 5, 5);
scene.add(light);
scene.add(new THREE.AmbientLight(0x404040));
animate();
}
function handleDeviceLoss(info) {
console.error('Device lost:', info.reason, info.message);
if (info.reason === 'unknown') {
// Cleanup
if (renderer) {
renderer.domElement.remove();
renderer.dispose();
}
// Attempt recovery after short delay
setTimeout(() => {
init();
}, 100);
}
}
function animate() {
if (!renderer) return;
requestAnimationFrame(animate);
mesh.rotation.x += 0.01;
mesh.rotation.y += 0.01;
renderer.render(scene, camera);
}
function showError(message) {
const div = document.createElement('div');
div.textContent = message;
div.style.cssText = 'position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);padding:20px;background:#f44;color:#fff;border-radius:8px;';
document.body.appendChild(div);
}
init();
```
## Best Practices
1. **Always listen for device loss** - Even if you just show an error message
2. **Get a fresh adapter before each device request** - The GPU hardware may have changed
3. **Don't parse the message field** - It's implementation-specific and changes between browsers
4. **Save critical application state** - Restore user progress after recovery
5. **Don't save transient state** - Particle positions, physics state can be reset
6. **Test your recovery path** - Use `destroy()` and Chrome's `about:gpucrash`
7. **Handle adapter failure gracefully** - Distinguish between initial failure and post-loss failure
8. **Add a short delay before recovery** - Give the system time to stabilize

View File

@@ -0,0 +1,353 @@
# TSL Node Materials
## Available Material Types
| Material | Description |
|----------|-------------|
| `MeshBasicNodeMaterial` | Unlit, no lighting calculations |
| `MeshStandardNodeMaterial` | PBR material with metalness/roughness |
| `MeshPhysicalNodeMaterial` | Advanced PBR with clearcoat, transmission, etc. |
| `MeshPhongNodeMaterial` | Classic Phong shading |
| `MeshToonNodeMaterial` | Cel/toon shading |
| `MeshLambertNodeMaterial` | Diffuse-only lighting |
| `MeshNormalNodeMaterial` | Visualize normals |
| `MeshMatcapNodeMaterial` | Matcap texture shading |
| `PointsNodeMaterial` | For point clouds |
| `LineBasicNodeMaterial` | For lines |
| `LineDashedNodeMaterial` | For dashed lines |
| `SpriteNodeMaterial` | For sprites/billboards |
## Creating Node Materials
```javascript
import * as THREE from 'three/webgpu';
// Standard PBR material
const material = new THREE.MeshStandardNodeMaterial();
// Physical material with advanced features
const physicalMat = new THREE.MeshPhysicalNodeMaterial();
// Unlit material
const basicMat = new THREE.MeshBasicNodeMaterial();
```
## Material Properties
### Color and Opacity
```javascript
import { texture, color, float } from 'three/tsl';
// Color from texture
material.colorNode = texture(diffuseMap);
// Solid color
material.colorNode = color(0xff0000);
// Computed color
material.colorNode = positionLocal.normalize();
// Opacity (requires material.transparent = true)
material.opacityNode = float(0.8);
material.transparent = true;
// Alpha test threshold
material.alphaTestNode = float(0.5);
```
### PBR Properties (MeshStandardNodeMaterial)
```javascript
import { texture, float, color } from 'three/tsl';
// Metalness (0 = dielectric, 1 = metal)
material.metalnessNode = texture(metalMap).r;
material.metalnessNode = float(0.0);
// Roughness (0 = smooth/mirror, 1 = rough)
material.roughnessNode = texture(roughnessMap).r;
material.roughnessNode = float(0.5);
// Emissive (self-illumination)
material.emissiveNode = color(0xff0000).mul(2.0);
material.emissiveNode = texture(emissiveMap);
```
### Normal Mapping
```javascript
import { texture, normalMap, bumpMap } from 'three/tsl';
// Normal map
material.normalNode = normalMap(texture(normalMapTexture));
// Normal map with strength
material.normalNode = normalMap(texture(normalMapTexture), float(0.5));
// Bump map (height to normal)
material.normalNode = bumpMap(texture(heightMap), 0.05);
```
### Physical Properties (MeshPhysicalNodeMaterial)
```javascript
const material = new THREE.MeshPhysicalNodeMaterial();
// Clearcoat (car paint effect)
material.clearcoatNode = float(1.0);
material.clearcoatRoughnessNode = float(0.1);
material.clearcoatNormalNode = normalMap(texture(clearcoatNormalMap));
// Transmission (glass/translucency)
material.transmissionNode = float(0.9);
material.thicknessNode = float(0.5);
material.attenuationDistanceNode = float(1.0);
material.attenuationColorNode = color(0xffffff);
// Iridescence (soap bubble effect)
material.iridescenceNode = float(1.0);
material.iridescenceIORNode = float(1.3);
material.iridescenceThicknessNode = float(400);
// Sheen (fabric effect)
material.sheenNode = float(1.0);
material.sheenRoughnessNode = float(0.5);
material.sheenColorNode = color(0xffffff);
// Anisotropy (brushed metal)
material.anisotropyNode = float(1.0);
material.anisotropyRotationNode = float(0);
// Specular
material.specularIntensityNode = float(1.0);
material.specularColorNode = color(0xffffff);
// Index of Refraction
material.iorNode = float(1.5);
// Dispersion (rainbow effect in glass)
material.dispersionNode = float(0.0);
```
### Environment and Lighting
```javascript
import { cubeTexture, envMap } from 'three/tsl';
// Environment map reflection
material.envMapNode = cubeTexture(envCubeMap);
// Custom lights
material.lightsNode = lights();
```
## Vertex Manipulation
### Position Displacement
```javascript
import { positionLocal, normalLocal, texture } from 'three/tsl';
// Displace vertices along normals
const displacement = texture(heightMap).r.mul(0.1);
material.positionNode = positionLocal.add(normalLocal.mul(displacement));
// Wave displacement
const wave = positionLocal.x.add(time).sin().mul(0.1);
material.positionNode = positionLocal.add(vec3(0, wave, 0));
```
### Custom Vertex Shader
```javascript
// Complete vertex position override
material.vertexNode = customVertexPosition;
```
## Fragment Override
```javascript
// Complete fragment output override
material.fragmentNode = vec4(finalColor, 1.0);
// Output node (respects lighting)
material.outputNode = outputStruct;
```
## Geometry Attributes
### Position Nodes
```javascript
import {
positionGeometry, // Original mesh position
positionLocal, // Position in model space
positionWorld, // Position in world space
positionView // Position in camera space
} from 'three/tsl';
```
### Normal Nodes
```javascript
import {
normalGeometry, // Original mesh normal
normalLocal, // Normal in model space
normalWorld, // Normal in world space (use for lighting)
normalView // Normal in camera space
} from 'three/tsl';
```
### Tangent/Bitangent
```javascript
import {
tangentLocal, tangentWorld, tangentView,
bitangentLocal, bitangentWorld, bitangentView
} from 'three/tsl';
```
### UV Coordinates
```javascript
import { uv } from 'three/tsl';
uv() // Primary UV set (UV0)
uv(1) // Secondary UV set (UV1)
uv(2) // Tertiary UV set (UV2)
```
### Other Attributes
```javascript
import { vertexColor, instanceIndex, vertexIndex } from 'three/tsl';
vertexColor() // Vertex colors (if present)
instanceIndex // Index for instanced meshes
vertexIndex // Current vertex index
```
## Camera Nodes
```javascript
import {
cameraPosition, // Camera world position
cameraNear, // Near plane distance
cameraFar, // Far plane distance
cameraViewMatrix, // View matrix
cameraProjectionMatrix, // Projection matrix
cameraWorldMatrix // Camera world matrix
} from 'three/tsl';
```
## Screen Space Nodes
```javascript
import {
screenUV, // Screen UV (0-1)
screenCoordinate, // Pixel coordinates
screenSize, // Screen dimensions
viewportUV, // Viewport UV
viewport, // Viewport dimensions
depth // Fragment depth
} from 'three/tsl';
```
## Examples
### Animated Color Material
```javascript
import * as THREE from 'three/webgpu';
import { color, time, oscSine, mix } from 'three/tsl';
const material = new THREE.MeshStandardNodeMaterial();
const colorA = color(0xff0000);
const colorB = color(0x0000ff);
const t = oscSine(time.mul(0.5));
material.colorNode = mix(colorA, colorB, t);
material.roughnessNode = float(0.5);
material.metalnessNode = float(0.0);
```
### Triplanar Mapping Material
```javascript
import * as THREE from 'three/webgpu';
import { texture, triplanarTexture, float } from 'three/tsl';
const material = new THREE.MeshStandardNodeMaterial();
// Apply texture from all three axes
material.colorNode = triplanarTexture(
texture(diffuseMap),
null, // Y-axis texture (optional)
null, // Z-axis texture (optional)
float(0.1) // Blend sharpness
);
```
### Glass Material
```javascript
import * as THREE from 'three/webgpu';
import { float, color } from 'three/tsl';
const material = new THREE.MeshPhysicalNodeMaterial();
material.colorNode = color(0xffffff);
material.transmissionNode = float(0.95);
material.roughnessNode = float(0.0);
material.metalnessNode = float(0.0);
material.iorNode = float(1.5);
material.thicknessNode = float(0.5);
```
### Fresnel Rim Material
```javascript
import * as THREE from 'three/webgpu';
import {
color, float, normalWorld, positionWorld,
cameraPosition, Fn
} from 'three/tsl';
const fresnel = Fn(() => {
const viewDir = cameraPosition.sub(positionWorld).normalize();
const nDotV = normalWorld.dot(viewDir).saturate();
return float(1.0).sub(nDotV).pow(3.0);
});
const material = new THREE.MeshStandardNodeMaterial();
material.colorNode = color(0x222222);
material.emissiveNode = color(0x00ffff).mul(fresnel());
```
### Dissolve Effect Material
```javascript
import * as THREE from 'three/webgpu';
import {
color, float, hash, positionLocal, uniform,
If, Discard, smoothstep
} from 'three/tsl';
const threshold = uniform(0.5);
const material = new THREE.MeshStandardNodeMaterial();
const noise = hash(positionLocal.mul(50));
// Discard fragments below threshold
If(noise.lessThan(threshold), () => {
Discard();
});
// Edge glow
const edge = smoothstep(threshold, threshold.add(0.1), noise);
material.colorNode = color(0x333333);
material.emissiveNode = color(0xff5500).mul(float(1.0).sub(edge));
```

View File

@@ -0,0 +1,475 @@
# TSL Post-Processing
Post-processing applies effects to the rendered image. TSL provides both built-in effects and the ability to create custom effects.
## Basic Setup
```javascript
import * as THREE from 'three/webgpu';
import { pass } from 'three/tsl';
// Create renderer
const renderer = new THREE.WebGPURenderer();
await renderer.init();
// Create post-processing
const postProcessing = new THREE.PostProcessing(renderer);
// Create scene pass
const scenePass = pass(scene, camera);
const scenePassColor = scenePass.getTextureNode('output');
// Output (passthrough)
postProcessing.outputNode = scenePassColor;
// Render with post-processing
function animate() {
postProcessing.render(); // Not renderer.render()
}
```
## Built-in Effects
### Bloom
```javascript
import { bloom } from 'three/addons/tsl/display/BloomNode.js';
const scenePass = pass(scene, camera);
const scenePassColor = scenePass.getTextureNode('output');
// Add bloom
const bloomPass = bloom(scenePassColor);
// Configure
bloomPass.threshold.value = 0.5; // Brightness threshold
bloomPass.strength.value = 1.0; // Bloom intensity
bloomPass.radius.value = 0.5; // Blur radius
// Combine original + bloom
postProcessing.outputNode = scenePassColor.add(bloomPass);
```
### Gaussian Blur
```javascript
import { gaussianBlur } from 'three/addons/tsl/display/GaussianBlurNode.js';
const blurred = gaussianBlur(scenePassColor, vec2(2.0)); // Blur strength
postProcessing.outputNode = blurred;
```
### FXAA (Anti-aliasing)
```javascript
import { fxaa } from 'three/addons/tsl/display/FXAANode.js';
postProcessing.outputNode = fxaa(scenePassColor);
```
### SMAA (Anti-aliasing)
```javascript
import { smaa } from 'three/addons/tsl/display/SMAANode.js';
postProcessing.outputNode = smaa(scenePassColor);
```
### Depth of Field
```javascript
import { dof } from 'three/addons/tsl/display/DepthOfFieldNode.js';
const scenePass = pass(scene, camera);
const colorNode = scenePass.getTextureNode('output');
const depthNode = scenePass.getTextureNode('depth');
const dofPass = dof(colorNode, depthNode, {
focus: 5.0, // Focus distance
aperture: 0.025, // Aperture size
maxblur: 0.01 // Maximum blur
});
postProcessing.outputNode = dofPass;
```
### Motion Blur
```javascript
import { motionBlur } from 'three/addons/tsl/display/MotionBlurNode.js';
const scenePass = pass(scene, camera);
const velocityPass = scenePass.getTextureNode('velocity');
const motionBlurPass = motionBlur(scenePassColor, velocityPass);
postProcessing.outputNode = motionBlurPass;
```
### Screen Space Reflections (SSR)
```javascript
import { ssr } from 'three/addons/tsl/display/SSRNode.js';
const scenePass = pass(scene, camera);
const colorNode = scenePass.getTextureNode('output');
const depthNode = scenePass.getTextureNode('depth');
const normalNode = scenePass.getTextureNode('normal');
const ssrPass = ssr(colorNode, depthNode, normalNode, camera);
postProcessing.outputNode = ssrPass;
```
### Ambient Occlusion (SSAO)
```javascript
import { ao } from 'three/addons/tsl/display/AmbientOcclusionNode.js';
const scenePass = pass(scene, camera);
const depthNode = scenePass.getTextureNode('depth');
const normalNode = scenePass.getTextureNode('normal');
const aoPass = ao(depthNode, normalNode, camera);
postProcessing.outputNode = scenePassColor.mul(aoPass);
```
### Film Grain
```javascript
import { film } from 'three/addons/tsl/display/FilmNode.js';
const filmPass = film(scenePassColor, {
intensity: 0.5,
grayscale: false
});
postProcessing.outputNode = filmPass;
```
### Outline
```javascript
import { outline } from 'three/addons/tsl/display/OutlineNode.js';
const outlinePass = outline(scene, camera, selectedObjects, {
edgeStrength: 3.0,
edgeGlow: 0.0,
edgeThickness: 1.0,
visibleEdgeColor: new THREE.Color(0xffffff),
hiddenEdgeColor: new THREE.Color(0x190a05)
});
postProcessing.outputNode = scenePassColor.add(outlinePass);
```
### Chromatic Aberration
```javascript
import { chromaticAberration } from 'three/addons/tsl/display/ChromaticAberrationNode.js';
const caPass = chromaticAberration(scenePassColor, {
offset: vec2(0.002, 0.002)
});
postProcessing.outputNode = caPass;
```
## Color Adjustments
### Grayscale
```javascript
import { grayscale } from 'three/tsl';
postProcessing.outputNode = grayscale(scenePassColor);
```
### Saturation
```javascript
import { saturation } from 'three/tsl';
// 0 = grayscale, 1 = normal, 2 = oversaturated
postProcessing.outputNode = saturation(scenePassColor, 1.5);
```
### Hue Shift
```javascript
import { hue } from 'three/tsl';
// Shift hue by radians
postProcessing.outputNode = hue(scenePassColor, time.mul(0.5));
```
### Vibrance
```javascript
import { vibrance } from 'three/tsl';
postProcessing.outputNode = vibrance(scenePassColor, 0.5);
```
### Posterize
```javascript
import { posterize } from 'three/tsl';
// Reduce color levels
postProcessing.outputNode = posterize(scenePassColor, 8);
```
### Sepia
```javascript
import { sepia } from 'three/addons/tsl/display/SepiaNode.js';
postProcessing.outputNode = sepia(scenePassColor);
```
### 3D LUT
```javascript
import { lut3D } from 'three/addons/tsl/display/Lut3DNode.js';
const lutTexture = new THREE.Data3DTexture(lutData, size, size, size);
postProcessing.outputNode = lut3D(scenePassColor, lutTexture, size);
```
## Custom Post-Processing
### Basic Custom Effect
```javascript
import { Fn, screenUV, float, vec4 } from 'three/tsl';
const customEffect = Fn(() => {
const color = scenePassColor.toVar();
// Invert colors
color.rgb.assign(float(1.0).sub(color.rgb));
return color;
});
postProcessing.outputNode = customEffect();
```
### Vignette Effect
```javascript
const vignette = Fn(() => {
const color = scenePassColor.toVar();
// Distance from center
const uv = screenUV;
const dist = uv.sub(0.5).length();
// Vignette falloff
const vignette = float(1.0).sub(dist.mul(1.5)).clamp(0, 1);
color.rgb.mulAssign(vignette);
return color;
});
postProcessing.outputNode = vignette();
```
### CRT/Scanline Effect
```javascript
import { viewportSharedTexture } from 'three/tsl';
const crtEffect = Fn(() => {
const uv = screenUV;
// Sample scene at offset UVs for RGB separation (chromatic aberration)
const uvR = uv.add(vec2(0.002, 0));
const uvG = uv;
const uvB = uv.sub(vec2(0.002, 0));
// Use viewportSharedTexture to sample at different UV coordinates
const r = viewportSharedTexture(uvR).r;
const g = viewportSharedTexture(uvG).g;
const b = viewportSharedTexture(uvB).b;
const color = vec4(r, g, b, 1.0).toVar();
// Scanlines
const scanline = uv.y.mul(screenSize.y).mul(0.5).sin().mul(0.1).add(0.9);
color.rgb.mulAssign(scanline);
// Vignette
const dist = uv.sub(0.5).length();
color.rgb.mulAssign(float(1.0).sub(dist.mul(0.5)));
return color;
});
// Note: For this effect, apply after scene rendering
postProcessing.outputNode = crtEffect();
```
### Pixelate Effect
```javascript
const pixelSize = uniform(8.0);
const pixelate = Fn(() => {
const uv = screenUV;
const pixelUV = uv.mul(screenSize).div(pixelSize).floor().mul(pixelSize).div(screenSize);
return texture(scenePassColor, pixelUV);
});
postProcessing.outputNode = pixelate();
```
### Edge Detection (Sobel)
```javascript
const sobelEdge = Fn(() => {
const uv = screenUV;
const texelSize = vec2(1.0).div(screenSize);
// Sample 3x3 kernel
const tl = luminance(texture(scenePassColor, uv.add(texelSize.mul(vec2(-1, -1)))));
const tc = luminance(texture(scenePassColor, uv.add(texelSize.mul(vec2(0, -1)))));
const tr = luminance(texture(scenePassColor, uv.add(texelSize.mul(vec2(1, -1)))));
const ml = luminance(texture(scenePassColor, uv.add(texelSize.mul(vec2(-1, 0)))));
const mr = luminance(texture(scenePassColor, uv.add(texelSize.mul(vec2(1, 0)))));
const bl = luminance(texture(scenePassColor, uv.add(texelSize.mul(vec2(-1, 1)))));
const bc = luminance(texture(scenePassColor, uv.add(texelSize.mul(vec2(0, 1)))));
const br = luminance(texture(scenePassColor, uv.add(texelSize.mul(vec2(1, 1)))));
// Sobel operators
const gx = tl.add(ml.mul(2)).add(bl).sub(tr).sub(mr.mul(2)).sub(br);
const gy = tl.add(tc.mul(2)).add(tr).sub(bl).sub(bc.mul(2)).sub(br);
const edge = sqrt(gx.mul(gx).add(gy.mul(gy)));
return vec4(vec3(edge), 1.0);
});
postProcessing.outputNode = sobelEdge();
```
## Multiple Render Targets (MRT)
Access multiple buffers from the scene pass:
```javascript
import { mrt, output } from 'three/tsl';
const scenePass = pass(scene, camera);
// Set up MRT
scenePass.setMRT(mrt({
output: output, // Color output
normal: normalView, // View-space normals
depth: depth // Depth buffer
}));
// Access individual targets
const colorTexture = scenePass.getTextureNode('output');
const normalTexture = scenePass.getTextureNode('normal');
const depthTexture = scenePass.getTextureNode('depth');
```
### Selective Bloom with MRT
Bloom only emissive objects by rendering emissive to a separate target:
```javascript
import { pass, mrt, output, emissive } from 'three/tsl';
import { bloom } from 'three/addons/tsl/display/BloomNode.js';
const postProcessing = new THREE.PostProcessing(renderer);
const scenePass = pass(scene, camera);
// Render both color and emissive to separate targets
scenePass.setMRT(mrt({
output: output,
emissive: emissive
}));
// Get the texture nodes
const colorTexture = scenePass.getTextureNode('output');
const emissiveTexture = scenePass.getTextureNode('emissive');
// Apply bloom only to emissive
const bloomPass = bloom(emissiveTexture);
bloomPass.threshold.value = 0.0; // Bloom all emissive
bloomPass.strength.value = 1.5;
bloomPass.radius.value = 0.5;
// Combine: original color + bloomed emissive
postProcessing.outputNode = colorTexture.add(bloomPass);
```
This approach prevents non-emissive bright areas (like white surfaces) from blooming.
## Chaining Effects
```javascript
const scenePass = pass(scene, camera);
const color = scenePass.getTextureNode('output');
// Chain multiple effects
let output = color;
// 1. Apply bloom
const bloomPass = bloom(output);
output = output.add(bloomPass.mul(0.5));
// 2. Apply color grading
output = saturation(output, 1.2);
// 3. Apply vignette
const dist = screenUV.sub(0.5).length();
const vignette = float(1.0).sub(dist.mul(0.5));
output = output.mul(vignette);
// 4. Apply FXAA
output = fxaa(output);
postProcessing.outputNode = output;
```
## Conditional Effects
```javascript
const effectEnabled = uniform(true);
const conditionalEffect = Fn(() => {
const color = scenePassColor;
return select(effectEnabled, grayscale(color), color);
});
postProcessing.outputNode = conditionalEffect();
// Toggle at runtime
effectEnabled.value = false;
```
## Transitions
```javascript
import { transition } from 'three/addons/tsl/display/TransitionNode.js';
const scenePassA = pass(sceneA, camera);
const scenePassB = pass(sceneB, camera);
const transitionProgress = uniform(0);
const transitionPass = transition(
scenePassA.getTextureNode('output'),
scenePassB.getTextureNode('output'),
transitionProgress,
texture(transitionTexture) // Optional transition texture
);
postProcessing.outputNode = transitionPass;
// Animate transition
function animate() {
transitionProgress.value = Math.sin(time) * 0.5 + 0.5;
postProcessing.render();
}
```

View File

@@ -0,0 +1,324 @@
# WGSL Integration
TSL allows embedding raw WGSL (WebGPU Shading Language) code when you need direct GPU control.
## wgslFn - Custom WGSL Functions
### Basic Usage
```javascript
import { wgslFn, float, vec3 } from 'three/tsl';
// Define WGSL function
const gammaCorrect = wgslFn(`
fn gammaCorrect(color: vec3<f32>, gamma: f32) -> vec3<f32> {
return pow(color, vec3<f32>(1.0 / gamma));
}
`);
// Use in TSL
material.colorNode = gammaCorrect(inputColor, float(2.2));
```
### Function with Multiple Parameters
```javascript
const blendColors = wgslFn(`
fn blendColors(a: vec3<f32>, b: vec3<f32>, t: f32) -> vec3<f32> {
return mix(a, b, t);
}
`);
material.colorNode = blendColors(colorA, colorB, blendFactor);
```
### Advanced Math Functions
```javascript
const fresnelSchlick = wgslFn(`
fn fresnelSchlick(cosTheta: f32, F0: vec3<f32>) -> vec3<f32> {
return F0 + (vec3<f32>(1.0) - F0) * pow(1.0 - cosTheta, 5.0);
}
`);
const GGX = wgslFn(`
fn distributionGGX(N: vec3<f32>, H: vec3<f32>, roughness: f32) -> f32 {
let a = roughness * roughness;
let a2 = a * a;
let NdotH = max(dot(N, H), 0.0);
let NdotH2 = NdotH * NdotH;
let num = a2;
let denom = (NdotH2 * (a2 - 1.0) + 1.0);
denom = 3.14159265359 * denom * denom;
return num / denom;
}
`);
```
### Noise Functions
```javascript
const simplexNoise = wgslFn(`
fn mod289(x: vec3<f32>) -> vec3<f32> {
return x - floor(x * (1.0 / 289.0)) * 289.0;
}
fn permute(x: vec3<f32>) -> vec3<f32> {
return mod289(((x * 34.0) + 1.0) * x);
}
fn snoise(v: vec2<f32>) -> f32 {
let C = vec4<f32>(
0.211324865405187,
0.366025403784439,
-0.577350269189626,
0.024390243902439
);
var i = floor(v + dot(v, C.yy));
let x0 = v - i + dot(i, C.xx);
var i1: vec2<f32>;
if (x0.x > x0.y) {
i1 = vec2<f32>(1.0, 0.0);
} else {
i1 = vec2<f32>(0.0, 1.0);
}
var x12 = x0.xyxy + C.xxzz;
x12 = vec4<f32>(x12.xy - i1, x12.zw);
i = mod289(vec3<f32>(i, 0.0)).xy;
let p = permute(permute(i.y + vec3<f32>(0.0, i1.y, 1.0)) + i.x + vec3<f32>(0.0, i1.x, 1.0));
var m = max(vec3<f32>(0.5) - vec3<f32>(dot(x0, x0), dot(x12.xy, x12.xy), dot(x12.zw, x12.zw)), vec3<f32>(0.0));
m = m * m;
m = m * m;
let x = 2.0 * fract(p * C.www) - 1.0;
let h = abs(x) - 0.5;
let ox = floor(x + 0.5);
let a0 = x - ox;
m = m * (1.79284291400159 - 0.85373472095314 * (a0 * a0 + h * h));
let g = vec3<f32>(
a0.x * x0.x + h.x * x0.y,
a0.y * x12.x + h.y * x12.y,
a0.z * x12.z + h.z * x12.w
);
return 130.0 * dot(m, g);
}
`);
// Use noise
const noiseValue = simplexNoise(uv().mul(10.0));
```
### FBM (Fractal Brownian Motion)
```javascript
const fbm = wgslFn(`
fn fbm(p: vec2<f32>, octaves: i32) -> f32 {
var value = 0.0;
var amplitude = 0.5;
var frequency = 1.0;
var pos = p;
for (var i = 0; i < octaves; i = i + 1) {
value = value + amplitude * snoise(pos * frequency);
amplitude = amplitude * 0.5;
frequency = frequency * 2.0;
}
return value;
}
`);
```
## WGSL Types Reference
### Scalar Types
```wgsl
bool // Boolean
i32 // 32-bit signed integer
u32 // 32-bit unsigned integer
f32 // 32-bit float
f16 // 16-bit float (if enabled)
```
### Vector Types
```wgsl
vec2<f32> // 2D float vector
vec3<f32> // 3D float vector
vec4<f32> // 4D float vector
vec2<i32> // 2D integer vector
vec2<u32> // 2D unsigned integer vector
```
### Matrix Types
```wgsl
mat2x2<f32> // 2x2 matrix
mat3x3<f32> // 3x3 matrix
mat4x4<f32> // 4x4 matrix
mat2x3<f32> // 2 columns, 3 rows
```
### Texture Types
```wgsl
texture_2d<f32>
texture_3d<f32>
texture_cube<f32>
texture_storage_2d<rgba8unorm, write>
```
## WGSL Syntax Reference
### Variables
```wgsl
let x = 1.0; // Immutable
var y = 2.0; // Mutable
const PI = 3.14159; // Compile-time constant
```
### Control Flow
```wgsl
// If-else
if (condition) {
// ...
} else if (other) {
// ...
} else {
// ...
}
// For loop
for (var i = 0; i < 10; i = i + 1) {
// ...
}
// While loop
while (condition) {
// ...
}
// Switch
switch (value) {
case 0: { /* ... */ }
case 1, 2: { /* ... */ }
default: { /* ... */ }
}
```
### Built-in Functions
```wgsl
// Math
abs(x), sign(x), floor(x), ceil(x), round(x)
fract(x), trunc(x)
min(a, b), max(a, b), clamp(x, lo, hi)
mix(a, b, t), step(edge, x), smoothstep(lo, hi, x)
sin(x), cos(x), tan(x), asin(x), acos(x), atan(x), atan2(y, x)
pow(x, y), exp(x), log(x), exp2(x), log2(x)
sqrt(x), inverseSqrt(x)
// Vector
length(v), distance(a, b)
dot(a, b), cross(a, b)
normalize(v), faceForward(n, i, nref)
reflect(i, n), refract(i, n, eta)
// Matrix
transpose(m), determinant(m)
// Texture
textureSample(t, s, coord)
textureLoad(t, coord, level)
textureStore(t, coord, value)
textureDimensions(t)
```
## Combining TSL and WGSL
### TSL Wrapper for WGSL
```javascript
import { Fn, wgslFn, float, vec2, vec3 } from 'three/tsl';
// WGSL implementation
const wgslNoise = wgslFn(`
fn noise2d(p: vec2<f32>) -> f32 {
return fract(sin(dot(p, vec2<f32>(12.9898, 78.233))) * 43758.5453);
}
`);
// TSL wrapper with nice API
const noise = Fn(([position, scale = 1.0]) => {
return wgslNoise(position.xy.mul(scale));
});
// Use
material.colorNode = vec3(noise(positionWorld, 10.0));
```
### Hybrid Approach
```javascript
// Complex math in WGSL
const complexMath = wgslFn(`
fn complexOperation(a: vec3<f32>, b: vec3<f32>, t: f32) -> vec3<f32> {
let blended = mix(a, b, t);
let rotated = vec3<f32>(
blended.x * cos(t) - blended.y * sin(t),
blended.x * sin(t) + blended.y * cos(t),
blended.z
);
return normalize(rotated);
}
`);
// Simple logic in TSL
const finalColor = Fn(() => {
const base = texture(diffuseMap).rgb;
const processed = complexMath(base, vec3(1, 0, 0), time);
return mix(base, processed, oscSine(time));
});
material.colorNode = finalColor();
```
## Performance Tips
### Avoid Branching When Possible
```wgsl
// Instead of:
if (x > 0.5) {
result = a;
} else {
result = b;
}
// Use:
result = mix(b, a, step(0.5, x));
```
### Use Local Variables
```wgsl
fn compute(p: vec2<f32>) -> f32 {
// Cache repeated calculations
let p2 = p * p;
let p4 = p2 * p2;
return p2.x + p2.y + p4.x * p4.y;
}
```
### Minimize Texture Samples
```wgsl
// Sample once, use multiple times
let sample = textureSample(tex, sampler, uv);
let r = sample.r;
let g = sample.g;
let b = sample.b;
```