移动skill位置
This commit is contained in:
87
.codex/skills/webgpu-threejs-tsl/examples/basic-setup.js
Normal file
87
.codex/skills/webgpu-threejs-tsl/examples/basic-setup.js
Normal file
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* Basic WebGPU Three.js Setup
|
||||
*
|
||||
* Minimal example showing WebGPU renderer initialization
|
||||
* with a simple animated mesh using TSL.
|
||||
*
|
||||
* Based on Three.js examples (MIT License)
|
||||
* https://github.com/mrdoob/three.js
|
||||
*/
|
||||
|
||||
import * as THREE from 'three/webgpu';
|
||||
import { color, time, oscSine, positionLocal, normalWorld } from 'three/tsl';
|
||||
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
||||
|
||||
let camera, scene, renderer, controls;
|
||||
|
||||
async function init() {
|
||||
// Camera
|
||||
camera = new THREE.PerspectiveCamera(
|
||||
70,
|
||||
window.innerWidth / window.innerHeight,
|
||||
0.1,
|
||||
100
|
||||
);
|
||||
camera.position.z = 4;
|
||||
|
||||
// Scene
|
||||
scene = new THREE.Scene();
|
||||
scene.background = new THREE.Color(0x111111);
|
||||
|
||||
// Lighting
|
||||
const ambientLight = new THREE.AmbientLight(0x404040);
|
||||
scene.add(ambientLight);
|
||||
|
||||
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
|
||||
directionalLight.position.set(5, 5, 5);
|
||||
scene.add(directionalLight);
|
||||
|
||||
// Create mesh with TSL material
|
||||
const geometry = new THREE.TorusKnotGeometry(1, 0.3, 128, 32);
|
||||
const material = new THREE.MeshStandardNodeMaterial();
|
||||
|
||||
// Animated color using TSL
|
||||
material.colorNode = color(0x0088ff).mul(
|
||||
oscSine(time.mul(0.5)).mul(0.5).add(0.5)
|
||||
);
|
||||
|
||||
// Add slight position wobble
|
||||
material.positionNode = positionLocal.add(
|
||||
normalWorld.mul(oscSine(time.mul(2.0).add(positionLocal.y)).mul(0.05))
|
||||
);
|
||||
|
||||
const mesh = new THREE.Mesh(geometry, material);
|
||||
scene.add(mesh);
|
||||
|
||||
// Renderer
|
||||
renderer = new THREE.WebGPURenderer({ antialias: true });
|
||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
||||
renderer.setPixelRatio(window.devicePixelRatio);
|
||||
document.body.appendChild(renderer.domElement);
|
||||
|
||||
// Initialize WebGPU
|
||||
await renderer.init();
|
||||
|
||||
// Controls
|
||||
controls = new OrbitControls(camera, renderer.domElement);
|
||||
controls.enableDamping = true;
|
||||
|
||||
// Handle resize
|
||||
window.addEventListener('resize', onWindowResize);
|
||||
|
||||
// Start animation loop
|
||||
renderer.setAnimationLoop(animate);
|
||||
}
|
||||
|
||||
function onWindowResize() {
|
||||
camera.aspect = window.innerWidth / window.innerHeight;
|
||||
camera.updateProjectionMatrix();
|
||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
||||
}
|
||||
|
||||
function animate() {
|
||||
controls.update();
|
||||
renderer.render(scene, camera);
|
||||
}
|
||||
|
||||
init();
|
||||
170
.codex/skills/webgpu-threejs-tsl/examples/custom-material.js
Normal file
170
.codex/skills/webgpu-threejs-tsl/examples/custom-material.js
Normal file
@@ -0,0 +1,170 @@
|
||||
/**
|
||||
* Custom TSL Material Example
|
||||
*
|
||||
* Demonstrates creating custom shader effects using TSL:
|
||||
* - Fresnel rim lighting
|
||||
* - Animated patterns
|
||||
* - Dynamic displacement
|
||||
*
|
||||
* Based on Three.js examples (MIT License)
|
||||
* https://github.com/mrdoob/three.js
|
||||
*/
|
||||
|
||||
import * as THREE from 'three/webgpu';
|
||||
import {
|
||||
Fn,
|
||||
color,
|
||||
float,
|
||||
vec2,
|
||||
vec3,
|
||||
uniform,
|
||||
texture,
|
||||
uv,
|
||||
time,
|
||||
mix,
|
||||
smoothstep,
|
||||
sin,
|
||||
cos,
|
||||
positionLocal,
|
||||
positionWorld,
|
||||
normalLocal,
|
||||
normalWorld,
|
||||
cameraPosition
|
||||
} from 'three/tsl';
|
||||
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
||||
|
||||
let camera, scene, renderer, controls;
|
||||
let rimColor, patternScale, displacementStrength;
|
||||
|
||||
async function init() {
|
||||
// Setup
|
||||
camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 100);
|
||||
camera.position.z = 3;
|
||||
|
||||
scene = new THREE.Scene();
|
||||
scene.background = new THREE.Color(0x000000);
|
||||
|
||||
// Uniforms for runtime control
|
||||
rimColor = uniform(new THREE.Color(0x00ffff));
|
||||
patternScale = uniform(5.0);
|
||||
displacementStrength = uniform(0.1);
|
||||
|
||||
// Create custom material
|
||||
const material = createCustomMaterial();
|
||||
|
||||
// Mesh
|
||||
const geometry = new THREE.IcosahedronGeometry(1, 64);
|
||||
const mesh = new THREE.Mesh(geometry, material);
|
||||
scene.add(mesh);
|
||||
|
||||
// Renderer
|
||||
renderer = new THREE.WebGPURenderer({ antialias: true });
|
||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
||||
renderer.setPixelRatio(window.devicePixelRatio);
|
||||
document.body.appendChild(renderer.domElement);
|
||||
await renderer.init();
|
||||
|
||||
// Controls
|
||||
controls = new OrbitControls(camera, renderer.domElement);
|
||||
controls.enableDamping = true;
|
||||
|
||||
// Events
|
||||
window.addEventListener('resize', onWindowResize);
|
||||
|
||||
// GUI (optional - requires lil-gui)
|
||||
setupGUI();
|
||||
|
||||
renderer.setAnimationLoop(animate);
|
||||
}
|
||||
|
||||
function createCustomMaterial() {
|
||||
const material = new THREE.MeshStandardNodeMaterial();
|
||||
|
||||
// --- Fresnel Rim Effect ---
|
||||
const fresnel = Fn(() => {
|
||||
const viewDir = cameraPosition.sub(positionWorld).normalize();
|
||||
const nDotV = normalWorld.dot(viewDir).saturate();
|
||||
return float(1.0).sub(nDotV).pow(3.0);
|
||||
});
|
||||
|
||||
// --- Animated Pattern ---
|
||||
const animatedPattern = Fn(() => {
|
||||
const uvCoord = uv().mul(patternScale);
|
||||
const t = time.mul(0.5);
|
||||
|
||||
// Create animated wave pattern
|
||||
const wave1 = sin(uvCoord.x.mul(10.0).add(t)).mul(0.5).add(0.5);
|
||||
const wave2 = sin(uvCoord.y.mul(10.0).sub(t.mul(1.3))).mul(0.5).add(0.5);
|
||||
const wave3 = sin(uvCoord.x.add(uvCoord.y).mul(7.0).add(t.mul(0.7))).mul(0.5).add(0.5);
|
||||
|
||||
return wave1.mul(wave2).mul(wave3);
|
||||
});
|
||||
|
||||
// --- Displacement ---
|
||||
const displacement = Fn(() => {
|
||||
const pattern = animatedPattern();
|
||||
return normalLocal.mul(pattern.mul(displacementStrength));
|
||||
});
|
||||
|
||||
// Apply displacement
|
||||
material.positionNode = positionLocal.add(displacement());
|
||||
|
||||
// --- Color ---
|
||||
const baseColor = color(0x222244);
|
||||
const highlightColor = color(0x4444ff);
|
||||
|
||||
// Mix colors based on pattern
|
||||
const pattern = animatedPattern();
|
||||
const surfaceColor = mix(baseColor, highlightColor, pattern);
|
||||
|
||||
material.colorNode = surfaceColor;
|
||||
|
||||
// --- Rim lighting ---
|
||||
material.emissiveNode = rimColor.mul(fresnel());
|
||||
|
||||
// --- PBR properties ---
|
||||
material.roughnessNode = float(0.3).add(pattern.mul(0.4));
|
||||
material.metalnessNode = float(0.1);
|
||||
|
||||
return material;
|
||||
}
|
||||
|
||||
function setupGUI() {
|
||||
// Only setup if lil-gui is available
|
||||
if (typeof window.GUI === 'undefined') {
|
||||
console.log('Add lil-gui for interactive controls');
|
||||
return;
|
||||
}
|
||||
|
||||
const gui = new GUI();
|
||||
const params = {
|
||||
rimColor: '#00ffff',
|
||||
patternScale: 5.0,
|
||||
displacementStrength: 0.1
|
||||
};
|
||||
|
||||
gui.addColor(params, 'rimColor').onChange((value) => {
|
||||
rimColor.value.set(value);
|
||||
});
|
||||
|
||||
gui.add(params, 'patternScale', 1, 20).onChange((value) => {
|
||||
patternScale.value = value;
|
||||
});
|
||||
|
||||
gui.add(params, 'displacementStrength', 0, 0.5).onChange((value) => {
|
||||
displacementStrength.value = value;
|
||||
});
|
||||
}
|
||||
|
||||
function onWindowResize() {
|
||||
camera.aspect = window.innerWidth / window.innerHeight;
|
||||
camera.updateProjectionMatrix();
|
||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
||||
}
|
||||
|
||||
function animate() {
|
||||
controls.update();
|
||||
renderer.render(scene, camera);
|
||||
}
|
||||
|
||||
init();
|
||||
292
.codex/skills/webgpu-threejs-tsl/examples/earth-shader.js
Normal file
292
.codex/skills/webgpu-threejs-tsl/examples/earth-shader.js
Normal file
@@ -0,0 +1,292 @@
|
||||
/**
|
||||
* Earth Shader Example
|
||||
*
|
||||
* Complete procedural Earth with:
|
||||
* - Day/night texture blending
|
||||
* - Atmospheric glow (fresnel)
|
||||
* - Cloud layer
|
||||
* - City lights at night
|
||||
* - Bump mapping
|
||||
*
|
||||
* Based on Three.js webgpu_tsl_earth example (MIT License)
|
||||
* https://github.com/mrdoob/three.js
|
||||
*/
|
||||
|
||||
import * as THREE from 'three/webgpu';
|
||||
import {
|
||||
Fn,
|
||||
If,
|
||||
float,
|
||||
vec2,
|
||||
vec3,
|
||||
vec4,
|
||||
color,
|
||||
uniform,
|
||||
texture,
|
||||
uv,
|
||||
time,
|
||||
mix,
|
||||
smoothstep,
|
||||
pow,
|
||||
clamp,
|
||||
normalize,
|
||||
dot,
|
||||
max,
|
||||
positionWorld,
|
||||
normalWorld,
|
||||
normalLocal,
|
||||
cameraPosition,
|
||||
bumpMap
|
||||
} from 'three/tsl';
|
||||
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
||||
|
||||
let camera, scene, renderer, controls;
|
||||
let earth, clouds, atmosphere;
|
||||
|
||||
// Uniforms
|
||||
const sunDirection = uniform(new THREE.Vector3(1, 0.2, 0.5).normalize());
|
||||
const atmosphereDayColor = uniform(new THREE.Color(0x4db2ff));
|
||||
const atmosphereTwilightColor = uniform(new THREE.Color(0xbd5f1b));
|
||||
const cloudSpeed = uniform(0.01);
|
||||
const cityLightIntensity = uniform(1.5);
|
||||
|
||||
async function init() {
|
||||
// Camera
|
||||
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 100);
|
||||
camera.position.set(0, 0, 4);
|
||||
|
||||
// Scene
|
||||
scene = new THREE.Scene();
|
||||
scene.background = new THREE.Color(0x000011);
|
||||
|
||||
// Load textures
|
||||
const loader = new THREE.TextureLoader();
|
||||
|
||||
// Note: Replace with actual texture paths
|
||||
const earthDayTexture = loader.load('textures/earth_day.jpg');
|
||||
const earthNightTexture = loader.load('textures/earth_night.jpg');
|
||||
const earthCloudsTexture = loader.load('textures/earth_clouds.jpg');
|
||||
const earthBumpTexture = loader.load('textures/earth_bump.jpg');
|
||||
|
||||
// Set texture properties
|
||||
[earthDayTexture, earthNightTexture, earthCloudsTexture, earthBumpTexture].forEach((tex) => {
|
||||
tex.colorSpace = THREE.SRGBColorSpace;
|
||||
tex.wrapS = THREE.RepeatWrapping;
|
||||
tex.wrapT = THREE.ClampToEdgeWrapping;
|
||||
});
|
||||
|
||||
// Create Earth
|
||||
earth = createEarth(earthDayTexture, earthNightTexture, earthBumpTexture);
|
||||
scene.add(earth);
|
||||
|
||||
// Create cloud layer
|
||||
clouds = createClouds(earthCloudsTexture);
|
||||
scene.add(clouds);
|
||||
|
||||
// Create atmosphere glow
|
||||
atmosphere = createAtmosphere();
|
||||
scene.add(atmosphere);
|
||||
|
||||
// Stars background
|
||||
createStars();
|
||||
|
||||
// Renderer
|
||||
renderer = new THREE.WebGPURenderer({ antialias: true });
|
||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
||||
renderer.setPixelRatio(window.devicePixelRatio);
|
||||
document.body.appendChild(renderer.domElement);
|
||||
await renderer.init();
|
||||
|
||||
// Controls
|
||||
controls = new OrbitControls(camera, renderer.domElement);
|
||||
controls.enableDamping = true;
|
||||
controls.minDistance = 2;
|
||||
controls.maxDistance = 10;
|
||||
|
||||
// Events
|
||||
window.addEventListener('resize', onWindowResize);
|
||||
|
||||
renderer.setAnimationLoop(animate);
|
||||
}
|
||||
|
||||
function createEarth(dayTex, nightTex, bumpTex) {
|
||||
const geometry = new THREE.SphereGeometry(1, 64, 64);
|
||||
const material = new THREE.MeshStandardNodeMaterial();
|
||||
|
||||
// Sun illumination factor
|
||||
const sunOrientation = Fn(() => {
|
||||
return normalWorld.dot(sunDirection).mul(0.5).add(0.5);
|
||||
});
|
||||
|
||||
// Day/night color mixing
|
||||
material.colorNode = Fn(() => {
|
||||
const dayColor = texture(dayTex, uv());
|
||||
const nightColor = texture(nightTex, uv());
|
||||
|
||||
const orientation = sunOrientation();
|
||||
const dayNight = smoothstep(0.4, 0.6, orientation);
|
||||
|
||||
// Add city lights on night side
|
||||
const cityLights = nightColor.mul(cityLightIntensity).mul(
|
||||
float(1.0).sub(dayNight)
|
||||
);
|
||||
|
||||
const baseColor = mix(nightColor, dayColor, dayNight);
|
||||
return baseColor.add(cityLights.mul(float(1.0).sub(orientation).pow(2.0)));
|
||||
})();
|
||||
|
||||
// Bump mapping for terrain
|
||||
material.normalNode = bumpMap(texture(bumpTex, uv()), 0.03);
|
||||
|
||||
// PBR properties vary with day/night
|
||||
material.roughnessNode = Fn(() => {
|
||||
const orientation = sunOrientation();
|
||||
return mix(float(0.8), float(0.4), smoothstep(0.3, 0.7, orientation));
|
||||
})();
|
||||
|
||||
material.metalnessNode = float(0.0);
|
||||
|
||||
// Subtle atmospheric rim on day side
|
||||
material.emissiveNode = Fn(() => {
|
||||
const viewDir = normalize(cameraPosition.sub(positionWorld));
|
||||
const fresnel = pow(float(1.0).sub(normalWorld.dot(viewDir).saturate()), 4.0);
|
||||
|
||||
const orientation = sunOrientation();
|
||||
const atmosphereColor = mix(atmosphereTwilightColor, atmosphereDayColor, orientation);
|
||||
|
||||
return atmosphereColor.mul(fresnel).mul(orientation).mul(0.3);
|
||||
})();
|
||||
|
||||
return new THREE.Mesh(geometry, material);
|
||||
}
|
||||
|
||||
function createClouds(cloudsTex) {
|
||||
const geometry = new THREE.SphereGeometry(1.01, 64, 64);
|
||||
const material = new THREE.MeshStandardNodeMaterial();
|
||||
|
||||
// Animated UV for cloud movement
|
||||
const cloudUV = Fn(() => {
|
||||
const baseUV = uv();
|
||||
const offset = time.mul(cloudSpeed);
|
||||
return vec2(baseUV.x.add(offset), baseUV.y);
|
||||
});
|
||||
|
||||
// Cloud color (white with transparency)
|
||||
material.colorNode = color(0xffffff);
|
||||
|
||||
// Cloud opacity from texture
|
||||
material.opacityNode = Fn(() => {
|
||||
const cloudAlpha = texture(cloudsTex, cloudUV()).r;
|
||||
|
||||
// Fade clouds on night side
|
||||
const sunOrientation = normalWorld.dot(sunDirection).mul(0.5).add(0.5);
|
||||
const dayFactor = smoothstep(0.2, 0.5, sunOrientation);
|
||||
|
||||
return cloudAlpha.mul(0.8).mul(dayFactor.mul(0.5).add(0.5));
|
||||
})();
|
||||
|
||||
material.transparent = true;
|
||||
material.depthWrite = false;
|
||||
material.side = THREE.DoubleSide;
|
||||
|
||||
// Slight self-illumination
|
||||
material.emissiveNode = Fn(() => {
|
||||
const sunOrientation = normalWorld.dot(sunDirection).mul(0.5).add(0.5);
|
||||
return color(0xffffff).mul(sunOrientation.mul(0.1));
|
||||
})();
|
||||
|
||||
return new THREE.Mesh(geometry, material);
|
||||
}
|
||||
|
||||
function createAtmosphere() {
|
||||
const geometry = new THREE.SphereGeometry(1.15, 64, 64);
|
||||
const material = new THREE.MeshBasicNodeMaterial();
|
||||
|
||||
material.colorNode = Fn(() => {
|
||||
const viewDir = normalize(cameraPosition.sub(positionWorld));
|
||||
const fresnel = pow(float(1.0).sub(normalWorld.dot(viewDir).abs()), 3.0);
|
||||
|
||||
const sunOrientation = normalWorld.dot(sunDirection).mul(0.5).add(0.5);
|
||||
const atmosphereColor = mix(atmosphereTwilightColor, atmosphereDayColor, sunOrientation);
|
||||
|
||||
return atmosphereColor;
|
||||
})();
|
||||
|
||||
material.opacityNode = Fn(() => {
|
||||
const viewDir = normalize(cameraPosition.sub(positionWorld));
|
||||
const fresnel = pow(float(1.0).sub(normalWorld.dot(viewDir).abs()), 2.5);
|
||||
|
||||
// Stronger on day side
|
||||
const sunOrientation = normalWorld.dot(sunDirection).mul(0.5).add(0.5);
|
||||
|
||||
return fresnel.mul(sunOrientation.mul(0.5).add(0.3));
|
||||
})();
|
||||
|
||||
material.transparent = true;
|
||||
material.depthWrite = false;
|
||||
material.side = THREE.BackSide;
|
||||
|
||||
return new THREE.Mesh(geometry, material);
|
||||
}
|
||||
|
||||
function createStars() {
|
||||
const starsGeometry = new THREE.BufferGeometry();
|
||||
const starCount = 2000;
|
||||
|
||||
const positions = new Float32Array(starCount * 3);
|
||||
const colors = new Float32Array(starCount * 3);
|
||||
|
||||
for (let i = 0; i < starCount; i++) {
|
||||
// Random position on sphere
|
||||
const theta = Math.random() * Math.PI * 2;
|
||||
const phi = Math.acos(Math.random() * 2 - 1);
|
||||
const radius = 50 + Math.random() * 50;
|
||||
|
||||
positions[i * 3] = radius * Math.sin(phi) * Math.cos(theta);
|
||||
positions[i * 3 + 1] = radius * Math.sin(phi) * Math.sin(theta);
|
||||
positions[i * 3 + 2] = radius * Math.cos(phi);
|
||||
|
||||
// Slight color variation
|
||||
const brightness = 0.5 + Math.random() * 0.5;
|
||||
colors[i * 3] = brightness;
|
||||
colors[i * 3 + 1] = brightness;
|
||||
colors[i * 3 + 2] = brightness + Math.random() * 0.2;
|
||||
}
|
||||
|
||||
starsGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
|
||||
starsGeometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
|
||||
|
||||
const starsMaterial = new THREE.PointsNodeMaterial();
|
||||
starsMaterial.colorNode = Fn(() => {
|
||||
return vec3(1.0);
|
||||
})();
|
||||
starsMaterial.sizeNode = float(2.0);
|
||||
starsMaterial.vertexColors = true;
|
||||
|
||||
const stars = new THREE.Points(starsGeometry, starsMaterial);
|
||||
scene.add(stars);
|
||||
}
|
||||
|
||||
function onWindowResize() {
|
||||
camera.aspect = window.innerWidth / window.innerHeight;
|
||||
camera.updateProjectionMatrix();
|
||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
||||
}
|
||||
|
||||
function animate() {
|
||||
// Rotate Earth slowly
|
||||
earth.rotation.y += 0.001;
|
||||
clouds.rotation.y += 0.0012;
|
||||
|
||||
// Animate sun direction (optional - creates day/night cycle)
|
||||
// const angle = time.value * 0.1;
|
||||
// sunDirection.value.set(Math.cos(angle), 0.2, Math.sin(angle)).normalize();
|
||||
|
||||
controls.update();
|
||||
renderer.render(scene, camera);
|
||||
}
|
||||
|
||||
init();
|
||||
|
||||
// Export for external control
|
||||
export { sunDirection, atmosphereDayColor, atmosphereTwilightColor, cloudSpeed, cityLightIntensity };
|
||||
259
.codex/skills/webgpu-threejs-tsl/examples/particle-system.js
Normal file
259
.codex/skills/webgpu-threejs-tsl/examples/particle-system.js
Normal file
@@ -0,0 +1,259 @@
|
||||
/**
|
||||
* GPU Particle System with Compute Shaders
|
||||
*
|
||||
* Demonstrates TSL compute shaders for particle simulation:
|
||||
* - Instanced array buffers
|
||||
* - Physics simulation on GPU
|
||||
* - Mouse interaction
|
||||
*
|
||||
* Based on Three.js webgpu_compute_particles example (MIT License)
|
||||
* https://github.com/mrdoob/three.js
|
||||
*/
|
||||
|
||||
import * as THREE from 'three/webgpu';
|
||||
import {
|
||||
Fn,
|
||||
If,
|
||||
uniform,
|
||||
float,
|
||||
vec3,
|
||||
color,
|
||||
instancedArray,
|
||||
instanceIndex,
|
||||
hash,
|
||||
time
|
||||
} from 'three/tsl';
|
||||
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
||||
|
||||
let camera, scene, renderer, controls;
|
||||
let computeInit, computeUpdate, computeHit;
|
||||
|
||||
// Particle count
|
||||
const PARTICLE_COUNT = 100000;
|
||||
|
||||
// Storage buffers
|
||||
const positions = instancedArray(PARTICLE_COUNT, 'vec3');
|
||||
const velocities = instancedArray(PARTICLE_COUNT, 'vec3');
|
||||
|
||||
// Uniforms
|
||||
const gravity = uniform(-9.8);
|
||||
const bounce = uniform(0.7);
|
||||
const friction = uniform(0.98);
|
||||
const deltaTimeUniform = uniform(0);
|
||||
const clickPosition = uniform(new THREE.Vector3());
|
||||
const hitStrength = uniform(5.0);
|
||||
|
||||
async function init() {
|
||||
// Camera
|
||||
camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 100);
|
||||
camera.position.set(0, 5, 15);
|
||||
|
||||
// Scene
|
||||
scene = new THREE.Scene();
|
||||
scene.background = new THREE.Color(0x111122);
|
||||
|
||||
// Create compute shaders
|
||||
createComputeShaders();
|
||||
|
||||
// Create particle mesh
|
||||
createParticleMesh();
|
||||
|
||||
// Floor
|
||||
const floorGeometry = new THREE.PlaneGeometry(30, 30);
|
||||
const floorMaterial = new THREE.MeshStandardNodeMaterial({
|
||||
color: 0x333333
|
||||
});
|
||||
const floor = new THREE.Mesh(floorGeometry, floorMaterial);
|
||||
floor.rotation.x = -Math.PI / 2;
|
||||
floor.receiveShadow = true;
|
||||
scene.add(floor);
|
||||
|
||||
// Lights
|
||||
const ambientLight = new THREE.AmbientLight(0x404040);
|
||||
scene.add(ambientLight);
|
||||
|
||||
const pointLight = new THREE.PointLight(0xffffff, 100);
|
||||
pointLight.position.set(5, 10, 5);
|
||||
scene.add(pointLight);
|
||||
|
||||
// Renderer
|
||||
renderer = new THREE.WebGPURenderer({ antialias: true });
|
||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
||||
renderer.setPixelRatio(window.devicePixelRatio);
|
||||
document.body.appendChild(renderer.domElement);
|
||||
await renderer.init();
|
||||
|
||||
// Initialize particles
|
||||
await renderer.computeAsync(computeInit);
|
||||
|
||||
// Controls
|
||||
controls = new OrbitControls(camera, renderer.domElement);
|
||||
controls.enableDamping = true;
|
||||
controls.target.set(0, 2, 0);
|
||||
|
||||
// Events
|
||||
window.addEventListener('resize', onWindowResize);
|
||||
renderer.domElement.addEventListener('click', onClick);
|
||||
|
||||
renderer.setAnimationLoop(animate);
|
||||
}
|
||||
|
||||
function createComputeShaders() {
|
||||
// Grid dimensions for initialization
|
||||
const gridSize = Math.ceil(Math.sqrt(PARTICLE_COUNT));
|
||||
const spacing = 0.15;
|
||||
const offset = (gridSize * spacing) / 2;
|
||||
|
||||
// Initialize particles in a grid
|
||||
computeInit = Fn(() => {
|
||||
const position = positions.element(instanceIndex);
|
||||
const velocity = velocities.element(instanceIndex);
|
||||
|
||||
// Calculate grid position
|
||||
const x = instanceIndex.mod(gridSize);
|
||||
const z = instanceIndex.div(gridSize);
|
||||
|
||||
// Set position
|
||||
position.x.assign(x.toFloat().mul(spacing).sub(offset));
|
||||
position.y.assign(float(5.0).add(hash(instanceIndex).mul(2.0)));
|
||||
position.z.assign(z.toFloat().mul(spacing).sub(offset));
|
||||
|
||||
// Random initial velocity
|
||||
velocity.x.assign(hash(instanceIndex.add(1)).sub(0.5).mul(2.0));
|
||||
velocity.y.assign(hash(instanceIndex.add(2)).mul(-2.0));
|
||||
velocity.z.assign(hash(instanceIndex.add(3)).sub(0.5).mul(2.0));
|
||||
})().compute(PARTICLE_COUNT);
|
||||
|
||||
// Physics update
|
||||
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));
|
||||
|
||||
// Apply friction
|
||||
velocity.mulAssign(friction);
|
||||
|
||||
// Ground collision
|
||||
If(position.y.lessThan(0), () => {
|
||||
position.y.assign(0);
|
||||
velocity.y.assign(velocity.y.abs().mul(bounce)); // Reverse and dampen
|
||||
|
||||
// Extra friction on ground
|
||||
velocity.x.mulAssign(0.9);
|
||||
velocity.z.mulAssign(0.9);
|
||||
});
|
||||
|
||||
// Boundary walls
|
||||
If(position.x.abs().greaterThan(15), () => {
|
||||
position.x.assign(position.x.sign().mul(15));
|
||||
velocity.x.assign(velocity.x.negate().mul(bounce));
|
||||
});
|
||||
|
||||
If(position.z.abs().greaterThan(15), () => {
|
||||
position.z.assign(position.z.sign().mul(15));
|
||||
velocity.z.assign(velocity.z.negate().mul(bounce));
|
||||
});
|
||||
})().compute(PARTICLE_COUNT);
|
||||
|
||||
// Hit/explosion effect
|
||||
computeHit = Fn(() => {
|
||||
const position = positions.element(instanceIndex);
|
||||
const velocity = velocities.element(instanceIndex);
|
||||
|
||||
// Distance to click
|
||||
const toClick = position.sub(clickPosition);
|
||||
const distance = toClick.length();
|
||||
|
||||
// Apply force within radius
|
||||
If(distance.lessThan(3.0), () => {
|
||||
const direction = toClick.normalize();
|
||||
const force = float(3.0).sub(distance).div(3.0).mul(hitStrength);
|
||||
|
||||
// Add randomness
|
||||
const randomForce = force.mul(hash(instanceIndex.add(time.mul(1000))).mul(0.5).add(0.75));
|
||||
|
||||
velocity.addAssign(direction.mul(randomForce));
|
||||
velocity.y.addAssign(randomForce.mul(0.5));
|
||||
});
|
||||
})().compute(PARTICLE_COUNT);
|
||||
}
|
||||
|
||||
function createParticleMesh() {
|
||||
// Simple sphere geometry for each particle
|
||||
const geometry = new THREE.SphereGeometry(0.08, 8, 8);
|
||||
|
||||
// Material using computed positions
|
||||
const material = new THREE.MeshStandardNodeMaterial();
|
||||
|
||||
// Position from compute buffer
|
||||
material.positionNode = positions.element(instanceIndex);
|
||||
|
||||
// Color based on velocity
|
||||
material.colorNode = Fn(() => {
|
||||
const velocity = velocities.element(instanceIndex);
|
||||
const speed = velocity.length();
|
||||
|
||||
// Color gradient: blue (slow) -> orange (fast)
|
||||
const t = speed.div(10.0).saturate();
|
||||
return color(0x0066ff).mix(color(0xff6600), t);
|
||||
})();
|
||||
|
||||
material.roughnessNode = float(0.5);
|
||||
material.metalnessNode = float(0.2);
|
||||
|
||||
// Create instanced mesh
|
||||
const mesh = new THREE.InstancedMesh(geometry, material, PARTICLE_COUNT);
|
||||
scene.add(mesh);
|
||||
}
|
||||
|
||||
function onClick(event) {
|
||||
// Raycast to find click position on floor
|
||||
const raycaster = new THREE.Raycaster();
|
||||
const mouse = new THREE.Vector2(
|
||||
(event.clientX / window.innerWidth) * 2 - 1,
|
||||
-(event.clientY / window.innerHeight) * 2 + 1
|
||||
);
|
||||
|
||||
raycaster.setFromCamera(mouse, camera);
|
||||
|
||||
// Intersect with floor plane (y = 0)
|
||||
const plane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0);
|
||||
const intersection = new THREE.Vector3();
|
||||
raycaster.ray.intersectPlane(plane, intersection);
|
||||
|
||||
if (intersection) {
|
||||
// Raise click position slightly
|
||||
intersection.y = 0.5;
|
||||
clickPosition.value.copy(intersection);
|
||||
|
||||
// Run hit compute shader
|
||||
renderer.compute(computeHit);
|
||||
}
|
||||
}
|
||||
|
||||
function onWindowResize() {
|
||||
camera.aspect = window.innerWidth / window.innerHeight;
|
||||
camera.updateProjectionMatrix();
|
||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
||||
}
|
||||
|
||||
const clock = new THREE.Clock();
|
||||
|
||||
function animate() {
|
||||
// Update delta time
|
||||
deltaTimeUniform.value = Math.min(clock.getDelta(), 0.1);
|
||||
|
||||
// Run physics compute
|
||||
renderer.compute(computeUpdate);
|
||||
|
||||
controls.update();
|
||||
renderer.render(scene, camera);
|
||||
}
|
||||
|
||||
init();
|
||||
199
.codex/skills/webgpu-threejs-tsl/examples/post-processing.js
Normal file
199
.codex/skills/webgpu-threejs-tsl/examples/post-processing.js
Normal file
@@ -0,0 +1,199 @@
|
||||
/**
|
||||
* Post-Processing Pipeline Example
|
||||
*
|
||||
* Demonstrates TSL post-processing:
|
||||
* - Bloom effect
|
||||
* - Custom vignette
|
||||
* - Color grading
|
||||
* - Effect chaining
|
||||
*
|
||||
* Based on Three.js webgpu_postprocessing examples (MIT License)
|
||||
* https://github.com/mrdoob/three.js
|
||||
*/
|
||||
|
||||
import * as THREE from 'three/webgpu';
|
||||
import {
|
||||
Fn,
|
||||
float,
|
||||
vec2,
|
||||
vec3,
|
||||
vec4,
|
||||
color,
|
||||
uniform,
|
||||
pass,
|
||||
screenUV,
|
||||
screenSize,
|
||||
time,
|
||||
oscSine,
|
||||
mix,
|
||||
smoothstep,
|
||||
texture,
|
||||
grayscale,
|
||||
saturation
|
||||
} from 'three/tsl';
|
||||
import { bloom } from 'three/addons/tsl/display/BloomNode.js';
|
||||
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
||||
|
||||
let camera, scene, renderer, controls;
|
||||
let postProcessing;
|
||||
|
||||
// Effect uniforms
|
||||
const bloomStrength = uniform(1.0);
|
||||
const bloomThreshold = uniform(0.5);
|
||||
const vignetteIntensity = uniform(0.5);
|
||||
const saturationAmount = uniform(1.2);
|
||||
const colorTint = uniform(new THREE.Color(1.0, 0.95, 0.9));
|
||||
|
||||
async function init() {
|
||||
// Camera
|
||||
camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 100);
|
||||
camera.position.set(0, 2, 8);
|
||||
|
||||
// Scene
|
||||
scene = new THREE.Scene();
|
||||
scene.background = new THREE.Color(0x111111);
|
||||
|
||||
// Add objects with emissive materials (for bloom)
|
||||
createScene();
|
||||
|
||||
// Renderer
|
||||
renderer = new THREE.WebGPURenderer({ antialias: true });
|
||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
||||
renderer.setPixelRatio(window.devicePixelRatio);
|
||||
document.body.appendChild(renderer.domElement);
|
||||
await renderer.init();
|
||||
|
||||
// Create post-processing pipeline
|
||||
setupPostProcessing();
|
||||
|
||||
// Controls
|
||||
controls = new OrbitControls(camera, renderer.domElement);
|
||||
controls.enableDamping = true;
|
||||
controls.target.set(0, 1, 0);
|
||||
|
||||
// Events
|
||||
window.addEventListener('resize', onWindowResize);
|
||||
|
||||
renderer.setAnimationLoop(animate);
|
||||
}
|
||||
|
||||
function createScene() {
|
||||
// Floor
|
||||
const floorGeometry = new THREE.PlaneGeometry(20, 20);
|
||||
const floorMaterial = new THREE.MeshStandardNodeMaterial({
|
||||
color: 0x222222
|
||||
});
|
||||
const floor = new THREE.Mesh(floorGeometry, floorMaterial);
|
||||
floor.rotation.x = -Math.PI / 2;
|
||||
scene.add(floor);
|
||||
|
||||
// Emissive spheres (will bloom)
|
||||
const sphereGeometry = new THREE.SphereGeometry(0.5, 32, 32);
|
||||
|
||||
const colors = [0xff0044, 0x00ff88, 0x4488ff, 0xffaa00, 0xff00ff];
|
||||
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const material = new THREE.MeshStandardNodeMaterial();
|
||||
|
||||
// Base color
|
||||
material.colorNode = color(colors[i]).mul(0.3);
|
||||
|
||||
// Animated emissive
|
||||
material.emissiveNode = Fn(() => {
|
||||
const pulse = oscSine(time.mul(1.0 + i * 0.2)).mul(0.5).add(0.5);
|
||||
return color(colors[i]).mul(pulse.mul(2.0).add(0.5));
|
||||
})();
|
||||
|
||||
material.roughnessNode = float(0.2);
|
||||
material.metalnessNode = float(0.8);
|
||||
|
||||
const sphere = new THREE.Mesh(sphereGeometry, material);
|
||||
sphere.position.set(
|
||||
Math.cos((i / 5) * Math.PI * 2) * 3,
|
||||
1 + Math.sin(i) * 0.5,
|
||||
Math.sin((i / 5) * Math.PI * 2) * 3
|
||||
);
|
||||
scene.add(sphere);
|
||||
}
|
||||
|
||||
// Central reflective sphere
|
||||
const centerMaterial = new THREE.MeshStandardNodeMaterial();
|
||||
centerMaterial.colorNode = color(0x888888);
|
||||
centerMaterial.roughnessNode = float(0.1);
|
||||
centerMaterial.metalnessNode = float(1.0);
|
||||
|
||||
const centerSphere = new THREE.Mesh(new THREE.SphereGeometry(1, 64, 64), centerMaterial);
|
||||
centerSphere.position.y = 1;
|
||||
scene.add(centerSphere);
|
||||
|
||||
// Lights
|
||||
const ambientLight = new THREE.AmbientLight(0x404040, 0.5);
|
||||
scene.add(ambientLight);
|
||||
|
||||
const pointLight = new THREE.PointLight(0xffffff, 50);
|
||||
pointLight.position.set(5, 10, 5);
|
||||
scene.add(pointLight);
|
||||
}
|
||||
|
||||
function setupPostProcessing() {
|
||||
// Create post-processing instance
|
||||
postProcessing = new THREE.PostProcessing(renderer);
|
||||
|
||||
// Create scene pass
|
||||
const scenePass = pass(scene, camera);
|
||||
const sceneColor = scenePass.getTextureNode('output');
|
||||
|
||||
// --- Effect Chain ---
|
||||
|
||||
// 1. Bloom
|
||||
const bloomPass = bloom(sceneColor);
|
||||
bloomPass.threshold = bloomThreshold;
|
||||
bloomPass.strength = bloomStrength;
|
||||
bloomPass.radius = uniform(0.5);
|
||||
|
||||
// Add bloom to scene
|
||||
let output = sceneColor.add(bloomPass);
|
||||
|
||||
// 2. Color Grading
|
||||
output = saturation(output, saturationAmount);
|
||||
output = output.mul(colorTint);
|
||||
|
||||
// 3. Vignette (custom effect)
|
||||
const vignette = Fn(() => {
|
||||
const uv = screenUV;
|
||||
const dist = uv.sub(0.5).length();
|
||||
return float(1.0).sub(dist.mul(vignetteIntensity).pow(2.0)).saturate();
|
||||
});
|
||||
|
||||
output = output.mul(vignette());
|
||||
|
||||
// 4. Optional: Scanlines
|
||||
const scanlines = Fn(() => {
|
||||
const scanline = screenUV.y.mul(screenSize.y).mul(0.5).sin().mul(0.05).add(0.95);
|
||||
return scanline;
|
||||
});
|
||||
|
||||
// Uncomment for CRT effect:
|
||||
// output = output.mul(scanlines());
|
||||
|
||||
// Set final output
|
||||
postProcessing.outputNode = output;
|
||||
}
|
||||
|
||||
function onWindowResize() {
|
||||
camera.aspect = window.innerWidth / window.innerHeight;
|
||||
camera.updateProjectionMatrix();
|
||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
||||
}
|
||||
|
||||
function animate() {
|
||||
controls.update();
|
||||
|
||||
// Render with post-processing
|
||||
postProcessing.render();
|
||||
}
|
||||
|
||||
init();
|
||||
|
||||
// Export uniforms for external control (e.g., GUI)
|
||||
export { bloomStrength, bloomThreshold, vignetteIntensity, saturationAmount, colorTint };
|
||||
Reference in New Issue
Block a user