Shaders and materials: WESL on wgpu
Shaders in Balaur are WESL (WGSL with imports and variants), linked at run time. A material asset names a shader and its values; meshes take one.
Auf dieser Seite
Shaders are WESL: WGSL plus imports, @if variants
and dead-code elimination, linked to WGSL at run time. A material asset
names a shader and its values. sprite, shape2d, shape3d, mesh and
tilemap take one. A node can pass one to its subtree. A node with neither
draws with the built-in material.
A material and its shader
# materials/glow.toml
type = "material"
shader = "shaders/glow.wesl"
[params]
speed = 2.0
rings = 5.0
tint = "#ffb347"
// shaders/glow.wesl
struct Params { speed: f32, rings: f32, tint: vec4<f32> }
@group(3) @binding(0) var<uniform> params: Params;
@vertex fn vs_main(in: VertexInput) -> VertexOutput {
return vertex(in);
}
@fragment fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
let uv: vec2<f32> = in.uv;
let dist: f32 = length(uv - vec2<f32>(0.5, 0.5)) * 2.0;
let wave: f32 = 0.5 + 0.5 * sin(dist * params.rings * 6.2832 - time() * params.speed);
let ramp: vec4<f32> = params.tint * wave * wave;
let albedo: vec4<f32> = sample_albedo(uv) * tint(in);
return albedo + ramp * albedo.a;
}
| Import | What it binds |
|---|---|
package::sprite |
The 2D contract: frame and object uniforms, vertex inputs, vertex and place, sample_albedo, tint, time |
package::mesh |
The 3D contract, with shade(in) for the scene's lights and fog |
package::post |
A pass over the whole frame: fullscreen, frame(uv) and texel() |
constants |
The engine's limits, MAX_LIGHTS, MAX_PROBES and MAX_JOINTS: the numbers its own buffers are sized by |
A shader file holds its two entry points; the rest is imported.
[params]are the fields of the shader'sParamsstruct, read off the linked shader.- A number is an
f32, an array avec2,vec3orvec4, a#rrggbbstring avec4. - A param the shader does not read warns. One it declares and the material omits is zero.
[features]are the@ifflags that pick a variant at link time.- A plugin can register shader modules for a project's shader to import.
- A contract's helpers marked
privatestay inside it. Importing one fails when the shader links, naming the helper.
A material for a whole subtree
The material component goes on any node, including one that draws nothing.
The node and its subtree draw with it.
[[nodes]]
id = "rig"
name = "Rig"
[nodes.material]
source = "materials/metal.toml"
- The nearest one up wins.
- A renderable's own
materialdoes not pass down. The split is the one betweencolorandtint(Godot'sself_modulateandmodulate). node.set_material(path)sets it from a script.node.material()reads the node's own, andnode.effective_material()reads what it draws with.- The contract a shader imports says which nodes it draws. A 3D material on a 2D node leaves it on the built-in material and warns once.
- In the inspector, an empty
materialfield shows what the node inherits and from where, asmetal ← Rig.
A pass over the whole frame
A material named on camera.post draws over
the frame. It imports package::post, has no mesh or texture of its own, and
keeps its Params at @group(1).
struct Params { amount: f32 }
@group(1) @binding(0) var<uniform> params: Params;
@vertex fn vs_main(@builtin(vertex_index) index: u32) -> PostInput {
return fullscreen(index);
}
@fragment fn fs_main(in: PostInput) -> @location(0) vec4<f32> {
let c: vec4<f32> = frame(in.uv);
return vec4<f32>(mix(c.rgb, vec3<f32>(1.0) - c.rgb, params.amount), 1.0);
}
frame(uv)is the pass's input: linear light beforetonemap, the finished picture after it.texel()is the size of one texel.- The same material may sit on both sides of
tonemap. It is compiled once per side.
In the editor
- The inspector lists a material's params off its linked shader, with an editor per type, and writes them back to the file.
- The shader opens in the code editor with WESL highlighting. On save, every material naming it relinks and every node drawing with it is rebuilt.
- A shader that fails to link is reported in the Problems dock with file, line
and column. The node draws with the built-in material until it links.
render::check_material(path)gives the same answer to a tool.
| Way | How |
|---|---|
| A line's value | Click a line number in the gutter; the viewport draws that line's value for every pixel that reaches it: a vec2 as red and green, an f32 as grey, a vec4 as colour. The line must declare a typed value (let dist: f32 = ...) or a constructor. Click again to restore the picture |
| Channels | render::set_debug_view("normals") draws the scene's normals. "uv", "depth" and "albedo" are the others; "" is the picture. The command palette has one command per channel |
Determinism
- A shader is outside the digest: rendering feeds nothing back to the
simulation.
time()is render time, not the tick. - A function marked
@construns on the CPU in headless tests. - A call to an undeclared name fails to link.
examples/shaders is the material above on a sprite, beside a plain one.