Skip to content

Commit

Permalink
WebGPURenderer: Instance Sprite Example (#29503)
Browse files Browse the repository at this point in the history
* WebGPURenderer: Introduce InstancedSprites

* adapt the geometry accordingly to feedbacks

* use instancedBufferAttribute approach instead

* cleanup

---------
  • Loading branch information
RenaudRohlinger authored Sep 28, 2024
1 parent a646cbc commit 64072a1
Show file tree
Hide file tree
Showing 5 changed files with 179 additions and 1 deletion.
1 change: 1 addition & 0 deletions examples/files.json
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,7 @@
"webgpu_equirectangular",
"webgpu_instance_mesh",
"webgpu_instance_points",
"webgpu_instance_sprites",
"webgpu_instance_uniform",
"webgpu_instancing_morph",
"webgpu_lensflares",
Expand Down
2 changes: 2 additions & 0 deletions examples/jsm/geometries/InstancedPointsGeometry.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ class InstancedPointsGeometry extends InstancedBufferGeometry {
this.computeBoundingBox();
this.computeBoundingSphere();

this.instanceCount = points.length / 3;

return this;

}
Expand Down
Binary file added examples/screenshots/webgpu_instance_sprites.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
172 changes: 172 additions & 0 deletions examples/webgpu_instance_sprites.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>three.js webgpu - instance sprites</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 - instance sprites
</div>

<script type="importmap">
{
"imports": {
"three": "../build/three.webgpu.js",
"three/tsl": "../build/three.webgpu.js",
"three/addons/": "./jsm/"
}
}
</script>

<script type="module">

import * as THREE from 'three';

import Stats from 'three/addons/libs/stats.module.js';
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';

import { uniform, timerGlobal, instanceIndex, instancedBufferAttribute } from 'three/tsl';


let camera, scene, renderer, stats, material;
let mouseX = 0, mouseY = 0;

let windowHalfX = window.innerWidth / 2;
let windowHalfY = window.innerHeight / 2;

init();

function init() {

camera = new THREE.PerspectiveCamera( 55, window.innerWidth / window.innerHeight, 2, 2000 );
camera.position.z = 1000;

scene = new THREE.Scene();
scene.fog = new THREE.FogExp2( 0x000000, 0.001 );

// positions

const count = 10000;

const positions = [];

for ( let i = 0; i < count; i ++ ) {

positions.push( 2000 * Math.random() - 1000, 2000 * Math.random() - 1000, 2000 * Math.random() - 1000 );

}

const positionAttribute = new THREE.InstancedBufferAttribute( new Float32Array( positions ), 3 );

// texture

const map = new THREE.TextureLoader().load( 'textures/sprites/snowflake1.png' );
map.colorSpace = THREE.SRGBColorSpace;

// material

const timer = timerGlobal();

material = new THREE.SpriteNodeMaterial( { sizeAttenuation: true, map, alphaMap: map, alphaTest: 0.1, transparent: true } );
material.color.setHSL( 1.0, 0.3, 0.7, THREE.SRGBColorSpace );
material.positionNode = instancedBufferAttribute( positionAttribute );
material.rotationNode = timer.add( instanceIndex ).sin();
material.scaleNode = uniform( 15 );

// sprites

const particles = new THREE.Sprite( material );
particles.count = count;

scene.add( particles );

//

renderer = new THREE.WebGPURenderer();
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 );

//

const gui = new GUI();

gui.add( material, 'sizeAttenuation' ).onChange( function () {

material.needsUpdate = true;
material.scaleNode.value = material.sizeAttenuation ? 15 : 0.03;

} );

gui.open();

//

document.body.style.touchAction = 'none';
document.body.addEventListener( 'pointermove', onPointerMove );

//

window.addEventListener( 'resize', onWindowResize );

}

function onWindowResize() {

windowHalfX = window.innerWidth / 2;
windowHalfY = window.innerHeight / 2;

camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();

renderer.setSize( window.innerWidth, window.innerHeight );

}

function onPointerMove( event ) {

if ( event.isPrimary === false ) return;

mouseX = event.clientX - windowHalfX;
mouseY = event.clientY - windowHalfY;

}

//

function animate() {

render();
stats.update();

}

function render() {

const time = Date.now() * 0.00005;

camera.position.x += ( mouseX - camera.position.x ) * 0.05;
camera.position.y += ( - mouseY - camera.position.y ) * 0.05;

camera.lookAt( scene.position );

const h = ( 360 * ( 1.0 + time ) % 360 ) / 360;
material.color.setHSL( h, 0.5, 0.5 );

renderer.render( scene, camera );

}

</script>
</body>
</html>
5 changes: 4 additions & 1 deletion src/materials/nodes/SpriteNodeMaterial.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import NodeMaterial from './NodeMaterial.js';
import { cameraProjectionMatrix } from '../../nodes/accessors/Camera.js';
import { materialRotation } from '../../nodes/accessors/MaterialNode.js';
import { modelViewMatrix, modelWorldMatrix } from '../../nodes/accessors/ModelNode.js';
import { positionLocal } from '../../nodes/accessors/Position.js';
import { positionLocal, positionView } from '../../nodes/accessors/Position.js';
import { rotate } from '../../nodes/utils/RotateNode.js';
import { float, vec2, vec3, vec4 } from '../../nodes/tsl/TSLBase.js';

Expand Down Expand Up @@ -83,6 +83,9 @@ class SpriteNodeMaterial extends NodeMaterial {

mvPosition = vec4( mvPosition.xy.add( rotatedPosition ), mvPosition.zw );

positionView.assign( mvPosition );
positionLocal.assign( rotatedPosition );

const modelViewProjection = cameraProjectionMatrix.mul( mvPosition );

context.vertex = vertex;
Expand Down

0 comments on commit 64072a1

Please sign in to comment.