3D shader: material and parameters
PBR slope coloring, regular and instance uniforms, independent materials and .rdata persistence.
Use the same scene
Start with the first lesson or download the complete project. Both relief meshes share one ShaderMaterial. The right mesh receives surface_tint through an instance uniform.
Evaluate surface properties
Terrain.reckfellshader fragment() blends low_color and rock_color by slope, adds a light peak color and altitude stripes, then writes ALBEDO. ROUGHNESS comes from surface_roughness; METALLIC=0.02 keeps the surface almost nonmetallic.
vertex() has already calculated NORMAL. It passes slope through a varying: the spatial shader fragment-stage built-in normal is in a different space, so blindly using NORMAL.y as world slope is incorrect.
source_color marks color uniforms for correct color-space handling. The heightmap contains numeric data, so its sampler does not use source_color. Do not apply gamma correction indiscriminately to every input.
Distinguish three changes
| Change | Path | Other effects |
|---|---|---|
| Height of both samples | TerrainMaterial.SetShaderParameter("height_scale", value) | affects every mesh sharing this material |
| Right mesh tint only | Right.SetInstanceShaderParameter("surface_tint", Color) | other meshes do not receive the override |
| Any regular uniform on the right only | MakeRightIndependent(), then edit the returned material | left retains the original material |
Regular parameters are declared with uniform; individual ones with instance uniform. SetInstanceShaderParameter does not replace SetShaderParameter for arbitrary names. Shader.GetShaderUniformList in the example lists regular material uniforms, excluding instance/global parameters.
Right: independent material with height_scale=0; left: original relief retained:
MakeRightIndependent() calls Duplicate() on the material and assigns the copy to the right mesh. Shader remains a shared resource. This creates independent parameter values rather than an independent copy of shader code. Changing the program itself is a separate action.
Understand defaults, Nil and typos
| Action | Getter and output |
|---|---|
| Material does not assign height_scale | GetShaderParameter returns Nil; shader uses default 0.9 |
| SetShaderParameter("height_scale", 0f) | stores float 0; relief is flat |
| SetShaderParameter("height_scale", default(Variant)) | removes override and visually restores the shader default |
| Assign Height_scale instead of height_scale | value can be read from the cache without changing the declared uniform |
| Reset the right mesh surface_tint with Nil | GetInstanceShaderParameter returns shader default White |
These are verified, different getter contracts. First check name case, declaration and resource assignment, then readback and actual output. Successful cache readback alone does not establish a working shader.
Save material settings
ResourceSaver.Save(material, "user://TerrainMaterial.rdata") saves the resource; load it with ResourceLoader.Load<ShaderMaterial>. Check Save’s Error and whether Load returned a resource. user:// suits running-application data; do not assume exported-game asset:// is writable.
Native verification saves height_scale=1.2, the shader reference and embedded heightmap ImageTexture. After loading, the parameter, 256×256 texture dimensions and actual displacement remain. The material still depends on accessible Terrain.reckfellshader: saving .rdata does not package the whole game into one file.
Check the result
| Symptom | Check |
|---|---|
| One material edit changes both meshes | They share one ShaderMaterial |
| Material copy does not change color | Was the assigned copy edited; is instance tint affecting the result? |
| Parameter reads back but has no effect | Misspelled name, wrong type, wrong material or an unused uniform |
| Getter returns Nil | A material uniform may have no explicit override; this is not necessarily an error |
| Reloaded surface has no relief | Accessible shader, height_map and saved height_scale |
Verification covers current Forward+/Vulkan and static frames. It does not establish animation behavior, every uniform type, live shader-program replacement or other renderers.