I have this setup for my "Collectible" scene:

CharacterBody3D
|-Label3D
|-MeshInstance3D
|-CollisionShape3D
|-Node

In MeshInstance3D, I've set a material in the Surface material override field.
In the Next Pass field of this material, I've set a shader I've created in the script "InteractiveShader.gdshader". This shader just changes the color of the material when the mouse hovers over the instance.

Now, I have several instances of this object on the main scene. Everything works as expected, but when I hovered with my mouse over an instance, all of them changed color instead of only the one being selected. To solve this, I tried using the "Local to scene" option and I managed to make it work like I wanted, but only when I set both the Surface material override and the Next Pass to be Local to scene. In any other combination all instances change color at the same time.

So my question is, what is the logic behind this? Setting Next pass to be Local to scene doesn't have any effect unless I also set Surface material override to be Local to scene. It seems a bit convoluted and I had to bang my head against a wall until I found the combination that worked, but this seems like a thing that I will forget and then find myself with the same headache when I need to do it again for something else...but if I manage to understand the logic behind it then it'll be a different story 🙂

  • Megalomaniak Probably because in the rendering pipeline the passes combine into a single shader.

    Wouldn't that defeat the purpose of rendering in passes? As the docs say next_pass "renders the object again using a different material". This sounds like an additional draw call.

    @correojon Roughly speaking, Godot has two main categories of objects - nodes and resources. Nodes live and do things in the scene. Resources are tucked somewhere in memory.

    Nodes use resources as... well... their data resources. A single resource object is often shared between multiple nodes. This is good because is makes things economical and efficient. For example; many MeshInstance3D nodes can use the same Mesh resource, as well as the same Material resource to draw that mesh. A node never "owns" a resource. It merely holds a reference to its data.

    It gets a bit more intricate though. A resource object itself can refer to another resource object, which can in turn refer to yet another and so on, creating a chain of resource references. In your particular case, there is a MeshInstance3D node that holds a reference to a Material resource. That reference is stored in node's material_override property. That Material in turn holds a reference to another Material resource. That reference is stored in first material's next_pass property. So now your resource reference chain looks like this:

    MeshInstance3D (node) -> Material (resource) -> Material (resource)

    When the node is duplicated at instantiation, the duplicate will keep the reference to the same material resource. Unless you mark the resource local to scene. In that case, the resource object will be duplicated as well. The duplicated node will now hold a reference to the duplicated resource.

    But.
    That duplicated resource holds a reference to another resource. And that other resource won't be duplicated. Unless, again, you set its local to scene flag.

    You can test what happens in all 3 possible cases:

    • only the first material resource is local to scene. You'll end up with this:
      MeshInstance3D (duplicate) -> Material (duplicate) -> Material (shared)

    • only the second material is local to scene:
      MeshInstance3D (duplicate) ->Material (shared) -> Material (shared)

    • both materials are local to scene
      MeshInstance3D (duplicate) -> Material (duplicate) -> Material (duplicate)

    You can see that in the second case, even though you set the second resource local to scene, it can't be duplicated because its "parent" resource in that chain is not duplicated. So if the "parent" is not duplicated it means that it will always hold a reference to the same sub-resource. Duplicating that sub-resource would just create an orphaned object, so it makes no sense to do it.

    From this follows that local to scene flag for a sub-resource will in practice only have effect if all of its ancestors in the resource chain have their local to scene flag set as well.

Probably because in the rendering pipeline the passes combine into a single shader. So they both need to be made unique or the combination brings the next pass into the top level shader which is shared.

    Megalomaniak That makes a lot of sense and it also helps understand other problems I've been having with shaders and materials. Thanks a lot!

    Megalomaniak Probably because in the rendering pipeline the passes combine into a single shader.

    Wouldn't that defeat the purpose of rendering in passes? As the docs say next_pass "renders the object again using a different material". This sounds like an additional draw call.

    @correojon Roughly speaking, Godot has two main categories of objects - nodes and resources. Nodes live and do things in the scene. Resources are tucked somewhere in memory.

    Nodes use resources as... well... their data resources. A single resource object is often shared between multiple nodes. This is good because is makes things economical and efficient. For example; many MeshInstance3D nodes can use the same Mesh resource, as well as the same Material resource to draw that mesh. A node never "owns" a resource. It merely holds a reference to its data.

    It gets a bit more intricate though. A resource object itself can refer to another resource object, which can in turn refer to yet another and so on, creating a chain of resource references. In your particular case, there is a MeshInstance3D node that holds a reference to a Material resource. That reference is stored in node's material_override property. That Material in turn holds a reference to another Material resource. That reference is stored in first material's next_pass property. So now your resource reference chain looks like this:

    MeshInstance3D (node) -> Material (resource) -> Material (resource)

    When the node is duplicated at instantiation, the duplicate will keep the reference to the same material resource. Unless you mark the resource local to scene. In that case, the resource object will be duplicated as well. The duplicated node will now hold a reference to the duplicated resource.

    But.
    That duplicated resource holds a reference to another resource. And that other resource won't be duplicated. Unless, again, you set its local to scene flag.

    You can test what happens in all 3 possible cases:

    • only the first material resource is local to scene. You'll end up with this:
      MeshInstance3D (duplicate) -> Material (duplicate) -> Material (shared)

    • only the second material is local to scene:
      MeshInstance3D (duplicate) ->Material (shared) -> Material (shared)

    • both materials are local to scene
      MeshInstance3D (duplicate) -> Material (duplicate) -> Material (duplicate)

    You can see that in the second case, even though you set the second resource local to scene, it can't be duplicated because its "parent" resource in that chain is not duplicated. So if the "parent" is not duplicated it means that it will always hold a reference to the same sub-resource. Duplicating that sub-resource would just create an orphaned object, so it makes no sense to do it.

    From this follows that local to scene flag for a sub-resource will in practice only have effect if all of its ancestors in the resource chain have their local to scene flag set as well.

      xyz Wouldn't that defeat the purpose of rendering in passes? As the docs say next_pass "renders the object again using a different material". This sounds like an additional draw call.

      A-yup. A complex graph can make my head spin just like any others.

      xyz I see, it makes total sense that a child object can't be duplicated if it's parent is shared, as then the reference itself can't be shared. Thanks a lot for taking the time to explain this so thoroughly, you're always so helpful!