Skip to content

Shaders and materials

Shaders and materials are data: a shader is a WGSL file with a typed parameter header, a material is a JSON file with a schema, a shader graph is JSON that compiles to WGSL. An agent authors all three without an editor, validates them with coded errors, previews them headless and compares the result with golden images. The crate is crates/sparklight-shader; it does not depend on a renderer (the optional gpu feature adds the wgpu adapter and the offscreen preview harness). Design background: docs/AAA_ROADMAP.md section 1 and docs/AGENT_FIRST.md.

  1. Write shaders/glow.wgsl (header + a surface function), see Shader files.
  2. Check it: cargo run -q -p sparklight-shader --bin spark-shader -- validate shaders/ --json. Errors come back as {code, message, file, line, column, snippet, hint, node?, param?} (Errors).
  3. Write shaders/glow_hot.material.json (Materials) and validate again. preview to the crate and a golden (Previews and goldens).
  4. Edit the files while a game or the editor runs: HotReloader::poll swaps in the new version and keeps the old one when the edit is broken (Hot reload).
use sparklight_shader::{ShaderSet, Variant, HotReloader};
let mut set = ShaderSet::new(); // built-in library included
let errors = set.load_dir("shaders".as_ref()); // *.wgsl (with a `//! shader:` or `//! kind:` header), *.graph.json, *.material.json
assert!(errors.is_empty(), "{errors:?}");
let description = set.describe("project::glow")?; // typed parameters, bindings, entry points as data
let mut material = set.material_instance("glow_hot")?;
material.set("gain", 2.5f32)?; // type and range checked; E_MATERIAL_PARAM_* on mistakes
let bytes = material.uniform_bytes(); // group 2 binding 0, offsets reflected from the compiled module
let mut hot = HotReloader::new(&set);
for event in hot.poll(&mut set) { /* ReloadEvent { path, kind, ids, outcome, materials } */ }
  • Colour: all shading is in linear space. color parameters and JSON colour arrays are linear RGBA; a "#rrggbb[aa]" string is sRGB and converted on load. Textures that hold colour are declared srgb (the renderer creates an sRGB view).
  • Tone mapping and encoding: surface shaders output exposure-scaled, optionally ACES tone-mapped (Narkowicz 2015) and optionally sRGB-encoded colour; the frame flags decide (FLAG_TONEMAP, FLAG_SRGB_ENCODE). Sprite shaders output straight alpha colour untouched.
  • Normal maps: tangent space, +Y up in the image (glTF/OpenGL). The tangent frame comes from screen-space derivatives (tangent_frame), so meshes need no tangents and mirrored UVs shade correctly.
  • UV origin is the top-left of the image; V grows downwards.
  • Light direction in the frame block is the direction the light travels; LightUniform::directional(direction, ...).
  • Winding: counter-clockwise front faces, right-handed world, +Y up, camera looks along -Z. Depth is 0 at the near plane.
  • Units: metres. Thickness, absorption distance and wave lengths are in metres.

A shader file is WGSL with a header of //! comment lines. The header is the contract: it declares the parameters, and the library generates the matching WGSL declarations (material.<name>, textures, samplers) so the two can never disagree.

//! shader: project::glow
//! title: Glow
//! doc: Flat colour that glows.
//! kind: surface
//! shading: unlit
//! render: blend=additive cull=none depth_write=false alpha_mode=blend queue=transparent
//! define: PULSE -- Pulse over time
//! param tint: color = [1, 0.5, 0.1, 1] hdr -- Glow colour
//! param gain: float = 1.0 range [0, 8] -- Brightness
//! param mask: texture2d = white -- Optional mask
#import sparklight::math
#import common::helpers // project module: <root>/common/helpers.wgsl
fn surface(in: SurfaceIn) -> SurfaceOut {
var s = surface_defaults();
s.base_color = scale_color(material.tint.rgb, material.gain) * textureSample(mask, mask_sampler, in.uv).r;
#ifdef PULSE
s.base_color *= 0.5 + 0.5 * sin(frame.time.x);
#endif
return s;
}
Line Meaning
shader: <id> Shader id used by materials ([a-z0-9_:/.-]). Default: the file stem.
title:, doc: Human text (several doc: lines are joined).
kind: surface | sprite | custom surface: the library adds vs_main, vs_shadow, fs_main; the author writes surface. sprite: instanced 2D quad, the author writes sprite_surface. custom: the author writes every entry point (compute, post effects).
shading: pbr | unlit | custom Surface lighting. custom needs fn shade(in: SurfaceIn, s: SurfaceOut) -> vec4<f32>.
render: key=value ... Default render state: blend (opaque, alpha, premultiplied, additive, multiply), cull (back, front, none), depth_write (true/false), depth_test (less, less_equal, equal, always), alpha_mode (opaque, mask, blend), queue (background, geometry, alpha_test, transparent, overlay or a number). Materials override any of them.
define: NAME -- doc An optional feature, off by default; a material turns it on with "defines": ["NAME"]; use #ifdef NAME.
enable: NAME ... Library features compiled in for this shader (for example TRANSMISSION, which makes the PBR code read the scene colour).
instancing: <int param> The int parameter holds the number of instances the renderer must draw (fur shells).
tags: a b c Free tags.
param NAME: TYPE [= DEFAULT] [range [MIN, MAX]] [srgb] [hdr] [-- DOC] A typed parameter, see below.
Type WGSL JSON value Notes
float, vec2, vec3, vec4 f32, vec2<f32>, … in material number / array range is checked per component
color vec4<f32> [r,g,b,a] linear or [r,g,b] or "#rrggbb[aa]" sRGB hdr allows values above 1
int i32 integer
bool u32 (0 or 1): write material.flag != 0u true/false
enum(a, b, c) u32 index with generated constants NAME_A, NAME_B, … variant name param mode: enum(noise, linear) gives MODE_NOISE, MODE_LINEAR
texture2d, cube texture_2d<f32> / texture_cube<f32> named NAME, plus a generated NAME_sampler texture key string default white, black, gray, normal or a key; srgb marks colour data
sampler sampler named NAME preset linear_repeat, nearest_clamp, … for shared or pixel-art sampling

Uniform parameters are packed into one Material struct at @group(2) @binding(0) in header order with WGSL layout rules (vec3 aligns to 16 and a following f32 packs into its last 4 bytes); the offsets come from reflection, never from a guess. Textures and samplers follow from binding 1 (or 0 without uniforms) in header order. A parameter named like a library function or a WGSL keyword is rejected (E_SHADER_PARAM_NAME, or E_SHADER_PARSE with param set for collisions).

Kind You write The library provides
surface fn surface(in: SurfaceIn) -> SurfaceOut; optional fn vertex_offset(world_pos, world_normal, uv, instance) -> vec3<f32> (world-space displacement, also used by vs_shadow); with shading: custom also fn shade(in, s) -> vec4<f32> vs_main, vs_shadow (depth only, uses frame.view_proj of the light), fs_main, lighting, tone mapping
sprite fn sprite_surface(in: SpriteIn) -> vec4<f32> (straight alpha) vs_main (instanced quad), fs_main, sample_sprite(in, uv), sprite_texel()
custom everything nothing implicit; #import what you need

SurfaceIn: world_pos, normal (unit, flipped for back faces), uv, view_dir (to the camera), front_facing, frag_coord, screen_uv, instance. SurfaceOut (start from surface_defaults()): base_color, alpha, metallic, roughness, occlusion, normal (world, zero = geometric), emissive, clearcoat, clearcoat_roughness, sheen_color, sheen_roughness, transmission, ior, thickness, attenuation_color, attenuation_distance, dispersion. SpriteIn: uv, color (tint), world_pos, local_uv, uv_rect, frag_xy.

Group Surface shaders Sprite shaders
0 (per view) frame uniform (cameras, time, viewport, exposure, environment, up to 8 lights), scene_color + scene_sampler (opaque pass copy), scene_depth sprite_frame uniform (view-projection, time, 2D light, ambient, viewport)
1 (per draw) object uniform (model, normal matrix, inverse model, previous model, object data); with the render define OBJECT_BATCH the storage array object_batch of all objects of the frame; with SKINNED also the storage array joint_palette at binding 1 sprite_texture + sprite_sampler (the batch texture)
2 (material) generated from the header: material uniform, textures, samplers same

Reflection reports requires.scene_color / requires.scene_depth: a shader that uses them must be drawn after the opaque pass with the colour copied and the depth bound read-only (it must not write depth). Declaring anything else in these groups, or using a group above 2, is E_SHADER_BINDING. Vertex input of surface shaders: @location(0) position vec3, (1) normal vec3, (2) uv vec2. Sprite instance input: corner (0), position (1), size (2), rotation (3), colour (4), UV rectangle (5).

The library leaves eight things to the renderer: per-fragment setup, the light list, the shadow lookup, the environment, specular reflections, screen-space occlusion, a last adjustment of the surface (weather) and the final colour. Each is a hook_* function of the module sparklight::hooks, which holds the library default of every hook (eight frame lights unshadowed, the analytic studio environment, no occlusion, exposure and optional tone mapping). A renderer replaces the whole module with ShaderSet::set_hooks(Some(text)) (ShaderLibrary::set_hooks; None restores the default), before it creates materials. The replacement defines the same functions with the same signatures and may declare its own resources: @group(0) @binding(4+) is accepted by the contract check only when the hooks were replaced. It may import sparklight::math, sparklight::frame and sparklight::surface, not sparklight::lighting or sparklight::environment (they import it). sparklight-3d does this (crates/sparklight-3d/src/shaders/hooks.wgsl); no composed WGSL is rewritten.

