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: InstancedMesh Supports Instance Color and Morph Target #27928

Merged
merged 7 commits into from
Mar 18, 2024
Merged
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
3 changes: 2 additions & 1 deletion examples/files.json
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,8 @@
"webgpu_mirror",
"webgpu_multisampled_renderbuffers",
"webgpu_materials_texture_anisotropy",
"webgpu_storage_buffer"
"webgpu_storage_buffer",
"webgpu_instancing_morph"
],
"webaudio": [
"webaudio_orientation",
Expand Down
27 changes: 25 additions & 2 deletions examples/jsm/nodes/accessors/InstanceNode.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import Node, { addNodeClass } from '../core/Node.js';
import { varyingProperty } from '../core/PropertyNode.js';
import { instancedBufferAttribute, instancedDynamicBufferAttribute } from './BufferAttributeNode.js';
import { normalLocal } from './NormalNode.js';
import { positionLocal } from './PositionNode.js';
import { nodeProxy, vec3, mat3, mat4 } from '../shadernode/ShaderNode.js';
import { DynamicDrawUsage, InstancedInterleavedBuffer } from 'three';
import { DynamicDrawUsage, InstancedInterleavedBuffer, InstancedBufferAttribute } from 'three';

class InstanceNode extends Node {

Expand All @@ -15,15 +16,18 @@ class InstanceNode extends Node {

this.instanceMatrixNode = null;

this.instanceColorNode = null;

}

setup( /*builder*/ ) {

let instanceMatrixNode = this.instanceMatrixNode;

const instanceMesh = this.instanceMesh;

if ( instanceMatrixNode === null ) {

const instanceMesh = this.instanceMesh;
const instanceAttribute = instanceMesh.instanceMatrix;
const buffer = new InstancedInterleavedBuffer( instanceAttribute.array, 16, 1 );

Expand All @@ -43,6 +47,17 @@ class InstanceNode extends Node {

}

const instanceColorAttribute = instanceMesh.instanceColor;

if ( instanceColorAttribute && this.instanceColorNode === null ) {

const buffer = new InstancedBufferAttribute( instanceColorAttribute.array, 3 );
const bufferFn = instanceColorAttribute.usage === DynamicDrawUsage ? instancedDynamicBufferAttribute : instancedBufferAttribute;

this.instanceColorNode = vec3( bufferFn( buffer, 'vec3', 3, 0 ) );

}

// POSITION

const instancePosition = instanceMatrixNode.mul( positionLocal ).xyz;
Expand All @@ -60,6 +75,14 @@ class InstanceNode extends Node {
positionLocal.assign( instancePosition );
normalLocal.assign( instanceNormal );

// COLOR

if ( this.instanceColorNode !== null ) {

varyingProperty( 'vec3', 'vInstanceColor' ).assign( this.instanceColorNode );

}

}

}
Expand Down
16 changes: 13 additions & 3 deletions examples/jsm/nodes/accessors/MorphNode.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import Node, { addNodeClass } from '../core/Node.js';
import { NodeUpdateType } from '../core/constants.js';
import { nodeProxy, tslFn } from '../shadernode/ShaderNode.js';
import { float, nodeProxy, tslFn } from '../shadernode/ShaderNode.js';
import { uniform } from '../core/UniformNode.js';
import { reference } from './ReferenceNode.js';
import { positionLocal } from './PositionNode.js';
import { normalLocal } from './NormalNode.js';
import { textureLoad } from './TextureNode.js';
import { vertexIndex } from '../core/IndexNode.js';
import { instanceIndex, vertexIndex } from '../core/IndexNode.js';
import { ivec2, int } from '../shadernode/ShaderNode.js';
import { DataArrayTexture, Vector2, Vector4, FloatType } from 'three';
import { loop } from '../utils/LoopNode.js';
Expand Down Expand Up @@ -188,7 +188,17 @@ class MorphNode extends Node {

loop( morphTargetsCount, ( { i } ) => {

const influence = reference( 'morphTargetInfluences', 'float' ).element( i );
const influence = float( 0 ).toVar();

if ( this.mesh.isInstancedMesh === true && this.mesh.morphTexture !== null ) {

influence.assign( textureLoad( this.mesh.morphTexture, ivec2( int( i ).add( 1 ), int( instanceIndex ) ) ).r );

} else {

influence.assign( reference( 'morphTargetInfluences', 'float' ).element( i ).toVar() );

}

if ( hasMorphPosition === true ) {

Expand Down
14 changes: 12 additions & 2 deletions examples/jsm/nodes/accessors/NormalNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { property } from '../core/PropertyNode.js';
import { normalize } from '../math/MathNode.js';
import { cameraViewMatrix } from './CameraNode.js';
import { modelNormalMatrix } from './ModelNode.js';
import { nodeImmutable } from '../shadernode/ShaderNode.js';
import { nodeImmutable, vec3 } from '../shadernode/ShaderNode.js';

class NormalNode extends Node {

Expand Down Expand Up @@ -37,7 +37,17 @@ class NormalNode extends Node {

if ( scope === NormalNode.GEOMETRY ) {

outputNode = attribute( 'normal', 'vec3' );
const geometryAttribute = builder.hasGeometryAttribute( 'normal' );

if ( geometryAttribute === false ) {

outputNode = vec3( 0, 1, 0 );

} else {

outputNode = attribute( 'normal', 'vec3' );

}

} else if ( scope === NormalNode.LOCAL ) {

Expand Down
14 changes: 12 additions & 2 deletions examples/jsm/nodes/materials/NodeMaterial.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Material, ShaderMaterial, NoColorSpace, LinearSRGBColorSpace } from 'three';
import { getNodeChildren, getCacheKey } from '../core/NodeUtils.js';
import { attribute } from '../core/AttributeNode.js';
import { output, diffuseColor } from '../core/PropertyNode.js';
import { output, diffuseColor, varyingProperty } from '../core/PropertyNode.js';
import { materialAlphaTest, materialColor, materialOpacity, materialEmissive, materialNormal } from '../accessors/MaterialNode.js';
import { modelViewProjection } from '../accessors/ModelViewProjectionNode.js';
import { transformedNormalView } from '../accessors/NormalNode.js';
Expand Down Expand Up @@ -220,7 +220,7 @@ class NodeMaterial extends ShaderMaterial {

}

setupDiffuseColor( { geometry } ) {
setupDiffuseColor( { object, geometry } ) {

let colorNode = this.colorNode ? vec4( this.colorNode ) : materialColor;

Expand All @@ -232,6 +232,16 @@ class NodeMaterial extends ShaderMaterial {

}

// Instanced colors

if ( object.instanceColor ) {

const instanceColor = varyingProperty( 'vec3', 'vInstanceColor' );

colorNode = instanceColor.mul( colorNode );

}

// COLOR

diffuseColor.assign( colorNode );
Expand Down
Binary file added examples/screenshots/webgpu_instancing_morph.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
195 changes: 195 additions & 0 deletions examples/webgpu_instancing_morph.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>three.js webgpu - instancing - Morph Target Animations</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>
<script type="importmap">
{
"imports": {
"three": "../build/three.module.js",
"three/addons/": "./jsm/",
"three/nodes": "./jsm/nodes/Nodes.js"
}
}
</script>

<script type="module">

import * as THREE from 'three';

import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';

import Stats from 'three/addons/libs/stats.module.js';

import WebGPURenderer from 'three/addons/renderers/webgpu/WebGPURenderer.js';
import { MeshStandardNodeMaterial } from 'three/nodes';

let camera, scene, renderer, stats, mesh, mixer, dummy;

const offset = 5000;

const timeOffsets = new Float32Array( 1024 );

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

timeOffsets[ i ] = Math.random() * 3;

}

const clock = new THREE.Clock( true );

init();

function init() {

camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 100, 10000 );

scene = new THREE.Scene();

scene.background = new THREE.Color( 0x99DDFF );

scene.fog = new THREE.Fog( 0x99DDFF, 5000, 10000 );


//

stats = new Stats();
document.body.appendChild( stats.dom );

const light = new THREE.DirectionalLight( 0xffffff, 1 );

light.position.set( 200, 1000, 50 );

light.shadow.mapSize.width = 2048;
light.shadow.mapSize.height = 2048;
light.castShadow = true;

light.shadow.camera.left = - 5000;
light.shadow.camera.right = 5000;
light.shadow.camera.top = 5000;
light.shadow.camera.bottom = - 5000;
light.shadow.camera.far = 2000;

light.shadow.bias = - 0.01;

light.shadow.camera.updateProjectionMatrix();

scene.add( light );

const hemi = new THREE.HemisphereLight( 0x99DDFF, 0x669933, 1 / 3 );

scene.add( hemi );

const ground = new THREE.Mesh(
new THREE.PlaneGeometry( 1000000, 1000000 ),
new THREE.MeshStandardMaterial( { color: 0x669933 } )
);

ground.rotation.x = - Math.PI / 2;

ground.receiveShadow = true;

scene.add( ground );

const loader = new GLTFLoader();

loader.load( 'models/gltf/Horse.glb', function ( glb ) {

dummy = glb.scene.children[ 0 ];

mesh = new THREE.InstancedMesh( dummy.geometry, new MeshStandardNodeMaterial( {
flatShading: true,
} ), 1024 );

mesh.castShadow = true;

for ( let x = 0, i = 0; x < 32; x ++ ) {

for ( let y = 0; y < 32; y ++ ) {

dummy.position.set( offset - 300 * x + 200 * Math.random(), 0, offset - 300 * y );

dummy.updateMatrix();

mesh.setMatrixAt( i, dummy.matrix );

mesh.setColorAt( i, new THREE.Color( `hsl(${Math.random() * 360}, 50%, 66%)` ) );

i ++;

}


}

scene.add( mesh );

mixer = new THREE.AnimationMixer( glb.scene );

const action = mixer.clipAction( glb.animations[ 0 ] );

action.play();

} );


// renderer

renderer = new WebGPURenderer( { antialias: true } );

renderer.setAnimationLoop( animate );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );

window.addEventListener( 'resize', onWindowResize );

}

function onWindowResize() {

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

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

}

//

function animate() {

const time = clock.getElapsedTime();

const r = 3000;
camera.position.set( Math.sin( time / 10 ) * r, 1500 + 1000 * Math.cos( time / 5 ), Math.cos( time / 10 ) * r );
camera.lookAt( 0, 0, 0 );

if ( mesh ) {

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

mixer.setTime( time + timeOffsets[ i ] );

mesh.setMorphAt( i, dummy );

}

mesh.morphTexture.needsUpdate = true;

}

renderer.render( scene, camera );

stats.update();

}

</script>

</body>
</html>
1 change: 1 addition & 0 deletions test/e2e/puppeteer.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ const exceptionList = [
'webgpu_tsl_transpiler',
'webgpu_portal',
'webgpu_custom_fog',
'webgpu_instancing_morph',

// WebGPU idleTime and parseTime too low
'webgpu_compute_particles',
Expand Down