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
- Instance uniforms support a limited set of types (scalars, vec4, color) and a capped count — check the docs for the limit.
- They’re for spatial/2D node instances; not all node types support them.
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.