移动skill位置
This commit is contained in:
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();
|
||||
Reference in New Issue
Block a user