You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
earthquake_3d_viewer_front/three/examples/webgpu_compute_particles.html

308 lines
7.7 KiB
HTML

<html lang="en">
<head>
<title>three.js - WebGPU - Compute Particles</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<link type="text/css" rel="stylesheet" href="main.css">
</head>
<body>
<div id="info">
<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> WebGPU - Compute - 500k Particles
<div id="timestamps" style="
position: absolute;
top: 60px;
left: 0;
padding: 10px;
background: rgba( 0, 0, 0, 0.5 );
color: #fff;
font-family: monospace;
font-size: 12px;
line-height: 1.5;
pointer-events: none;
text-align: left;
"></div>
</div>
<script type="importmap">
{
"imports": {
"three": "../build/three.webgpu.js",
"three/webgpu": "../build/three.webgpu.js",
"three/tsl": "../build/three.tsl.js",
"three/addons/": "./jsm/"
}
}
</script>
<script type="module">
import * as THREE from 'three';
import { Fn, If, uniform, float, uv, vec2, vec3, hash, shapeCircle,
instancedArray, instanceIndex } from 'three/tsl';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import Stats from 'three/addons/libs/stats.module.js';
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
const particleCount = 200_000;
const gravity = uniform( - .00098 );
const bounce = uniform( .8 );
const friction = uniform( .99 );
const size = uniform( .12 );
const clickPosition = uniform( new THREE.Vector3() );
let camera, scene, renderer;
let controls, stats;
let computeParticles;
let isOrbitControlsActive;
const timestamps = document.getElementById( 'timestamps' );
init();
function init() {
const { innerWidth, innerHeight } = window;
camera = new THREE.PerspectiveCamera( 50, innerWidth / innerHeight, .1, 1000 );
camera.position.set( 0, 5, 20 );
scene = new THREE.Scene();
//
const positions = instancedArray( particleCount, 'vec3' );
const velocities = instancedArray( particleCount, 'vec3' );
const colors = instancedArray( particleCount, 'vec3' );
// compute
const separation = 0.2;
const amount = Math.sqrt( particleCount );
const offset = float( amount / 2 );
const computeInit = Fn( () => {
const position = positions.element( instanceIndex );
const color = colors.element( instanceIndex );
const x = instanceIndex.mod( amount );
const z = instanceIndex.div( amount );
position.x = offset.sub( x ).mul( separation );
position.z = offset.sub( z ).mul( separation );
color.x = hash( instanceIndex );
color.y = hash( instanceIndex.add( 2 ) );
} )().compute( particleCount );
//
const computeUpdate = Fn( () => {
const position = positions.element( instanceIndex );
const velocity = velocities.element( instanceIndex );
velocity.addAssign( vec3( 0.00, gravity, 0.00 ) );
position.addAssign( velocity );
velocity.mulAssign( friction );
// floor
If( position.y.lessThan( 0 ), () => {
position.y = 0;
velocity.y = velocity.y.negate().mul( bounce );
// floor friction
velocity.x = velocity.x.mul( .9 );
velocity.z = velocity.z.mul( .9 );
} );
} );
computeParticles = computeUpdate().compute( particleCount );
// create particles
const material = new THREE.SpriteNodeMaterial();
material.colorNode = uv().mul( colors.element( instanceIndex ) );
material.positionNode = positions.toAttribute();
material.scaleNode = size;
material.opacityNode = shapeCircle();
material.alphaToCoverage = true;
material.transparent = true;
const particles = new THREE.Sprite( material );
particles.count = particleCount;
particles.frustumCulled = false;
scene.add( particles );
//
const helper = new THREE.GridHelper( 90, 45, 0x303030, 0x303030 );
scene.add( helper );
const geometry = new THREE.PlaneGeometry( 200, 200 );
geometry.rotateX( - Math.PI / 2 );
const plane = new THREE.Mesh( geometry, new THREE.MeshBasicMaterial( { visible: false } ) );
scene.add( plane );
const raycaster = new THREE.Raycaster();
const pointer = new THREE.Vector2();
//
renderer = new THREE.WebGPURenderer( { antialias: true, trackTimestamp: true } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setAnimationLoop( animate );
document.body.appendChild( renderer.domElement );
stats = new Stats();
document.body.appendChild( stats.dom );
//
renderer.computeAsync( computeInit );
// Hit
const computeHit = Fn( () => {
const position = positions.element( instanceIndex );
const velocity = velocities.element( instanceIndex );
const dist = position.distance( clickPosition );
const direction = position.sub( clickPosition ).normalize();
const distArea = float( 3 ).sub( dist ).max( 0 );
const power = distArea.mul( .01 );
const relativePower = power.mul( hash( instanceIndex ).mul( 1.5 ).add( .5 ) );
velocity.assign( velocity.add( direction.mul( relativePower ) ) );
} )().compute( particleCount );
//
function onMove( event ) {
if ( isOrbitControlsActive ) return;
pointer.set( ( event.clientX / window.innerWidth ) * 2 - 1, - ( event.clientY / window.innerHeight ) * 2 + 1 );
raycaster.setFromCamera( pointer, camera );
const intersects = raycaster.intersectObject( plane, false );
if ( intersects.length > 0 ) {
const { point } = intersects[ 0 ];
// move to uniform
clickPosition.value.copy( point );
clickPosition.value.y = - 1;
// compute
renderer.computeAsync( computeHit );
}
}
renderer.domElement.addEventListener( 'pointermove', onMove );
// controls
controls = new OrbitControls( camera, renderer.domElement );
controls.enableDamping = true;
controls.minDistance = 5;
controls.maxDistance = 200;
controls.target.set( 0, -8, 0 );
controls.update();
controls.addEventListener( 'start', () => { isOrbitControlsActive = true; } );
controls.addEventListener( 'end', () => { isOrbitControlsActive = false; } );
controls.touches = {
ONE: null,
TWO: THREE.TOUCH.DOLLY_PAN
};
//
window.addEventListener( 'resize', onWindowResize );
// gui
const gui = new GUI();
gui.add( gravity, 'value', - .0098, 0, 0.0001 ).name( 'gravity' );
gui.add( bounce, 'value', .1, 1, 0.01 ).name( 'bounce' );
gui.add( friction, 'value', .96, .99, 0.01 ).name( 'friction' );
gui.add( size, 'value', .12, .5, 0.01 ).name( 'size' );
}
function onWindowResize() {
const { innerWidth, innerHeight } = window;
camera.aspect = innerWidth / innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( innerWidth, innerHeight );
}
async function animate() {
stats.update();
controls.update();
await renderer.computeAsync( computeParticles );
renderer.resolveTimestampsAsync( THREE.TimestampQuery.COMPUTE );
await renderer.renderAsync( scene, camera );
renderer.resolveTimestampsAsync( THREE.TimestampQuery.RENDER );
// throttle the logging
if ( renderer.hasFeature( 'timestamp-query' ) ) {
if ( renderer.info.render.calls % 5 === 0 ) {
timestamps.innerHTML = `
Compute ${renderer.info.compute.frameCalls} pass in ${renderer.info.compute.timestamp.toFixed( 6 )}ms<br>
Draw ${renderer.info.render.drawCalls} pass in ${renderer.info.render.timestamp.toFixed( 6 )}ms`;
}
} else {
timestamps.innerHTML = 'Timestamp queries not supported';
}
}
</script>
</body>
</html>