移动skill位置
This commit is contained in:
404
.codex/skills/webgpu-threejs-tsl/docs/compute-shaders.md
Normal file
404
.codex/skills/webgpu-threejs-tsl/docs/compute-shaders.md
Normal 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);
|
||||
}
|
||||
```
|
||||
453
.codex/skills/webgpu-threejs-tsl/docs/core-concepts.md
Normal file
453
.codex/skills/webgpu-threejs-tsl/docs/core-concepts.md
Normal 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)
|
||||
```
|
||||
359
.codex/skills/webgpu-threejs-tsl/docs/device-loss.md
Normal file
359
.codex/skills/webgpu-threejs-tsl/docs/device-loss.md
Normal 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
|
||||
353
.codex/skills/webgpu-threejs-tsl/docs/materials.md
Normal file
353
.codex/skills/webgpu-threejs-tsl/docs/materials.md
Normal 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));
|
||||
```
|
||||
475
.codex/skills/webgpu-threejs-tsl/docs/post-processing.md
Normal file
475
.codex/skills/webgpu-threejs-tsl/docs/post-processing.md
Normal 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();
|
||||
}
|
||||
```
|
||||
324
.codex/skills/webgpu-threejs-tsl/docs/wgsl-integration.md
Normal file
324
.codex/skills/webgpu-threejs-tsl/docs/wgsl-integration.md
Normal 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;
|
||||
```
|
||||
Reference in New Issue
Block a user