Quick answer: Declare the uniform as instance uniform in the shader, then set it with set_instance_shader_parameter on each node. A plain uniform is shared by every user of the material.

A hundred enemies share one material. Tinting one red via set_shader_parameter turns them all red — the uniform is material-wide.

Regular Uniform = Material-Wide

set_shader_parameter sets a value on the material itself. Every node using that material sees it. To vary per node without duplicating the material, you need instance uniforms.

Declare instance uniform

shader_type spatial;
instance uniform vec4 tint : source_color = vec4(1.0);

void fragment() {
    ALBEDO *= tint.rgb;
}

Set Per Node

# per-enemy, same shared material
enemy.set_instance_shader_parameter("tint", Color.RED)

Each MeshInstance carries its own value for instance uniforms — no material duplication, batching preserved.

Limitations

Verifying

Tint one enemy red — only that enemy changes. The rest stay default. Draw-call count stays low because the material is still shared.

“Plain uniform = whole material. instance uniform = per node. Pick the scope you actually want.”

Instance uniforms are the cheap way to vary hundreds of objects — far better than per-object material duplicates that kill batching.