Replacing a Material's Shader at Runtime - Inconsistent results

Hey there.

I’ve been experimenting with shaders / shader graphs to graphically improve a chess game I made some time ago. Now I made some neat hologram shader for my chess pieces, and added an option in the pause menu to swap between that and another shader that looks less cool but is more visible. (basically a skin-selection option). Also using HDRP.

I got a material for the black pieces, and a material for the white pieces. Both use the same shader (either defaultShader or holoShader).

Now what changing the option in the menu does, is to simply change the ‘blackpieceMaterial.shader’ and ‘whitepieceMaterial.shader’ to the other of the two available shaders (both are shader graphs).

It does seem to work, but … its very inconsistent. All my white pieces use the same material. All my black pieces use the same material. Yet when changing the shader at runtime, it changes some of the pieces - but there’s always a few or many that keep the former shader on their material. (seemingly random, not always the same ones)

I guess it may be some obvious issue that is basic knowledge for some (like that changing shaders at runtime is bad or something? or that the scene has to be re-rendered? no idea, just guessing). But it’s really hard to find an answer to this issue cause its hard to google for specific results with a more complex question.

Hope someone can help me here.

Relevant code snippets:

    public Shader[] skins;
    public Material lightMat;
    public Material darkMat;

  public Dropdown dropdownSkins;

private void Awake()
{
dropdownSkins.onValueChanged.AddListener(skinDropdownChanged);
}

  private void skinDropdownChanged(int choice)
    {
        if (choice == 0)
        {
            lightMat.shader = skins[0];
            darkMat.shader = skins[0];
        }
        else if (choice == 1)
        {
            lightMat.shader = skins[1];
            darkMat.shader = skins[1];
        }
    }

Do you have any code anywhere that uses rendererComponent.material or rendererComponent.materials? If you do, then Unity makes a copy of the material you’re modifying and applies it to the model, meaning any modifications to the original material will be ignored from that point forward. Even assigning a material will make a copy.

For example:
GetComponent<MeshRenderer>().material = originalMaterial; // makes a copy!

That made a copy of original material and applied it to the mesh. If you want the actual original material to be applied you need to use .sharedMaterial.
GetComponent<MeshRenderer>().sharedMaterial = originalMaterial; // actually use original material

To reiterate: any access to or assignment of .material or .materials will result in the mesh using a copy. In the editor if you select those parts that aren’t being updated, they’ll likely show their materials as being “Material Name (instance)” rather than the original “Material Name”.

If you’re looking to modify materials at runtime, and not lose the connection back to the original material, you probably want to look into using a MaterialPropertyBlock to modify those properties. I’m going to guess you have some kind of code for highlighting pieces that’s changing some color or a “highlight” property and that’s what’s causing this.

2 Likes

Hey, thanks for explaining. Quite sure that’s exactly it. I do have a highlighting function that lights up the chesspiece which the cursor hovers over when it’s that player’s turn. So that creates a new copy of the material for just the higlighted piece, cause if it directly changed the material… all pieces would light up. Never thought of that.

I’ll look up that MaterialPropertyBlock to see how it can help. Thanks for the help ^^

Update: Managed to fix it now with MaterialPropertyBlock. Everything works fine now when I playtest it.

But… when I run the build and change the skins, the texture doesn’t change until I move my mouse over the pieces. The texture is referenced directly inside the shader graph that is then applied when the skin change is selected.

Oddly this problem only occurs inside the build, when playtesting from the editor the objects to change their texture right when the change is applied.

You’re saying you’re using textures that are not exposed as material properties, only as nodes inside the Shader Graph? I honestly am not sure how Unity handles that, as it’s not something that works in regular vertex fragment shaders. There’s some defaults you can set, but these were always editor only things used for edit time material creation. If used for runtime material creation they wouldn’t be used at all.

As such it seems like this is a new feature they’ve added, and likely has some bugs. You may want to report it as such with an example project via the in editor “report bug” dialog.

Alternatively, I would say expose the textures as material properties and assign them with the same script you’re using to swap the shader. Or actually swap the materials on your chess pieces rather than relying on editing a shared material asset.

Yeah. If you create a Sample Texture2D node, instead of connecting a texture property node into it, you can just click on the little dot to directly select a Texture to use.

I guess the reason why I used the approach with keeping the same material and just changing the shader was so that I wouldn’t have to access each chess piece individually. I still kept it the same now, but anyways ended up having to call up each existing chesspiece with a foreach loop and getcomponent to change the shader’s texture property with materialpropertyblocks (cause else I’d end up with the former issue again of creating new material instances).

Now that I accessed all pieces anyways, could as well create a material for each skin and just swap those out. Will probably do it that way the next time if something similar comes up, but for now I’m satisfied with it working properly.

Thanks for all the help ^^
(also helped further with some of your responses in topics regarding materialpropertyblock)