+1 vote

I would like to load a tscn with a material attached. This material has a shader, which I would like to manipulate. However, the manipulated values seem to be shared across all shader (material) instances.

After some search I found following solution which does not work for me:

self.set_material(self.get_material().duplicate(true))

Making the material unique, doesn't work either:

enter image description here

Furthermore, marking the shader directly as unique, doesn't work either

enter image description here

Another try was to set a ShaderMaterial and the Shader directly within the GoDot Editor, marking their resources as Local To Scene. That didn't work either.

enter image description here

The shader resource itself is also set to Local to Scene

enter image description here

I did play around with load/preload and duplicate on nearly everything, without any change. This is how I create the component with the material + shader attached

var shockWave = load("res://Components/ShockWaveComponent.tscn").instance()
get_tree().get_root().add_child(shockWave)
shockWave.explode(get_parent().position, 0.51, 0.15, 0.22, 0.02, 5000)

and this is the components ready function

func _ready():
    _shockWave_Material = self.get_material().duplicate(true)
    self.set_material(_shockWave_Material)

What else did I try: Loading the material with preload and setting the sprite's material property afterwards with: preloadedmaterial.duplicate(), sadly with the same result.

GoDot Version:

Godot Engine v3.2.3.stable.official - https://godotengine.org
OpenGL ES 3.0 Renderer: GeForce RTX 3070/PCIe/SSE2

in Engine by (27 points)
edited by

3 Answers

0 votes

I would first off save your material out to its own file. Then I'd load it dynamically in ready. I'd duplicate() it after that, then just do material = duplicatedMaterial afterward. No need to call the setter directly like that, nor the getter. If you're manipulating the material in code, you can probably just do:

material = material.duplicate()

Although I haven't tried that specifically.

by (108 points)

Sadly that doesn't change anything. Still all shaders are affected while manipulating the parameters. Here is what you did suggest:

var _shader = load("res://Shaders/DonutWave.shader").duplicate(true)
_shockWave_Material = ShaderMaterial.new()
_shockWave_Material.shader = _shader
material = _shockWave_Material

So the material is not only unique, but new for each tscn instance. The shader has been loaded and is also a duplicate. I did use the property directly, instead of get_ set_ (is there a difference?)

I did update my question, as there were some new findings. (Local To Scene) But the issue is still present.

0 votes

Oh, man, I ran into the same issue and did the exact same stuff you did!

The curious thing is that, in 2D, the Duplicate() thing did actually work:

C#:

_sprite.Material = new ShaderMaterial() { Shader = (_sprite.Material as ShaderMaterial).Shader.Duplicate() as Shader };

But in 3D, I had to instantiate a new ShaderMaterial with a Duplicate() of the shader (it could've been an Instance(), for that matter) when the effect was called, and then again a new ShaderMaterial without a Shader when not.

C#:

public void Highlight(object sender, EventArgs args)
{
    if(LobbyGlobals.ObjectHoveredByMouse == (this as Node))
    {
        meshInstance.GetSurfaceMaterial(0).NextPass = new ShaderMaterial()
        {
            Shader = (Shader)highlightShader.Duplicate()
        };
    }
    else
    {
         meshInstance.GetSurfaceMaterial(0).NextPass = null;
    }
}

We should definitely post this as a ticket on GitHub, especially because the workarounds are never obvious and very cumbersome, if not performance-heavy.

But if I do find any better solution, I'll get back to you.

by (31 points)
edited by
0 votes

Hi there. I have run into same issue as you, but I could make it work somehow. What helped me was to set Material resource - local to scene (same for shader as you are showing in pics) and save this as separate scene.

To not get confused calling everything "scene", lets assume your instances with shader are called "Enemies" and your main scene is called "Level"

Now the crucial step (as I think this is the reason, why it works for me) is NOT to duplicate this "Enemies" to your "Level" using duplicate (cmd+d or ctrl+d) BUT by drag and dropping your instances from FileSystem -> this did trick for me.

I didn't tried to add "enemies" programatically so I am not sure if that is working, but for me it worked nicely - I tried to apply wind shader on AtlasTexture, where I needed to adjust displacement by wind depending the current frame I need to use.

If you are still interested, I hope it will help.

Also here is link to youtube video, where was covered the topic (approximately at 3:30) by ACB_Gamez: https://www.youtube.com/watch?v=tCcKzEUXkIw

by (14 points)
Welcome to Godot Engine Q&A, where you can ask questions and receive answers from other members of the community.

Please make sure to read How to use this Q&A? before posting your first questions.
Social login is currently unavailable. If you've previously logged in with a Facebook or GitHub account, use the I forgot my password link in the login box to set a password for your account. If you still can't access your account, send an email to webmaster@godotengine.org with your username.