Proposal: Post-instanceMatrix vertex transformation hook for InstancedMesh / BatchMesh

Yes, you’re right. And we also need to think about WebGL backward compatibility, which was never designed for that.

It’s great to see that there are such dedicated people willing to carefully answer questions from newcomers like me.
I’ll see soon if I can play around with GitHub and suggest a clearer improvement.

Thank you for your clear answers.

In the meantime, for those who might be interested, here’s a less disruptive temporary hack that works according to my latest tests, with lighting, shadows, reflection, refraction, fog, and postProcessing.

Custom InstanceNode, allow to get instanceMatrixNode and after-instance control:

THREE.InstanceNode.prototype.setup=function(builder){
const {instanceMatrix,instanceColor}=this
let {instanceMatrixNode,instanceColorNode}=this
const {count}=instanceMatrix
if(instanceMatrixNode===null){
if(count<=1000){instanceMatrixNode=buffer(instanceMatrix.array,'mat4',Math.max(count,1)).element(instanceIndex)}
else{
const buffer=new THREE.InstancedInterleavedBuffer(instanceMatrix.array,16,1)
this.buffer=buffer
const bufferFn=instanceMatrix.usage===THREE.DynamicDrawUsage?instancedDynamicBufferAttribute:instancedBufferAttribute,
instanceBuffers=[bufferFn(buffer,'vec4',16,0),bufferFn(buffer,'vec4',16,4),bufferFn(buffer,'vec4',16,8),bufferFn(buffer,'vec4',16,12)]
instanceMatrixNode=mat4(...instanceBuffers)}
this.instanceMatrixNode=instanceMatrixNode}

  // expose instanceMatrixNode in the builder
  builder.instanceMatrixNode=instanceMatrixNode

if(instanceColor&&instanceColorNode===null){
const buffer=new THREE.InstancedBufferAttribute(instanceColor.array,3),
bufferFn=instanceColor.usage===THREE.DynamicDrawUsage?instancedDynamicBufferAttribute:instancedBufferAttribute
this.bufferColor=buffer
instanceColorNode=vec3(bufferFn(buffer,'vec3',3,0))
this.instanceColorNode=instanceColorNode}
const instancePosition=instanceMatrixNode.mul(positionLocal).xyz
positionLocal.assign(instancePosition)
if(builder.hasGeometryAttribute('normal')){
const instanceNormal=transformNormal(normalLocal,instanceMatrixNode)
normalLocal.assign(instanceNormal)
if(this.instanceColorNode!==null){varyingProperty('vec3','vInstanceColor').assign(this.instanceColorNode)}

  // add a new stuff after instance work
  if(builder.material.instancePositionNode){
  positionLocal.assign(builder.material.instancePositionNode)
  }

}

Custom BatchNode

THREE.BatchNode.prototype.setup=function(builder){
if(this.batchingIdNode===null){
if(builder.getDrawIndex()===null){this.batchingIdNode=instanceIndex}
else{this.batchingIdNode=drawIndex}}
const getIndirectIndex=Fn(([id])=>{
const size=int(textureSize(textureLoad(this.batchMesh._indirectTexture),0).x),
x=int(id).mod(size),
y=int(id).div(size)
return textureLoad(this.batchMesh._indirectTexture,ivec2(x,y)).x}).setLayout({name:'getIndirectIndex',type:'uint',inputs:[{name:'id',type:'int'}]})
const indirectId=getIndirectIndex(int(this.batchingIdNode)),
matricesTexture=this.batchMesh._matricesTexture,
size=int(textureSize(textureLoad(matricesTexture),0).x),
j=float(indirectId).mul(4).toInt().toVar(),
x=j.mod(size),
y=j.div(size),
batchingMatrix=mat4(
textureLoad(matricesTexture,ivec2(x,y)),
textureLoad(matricesTexture,ivec2(x.add(1),y)),
textureLoad(matricesTexture,ivec2(x.add(2),y)),
textureLoad(matricesTexture,ivec2(x.add(3),y))),
colorsTexture=this.batchMesh._colorsTexture

  // expose batchingMatrix as instanceMatrixNode for better compatibility
  this.instanceMatrixNode=batchingMatrix
  builder.instanceMatrixNode=batchingMatrix

if(colorsTexture!==null){
const getBatchingColor=Fn(([id])=>{
const size=int(textureSize(textureLoad(colorsTexture),0).x),
j=id,
x=j.mod(size),
y=j.div(size)
return textureLoad(colorsTexture,ivec2(x,y)).rgb}).setLayout({name:'getBatchingColor',type:'vec3',inputs:[{ name:'id',type:'int'}]})
const color=getBatchingColor(indirectId)
varyingProperty('vec3','vBatchColor').assign(color)

  // add vInstanceColor for the InstancedMesh / BatchedMesh compatibility
  varyingProperty('vec3','vInstanceColor').assign(color) // < added

}
const bm=mat3(batchingMatrix)
positionLocal.assign(batchingMatrix.mul(positionLocal))
const transformedNormal=normalLocal.div(vec3(bm[0].dot(bm[0]),bm[1].dot(bm[1]),bm[2].dot(bm[2]))),
batchingNormal=bm.mul(transformedNormal).xyz
normalLocal.assign(batchingNormal)
if(builder.hasGeometryAttribute('tangent')){tangentLocal.mulAssign(bm)}

  // add a new stuff after batching work
  if(builder.material.instancePositionNode){
  positionLocal.assign(builder.material.instancePositionNode)
  }

}

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
  if(object.isInstancedMesh){
  varyingProperty('vec3','vInstanceColor').mulAssign(myCustomColor)
  }
  else if(object.isBatchedMesh){
  varyingProperty('vec3','vBatchColor').mulAssign(myCustomColor)
  }

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

  // return the transformed positionLocal
  return pos

})()

Hope this helps