Hook Called by Meaning
hook_begin_fragment(frag) fs_main first remember the fragment position (xy pixels, z hardware depth)
hook_light_count() / hook_light(i) light_count() / light_sample the lights of this fragment (Light, see sparklight::frame)
hook_shadow_visibility(i, world_pos, n) shade_pbr 1 lit, 0 shadowed, for light i of that list
hook_env_radiance(dir, roughness), hook_env_irradiance(n) env_radiance, env_irradiance diffuse and specular image based lighting
hook_specular(world_pos, dir, roughness) shade_pbr specular reflection radiance seen from a point (reflection probes, screen-space reflections)
hook_ambient_occlusion(frag) shade_pbr screen-space occlusion, 1 = unoccluded
hook_surface(si, s) fs_main, after surface the surface after the renderer’s last say (wet and snowy surfaces); the default returns it unchanged
hook_finish_color(linear_hdr) fs_main last the colour written to the target

Other renderer inputs are plain library features: the render define OBJECT_BATCH (one draw call, many objects), the render define SKINNED (needs OBJECT_BATCH: vertex input @location(3) joint indices vec4<u32> and (4) weights vec4<f32>, group 1 binding 1 joint_palette with the joint matrices of the whole frame; object.data.y is the first matrix of the object’s pose and object.data.z of the previous frame’s pose; vs_main, vs_shadow and vs_velocity skin position and normal in object space before object.model, and vertex_offset sees the skinned position; a pose matrix is model_matrix[joint] * inverse_bind), the frame block’s reversed depth flag (camera_params.w; linearize_depth and depth_is_far follow it), @invariant clip positions, the entry point fs_depth_mask for alpha-tested depth passes, and vs_velocity / fs_velocity (the motion of the object itself in UV units, camera motion taken out, from prev_view_proj and object.prev_model; the previous camera is frame.prev_view_proj, the unjittered current one frame.view_proj_unjittered).

#import sparklight::math (library), #import foo::bar (<root>/foo/bar.wgsl of a project root), #import "rel/path.wgsl" (relative to the importing file, then the roots). A module is included once, at its first import, depth first; the composition is deterministic and byte-identical for the same inputs (description.source_hash). #ifdef NAME / #ifndef / #else / #endif, #define NAME, #error text. Import errors carry file, line, column and the import chain of a cycle; a missing module suggests the closest name. Naga errors (parse and validation) are mapped back to the original file and line, also inside imported modules. Automatic defines: KIND_SURFACE|SPRITE|CUSTOM, SHADING_PBR|UNLIT|CUSTOM, HAS_PARAM_<NAME> for every parameter, HAS_VERTEX_OFFSET, and ALPHA_MASK, ALPHA_BLEND, PREMULTIPLY from the render state.

Microfacet BRDF terms. Every function names the model it implements.

  • fn d_ggx(n_dot_h: f32, alpha: f32) -> f32
  • fn v_smith_ggx_correlated(n_dot_v: f32, n_dot_l: f32, alpha: f32) -> f32
  • fn f_schlick(f0: vec3<f32>, f90: f32, v_dot_h: f32) -> vec3<f32>
  • fn f_schlick_scalar(f0: f32, f90: f32, v_dot_h: f32) -> f32
  • fn fd_lambert() -> f32 { return INV_PI; }
  • fn v_kelemen(l_dot_h: f32) -> f32
  • fn d_charlie(sheen_roughness: f32, n_dot_h: f32) -> f32
  • fn v_neubelt(n_dot_v: f32, n_dot_l: f32) -> f32
  • const DFG_A: array<f32, 256> - Split-sum scale and bias (Karis 2013, “Real Shading in Unreal Engine 4”): the specular environment reflectance of a surface with F0 is F0 * A + B (F90 = 1). A and B are integrated for this library’s own BRDF (GGX, height-correlated Smith, Schlick) with GGX importance sampling, 16384 Hammersley samples per entry, and tabulated by examples/gen_dfg_lut.rs. Rows: perceptual roughness i/15 (0.02 for row 0); columns: n.v = i/15 (0.02 for column 0); bilinear interpolation.
  • const DFG_B: array<f32, 256>
  • fn env_brdf(n_dot_v: f32, perceptual_roughness: f32) -> vec2<f32>
  • fn specular_occlusion(n_dot_v: f32, ao: f32, perceptual_roughness: f32) -> f32 - Specular occlusion from ambient occlusion (Lagarde and de Rousiers 2014, “Moving Frostbite to PBR”).
  • fn specular_dominant_dir(n: vec3<f32>, r: vec3<f32>, perceptual_roughness: f32) -> vec3<f32> - Direction the specular lobe is dominated by (Lagarde): between the normal and the reflection vector.
  • const SHEEN_E: array<f32, 25> - Directional albedo E(NoV, roughness) of the sheen lobe (Charlie D, Neubelt V) with colour 1. Tabulated by numerical integration of the lobe over the hemisphere (96 x 192 midpoint samples) at roughness {0.07, 0.25, 0.5, 0.75, 1.0} and NoV {0.05, 0.25, 0.5, 0.75, 1.0}, bilinearly interpolated. The Neubelt visibility is not normalised, so values near grazing exceed 1; callers clamp.
  • fn sheen_axis(x: f32, a: f32, b: f32, c: f32, d: f32, e: f32) -> vec2<f32>
  • fn sheen_albedo(n_dot_v: f32, sheen_roughness: f32) -> f32

Image based lighting as seen by shaders.

  • fn env_radiance(dir: vec3<f32>, perceptual_roughness: f32) -> vec3<f32>
  • fn env_irradiance(n: vec3<f32>) -> vec3<f32>

Bind group 0 of 3D (surface) shaders, filled once per view by the renderer.

  • struct Light
  • struct Frame
  • fn linearize_depth(d: f32) -> f32 - Linear view-space distance along the camera axis for a hardware depth value in 0..1 (right-handed perspective; depth 0 at the near plane, or 1 at the near plane when camera_params.w is 1).
  • fn depth_is_far(d: f32) -> bool - True when d is the depth of the far plane (nothing was drawn there), for either depth convention.
  • fn scene_depth_linear(frag_xy: vec2<f32>) -> f32 - Linear scene depth under the fragment at pixel coordinates frag_xy.
  • fn view_depth_of(world_pos: vec3<f32>) -> f32 - Linear view depth of a world-space point.
  • fn scene_world_position(frag_xy: vec2<f32>) -> vec3<f32> - World position under pixel frag_xy reconstructed from the scene depth.
  • fn sample_scene_color_lod(uv: vec2<f32>, lod: f32) -> vec3<f32> - Opaque scene colour at uv (0..1, y down), mip level lod (levels beyond the copy’s chain clamp to its last).
  • fn sample_scene_color(uv: vec2<f32>) -> vec3<f32> - Opaque scene colour at uv (0..1, y down), level 0.

The interface between the shader library and the render pipeline.

  • fn hook_begin_fragment(frag: vec4<f32>)
  • fn hook_surface(si: SurfaceIn, s: SurfaceOut) -> SurfaceOut - The surface as the shader made it, adjusted by the render pipeline (wet or snowy weather); the default changes nothing.
  • fn hook_light_count() -> u32
  • fn hook_light(i: u32) -> Light
  • fn hook_shadow_visibility(i: u32, world_pos: vec3<f32>, n: vec3<f32>) -> f32
  • fn hook_env_radiance(dir: vec3<f32>, perceptual_roughness: f32) -> vec3<f32>
  • fn hook_env_irradiance(n: vec3<f32>) -> vec3<f32>
  • fn hook_specular(world_pos: vec3<f32>, dir: vec3<f32>, perceptual_roughness: f32) -> vec3<f32>
  • fn hook_ambient_occlusion(frag: vec4<f32>) -> f32
  • fn hook_finish_color(linear_hdr: vec3<f32>) -> vec3<f32> - Exposure, optional tone mapping (flags bit 0) and optional sRGB encoding (flags bit 1).

Light sampling, physically based shading and output encoding.

  • struct LightSample
  • fn light_range_window(d: f32, range: f32) -> f32 - Smooth range window, Karis 2013 (“Real Shading in Unreal Engine 4”): saturate(1 - (d/r)^4)^2.
  • fn light_count() -> u32 - Number of lights the fragment is lit by (the render pipeline decides which: hook_light_count).
  • fn light_sample(i: u32, p: vec3<f32>) -> LightSample
  • fn shadow_visibility(i: u32, world_pos: vec3<f32>, n: vec3<f32>) -> f32 - Shadow visibility of light i at a point, 1 = lit (the render pipeline decides: hook_shadow_visibility).
  • fn transmission_color(si: SurfaceIn, s: SurfaceOut, n: vec3<f32>, perceptual_roughness: f32) -> vec3<f32> - Light that reaches the camera through the surface: refracted opaque scene colour.
  • fn shade_pbr(si: SurfaceIn, s: SurfaceOut) -> vec3<f32>
  • fn shade_unlit(s: SurfaceOut) -> vec3<f32> - Unlit shading: base colour plus emissive.
  • fn finish_color(linear_hdr: vec3<f32>) -> vec3<f32> - The colour written to the target (the render pipeline decides: hook_finish_color; the library default applies exposure and the optional tone mapping and sRGB encoding flags of the frame block).

Constants and small helpers shared by every library module.

  • const PI: f32
  • const TAU: f32
  • const INV_PI: f32
  • const HALF_PI: f32
  • const EPS: f32
  • fn sq(x: f32) -> f32 { return x * x; }
  • fn pow5(x: f32) -> f32 { let x2 = x * x; return x2 * x2 * x; }
  • fn max3(v: vec3<f32>) -> f32 { return max(v.x, max(v.y, v.z)); }
  • fn luminance(c: vec3<f32>) -> f32 { return dot(c, vec3<f32>(0.2126, 0.7152, 0.0722)); }
  • fn remap(x: f32, a: f32, b: f32, c: f32, d: f32) -> f32 { return c + (x - a) * (d - c) / (b - a); } - Linear remap of x from [a, b] to [c, d] (not clamped).
  • fn inverse_lerp(a: f32, b: f32, x: f32) -> f32 { return saturate((x - a) / (b - a)); } - Fraction of x between a and b, clamped to 0..1.
  • fn safe_normalize(v: vec3<f32>) -> vec3<f32>
  • fn rotate2d(p: vec2<f32>, angle: f32) -> vec2<f32>
  • fn linear_to_srgb(c: vec3<f32>) -> vec3<f32> - Piecewise sRGB transfer functions (IEC 61966-2-1).
  • fn srgb_to_linear(c: vec3<f32>) -> vec3<f32>
  • fn rgb_to_hsv(c: vec3<f32>) -> vec3<f32> - RGB to HSV (h, s, v in 0..1).
  • fn hsv_to_rgb(c: vec3<f32>) -> vec3<f32>
  • fn interleaved_gradient_noise(pixel: vec2<f32>) -> f32 - Interleaved gradient noise (Jimenez 2014), a cheap screen-space dither in 0..1.
  • fn vogel_disc(k: u32, n: u32, phi: f32) -> vec2<f32> - Vogel disc sample k of n, radius 0..1, rotated by phi.
  • fn tonemap_aces(x: vec3<f32>) -> vec3<f32> - ACES filmic tone mapping curve fit (Narkowicz 2015, “ACES Filmic Tone Mapping Curve”).

Integer-hash based procedural noise. All functions are deterministic on every GPU (integer arithmetic only in the hash), so goldens do not depend on the driver’s trigonometry.

  • fn pcg3d(v_in: vec3<u32>) -> vec3<u32> - PCG3D hash (Jarzynski and Olano, “Hash Functions for GPU Rendering”, JCGT 2020).
  • fn cell_hash3(c: vec3<i32>) -> vec3<f32>
  • fn hash31(p: vec3<f32>) -> f32 { return cell_hash3(vec3<i32>(floor(p))).x; } - Hash of a 3D point’s cell to 0..1.
  • fn hash21(p: vec2<f32>) -> f32 { return cell_hash3(vec3<i32>(vec2<i32>(floor(p)), 0)).x; } - Hash of a 2D point’s cell to 0..1.
  • fn hash22(p: vec2<f32>) -> vec2<f32> { return cell_hash3(vec3<i32>(vec2<i32>(floor(p)), 0)).xy; } - Hash of a 2D point’s cell to two values in 0..1.
  • fn hash11(x: f32) -> f32 { return cell_hash3(vec3<i32>(i32(floor(x)), 0, 0)).x; }
  • fn quintic(t: vec3<f32>) -> vec3<f32> { return t * t * t * (t * (t * 6.0 - 15.0) + 10.0); }
  • fn value_noise3(p: vec3<f32>) -> f32 - Value noise in 0..1 (trilinear on hashed lattice values, quintic fade).
  • fn grad_at(i: vec3<i32>, f: vec3<f32>) -> f32
  • fn gradient_noise3(p: vec3<f32>) -> f32 - Gradient (Perlin-style) noise, approximately in -1..1.
  • fn fbm3(p: vec3<f32>, octaves: i32, lacunarity: f32, gain: f32) -> f32 - Fractal Brownian motion of gradient noise, remapped to about 0..1. octaves is clamped to 1..8.
  • fn voronoi3(p: vec3<f32>, jitter: f32) -> vec3<f32> - Worley noise: x = distance to the nearest feature point, y = to the second nearest, z = id of the nearest cell (0..1). Feature points are jittered by jitter (0 = regular grid, 1 = fully random).

Bind group 1 of 3D (surface) shaders, filled per draw by the renderer.

  • struct Object
  • fn object_select(instance: u32) -> u32
  • fn object_select(instance: u32) -> u32
  • fn palette_skin_matrix(base: u32, joints: vec4<u32>, weights: vec4<f32>) -> mat4x4<f32> - Weighted sum of the four palette matrices of a vertex; base is the index of the first matrix of the pose.

Bind groups 0 and 1 of 2D (sprite) shaders.

  • struct SpriteFrame

The analytic studio environment: the default of the hook_env_* render pipeline hooks.

  • const ENV_ZENITH: vec3<f32>
  • const ENV_HORIZON: vec3<f32>
  • const ENV_GROUND: vec3<f32>
  • const ENV_SKY_AVG: vec3<f32> - Mean radiance of the sky (upper hemisphere) and ground (lower hemisphere) gradients.
  • const ENV_GROUND_AVG: vec3<f32>
  • const LOBE_COUNT: u32
  • fn lobe_dir(i: u32) -> vec3<f32>
  • fn lobe_irradiance(i: u32) -> vec3<f32> - Normal-incidence irradiance E of the lobe, colour included.
  • fn lobe_size(i: u32) -> f32 - Angular width (radians^2) of the lobe’s source at zero roughness.
  • fn env_gradient(dir: vec3<f32>) -> vec3<f32>
  • fn studio_radiance(dir: vec3<f32>, perceptual_roughness: f32) -> vec3<f32>
  • fn studio_irradiance(n: vec3<f32>) -> vec3<f32>

The contract between a surface shader and the library template.

  • struct VertexIn
  • struct SurfaceIn
  • struct SurfaceOut
  • fn surface_defaults() -> SurfaceOut
  • fn unpack_normal(texel: vec3<f32>, scale: f32) -> vec3<f32> - Decodes a tangent-space normal from a texture sample (glTF / OpenGL convention: +Y up in the image).
  • fn tangent_frame(n: vec3<f32>, p: vec3<f32>, uv: vec2<f32>) -> mat3x3<f32> - Tangent frame from screen-space derivatives (no vertex tangents needed). Columns: tangent (direction of increasing u), bitangent (the image’s +Y, i.e. direction of DEcreasing v, the glTF convention) and the normal. The bitangent is derived from dP/dv, not from a cross product, so mirrored UV mappings (such as the preview sphere) shade correctly. Must be called in uniform control flow.
  • fn apply_normal_map(si: SurfaceIn, tangent_normal: vec3<f32>) -> vec3<f32> - World-space normal from a tangent-space normal.
  • fn object_position(si: SurfaceIn) -> vec3<f32> - World-space position in object space of the draw (for object-space effects).
  • fn object_scale_x() -> f32
  • fn time_seconds() -> f32

Entry points of a sprite shader (//! kind: sprite).

  • struct SpriteVertexIn
  • struct SpriteVsOut
  • struct SpriteIn
  • fn sample_sprite(in: SpriteIn, uv: vec2<f32>) -> vec4<f32> - Sprite texture sample at uv, transparent outside the sprite’s UV rectangle.
  • fn sprite_texel() -> vec2<f32> - Size of one texel of the batch texture in UV units.
  • fn vs_main(in: SpriteVertexIn) -> SpriteVsOut
  • fn fs_main(in: SpriteVsOut) -> @location(0) vec4<f32>

Vertex, shadow and fragment entry points of a surface shader.

  • struct VsOut
  • struct LocalVertex - Position and normal in the object’s space after skinning (the vertex as it is without SKINNED).
  • fn local_vertex(in: VertexIn) -> LocalVertex
  • fn world_vertex(lv: LocalVertex, uv: vec2<f32>, instance: u32) -> vec4<f32>
  • fn clip_position(world: vec4<f32>) -> vec4<f32>
  • fn vs_main(in: VertexIn, @builtin(instance_index) instance: u32) -> VsOut
  • fn vs_shadow(in: VertexIn, @builtin(instance_index) instance: u32) -> @builtin(position) vec4<f32>
  • fn surface_input(in: VsOut, front: bool) -> SurfaceIn
  • fn fs_main(in: VsOut, @builtin(front_facing) front: bool) -> @location(0) vec4<f32>
  • fn fs_depth_mask(in: VsOut, @builtin(front_facing) front: bool)
  • struct VelocityOut
  • fn vs_velocity(in: VertexIn, @builtin(instance_index) instance: u32) -> VelocityOut
  • fn fs_velocity(in: VelocityOut, @builtin(front_facing) front: bool) -> @location(0) vec2<f32>
{
"version": 1, "kind": "material", "name": "red_plastic", "shader": "builtin::pbr",
"params": { "base_color": "#c01818", "roughness": 0.35, "normal_tex": "textures/plastic_n.png" },
"render": { "cull": "back" }
}
  • shader names a shader id; parent names another material (an instance: only the overrides are written; chains are resolved, cycles are E_MATERIAL_PARENT_CYCLE). Exactly one of the two.
  • params values are checked against the shader: unknown names (E_MATERIAL_PARAM_UNKNOWN, with a “did you mean”), wrong types (E_MATERIAL_PARAM_TYPE), out-of-range values (E_MATERIAL_PARAM_RANGE).
  • render overrides the shader’s default render state field by field: blend, cull, depth_write, depth_test, alpha_mode, queue. alpha_mode: mask compiles the discard (threshold: the shader’s alpha_cutoff parameter, default 0.5), blend passes alpha to the blend state, premultiplied blending premultiplies the colour in the shader.
  • defines turns on the shader’s optional features. The compiled variant is cached per define set.
  • Schema: schemas/material.schema.json (generated from the Rust types, checked by a test). A material is versioned (version: 1); other versions are E_MATERIAL_VERSION.

MaterialInstance (runtime): set(name, value) with f32, [f32; N], i32, bool, &str (texture key, enum variant or sampler preset), set_json, get, get_json, reset, uniform_bytes() (group 2 binding 0), textures() and samplers() (bindings with keys and the srgb flag), render_state(), variant(), revision() (bumped by every change), rebind(new_shader) after a hot reload, to_def(shader_id) to save only the non-default values. A failed set changes nothing.

Material Shader Description
decal_splat builtin::decal Dark red splat decal.
dissolve_stone builtin::dissolve Stone that burns away with an orange edge.
flat_cobble builtin::pbr The same cobblestones with a normal map only, for comparison.
floor_stone builtin::pbr Stone floor used as scenery in previews.
forcefield_blue builtin::forcefield Blue energy shield with an impact ripple.
fur_brown builtin::fur Brown shell fur.
glass_clear builtin::glass Clear glass, slight green absorption.
glass_dispersive builtin::glass Dense glass with chromatic dispersion.
glass_green builtin::glass Green bottle glass.
hologram_cyan builtin::hologram Cyan hologram.
lava_flow graph::lava Shader graph: animated lava.
marble_polished graph::marble Shader graph: procedural marble.
parallax_cobble builtin::parallax Cobblestones with real depth.
pbr_brick builtin::pbr Brick wall: sRGB albedo, glTF-style ORM, tangent-space normal map.
pbr_car_paint builtin::pbr Metallic red paint under a glossy clear coat.
pbr_emissive_panel builtin::pbr Dark metal with an emissive checker pattern.
pbr_velvet builtin::pbr Cloth with a sheen lobe: dark red velvet.
rim_glow_blue graph::rim_glow Shader graph: pulsing fresnel rim.
scenery_checker builtin::pbr Checker wall used as scenery in previews.
scenery_red builtin::pbr Red plastic used as scenery in previews.
scenery_sand builtin::pbr Sand used as scenery in previews.
scenery_soil builtin::pbr Dark soil used as scenery in previews.
sprite_distort_wobble builtin::sprite_distort Wobbling sprite.
sprite_lit_hero builtin::sprite_lit Lit pixel-art sprite with a normal map.
sprite_outline_gold builtin::sprite_outline Gold selection outline.
sprite_palette_a builtin::sprite_palette Blue palette.
sprite_palette_b builtin::sprite_palette Purple palette.
toon_ink builtin::toon_outline Ink outline pass for toon materials.
toon_orange builtin::toon Orange cel-shaded surface with purple shadows.
toon_violet builtin::toon Violet four-band cel shading.
triplanar_rock builtin::triplanar Rock with moss on the upward-facing parts, no UVs needed.
unlit_checker builtin::unlit Checker pattern, no lighting.
unlit_glow builtin::unlit Hot orange, above 1 so it glows after tone mapping.
water_ocean builtin::water Stylised ocean.
wind_leaf builtin::wind Leaves swaying in the wind.

Texture keys are strings the renderer resolves. The preview harness provides these procedural ones (deterministic, tileable where it matters):

  • white
  • black
  • gray
  • normal
  • builtin:checker
  • builtin:noise
  • builtin:noise_normal
  • builtin:bricks_albedo
  • builtin:bricks_normal
  • builtin:bricks_height
  • builtin:bricks_orm
  • builtin:cobble_albedo
  • builtin:cobble_normal
  • builtin:cobble_height
  • builtin:rock_albedo
  • builtin:rock_normal
  • builtin:leaf
  • builtin:decal
  • builtin:sprite_hero
  • builtin:sprite_hero_normal
  • builtin:sprite_hero_indexed
  • builtin:palette_a
  • builtin:palette_b

A shader graph is JSON that compiles to a surface or sprite shader through the same pipeline (module system, naga validation, reflection). Use it when an agent wants an effect without writing WGSL; spark-shader describe file.graph.json shows the result and spark-shader wgsl file.graph.json the generated code.

{
"version": 1, "kind": "shader_graph", "name": "graph::tint", "shader_kind": "surface", "shading": "pbr",
"params": [ { "name": "tint", "type": "color", "default": [1, 0.5, 0.2, 1] } ],
"nodes": [
{ "id": "tint", "type": "parameter", "props": { "name": "tint" } },
{ "id": "rgb", "type": "swizzle", "inputs": { "v": "tint" }, "props": { "mask": "rgb" } },
{ "id": "out", "type": "output", "inputs": { "base_color": "rgb" } }
]
}
  • An input is a node reference ("node" or "node.port"), a number or a vector constant [x, y(, z, w)]. Properties are compile-time settings (name, value, mode, texture, octaves, …). Exactly one output node.
  • Types are float, vec2, vec3, vec4. A float connects to any vector input (it is splatted). Vectors of different sizes never convert implicitly: use swizzle, split, combine. Texture parameters are read with texture_sample (props.texture).
  • Only nodes reachable from the output are compiled. Surface graphs set SurfaceOut fields through the output node; sprite graphs set color.
  • Errors name the node (error.node): unknown type, duplicate id, unknown node or port, missing input, type mismatch, cycle (with the chain), missing or duplicate output, bad parameter. Schema: schemas/shader-graph.schema.json.
  • Example graphs: graphs/lava.graph.json, graphs/rim_glow.graph.json, graphs/marble.graph.json (each has a golden).
Node type What it does Inputs Outputs Properties Kinds
uv Mesh UV coordinates. - out (vec2) - surface sprite
local_uv 0..1 across the sprite quad, y down. - out (vec2) - sprite
world_position World-space position of the fragment. - out (vec3) - surface
world_normal Interpolated geometric world normal. - out (vec3) - surface
view_direction Unit vector from the surface to the camera. - out (vec3) - surface
screen_uv Fragment position in 0..1 screen space (y down). - out (vec2) - surface
object_position Fragment position in object space. - out (vec3) - surface
time Seconds since start. - out (float) - surface sprite
constant A constant. Type follows the value: number, [x, y], [x, y, z], [x, y, z, w] or a “#rrggbb[aa]” colour (vec4, linear). - out (float|vec2|vec3|vec4) value surface sprite
parameter Reads a declared non-texture parameter (int, bool and enum become float). - out (float|vec2|vec3|vec4) name surface sprite
abs Absolute value. x (any) out (same) - surface sprite
sin Sine (radians). x (any) out (same) - surface sprite
cos Cosine (radians). x (any) out (same) - surface sprite
floor Round down. x (any) out (same) - surface sprite
ceil Round up. x (any) out (same) - surface sprite
fract Fractional part. x (any) out (same) - surface sprite
saturate Clamp to 0..1. x (any) out (same) - surface sprite
one_minus 1 - x. x (any) out (same) - surface sprite
negate -x. x (any) out (same) - surface sprite
sqrt Square root. x (any) out (same) - surface sprite
length Vector length (float input: absolute value). x (any) out (float) - surface sprite
normalize Unit vector (vec2, vec3, vec4). x (vec2|vec3|vec4) out (same) - surface sprite
add a + b. a (any), b (any) out (same) - surface sprite
subtract a - b. a (any), b (any) out (same) - surface sprite
multiply a * b (per component). a (any), b (any) out (same) - surface sprite
divide a / b (per component). a (any), b (any) out (same) - surface sprite
min Component-wise minimum. a (any), b (any) out (same) - surface sprite
max Component-wise maximum. a (any), b (any) out (same) - surface sprite
pow a ^ b (a is clamped to >= 0). a (any), b (any) out (same) - surface sprite
mod a - b * floor(a / b). a (any), b (any) out (same) - surface sprite
step 0 where x < edge, else 1. edge (any), x (any) out (same) - surface sprite
dot Dot product. a (any), b (any) out (float) - surface sprite
distance Distance between two points. a (any), b (any) out (float) - surface sprite
cross Cross product. a (vec3), b (vec3) out (vec3) - surface sprite
mix Linear interpolation a + (b - a) * t. a (any), b (any), t (float|same) out (same) - surface sprite
clamp Clamp x to [min, max] (defaults 0 and 1). x (any), min (any), max (any) out (same) - surface sprite
smoothstep Hermite interpolation between edge0 and edge1. edge0 (any), edge1 (any), x (any) out (same) - surface sprite
remap Remaps x from [in_min, in_max] to [out_min, out_max] (defaults 0..1). x (any), in_min (float), in_max (float), out_min (float), out_max (float) out (same) - surface sprite
split Splits a vector into floats. v (any) x (float), y (float), z (float), w (float) - surface sprite
combine Builds a vector from floats; connected inputs must be contiguous from x. x (float), y (float), z (float), w (float) out (vec2|vec3|vec4) - surface sprite
swizzle Selects components by mask, for example “xy”, “rgb”, “zyx”. v (any) out (float|vec2|vec3|vec4) mask surface sprite
texture_sample Samples a texture2d parameter (level 0 when lod is connected). uv (vec2 (default uv)), lod (float) rgba (vec4), rgb (vec3), r (float), g (float), b (float), a (float) texture surface sprite
sprite_texture Samples the sprite batch texture inside the sprite rectangle. uv (vec2 (default uv)) rgba (vec4), rgb (vec3), a (float) - sprite
normal_map World-space normal from a tangent-space normal map parameter. uv (vec2 (default uv)), strength (float (default 1)) out (vec3) texture surface
noise_value Value noise in 0..1. p (vec2|vec3), scale (float (default 1)) out (float) - surface sprite
noise_gradient Gradient noise remapped to 0..1. p (vec2|vec3), scale (float (default 1)) out (float) - surface sprite
fbm Fractal gradient noise in 0..1. p (vec2|vec3), scale (float (default 1)) out (float) octaves (1..8, default 4), lacunarity (default 2), gain (default 0.5) surface sprite
voronoi Worley noise. p (vec2|vec3), scale (float (default 1)) distance (float), edge (float), cell (float) jitter (0..1, default 1) surface sprite
fresnel pow(1 - saturate(dot(n, v)), power). normal (vec3 (default world normal)), view (vec3 (default view direction)), power (float (default 5)) out (float) - surface
uv_tiling_offset uv * tiling + offset. uv (vec2 (default uv)), tiling (vec2 (default 1,1)), offset (vec2 (default 0,0)) out (vec2) - surface sprite
uv_rotate Rotates uv around center by angle radians. uv (vec2 (default uv)), angle (float), center (vec2 (default 0.5,0.5)) out (vec2) - surface sprite
uv_panner uv + speed * time. uv (vec2 (default uv)), speed (vec2), time (float (default time)) out (vec2) - surface sprite
uv_polar Polar coordinates around center: x angle 0..1, y radius. uv (vec2 (default uv)), center (vec2 (default 0.5,0.5)) out (vec2) - surface sprite
blend Blends layer over base; result = mix(base, blended, opacity). base (any), layer (any), opacity (float (default 1)) out (same) mode (multiply, screen, overlay, add, subtract, soft_light, difference, darken, lighten) surface sprite
rgb_to_hsv Colour space conversion. rgb (vec3) out (vec3) - surface sprite
hsv_to_rgb Colour space conversion. hsv (vec3) out (vec3) - surface sprite
gradient Colour ramp: linear interpolation between ascending stops. t (float) out (vec3) stops: [[position, colour], …] with colours as [r,g,b] or “#rrggbb” surface sprite
output The shader output (exactly one). Surface ports: base_color, alpha, metallic, roughness, occlusion, normal, emissive, clearcoat, clearcoat_roughness, sheen_color, sheen_roughness, transmission, ior, thickness, attenuation_color, attenuation_distance, dispersion. Sprite port: color (vec4). - - - surface sprite

Every failure is a ShaderError ({code, message, file?, line?, column?, snippet?, hint?, node?, param?, chain?}; JSON via to_json()). Codes are stable; new ones are added, none is renamed. They live in this crate (ShaderErrorCode), the engine-wide registry in sparklight-core can adopt them later.

Code Meaning Fix
E_SHADER_IO A shader, material or graph file could not be read. Check that the path exists and is readable.
E_SHADER_IMPORT_MISSING An #import names a module that is not in the library or the project folders. List modules with ShaderLibrary::module_names() or fix the name; project modules are addressed by their path below the shader root.
E_SHADER_IMPORT_CYCLE Modules import each other in a cycle. Move the shared declarations into a third module that both import.
E_SHADER_IMPORT_SYNTAX An #import line is malformed. Write #import sparklight::math or #import "relative/path.wgsl".
E_SHADER_DIRECTIVE A preprocessor directive is unknown or unbalanced. Supported: #import, #ifdef, #ifndef, #else, #endif, #define, #error.
E_SHADER_HEADER A //! header line is malformed. Header lines look like //! param roughness: float = 0.5 range [0, 1] -- Surface roughness.
E_SHADER_PARAM_TYPE A parameter has an unknown type or a default of the wrong type. Types: float, vec2, vec3, vec4, color, int, bool, enum(a, b), texture2d, cube, sampler.
E_SHADER_PARAM_DUPLICATE Two parameters share a name. Rename one of them.
E_SHADER_PARAM_RANGE A range is invalid or the default lies outside it. Use range [min, max] with min <= max and a default inside it.
E_SHADER_PARAM_NAME A parameter name is not a usable WGSL identifier. Use lowercase snake_case letters, digits and underscores; do not use WGSL keywords or names starting with an underscore.
E_SHADER_PARSE The WGSL source does not parse. Fix the syntax at the reported line and column.
E_SHADER_VALIDATE The WGSL module parses but fails validation. Fix the type or usage error at the reported line and column.
E_SHADER_ENTRY_MISSING A required entry point or hook function is missing. Define the function named in the message with the documented signature.
E_SHADER_BINDING The shader declares a resource binding that breaks the bind group contract. Declare material resources only through //! param lines; groups 0 and 1 belong to the engine.
E_SHADER_REFLECT The compiled module does not match the layout implied by the header. Do not redeclare the generated material block; report this if the header is the only source of bindings.
E_SHADER_UNKNOWN No shader with this id is registered. List shader ids with ShaderSet::ids() or load the file first.
E_SHADER_VARIANT A shader variant (define set) is not valid for this shader. Use only defines named in the shader header (//! define: NAME).
E_MATERIAL_SCHEMA The material file does not match the material format. Validate the file against schemas/material.schema.json.
E_MATERIAL_VERSION The material format version is not supported. Set version to 1 or migrate the file.
E_MATERIAL_SHADER_UNKNOWN The material names a shader that is not registered. Use the id from a shader header (//! shader: ...) or the id of a shader graph.
E_MATERIAL_PARAM_UNKNOWN The material sets a parameter the shader does not declare. List the shader’s parameters with its description and use one of them verbatim.
E_MATERIAL_PARAM_TYPE A parameter value has the wrong type for the shader declaration. Send a value of the declared type (colors are [r, g, b, a] linear or “#rrggbb[aa]” sRGB).
E_MATERIAL_PARAM_RANGE A parameter value lies outside the declared range. Use a value inside the range of the declaration.
E_MATERIAL_PARENT_UNKNOWN The material instance names a parent that is not loaded. Load the parent material first or fix the name.
E_MATERIAL_PARENT_CYCLE Material parents form a cycle. Break the cycle; an instance chain must end at a material that names a shader.
E_MATERIAL_TEXTURE A texture key could not be resolved. Check the texture key; built-in procedural keys start with builtin:.
E_SHADER_GRAPH_SCHEMA The shader graph file does not match the graph format. Validate the file against schemas/shader-graph.schema.json.
E_SHADER_GRAPH_VERSION The shader graph format version is not supported. Set version to 1.
E_SHADER_GRAPH_NODE_TYPE A graph node has an unknown type. Use a node type from docs/agent/shaders.md (shader graph catalogue).
E_SHADER_GRAPH_NODE_DUPLICATE Two graph nodes share an id. Give every node a unique id.
E_SHADER_GRAPH_NODE_UNKNOWN An input refers to a node id that does not exist. Use the id of an existing node (node or node.port).
E_SHADER_GRAPH_PORT_UNKNOWN An input or output port name does not exist on the node type. Use a port listed for the node type.
E_SHADER_GRAPH_INPUT_MISSING A required node input is not connected. Connect the input to a node output or give it a constant.
E_SHADER_GRAPH_TYPE A connection joins incompatible value types. Convert with split/combine or connect a float (floats widen automatically).
E_SHADER_GRAPH_CYCLE The graph contains a cycle. Graphs are acyclic; break the loop.
E_SHADER_GRAPH_OUTPUT_MISSING The graph has no output node (or more than one). Add exactly one node of type output.
E_SHADER_GRAPH_PARAM A graph parameter declaration or reference is invalid. Declare the parameter in params and reference it by name.

HotReloader::new(&set) records the modification time and size of every file the set was loaded from and every project file its shaders import. poll(&mut set) (call it once per frame or on demand) returns one ReloadEvent per changed file:

  • outcome: Reloaded { version }: the new version is active. ShaderSet::version(id) grew; materials using the shader are listed in event.materials (rebind their instances with MaterialInstance::rebind; values with the same name and type survive).
  • outcome: Failed(error): the edit did not parse, validate, resolve its imports or fit its material. The previous version stays active, ShaderSet::last_error(id) (or material_error) holds the coded error until the next successful edit.
  • outcome: Removed: the file was deleted and its shader or material removed (a deleted imported module is reported as Failed on its importers).
  • A changed imported module recompiles all importers; a graph file is regenerated and recompiled; a material file is re-resolved against its shader and rolled back when it does not fit. watch_dir(dir) also picks up new files.
  • The default variant and every variant that was already compiled must compile for a reload to be accepted.

A renderer keeps (shader id, version) next to each pipeline and rebuilds the pipeline when the version changed (examples/adapter.rs).

With the gpu feature (on by default) sparklight_shader::gpu turns compiled shaders into wgpu objects. examples/adapter.rs is a complete, runnable template (about 150 lines) for the render pipeline:

  1. Layouts::new(device, &shader.description) - bind group layouts for groups 0..=2 from the reflected bindings.
  2. create_shader_module(device, &compiled) and create_pipeline(device, &compiled, &module, &pipeline_layout, material.render_state(), Target { .. }) - blend, cull, depth and alpha mode come from the material’s render state; validation errors come back as ShaderError, not as panics.
  3. MaterialGpu::new(&ctx, &layouts.groups[2], &material, &mut textures) - uniform buffer + texture/sampler bind group; implement TextureProvider over the asset system (the crate ships ProceduralTextures for tests and previews). update_uniforms re-uploads after a set.
  4. FrameUniform::new(...), ObjectUniform::new(model, id), LightUniform::{directional, point, spot}, SpriteFrameUniform, SpriteInstance mirror the WGSL blocks; a test checks their sizes against the reflected sizes.
  5. Draw with the vertex layout above; draw instancing instances when description.instancing is set; draw shaders that need the scene colour or depth in a second pass.

A preview (previews/<name>.preview.json, schema schemas/shader-preview.schema.json) is a tiny scene: size, time, camera (or camera2d), lights, environment intensity, exposure, background, supersampling and a list of draws (mesh: sphere, quad, plane, cube, torus, sprite; material: a material name or an inline material; transform; sprite for sprite draws). PreviewRenderer::render draws it offscreen with the real pipeline (RGBA16F target, 2x2 supersampling, linear average, sRGB encode on the CPU) and returns RGBA8.

  • cargo test -p sparklight-shader --release --test golden_shaders compares every built-in preview with goldens/shaders/<name>.png (tolerance: 6/255 per channel on at most 0.4 % of the pixels, mean difference 0.6). A mismatch writes the actual image and a diff under
  • SPARKLIGHT_UPDATE_GOLDENS=1 rewrites the goldens; look at every image before committing. SPARKLIGHT_PREVIEW=water,glass limits the run.
  • A test fails when a built-in shader has no preview (so no golden) or a golden has no preview.
  • Goldens were produced on an Apple Silicon GPU (Metal). Other GPUs can differ in the last bits of filtering and transcendental functions; regenerate or compare the diff images when a new adapter family is added.
  • Adding a built-in: put the shader in wgsl/builtin/, a material in materials/, a preview in previews/; build.rs registers all three. Then update the goldens.

Measured on Apple M5 Max (Metal), release build, timestamp queries around the render passes, 40 frames averaged after a warm-up. A scene row is the cost of the whole scene (shader under test plus its backdrop and floor objects) at 1280x720, 1 sample per pixel, no supersampling; the CPU compile cost is composition + naga parse + validation + reflection of the default variant. Regenerate with cargo run -p sparklight-shader --release --example shader_perf -- --write-docs.

Preview scene GPU ms per frame at 1280x720
decal 1.112
dissolve 1.466
forcefield 0.806
fur 2.575
glass 1.134
graph_lava 0.541
graph_marble 0.627
graph_rim_glow 0.255
hologram 0.244
parallax 0.442
pbr_clearcoat 0.384
pbr_grid 0.633
pbr_sheen 0.311
pbr_standard 0.255
sprite_distort 0.120
sprite_lit 0.101
sprite_outline 0.192
sprite_palette 0.065
toon 0.187
triplanar 0.288
unlit 0.088
water 1.510
wind 1.446
Shader Compile ms (best of 15) Composed WGSL lines
builtin::decal 2.46 892
builtin::dissolve 2.95 1001
builtin::forcefield 3.18 1053
builtin::fur 3.21 1016
builtin::glass 2.50 877
builtin::hologram 3.13 1005
builtin::parallax 2.80 915
builtin::pbr 2.47 906
builtin::sprite_distort 1.16 340
builtin::sprite_lit 0.60 214
builtin::sprite_outline 0.55 212
builtin::sprite_palette 0.56 205
builtin::toon 2.77 915
builtin::toon_outline 2.53 862
builtin::triplanar 2.62 918
builtin::unlit 2.33 861
builtin::water 3.45 1128
builtin::wind 3.15 1013
graph::lava 3.13 1007
graph::marble 3.18 998
graph::rim_glow 2.63 998

cargo run -q -p sparklight-shader --bin spark-shader -- <command>; --json for machine output, --shaders <dir> adds a project folder.

Command Result
list shader ids, material names, importable modules
describe <id or file> the shader description as JSON (parameters with offsets and bindings, bind groups, entry points, requires, hash)
validate <path>... compiles every shader, graph and material under the paths; exit code 1 and coded errors on failure
wgsl <id or file> [--define X] the composed WGSL that reaches the GPU
errors / nodes the error registry / the graph node catalogue
preview <name or file>... [--out dir] renders previews to PNG

Box decal. Draw a unit cube volume; the shader rebuilds the world position under each pixel from the scene depth and projects it into the box. The decal image lies in the local XY plane and projects along local Z. The result fades by surface angle and near the box faces, and is lit with the surface normal reconstructed from the depth. Needs the scene depth.

  • kind surface, shading custom, default render state: blend alpha, cull front, depth write false, alpha mode blend, queue 3100
  • needs from the renderer: scene depth (draw it after the opaque pass)
  • golden images: goldens/shaders/decal.png, goldens/shaders/library_matrix.png
Parameter Type Default Range Description
decal_tex texture2d (sRGB) "white" - Decal colour and alpha
tint color [1.0,1.0,1.0,1.0] - Colour multiplier
opacity float 1.0 0 .. 1 Opacity multiplier
roughness float 0.55 0 .. 1 Roughness of the decal surface
angle_fade float 0.25 0 .. 1 Fade on surfaces turned away from the projection (0 = no fade)
edge_fade float 0.08 0 .. 0.5 Fade near the box faces along the projection axis

Noise-driven dissolve with a glowing edge. progress 0 is intact, 1 is gone. The threshold comes from 3D fractal noise in object space, optionally biased by height so the effect sweeps upwards.

  • kind surface, shading pbr, default render state: blend opaque, cull none, depth write true, alpha mode mask, queue 2450
  • golden images: goldens/shaders/dissolve.png, goldens/shaders/library_matrix.png
Parameter Type Default Range Description
base_color color [0.75,0.75,0.8,1.0] - Base colour
base_color_tex texture2d (sRGB) "white" - Base colour texture
metallic float 0.0 0 .. 1 Metallic
roughness float 0.55 0 .. 1 Roughness
progress float 0.45 0 .. 1 Dissolve amount: 0 intact, 1 fully dissolved
edge_width float 0.08 0 .. 0.5 Width of the glowing edge (in noise units)
edge_color color (HDR) [1.0,0.42,0.06,1.0] - Edge glow colour
edge_intensity float 3.6 0 .. 50 Edge glow multiplier
noise_scale float 2.6 0.1 .. 32 Noise frequency in object space
height_bias float 0.35 0 .. 1 0 = pure noise, 1 = dissolves from the top down
alpha_cutoff float 0.5 0 .. 1 Threshold of alpha_mode mask

Energy shield: Fresnel rim, a hexagonal cell pattern projected along three axes, an intersection glow where the shield meets geometry (soft edge from the scene depth), a travelling pulse and an impact ripple. Needs the scene depth.

  • kind surface, shading unlit, default render state: blend additive, cull none, depth write false, alpha mode blend, queue 3000
  • needs from the renderer: scene depth (draw it after the opaque pass)
  • golden images: goldens/shaders/forcefield.png, goldens/shaders/library_matrix.png
Parameter Type Default Range Description
color color (HDR) [0.2,0.6,1.0,1.0] - Field colour
intensity float 2.0 0 .. 20 Overall brightness
base_alpha float 0.05 0 .. 1 Opacity of the body
fresnel_power float 2.5 0.2 .. 8 Rim falloff
fresnel_strength float 1.3 0 .. 5 Rim brightness
hex_scale float 4.5 0.5 .. 40 Hexagon cells per object unit
hex_thickness float 0.07 0.005 .. 0.3 Hexagon line width
hex_strength float 1.2 0 .. 5 Hexagon brightness
intersection_width float 0.35 0.01 .. 3 Distance (metres) over which the glow fades away from geometry
intersection_strength float 3.0 0 .. 20 Intersection glow brightness
pulse_speed float 0.4 -4 .. 4 Speed of the upward pulse
hit_direction vec3 [0.0,0.2,1.0] - Impact direction in object space
hit_time float 0.35 -1 .. 1000 Time of the last impact in seconds (negative = no impact)
hit_speed float 1.6 0.1 .. 10 Ripple speed (radians per second)

Shell fur: the mesh is drawn shell_count times (the renderer reads instancing: shell_count and draws that many instances); each instance is a shell pushed out along the normal. Strands are alpha-tested cells of a hash grid in UV space, tapering towards the tip, with root-to-tip colour, ambient occlusion at the roots, gravity and wind.

  • kind surface, shading pbr, default render state: blend opaque, cull back, depth write true, alpha mode mask, queue 2450
  • instanced: draw shell_count instances
  • golden images: goldens/shaders/fur.png, goldens/shaders/library_matrix.png
Parameter Type Default Range Description
base_color color [0.3,0.17,0.09,1.0] - Colour at the roots
tip_color color [0.88,0.66,0.38,1.0] - Colour at the tips
shell_count int 36 2 .. 64 Number of shells (instances to draw)
fur_length float 0.13 0 .. 1 Fur length in object units
density float 150.0 4 .. 600 Strands per UV unit
strand_thickness float 0.7 0.05 .. 1 Strand radius at the roots (fraction of a cell)
gravity vec3 [0.0,-0.45,0.0] - Direction and strength the fur bends towards
wind vec3 [0.0,0.0,0.0] - Wind push on the tips
curl float 0.55 0 .. 2 Random sideways lean of each strand (in cells)
ao_strength float 0.7 0 .. 1 Darkening at the roots
roughness float 0.62 0 .. 1 Roughness
alpha_cutoff float 0.5 0 .. 1 Threshold of alpha_mode mask

Screen-space refraction of the opaque scene colour with Fresnel reflection, Beer-Lambert absorption, roughness blur and optional chromatic dispersion (KHR_materials_transmission, volume and dispersion semantics). Needs the opaque scene colour: the renderer copies it before the pass.

  • kind surface, shading pbr, default render state: blend opaque, cull back, depth write false, alpha mode opaque, queue 3000
  • needs from the renderer: scene colour (draw it after the opaque pass)
  • golden images: goldens/shaders/glass.png, goldens/shaders/library_matrix.png
Parameter Type Default Range Description
tint color [1.0,1.0,1.0,1.0] - Colour of the transmitted light
ior float 1.5 1 .. 2.5 Index of refraction (glass 1.5, water 1.33, diamond 2.42)
roughness float 0.03 0 .. 1 Surface roughness (blurs reflections and refraction)
thickness float 0.9 0 .. 4 Thickness of the volume in metres (refraction offset and absorption path)
thickness_from_view bool true - Scale the thickness by the viewing angle (a good approximation for convex solids)
absorption_color color [0.78,0.95,0.9,1.0] - Colour the light has after absorption_distance metres
absorption_distance float 1.2 0 .. 20 Distance for the absorption colour (0 = no absorption)
dispersion float 0.0 0 .. 10 Chromatic dispersion (20 / Abbe number)

Additive sci-fi hologram: Fresnel rim, moving scan lines, horizontal glitch slices and flicker. Everything is derived from world position and time, so it needs no textures.

  • kind surface, shading unlit, default render state: blend additive, cull none, depth write false, alpha mode blend, queue 3000
  • golden images: goldens/shaders/hologram.png, goldens/shaders/library_matrix.png
Parameter Type Default Range Description
color color (HDR) [0.15,0.85,1.0,1.0] - Hologram colour
intensity float 2.2 0 .. 20 Overall brightness
base_alpha float 0.16 0 .. 1 Opacity of the body
fresnel_power float 2.2 0.2 .. 8 Rim falloff
fresnel_strength float 1.6 0 .. 5 Rim brightness
scan_density float 38.0 1 .. 200 Scan lines per world unit
scan_speed float 0.35 -5 .. 5 Scan line speed (world units per second)
scan_strength float 0.55 0 .. 1 How dark the gaps between scan lines are
flicker_speed float 9.0 0 .. 60 Flicker rate
flicker_amount float 0.12 0 .. 1 Flicker depth
glitch_amount float 0.35 0 .. 1 Brightness of glitch slices

Ray-marches the height map in tangent space (Tatarchuk 2006, “Dynamic Parallax Occlusion Mapping”), so flat geometry shows real depth with correct occlusion. The sample count adapts to the viewing angle, and the final hit is refined by linear interpolation between the last two samples.

  • kind surface, shading pbr, default render state: blend opaque, cull back, depth write true, alpha mode opaque, queue 2000
  • golden images: goldens/shaders/library_matrix.png, goldens/shaders/parallax.png
Parameter Type Default Range Description
base_color color [1.0,1.0,1.0,1.0] - Tint
base_color_tex texture2d (sRGB) "white" - Colour texture
height_tex texture2d "white" - Height map (1 = surface, 0 = deepest), red channel
normal_tex texture2d "normal" - Tangent-space normal map
normal_scale float 1.0 0 .. 4 Normal map strength
height_scale float 0.09 0 .. 0.5 Depth of the relief in UV units
min_layers int 8 1 .. 64 Samples when looking straight at the surface
max_layers int 40 1 .. 128 Samples at grazing angles
uv_tiling vec2 [1.0,1.0] 0 .. 64 UV scale
roughness float 0.8 0 .. 1 Roughness
metallic float 0.0 0 .. 1 Metallic

Physically based metallic-roughness surface with normal map, occlusion, emissive, clear coat and sheen. Texture layout follows glTF 2.0: base colour in sRGB, occlusion in R, roughness in G and metallic in B of the ORM texture, tangent-space normals with +Y up.

  • kind surface, shading pbr, default render state: blend opaque, cull back, depth write true, alpha mode opaque, queue 2000
  • golden images: goldens/shaders/decal.png, goldens/shaders/forcefield.png, goldens/shaders/glass.png, goldens/shaders/library_matrix.png, goldens/shaders/parallax.png, goldens/shaders/pbr_clearcoat.png, goldens/shaders/pbr_emissive.png, goldens/shaders/pbr_grid.png, goldens/shaders/pbr_sheen.png, goldens/shaders/pbr_standard.png, goldens/shaders/water.png, goldens/shaders/wind.png
Parameter Type Default Range Description
base_color color [1.0,1.0,1.0,1.0] - Base colour multiplier (linear RGBA); alpha drives masking and blending
base_color_tex texture2d (sRGB) "white" - Base colour texture
metallic float 0.0 0 .. 1 Metallic factor (multiplies the ORM blue channel)
roughness float 0.5 0 .. 1 Perceptual roughness factor (multiplies the ORM green channel)
orm_tex texture2d "white" - Occlusion (R), roughness (G), metallic (B)
occlusion_strength float 1.0 0 .. 1 Strength of the occlusion texture
normal_tex texture2d "normal" - Tangent-space normal map (+Y up)
normal_scale float 1.0 0 .. 4 Normal map strength
emissive color (HDR) [0.0,0.0,0.0,1.0] - Emissive colour (linear)
emissive_tex texture2d (sRGB) "white" - Emissive texture
emissive_intensity float 1.0 0 .. 100 Emissive multiplier
clearcoat float 0.0 0 .. 1 Clear coat layer strength
clearcoat_roughness float 0.1 0 .. 1 Clear coat roughness
sheen_color color [0.0,0.0,0.0,1.0] - Sheen colour (cloth, velvet); black disables sheen
sheen_roughness float 0.5 0 .. 1 Sheen roughness
ior float 1.5 1 .. 2.5 Index of refraction of the dielectric layer (F0 = ((ior-1)/(ior+1))^2)
uv_tiling vec2 [1.0,1.0] 0 .. 64 UV scale
uv_offset vec2 [0.0,0.0] -64 .. 64 UV offset
alpha_cutoff float 0.5 0 .. 1 Threshold of alpha_mode mask

builtin::sprite_distort - Sprite distortion

Section titled “builtin::sprite_distort - Sprite distortion”

Distorts the sprite’s own UVs with travelling sine waves and noise (heat haze, water reflection, shock waves, wobble). The offset is clamped inside the sprite rectangle so atlas neighbours never bleed in.

  • kind sprite, shading pbr, default render state: blend alpha, cull none, depth write false, alpha mode opaque, queue 2000
  • golden images: goldens/shaders/library_matrix_sprite.png, goldens/shaders/sprite_distort.png, goldens/shaders/sprite_lit.png
Parameter Type Default Range Description
amplitude float 0.035 0 .. 0.5 Offset in sprite UV units
frequency float 14.0 0 .. 100 Wave frequency across the sprite
speed float 3.0 -20 .. 20 Wave speed
direction enum(horizontal, vertical, ripple) "horizontal" - Offset direction: horizontal shifts x with y, vertical shifts y with x, ripple expands from the centre
noise_amount float 0.35 0 .. 1 Share of noise in the offset

builtin::sprite_lit - Sprite lit (normal map)

Section titled “builtin::sprite_lit - Sprite lit (normal map)”

2D sprite lit by the frame’s 2D light using a normal map that shares the sprite’s UV rectangle (a normal atlas laid out like the colour atlas). Lambert diffuse, a Blinn-Phong specular and ambient. Assumes the sprite is not rotated (the normal map is in screen space).

  • kind sprite, shading pbr, default render state: blend alpha, cull none, depth write false, alpha mode opaque, queue 2000
  • golden images: goldens/shaders/library_matrix_sprite.png, goldens/shaders/sprite_lit.png
Parameter Type Default Range Description
normal_tex texture2d "normal" - Tangent-space normal map with the same layout as the sprite texture
pixel_sampler sampler "nearest_clamp" - Sampler of the normal map (nearest for pixel art)
normal_strength float 1.0 0 .. 4 Normal map strength
specular float 0.35 0 .. 2 Specular strength
shininess float 24.0 1 .. 200 Specular sharpness
light_boost float 1.0 0 .. 4 Multiplier of the light colour

Adds a coloured outline around the opaque pixels of a sprite by sampling the alpha of the 8 neighbours at a texel offset. The sprite’s UV rectangle needs a transparent margin at least as wide as the outline.

  • kind sprite, shading pbr, default render state: blend alpha, cull none, depth write false, alpha mode opaque, queue 2000
  • golden images: goldens/shaders/library_matrix_sprite.png, goldens/shaders/sprite_outline.png
Parameter Type Default Range Description
outline_color color (HDR) [1.0,0.85,0.15,1.0] - Outline colour
outline_width float 2.0 0 .. 8 Outline width in texels
pulse float 0.0 0 .. 1 Pulsing of the outline brightness over time
alpha_threshold float 0.5 0 .. 1 Alpha above which a texel counts as solid

builtin::sprite_palette - Sprite palette swap

Section titled “builtin::sprite_palette - Sprite palette swap”

Indexed sprite recolouring. The sprite texture stores a palette index in its red channel (0..255 as the byte value); the palette texture is a row of colours sampled with nearest filtering. Swap the palette texture to recolour a character with one draw call. A flash colour mixes over the result (hit flash).

  • kind sprite, shading pbr, default render state: blend alpha, cull none, depth write false, alpha mode opaque, queue 2000
  • golden images: goldens/shaders/library_matrix_sprite.png, goldens/shaders/sprite_palette.png
Parameter Type Default Range Description
palette_tex texture2d (sRGB) "builtin:palette_a" - Palette row: texel i is the colour of index i
pixel_sampler sampler "nearest_clamp" - Sampler of the palette (nearest)
palette_size float 16.0 2 .. 256 Width of the palette texture in texels
flash_color color [1.0,1.0,1.0,1.0] - Flash colour
flash_amount float 0.0 0 .. 1 How much of the flash colour is mixed in

Banded cel shading with a stylised specular dot, rim light and shadow tint. Pair it with builtin::toon_outline (inverted hull) for ink outlines.

  • kind surface, shading custom, default render state: blend opaque, cull back, depth write true, alpha mode opaque, queue 2000
  • golden images: goldens/shaders/library_matrix.png, goldens/shaders/toon.png
Parameter Type Default Range Description
base_color color [0.95,0.55,0.25,1.0] - Lit colour
base_color_tex texture2d (sRGB) "white" - Colour texture
shade_color color [0.45,0.3,0.6,1.0] - Tint of the shadowed bands
bands int 3 2 .. 8 Number of light bands
band_softness float 0.015 0 .. 0.2 Width of the transition between bands
specular_size float 0.06 0 .. 0.5 Size of the specular highlight (0 = off)
specular_color color [1.0,1.0,1.0,1.0] - Highlight colour
rim_strength float 0.6 0 .. 2 Rim light strength
rim_power float 3.0 0.5 .. 8 Rim light falloff
rim_color color [1.0,0.9,0.8,1.0] - Rim light colour
ambient_strength float 0.35 0 .. 1 How much environment light lifts the shadow bands
alpha_cutoff float 0.5 0 .. 1 Threshold of alpha_mode mask

Inverted-hull outline pass: the mesh is pushed out along its normals and only back faces are drawn in a flat colour. Draw it with the same mesh as builtin::toon, after it.

  • kind surface, shading unlit, default render state: blend opaque, cull front, depth write true, alpha mode opaque, queue 2000
  • golden images: goldens/shaders/library_matrix.png, goldens/shaders/toon.png
Parameter Type Default Range Description
outline_color color [0.05,0.03,0.08,1.0] - Outline colour
outline_width float 0.022 0 .. 0.5 Outline thickness in world units
scale_with_distance bool true - Keep a constant on-screen width (width is measured at 4 m)

Projects textures along the three axes and blends them by the surface normal, so meshes without UVs (rocks, terrain, procedural shapes) get seamless texturing. Supports a second texture on upward-facing surfaces (moss, snow) and whiteout-blended normal maps (Golus 2017).

  • kind surface, shading pbr, default render state: blend opaque, cull back, depth write true, alpha mode opaque, queue 2000
  • golden images: goldens/shaders/library_matrix.png, goldens/shaders/triplanar.png
Parameter Type Default Range Description
base_color color [1.0,1.0,1.0,1.0] - Tint
base_tex texture2d (sRGB) "white" - Colour texture (side faces)
normal_tex texture2d "normal" - Tangent-space normal map (side faces)
normal_scale float 1.0 0 .. 4 Normal map strength
top_tex texture2d (sRGB) "white" - Colour texture of upward-facing surfaces
top_color color [1.0,1.0,1.0,1.0] - Tint of the top texture
top_amount float 0.0 0 .. 1 How much of the surface the top texture covers (0 = none)
top_sharpness float 6.0 1 .. 32 Edge sharpness of the top coverage
tex_scale float 0.8 0.01 .. 16 Texture repeats per metre (or per object unit)
sharpness float 4.0 1 .. 16 Blend sharpness between the three projections
object_space bool false - Project in object space (texture follows the object) instead of world space
roughness float 0.85 0 .. 1 Roughness
metallic float 0.0 0 .. 1 Metallic

Flat colour with an optional texture and emissive glow; no lighting. For UI in the world, debug views, skies, stylised flat looks.

  • kind surface, shading unlit, default render state: blend opaque, cull back, depth write true, alpha mode opaque, queue 2000
  • golden images: goldens/shaders/glass.png, goldens/shaders/library_matrix.png, goldens/shaders/unlit.png
Parameter Type Default Range Description
color color (HDR) [1.0,1.0,1.0,1.0] - Colour multiplier (linear RGBA); values above 1 glow after tone mapping
color_tex texture2d (sRGB) "white" - Colour texture
uv_tiling vec2 [1.0,1.0] 0 .. 64 UV scale
uv_offset vec2 [0.0,0.0] -64 .. 64 UV offset
alpha_cutoff float 0.5 0 .. 1 Threshold of alpha_mode mask

Stylised ocean/lake surface. Four Gerstner waves (GPU Gems 1, ch. 1) displace the mesh and give analytic normals; two scrolling normal-map layers add detail. The colour comes from the water depth under each pixel (shallow to deep, Beer-Lambert style opacity), the opaque scene is seen through it with depth-checked refraction, the sky is reflected with a Fresnel term (F0 = 0.02), sun glints use GGX, and foam appears on the shoreline and on wave crests. Needs the opaque scene colour and depth.

  • kind surface, shading custom, default render state: blend opaque, cull none, depth write false, alpha mode opaque, queue 3000
  • needs from the renderer: scene colour and scene depth (draw it after the opaque pass)
  • golden images: goldens/shaders/library_matrix.png, goldens/shaders/water.png
Parameter Type Default Range Description
shallow_color color [0.04,0.46,0.52,1.0] - Colour in shallow water
deep_color color [0.01,0.1,0.24,1.0] - Colour in deep water
depth_fade float 1.1 0.05 .. 30 Depth (metres) where the colour reaches deep_color
clarity float 1.0 0.05 .. 30 Depth (metres) over which the water becomes opaque
wave_amplitude float 0.07 0 .. 1 Height of the largest wave in metres
wave_length float 2.4 0.2 .. 50 Wavelength of the largest wave in metres
wave_speed float 0.8 0 .. 4 Wave speed multiplier (1 = deep-water dispersion)
wave_direction vec2 [1.0,0.35] - Direction of the largest wave (x, z)
choppiness float 0.55 0 .. 1 Gerstner steepness: how much crests sharpen
normal_tex texture2d "builtin:noise_normal" - Tangent-space detail normal map (tileable)
detail_scale float 0.35 0.01 .. 8 Detail normal tiling per metre
detail_strength float 0.55 0 .. 2 Detail normal strength
refraction_strength float 0.05 0 .. 0.3 Screen-space refraction offset
specular_roughness float 0.07 0.02 .. 1 Roughness of sun glints
foam_color color [0.95,0.98,1.0,1.0] - Foam colour
foam_width float 0.45 0 .. 5 Shoreline foam width in metres of depth
crest_foam float 0.6 0 .. 1 Foam on wave crests

Vertex wind for leaves and grass: a travelling gust field bends vertices in proportion to their height weight (uv.y = 0 is the tip), plus high-frequency flutter. The displacement also applies in the shadow pass. Alpha-tested, double sided, with a translucent back-face tint.

  • kind surface, shading pbr, default render state: blend opaque, cull none, depth write true, alpha mode mask, queue 2450
  • golden images: goldens/shaders/library_matrix.png, goldens/shaders/wind.png
Parameter Type Default Range Description
base_color color [1.0,1.0,1.0,1.0] - Tint
base_color_tex texture2d (sRGB) "white" - Colour texture with alpha mask
wind_direction vec2 [1.0,0.35] - Wind direction on the ground plane (x, z)
wind_strength float 0.28 0 .. 2 Bend distance of the tips in metres
wind_speed float 1.3 0 .. 10 Gust speed
gust_scale float 0.45 0.01 .. 4 Spatial frequency of the gust field (cycles per metre)
flutter float 0.035 0 .. 0.5 High-frequency leaf flutter amplitude
bend_exponent float 1.6 0.5 .. 4 How quickly the bend grows towards the tip
roughness float 0.6 0 .. 1 Roughness
translucency float 0.35 0 .. 1 Brightening of back faces facing the light
alpha_cutoff float 0.5 0 .. 1 Threshold of alpha_mode mask

Animated molten rock: 3D fractal noise in object space (no UV seams) drives a black-to-yellow ramp; the cooled crust stays dark and rough, the cracks glow.

  • kind surface, shading pbr, default render state: blend opaque, cull back, depth write true, alpha mode opaque, queue 2000
  • golden images: goldens/shaders/graph_lava.png, goldens/shaders/library_matrix.png
Parameter Type Default Range Description
scale float 1.6 0.1 .. 16 Noise scale
speed float 0.35 0 .. 4 Flow speed
glow float 3.0 0 .. 20 Emissive intensity of the cracks

Procedural polished marble: sine veins warped by 3D fractal noise in object space, mapped through a colour ramp, with a clear coat. No textures and no UV seams.

  • kind surface, shading pbr, default render state: blend opaque, cull back, depth write true, alpha mode opaque, queue 2000
  • golden images: goldens/shaders/graph_marble.png, goldens/shaders/library_matrix.png
Parameter Type Default Range Description
frequency float 4.5 0.5 .. 40 Vein frequency
warp float 4.0 0 .. 8 How strongly noise bends the veins
scale float 1.1 0.1 .. 20 Noise scale

Fresnel rim light that pulses over time, on top of a plain tinted PBR base.

  • kind surface, shading pbr, default render state: blend opaque, cull back, depth write true, alpha mode opaque, queue 2000
  • golden images: goldens/shaders/graph_rim_glow.png, goldens/shaders/library_matrix.png
Parameter Type Default Range Description
base color [0.05,0.08,0.18,1.0] - Base colour
rim color (HDR) [0.1,0.8,1.0,1.0] - Rim colour
power float 3.0 0.5 .. 12 Rim falloff
pulse float 2.0 0 .. 10 Pulse speed (radians per second)
Part Model
Specular distribution GGX / Trowbridge-Reitz, alpha = roughness^2 (Walter et al. 2007)
Visibility height-correlated Smith GGX, V = G2 / (4 NoL NoV) (Heitz 2014)
Fresnel Schlick (1994); dielectric F0 = ((ior-1)/(ior+1))^2
Diffuse Lambert
Environment split-sum DFG (Karis 2013) tabulated from the library’s own BRDF by importance sampling (examples/gen_dfg_lut.rs); multiple-scattering energy compensation (Fdez-Aguera 2019, as in Filament); specular occlusion (Lagarde and de Rousiers 2014)
Clear coat second GGX lobe, F0 = 0.04, Kelemen and Szirmay-Kalos 2001 visibility, base layer attenuated by (1 - Fc)
Sheen Charlie distribution (Estevez and Kulla 2017), Neubelt-Pettineo 2013 visibility, directional albedo tabulated by numerical integration
Transmission screen-space refraction, Beer-Lambert absorption, dispersion (KHR_materials_transmission, volume, dispersion)
Lights directional, point (inverse square with the Karis 2013 range window), spot (glTF KHR_lights_punctual cone)
Tone mapping ACES filmic fit (Narkowicz 2015)
Waves Gerstner waves (GPU Gems 1, ch. 1)
Parallax parallax occlusion mapping (Tatarchuk 2006)
Triplanar normals whiteout blend (Golus 2017)
Hash PCG3D (Jarzynski and Olano 2020), integer only: identical on every GPU

The numbers are checked on the GPU in tests/brdf_gpu.rs: GGX normalisation (the integral of D (n.h) is 1), Smith visibility against the Lambda-function G2, the tabulated DFG and sheen albedo against brute-force integration of the BRDF code, Schlick end points, sRGB and ACES, and the hash against a CPU implementation bit for bit.

  • sparklight-render (2D) does not use the shader adapter yet (examples/adapter.rs is the recipe).
  • Shadows, environment, light list, occlusion, reflections and the output colour are hooks (see above); the 3D renderer implements all of them.
  • Hook spark-shader validate into the editor.