Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WebGPURenderer: InstancedPoints Revision #29374

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/files.json
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,7 @@
"webgpu_equirectangular",
"webgpu_instance_mesh",
"webgpu_instance_points",
"webgpu_instance_points_billboards",
"webgpu_instance_uniform",
"webgpu_instancing_morph",
"webgpu_lightprobe",
Expand Down
3 changes: 1 addition & 2 deletions examples/jsm/geometries/InstancedPointsGeometry.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,8 @@ class InstancedPointsGeometry extends InstancedBufferGeometry {
this.isInstancedPointsGeometry = true;

this.type = 'InstancedPointsGeometry';

const positions = [ - 1, 1, 0, 1, 1, 0, - 1, - 1, 0, 1, - 1, 0 ];
const uvs = [ - 1, 1, 1, 1, - 1, - 1, 1, - 1 ];
const uvs = [ 0, 1, 1, 1, 0, 0, 1, 0 ];
const index = [ 0, 2, 1, 2, 3, 1 ];

this.setIndex( index );
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions examples/webgpu_instance_points.html
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@
pointWidth: 10, // in pixel units
vertexColors: true,
alphaToCoverage: true,
sizeAttenuation: false

} );

Expand Down
161 changes: 161 additions & 0 deletions examples/webgpu_instance_points_billboards.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>three.js webgpu - particles - billboards</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 particle billboards example
</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 InstancedPoints from 'three/addons/objects/InstancedPoints.js';
import InstancedPointsGeometry from 'three/addons/geometries/InstancedPointsGeometry.js';

import { densityFog, color } 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();

const geometry = new InstancedPointsGeometry();
const vertices = [];

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

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

const x = 2000 * Math.random() - 1000;
const y = 2000 * Math.random() - 1000;
const z = 2000 * Math.random() - 1000;

vertices.push( x, y, z );

}

geometry.setPositions( vertices );

geometry.instanceCount = vertices.length / 3; // this should not be necessary

material = new THREE.InstancedPointsNodeMaterial( { pointWidth: 35, sizeAttenuation: true, map: sprite, alphaTest: 0.5, transparent: true } );
material.color.setHSL( 1.0, 0.3, 0.7, THREE.SRGBColorSpace );

const particles = new InstancedPoints( geometry, material );
scene.add( particles );

scene.fogNode = densityFog( color( 0x000000 ), 0.001 );

//

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' );

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>
109 changes: 73 additions & 36 deletions src/materials/nodes/InstancedPointsNodeMaterial.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import NodeMaterial from './NodeMaterial.js';
import { attribute } from '../../nodes/core/AttributeNode.js';
import { cameraProjectionMatrix } from '../../nodes/accessors/Camera.js';
import { materialColor, materialOpacity, materialPointWidth } from '../../nodes/accessors/MaterialNode.js'; // or should this be a property, instead?
import { materialAlphaTest, materialColor, materialOpacity, materialPointWidth } from '../../nodes/accessors/MaterialNode.js'; // or should this be a property, instead?
import { modelViewMatrix } from '../../nodes/accessors/ModelNode.js';
import { positionGeometry } from '../../nodes/accessors/Position.js';
import { smoothstep, lengthSq } from '../../nodes/math/MathNode.js';
import { Fn, vec4, float } from '../../nodes/tsl/TSLBase.js';
import { positionGeometry, positionLocal, positionView } from '../../nodes/accessors/Position.js';
import { lengthSq, smoothstep } from '../../nodes/math/MathNode.js';
import { float, Fn, If, vec4 } from '../../nodes/tsl/TSLBase.js';
import { uv } from '../../nodes/accessors/UV.js';
import { viewport } from '../../nodes/display/ViewportNode.js';

