Skip to content

Proposal: instancePositionNode, a post-instanceMatrix hook #32465

@elhote-byte

Description

@elhote-byte

Description

Related to the discussion:
www.https://discourse.threejs.org/t/proposal-post-instancematrix-vertex-transformation-hook-for-instancedmesh-batchmesh/88362/7

In Three.js (TSL / WebGPU) vertex-level transforms like wind/displacement/animations cannot be applied after the instanceMatrix. positionNode and vertexNode run before instanceMatrix, which prevents consistent transformed-space effects across instances.

  • instanceMatrix is applied in InstanceNode/BatchNode, but instanceMatrixNode is not exposed.
  • positionNode/vertexNode hooks run before instanceMatrix.
  • As a consequence, post-instance transforms are awkward/impossible to write reliably.

Otherwise, it would be very practical to also expose instanceMatrixNode in BatchedMesh, with the same color varyingProperty, to facilitate material compatibility between InstancedMesh and BatchedMesh, especially when batching InstanceMesh.

Solution

  1. Expose builder.instanceMatrixNode in InstanceNode and BatchNode so materials can read it.
  2. Add optional material hook like instancePositionNode

I implemented in my project a local patch that:

  • sets builder.instanceMatrixNode in InstanceNode/BatchNode setup
  • calls material.instancePositionNode when defined

Tested ok in my project with lighting, shadows, reflection, refraction, fog, and postProcessing (v180).
Not tested with WEBGL / WEBGL2 fallback yet.

In the THREE.InstanceNode.prototype.setup function, after
this.instanceMatrixNode = instanceMatrixNode

add :
builder.instanceMatrixNode = instanceMatrixNode

At the end of THREE.InstanceNode.prototype.setup and THREE.BatchNode.prototype.setup, add:

if ( builder.material.instancePositionNode ){
  positionLocal.assign( builder.material.instancePositionNode )
}

In the THREE.BatchNode.prototype.setup, add:

this.instanceMatrixNode = batchingMatrix

builder.instanceMatrixNode = batchingMatrix

varyingProperty ( 'vec3', 'vInstanceColor' ).assign( color ) // or rename vBatchColor as vInstanceColor ?

Why this helps

Enables proper world-space vertex effects after instance transform

Avoids hacks (double matrix multiply or CPU shadow proxies)

Keeps shadow/light consistency

Example of use:

myMaterial.instancePositionNode = /*#__PURE__*/ Fn( ( builder ) => {

  const { object, instanceMatrixNode }=builder

  // get the local position transformed by instanceMatrixNode
  const pos=positionLocal.toVar()

  // some deal with the position in instance space
  pos.yz.addAssign( myWindNode.yz )

  // some deal with the normal in instance space
  normalLocal.assign( normalLocal.mul( myPerturbNormalNode ).normalize() )

  // some deal with the color after the instance color has been applyed
  varyingProperty( 'vec3', 'vInstanceColor' ).mulAssign( myCustomColor )

  // some deal with uv and the instance matrix with stretched scale correction according to instance space
  const getInstanceScale = vec3( length( instanceMatrixNode[0].xyz ), length( instanceMatrixNode[1].xyz ), length( instanceMatrixNode[2].xyz ) )
  myCustomUV.assign( makeTriplanarUV( positionGeometry, normalGeometry, getInstanceScale ) )

  // return the positionLocal
  return pos

})()

Request

Would the core team consider adding official instancePositionNode hook and exposing instanceMatrixNode to NodeMaterial pipelines?

Thank you — happy to provide more tests or a PR if the team is interested.

Alternatives

Right now the only alternative is to override InstanceNode.prototype.setup() and BatchNode.prototype.setup() in my local project. This lets me expose instanceMatrixNode and apply post-instance transformations, but it’s fragile and incompatible with upstream changes. It also forces maintaining a custom patched version of THREE.JS.

Additional context

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    TSLThree.js Shading Language

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions