Is there a way to change the properties of a visual shader material using code?

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By johnygames

I believe you can change spatial materials by calling their functions via GDscript, but I don’t know if that is possible for materials made with the new visual shader as well. Any insights?

:bust_in_silhouette: Reply From: Zylann

You can change parameters of ShaderMaterial by calling set_shader_param() and get_shader_param(): ShaderMaterial — Godot Engine (3.1) documentation in English

I made a reference to the visual shader by calling:
onready var fancy_material = preload(“res://Materials/fancy_material.tres”)

Then I tried calling the get_shader_param() on said material, but it threw error:

“Non-existent function ‘get_shader_param’ in base VisualShader”.

What should I do?

johnygames | 2019-07-18 14:59

Check what fancy_material.tres actually is. Godot is telling you it’s a VisualShader. These functions exist on ShaderMaterial, NOT VisualShader.

In Godot, to be able to use a shader, you need to create a ShaderMaterial and assign your shader in it. ShaderMaterial then holds the value of parameters, and you can even create multiple independent materials with different parameters, while referencing the same shader on all of them.

The documentation explains it here: Shader materials — Godot Engine (3.1) documentation in English

Zylann | 2019-07-18 18:55

Thank you for your response. I see now; After a bit of research I found a way to reference the ShaderMaterial like so:

onready var mat = $MeshInstance.get_surface_material(0) var mat_prop = mat.get_shader_param('albedo')

However, the get_shader_param methods needs a String type argument and I have no Idea what that should be. What’s more, I still fail to see a way to reference the VisualShader parameters that I have set up, since they have no names (I mean, let’s say I have a ScalarOp that controls the opacity of the object, how do I reference that?). Would you kindly provide a minimal example to illustrate it?

johnygames | 2019-07-18 23:33

get/set_shader_param require the name of your parameter. Shader parameters are also called uniform. This terminology comes from code-based shading language GLSL, which is what Godot uses internally.

A ScalarOp is not a uniform, it’s an operation. It does not hold a variable, only inputs and constants.
To create a uniform parameter, look at the list of nodes you can create. There is a Uniforms category in which you can choose among a bunch of data types. When you create one of these, you can give it a name, and that’s the name you will use in GDScript.

Zylann | 2019-07-19 00:17

Indeed, I can reference uniforms in my code. But, even though I am able to change their value, they have no effect in game. I changed a uniform of the material of a mesh and though its value appears to have changed (according to a simple print statement), visually there’s no difference. Though when I change the value directly from the property slot, it behaves as expected.

Also, not being able to control every aspect of your materials is a dealbreaker and something that has to be addressed in the future.

For now, I think that swapping materials dynamically through code is the best option. Do you happen to know how one can change the material of an object through code? Thank you!

johnygames | 2019-07-19 01:05

Can you provide a simple project showing your problem? Because if you set the uniform in GDScript and it has no effect, there are a few possibilities.
Either you did a typo, the uniform wasn’t used correctly in the shader, the shader is not setup properly, or there is a bug in Godot.

Zylann | 2019-07-19 01:07

Since I dont have an easy way to upload a project right now, I made a small example that illustrates my point and which can easily be recreated:

In a new scene I placed a meshinstance and a camera. I made a new VisualShader material for the meshinstance and I hooked a scalar uniform in the alpha slot, I named that uniform ‘transparency’. Then I made a simple script for the meshinstance and I wrote the following:

extends MeshInstance
onready var mat = get_surface_material(0)
onready var mat_prop = mat.get_shader_param(‘transparency’)
func _ready():
pass

func _process(delta):
mat_prop+=0.1

This code should be increasing ‘transparency’ by 0.1 every tick, right? But it doesn’t.

johnygames | 2019-07-19 01:49

This code isn’t correct. What you did was to copy the value of the transparency once when the game starts, and increase the copy every frame without setting it back to the material, no wonder your material doesn’t change.

If you look at the doc, there is get_shader_param() and set_shader_param().

You should do it this way instead:

func _process(delta):
	var mat_prop = mat.get_shader_param("transparency")
	mat_prop += 0.1
	mat.set_shader_param("transparency", mat_prop)

Zylann | 2019-07-20 15:41

It works, thanks!
How does one swap materials, then? Let’s say I have two materials and I want to use the second one when the player dies or something, how would I go about doing that?

johnygames | 2019-07-20 15:54

Again, look at the docs: you obtained your material with get_surface_material(0). Now guess how to replace it with another: MeshInstance — Godot Engine (3.1) documentation in English

Zylann | 2019-07-20 15:56

Silly me! That should do the trick. Thanks!
I just hope that more control over parameters will be added in the future, those getters and setters seem incomplete. I am looking forward to a system like Unreal Engine’s, where you can mess with any parameter you want.

johnygames | 2019-07-20 16:08

How incomplete is it? That’s all you can modify when you deal with shaders in general. Which parameter are you referring to?

Zylann | 2019-07-20 16:30

Maybe you’re right, as far as existing parameters go. But I think there should be more flexibility. For example, what if I want to change the input type from uv to view in real time. Or what if I want to change the ScalarOP from add to subtract dynamically. It would also be nice if I could get the output of any node, not just the uniforms. Let’s say I want the dotproduct of two nodes, how do I acquire it? Last but not least, being able to “rewire” your nodes dynamically is a nifty feature, sort of what UE does with switch and if statements.

johnygames | 2019-07-20 16:45

what if I want to change the input type from uv to view in real time

I don’t understand what you mean. If you want to see the UVs in the final render, why not just output them to ALBEDO?

what if I want to change the ScalarOP from add to subtract dynamically

You should be able to use a bool parameter to control a branching point in your shader graph (aka an if), where one side does Subtract and the other does Add. I think they are not available yet in VisualShader (it’s coming in 3.2). However, you can use if in text shaders. If you really want to stick with VisualShader anyways, you could emulate an if by using a interpolation node.

It would also be nice if I could get the output of any node, not just the uniforms. Let’s say I want the dotproduct of two nodes, how do I acquire it?

Not sure again what you mean by this. Just wire the output to whatever node you want to use after them? Also getting the output value of a shader calculation happening on the GPU half-way through from a script running on the CPU is impossible, it’s not just a Godot thing. Or do you mean using a visualization node to see the intermediate values?

Last but not least, being able to “rewire” your nodes dynamically is a nifty feature, sort of what UE does with switch and if statements.

Said again above. There are a few ways:

VisualShader is a recent addition to the engine so it still has room for improvements (while Unreal had a whole team working on it for years already).

Zylann | 2019-07-20 17:27

There is no way I am messing with the text editor, I find it intimidating. But your are correct about the rest. Guess I will have to wait until the shader editor gets improved. Until then I will be swapping materials when needed.

johnygames | 2019-07-20 17:39