Expand All @@ -27,6 +27,10 @@ class InstancedPointsNodeMaterial extends NodeMaterial {

this.lights = false;

this.sizeAttenuation = true;

this.useSizeAttenuation = true;

this.useAlphaToCoverage = true;

this.useColor = params.vertexColors;
Expand All @@ -39,98 +43,114 @@ class InstancedPointsNodeMaterial extends NodeMaterial {

this.setDefaultValues( _defaultValues );

this.setupShaders();

this.setValues( params );

}

setup( builder ) {

this.setupShaders();
this.setupShaders( builder );

super.setup( builder );

}

setupShaders() {
setupShaders( { renderer } ) {

const useAlphaToCoverage = this.alphaToCoverage;
const useSizeAttenuation = this.sizeAttenuation;
const useColor = this.useColor;

this.vertexNode = Fn( () => {

const instancePosition = attribute( 'instancePosition' ).xyz;

// camera space
const mvPos = vec4( modelViewMatrix.mul( vec4( instancePosition, 1.0 ) ) );
positionLocal.assign( positionGeometry.add( instancePosition ) );

const aspect = viewport.z.div( viewport.w );
const viewPosition = modelViewMatrix.mul( vec4( instancePosition, 1.0 ) );
positionView.assign( viewPosition );

// clip space
const clipPos = cameraProjectionMatrix.mul( mvPos );
const clipPos = cameraProjectionMatrix.mul( viewPosition );
const offset = positionGeometry.xy;

// offset in ndc space
const offset = positionGeometry.xy.toVar();
let size = this.pointWidthNode || materialPointWidth;

offset.mulAssign( this.pointWidthNode ? this.pointWidthNode : materialPointWidth );
if ( useSizeAttenuation ) {

offset.assign( offset.div( viewport.z ) );
offset.y.assign( offset.y.mul( aspect ) );
// Convert size (diameter) to radius and apply perspective scaling
size = size.div( clipPos.w.div( viewport.w ) ).div( 2 );

// back to clip space
offset.assign( offset.mul( clipPos.w ) );
}

//clipPos.xy += offset;
clipPos.addAssign( vec4( offset, 0, 0 ) );
const adjustedOffset = offset.mul( size )
.div( viewport.zw )
.mul( clipPos.w );

return clipPos;
return clipPos.add( vec4( adjustedOffset, 0, 0 ) );

} )();

this.fragmentNode = Fn( () => {

// force assignment into correct place in flow
const alpha = float( 1 ).toVar();

const len2 = lengthSq( uv() );
If( materialAlphaTest.equal( 0.0 ), () => {

if ( useAlphaToCoverage ) {
const len2 = lengthSq( uv().mul( 2 ).sub( 1 ) );

const dlen = float( len2.fwidth() ).toVar();

alpha.assign( smoothstep( dlen.oneMinus(), dlen.add( 1 ), len2 ).oneMinus() );
if ( useAlphaToCoverage && renderer.samples > 1 ) {

} else {
const dlen = float( len2.fwidth() ).toVar();

len2.greaterThan( 1.0 ).discard();
alpha.assign( smoothstep( dlen.oneMinus(), dlen.add( 1 ), len2 ).oneMinus() );

} else {

len2.greaterThan( 1.0 ).discard();

}

} );

}

let pointColorNode;

const output = vec4( 1 ).toVar();

if ( this.pointColorNode ) {

pointColorNode = this.pointColorNode;
output.assign( this.pointColorNode );

} else {

if ( useColor ) {

const instanceColor = attribute( 'instanceColor' );

pointColorNode = instanceColor.mul( materialColor );
output.assign( instanceColor.mul( materialColor ) );

} else {

pointColorNode = materialColor;
output.assign( materialColor );

}

}

alpha.mulAssign( materialOpacity );
output.a.mulAssign( alpha );
output.a.mulAssign( materialOpacity );


If( materialAlphaTest.greaterThan( 0.0 ), () => {

output.a.lessThanEqual( materialAlphaTest ).discard();

return vec4( pointColorNode, alpha );
} );



return output;

} )();

Expand All @@ -153,6 +173,23 @@ class InstancedPointsNodeMaterial extends NodeMaterial {

}

get sizeAttenuation() {

return this.useSizeAttenuation;

}

set sizeAttenuation( value ) {

if ( this.useSizeAttenuation !== value ) {

this.useSizeAttenuation = value;
this.needsUpdate = true;

}

}

}

export default InstancedPointsNodeMaterial;
4 changes: 3 additions & 1 deletion src/materials/nodes/NodeMaterial.js
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,9 @@ class NodeMaterial extends Material {

if ( globalClippingCount || localClippingCount ) {

if ( this.alphaToCoverage ) {
const samples = builder.renderer.samples;

if ( this.alphaToCoverage && samples > 1 ) {

// to be added to flow when the color/alpha value has been determined
result = clippingAlpha();
Expand Down
Loading