Instanced Buffer Attribute - Can't set from onBeforeRender callback

Right. So in the before times, if you had three geometries

g0: [position, uv, normal, INSTANCE_A]
g1: [position, uv, normal, INSTANCE_B]
g2: [position, uv, normal, INSTANCE_C]

And you render these in three meshes, three would make 12 calls to bind stuff which was sub optimal. It would go, “oh, here is a new geometry, let me bind all of its buffers”.

Modern three however, should be a lot better about this. Basically, if you call these three meshes one after another, only the INSTANCE_* attribute should be bound multiple times, the first three will be bound once.

However, i don’t think that this was ever that detrimental to performance. You dont have 10000 meshes, you have one instanced mesh.

You have to keep your A,B,C in memory anyway, unless you are expecting something else to happen with your code. So, creating three geometries, is a valid solution.

The project I am working on is quite big and it would require some time to implement non-onBeforeRender approach.

This is not true.

  1. Find where renderer.render(scene,camera) is happening, it has to be happening somewhere, otherwise you are not even calling the onBeforeRender
  2. before that call add this:
scene.traverse(o=>{
   if(o instanceof Mesh){ 
      if(typeof o.userData.MY_ON_BEFORE_RENDER === 'function'){
          o.userData.MY_ON_BEFORE_RENDER(renderer,scene,camera,elephant,taylorSwift...)
      }
   }
})

Your other code

- this.onBeforeRender = myOnBeforeRender
+ this.userData.MY_ON_BEFORE_RENDER = myOnBeforeRender

EDIT

Right, i forgot, instead of changing the buffers you can try this

this.userData.MY_ON_BEFORE_RENDER = ()=>{
  this.geometry = geometries[myIndex.value]
}

Alternatively, all in scene graph:

const yourMesh = new Mesh(g,m)
somewhere.add(yourMesh)
const yourMesh = new Object3D()
const a = new Mesh(ga,m)
const b = new Mesh(gb,m)
const c = new Mesh(gc,m)
yourMesh.add(a,b,c)
somewhere.add(yourMesh)

and you can further do

class YourMesh extends Object3D {
  material = new MyMaterial()
  constructor(){
     super()
     const a = new Mesh(ga, this.material)
     const b = new Mesh(ga, this.material)
     const c = new Mesh(ga, this.material)
     this.add(a,b,c)
  }
  show(index){
   this.children.forEach((c,i)=>{
     c.visible = i==index
   })
  }
}

somewhere.add(new YourMesh())

scene.traverse(o=>{
  if(o instanceof YourMesh){
    o.show(yourIndex)
  }
})

Also worth noting… no, you should consider that if it’s one instanced mesh that you are referring, it’s probably not too deep or shouldnt be too deep in the scene graph,.

If you individually had to change the color of 100k things, a callback like onBeforeRender is good. Again, i’m the author of it.

Those 100k things could be many different types of things, trees, bullets, vehicles, clouds etc and each could have its own recipe to run.

However, you have one instanced mesh managing 100k of very similar things. This type of object is a manager, it can exist in parallel with 100k meshes that are in a different scene that is not being rendered. This type of thing should really be very accessible on your scene or near it.

so for example, it could be the first thing that is added to the scene

const myManyElementsManager = new ManagerFor100k()
scene.add(myManyElementsManager)
//.. and so on and so forth until
scene.add(myOldScene)

Basically, the scene is also just an Object3D you can push whatever you are currently adding to it there, and then one level higher up have abstractions like instanced drawing managers, atlases and whatnots.
You can use layers to manage what is drawn too, so inside of your old scene, you could have some meshes that exist say for raycasting, but are filtered out from being drawn by the camera.

Also, userData is sort of there for vanilla js, you can always do

scene.add(my100kInstances)
scene.userData.my100kInstances = my100kInstances

And you then dont have to do scene.traverse.

I think there is a lot of misunderstanding and assumptions here. I get your myOnBeforeRender and I already do similar approach and it works. What I am trying to find out is why this can’t be done from onBeforeRender (I already kind of got answer from the issue I posted about this).

But I would want to still explain you my exact situation, because it might seem confusing.

I have one instancedMesh. I created a customMaterial class which was supposed to be able to render individual instances with different color. I managed to do it when setting attribute for instances. The problem rose when I tried to have another instance of the same material. Now when I would set custom color to instancedMesh instance, the other material would also render it because these colors are stored in attributes which are geometry property (naturally). Then I came up with solution to store attributes internally in each material instance, but onBeforeRender I would call

setAttribute(...)

to update color for current material instance (note: material instance vs InstancedMesh instance).

Now this is easily fixable with customOnBeforeRender system, but that was all I was asking and trying to understand, why wasn’t original onBeforeRender already doing this? I haven’t gotten concrete answer, but I got a good pointers on what to do and what NOT to do (which I am thankful for).

Again I might be talking gibberish so feel free to correct me and let me know if I was confusing you even more. :smiley:

I am confused even more unfortunately. I dont understand how this works:

Maybe this is a webgl 2 or webgpu thing and its too modern for my understanding, but back in the day attributes were related to geometries, not materials. I’m sorry i can’t help :frowning:

No, attributes are still very much a geometry thing.

The thing that I am trying to achieve in my project is to assimilate Instances from InstancedMesh to normal Object3Ds and maybe that’s where this confusion strives from.

So for example I create a custom material. And in this custom material I will create a methods that will set color of either Instance or Object3D.

For the sake of clarity lets call single instance from InstancedMesh ObjectInstance. This will most likely be something like: type = {mesh: InstancesMesh, index: number}

setObjectColor(object: ObjectInstance | Object3D) {
    if (object instanceof ObjectInstance) {
        setColorThroughAttribute(); // This is where onBeforeRender call goes to set geometry attribute.
    }
    else {
        setColorThroughMaterialUniform();
    }
}

Then this custom material can be used as you want and it will treat ObjectInstances similarly to Object3Ds. Note that this material knows nothing about organization of the project it only will set a color for either Object3D or ObjectInstance.

Hope this helps at least a bit to understand my use case. :sweat_smile:

PS: No need to feel bad about anything here, it was refreshing to hear your advices and I even learned something. :slight_smile:

Absolutely! I have no idea what this means. Everything else sounds like a normal instancing approach.