Skip to content

Commit 54a2507

Browse files
authored
InstanceNode: Support velocity. (#32586)
1 parent 5c4f7ef commit 54a2507

4 files changed

Lines changed: 129 additions & 56 deletions

File tree

src/nodes/accessors/InstanceNode.js

Lines changed: 102 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import Node from '../core/Node.js';
22
import { varyingProperty } from '../core/PropertyNode.js';
33
import { instancedBufferAttribute, instancedDynamicBufferAttribute } from './BufferAttributeNode.js';
44
import { normalLocal, transformNormal } from './Normal.js';
5-
import { positionLocal } from './Position.js';
5+
import { positionLocal, positionPrevious } from './Position.js';
66
import { nodeProxy, vec3, mat4 } from '../tsl/TSLBase.js';
77
import { NodeUpdateType } from '../core/constants.js';
88
import { buffer } from '../accessors/BufferNode.js';
@@ -99,6 +99,14 @@ class InstanceNode extends Node {
9999
*/
100100
this.bufferColor = null;
101101

102+
/**
103+
* The previous instance matrices. Required for computing motion vectors.
104+
*
105+
* @type {?Node}
106+
* @default null
107+
*/
108+
this.previousInstanceMatrixNode = null;
109+
102110
}
103111

104112
/**
@@ -136,51 +144,22 @@ class InstanceNode extends Node {
136144
*/
137145
setup( builder ) {
138146

139-
const { instanceMatrix, instanceColor, isStorageMatrix, isStorageColor } = this;
140-
141-
const { count } = instanceMatrix;
142-
143147
let { instanceMatrixNode, instanceColorNode } = this;
144148

145-
if ( instanceMatrixNode === null ) {
146-
147-
if ( isStorageMatrix ) {
148-
149-
instanceMatrixNode = storage( instanceMatrix, 'mat4', Math.max( count, 1 ) ).element( instanceIndex );
150-
151-
} else {
152-
153-
// Both backends have ~64kb UBO limit; fallback to attributes above 1000 matrices.
154-
155-
if ( count <= 1000 ) {
156-
157-
instanceMatrixNode = buffer( instanceMatrix.array, 'mat4', Math.max( count, 1 ) ).element( instanceIndex );
158-
159-
} else {
160-
161-
const interleaved = new InstancedInterleavedBuffer( instanceMatrix.array, 16, 1 );
162-
163-
this.buffer = interleaved;
149+
// instance matrix
164150

165-
const bufferFn = instanceMatrix.usage === DynamicDrawUsage ? instancedDynamicBufferAttribute : instancedBufferAttribute;
166-
167-
const instanceBuffers = [
168-
bufferFn( interleaved, 'vec4', 16, 0 ),
169-
bufferFn( interleaved, 'vec4', 16, 4 ),
170-
bufferFn( interleaved, 'vec4', 16, 8 ),
171-
bufferFn( interleaved, 'vec4', 16, 12 )
172-
];
173-
174-
instanceMatrixNode = mat4( ...instanceBuffers );
175-
176-
}
151+
if ( instanceMatrixNode === null ) {
177152

178-
}
153+
instanceMatrixNode = this._createInstanceMatrixNode( true );
179154

180155
this.instanceMatrixNode = instanceMatrixNode;
181156

182157
}
183158

159+
// instance color
160+
161+
const { instanceColor, isStorageColor } = this;
162+
184163
if ( instanceColor && instanceColorNode === null ) {
185164

186165
if ( isStorageColor ) {
@@ -208,6 +187,12 @@ class InstanceNode extends Node {
208187
const instancePosition = instanceMatrixNode.mul( positionLocal ).xyz;
209188
positionLocal.assign( instancePosition );
210189

190+
if ( builder.needsPreviousData() ) {
191+
192+
positionPrevious.assign( this.getPreviousInstancedPosition( builder ) );
193+
194+
}
195+
211196
// NORMAL
212197

213198
if ( builder.hasGeometryAttribute( 'normal' ) ) {
@@ -235,7 +220,7 @@ class InstanceNode extends Node {
235220
*
236221
* @param {NodeFrame} frame - The current node frame.
237222
*/
238-
update( /*frame*/ ) {
223+
update( frame ) {
239224

240225
if ( this.buffer !== null && this.isStorageMatrix !== true ) {
241226

@@ -265,6 +250,85 @@ class InstanceNode extends Node {
265250

266251
}
267252

253+
if ( this.previousInstanceMatrixNode !== null ) {
254+
255+
frame.object.previousInstanceMatrix.array.set( this.instanceMatrix.array );
256+
257+
}
258+
259+
}
260+
261+
/**
262+
* Computes the transformed/instanced vertex position of the previous frame.
263+
*
264+
* @param {NodeBuilder} builder - The current node builder.
265+
* @return {Node<vec3>} The instanced position from the previous frame.
266+
*/
267+
getPreviousInstancedPosition( builder ) {
268+
269+
const instancedMesh = builder.object;
270+
271+
if ( this.previousInstanceMatrixNode === null ) {
272+
273+
instancedMesh.previousInstanceMatrix = this.instanceMatrix.clone();
274+
275+
this.previousInstanceMatrixNode = this._createInstanceMatrixNode( false );
276+
277+
}
278+
279+
return this.previousInstanceMatrixNode.mul( positionPrevious ).xyz;
280+
281+
}
282+
283+
/**
284+
* Creates a node representing the instance matrix data.
285+
*
286+
* @private
287+
* @param {boolean} assignBuffer - Whether the created interleaved buffer should be assigned to the `buffer` member or not.
288+
* @return {Node} The instance matrix node.
289+
*/
290+
_createInstanceMatrixNode( assignBuffer ) {
291+
292+
let instanceMatrixNode;
293+
294+
const { instanceMatrix } = this;
295+
const { count } = instanceMatrix;
296+
297+
if ( this.isStorageMatrix ) {
298+
299+
instanceMatrixNode = storage( instanceMatrix, 'mat4', Math.max( count, 1 ) ).element( instanceIndex );
300+
301+
} else {
302+
303+
// Both backends have ~64kb UBO limit; fallback to attributes above 1000 matrices.
304+
305+
if ( count <= 1000 ) {
306+
307+
instanceMatrixNode = buffer( instanceMatrix.array, 'mat4', Math.max( count, 1 ) ).element( instanceIndex );
308+
309+
} else {
310+
311+
const interleaved = new InstancedInterleavedBuffer( instanceMatrix.array, 16, 1 );
312+
313+
if ( assignBuffer === true ) this.buffer = interleaved;
314+
315+
const bufferFn = instanceMatrix.usage === DynamicDrawUsage ? instancedDynamicBufferAttribute : instancedBufferAttribute;
316+
317+
const instanceBuffers = [
318+
bufferFn( interleaved, 'vec4', 16, 0 ),
319+
bufferFn( interleaved, 'vec4', 16, 4 ),
320+
bufferFn( interleaved, 'vec4', 16, 8 ),
321+
bufferFn( interleaved, 'vec4', 16, 12 )
322+
];
323+
324+
instanceMatrixNode = mat4( ...instanceBuffers );
325+
326+
}
327+
328+
}
329+
330+
return instanceMatrixNode;
331+
268332
}
269333

270334
}

src/nodes/accessors/SkinningNode.js

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import { positionLocal, positionPrevious } from './Position.js';
99
import { tangentLocal } from './Tangent.js';
1010
import { uniform } from '../core/UniformNode.js';
1111
import { buffer } from './BufferNode.js';
12-
import { getDataFromObject } from '../core/NodeUtils.js';
1312
import { storage } from './StorageBufferNode.js';
1413
import { InstancedBufferAttribute } from '../../core/InstancedBufferAttribute.js';
1514
import { instanceIndex } from '../core/IndexNode.js';
@@ -199,21 +198,6 @@ class SkinningNode extends Node {
199198

200199
}
201200

202-
/**
203-
* Returns `true` if bone matrices from the previous frame are required. Relevant
204-
* when computing motion vectors with {@link VelocityNode}.
205-
*
206-
* @param {NodeBuilder} builder - The current node builder.
207-
* @return {boolean} Whether bone matrices from the previous frame are required or not.
208-
*/
209-
needsPreviousBoneMatrices( builder ) {
210-
211-
const mrt = builder.renderer.getMRT();
212-
213-
return ( mrt && mrt.has( 'velocity' ) ) || getDataFromObject( builder.object ).useVelocity === true;
214-
215-
}
216-
217201
/**
218202
* Setups the skinning node by assigning the transformed vertex data to predefined node variables.
219203
*
@@ -222,7 +206,7 @@ class SkinningNode extends Node {
222206
*/
223207
setup( builder ) {
224208

225-
if ( this.needsPreviousBoneMatrices( builder ) ) {
209+
if ( builder.needsPreviousData() ) {
226210

227211
positionPrevious.assign( this.getPreviousSkinnedPosition( builder ) );
228212

src/nodes/core/NodeBuilder.js

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import ParameterNode from './ParameterNode.js';
88
import StructType from './StructType.js';
99
import FunctionNode from '../code/FunctionNode.js';
1010
import NodeMaterial from '../../materials/nodes/NodeMaterial.js';
11-
import { getTypeFromLength } from './NodeUtils.js';
11+
import { getDataFromObject, getTypeFromLength } from './NodeUtils.js';
1212
import { NodeUpdateType, defaultBuildStages, shaderStages } from './constants.js';
1313

1414
import {
@@ -3118,6 +3118,20 @@ class NodeBuilder {
31183118

31193119
}
31203120

3121+
/**
3122+
* Returns `true` if data from the previous frame are required. Relevant
3123+
* when computing motion vectors with {@link VelocityNode}.
3124+
*
3125+
* @return {boolean} Whether data from the previous frame are required or not.
3126+
*/
3127+
needsPreviousData() {
3128+
3129+
const mrt = this.renderer.getMRT();
3130+
3131+
return ( mrt && mrt.has( 'velocity' ) ) || getDataFromObject( this.object ).useVelocity === true;
3132+
3133+
}
3134+
31213135
}
31223136

31233137
export default NodeBuilder;

src/objects/InstancedMesh.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,15 @@ class InstancedMesh extends Mesh {
5656
*/
5757
this.instanceMatrix = new InstancedBufferAttribute( new Float32Array( count * 16 ), 16 );
5858

59+
/**
60+
* Represents the local transformation of all instances of the previous frame.
61+
* Required for computing velocity. Maintained in {@link InstanceNode}.
62+
*
63+
* @type {?InstancedBufferAttribute}
64+
* @default null
65+
*/
66+
this.previousInstanceMatrix = null;
67+
5968
/**
6069
* Represents the color of all instances. You have to set its
6170
* {@link BufferAttribute#needsUpdate} flag to true if you modify instanced data
@@ -185,6 +194,8 @@ class InstancedMesh extends Mesh {
185194

186195
this.instanceMatrix.copy( source.instanceMatrix );
187196

197+
if ( source.previousInstanceMatrix !== null ) this.previousInstanceMatrix = source.previousInstanceMatrix.clone();
198+
188199
if ( source.morphTexture !== null ) this.morphTexture = source.morphTexture.clone();
189200
if ( source.instanceColor !== null ) this.instanceColor = source.instanceColor.clone();
190201

0 commit comments

Comments
 (0)