Quick answer: Pass a Packed*Array matching the shader’s element type. PackedVector3Array for vec3, PackedFloat32Array for float. Mismatched types silently fail.
A custom lighting shader samples up to 8 point lights via an array uniform. set_shader_parameter is called with positions, but the scene renders unlit. The uniform never updated.
Match Shader Element Type
// shader
uniform vec3 light_positions[8];
# GDScript
var positions: PackedVector3Array = [
Vector3(0, 2, 0),
Vector3(5, 2, 0),
]
material.set_shader_parameter("light_positions", positions)
Note the typed array. Plain Array of Vector3 works in some versions but is slower and version-fragile.
Fixed Length
The CPU array can be shorter than the shader array; unwritten elements are zero. Pad to expected length if your shader assumes all slots filled:
positions.resize(8) # pad with Vector3.ZERO
material.set_shader_parameter("light_positions", positions)
Verify Uniform Name
Typo-safe lookup:
var shader = material.shader
for u in shader.get_shader_uniform_list():
print(u.name, " ", u.get("type"))
Confirms the exact uniform name and type the shader expects. Spelling mistakes are silent failures.
For Per-Instance Arrays
set_shader_parameter is per-material. For per-instance arrays use ShaderMaterial duplication or MultiMesh custom data:
multimesh.use_custom_data = true
multimesh.set_instance_custom_data(idx, Color(...))
Verifying
Render scene; lights appear. Drag a light position; scene updates. Profile: array uniform shows up in the RenderingServer profiler as one upload per material change.
“Shader arrays are strongly typed. Match the Packed type and pad to length; otherwise the uniform stays zero.”
For dynamic light counts, declare the array max-length in shader and pass a count uniform too — the shader can loop only over used slots.