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.
Five-minute workflow
Section titled “Five-minute workflow”- Write
shaders/glow.wgsl(header + asurfacefunction), see Shader files. - 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). - Write
shaders/glow_hot.material.json(Materials) and validate again. preview to the crate and a golden (Previews and goldens). - Edit the files while a game or the editor runs:
HotReloader::pollswaps 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 includedlet errors = set.load_dir("shaders".as_ref()); // *.wgsl (with a `//! shader:` or `//! kind:` header), *.graph.json, *.material.jsonassert!(errors.is_empty(), "{errors:?}");let description = set.describe("project::glow")?; // typed parameters, bindings, entry points as datalet mut material = set.material_instance("glow_hot")?;material.set("gain", 2.5f32)?; // type and range checked; E_MATERIAL_PARAM_* on mistakeslet bytes = material.uniform_bytes(); // group 2 binding 0, offsets reflected from the compiled modulelet mut hot = HotReloader::new(&set);for event in hot.poll(&mut set) { /* ReloadEvent { path, kind, ids, outcome, materials } */ }Conventions
Section titled “Conventions”- Colour: all shading is in linear space.
colorparameters and JSON colour arrays are linear RGBA; a"#rrggbb[aa]"string is sRGB and converted on load. Textures that hold colour are declaredsrgb(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.
Shader files
Section titled “Shader files”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;}Header lines
Section titled “Header lines”| 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. |
Parameter types
Section titled “Parameter types”| 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).
Entry points and hooks
Section titled “Entry points and hooks”| 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.
Bind group contract
Section titled “Bind group contract”| 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).
Render pipeline hooks (sparklight::hooks)
Section titled “Render pipeline hooks (sparklight::hooks)”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).
Module system
Section titled “Module system”#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.
Library modules
Section titled “Library modules”sparklight::brdf
Section titled “sparklight::brdf”Microfacet BRDF terms. Every function names the model it implements.
fn d_ggx(n_dot_h: f32, alpha: f32) -> f32fn v_smith_ggx_correlated(n_dot_v: f32, n_dot_l: f32, alpha: f32) -> f32fn 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) -> f32fn fd_lambert() -> f32 { return INV_PI; }fn v_kelemen(l_dot_h: f32) -> f32fn d_charlie(sheen_roughness: f32, n_dot_h: f32) -> f32fn v_neubelt(n_dot_v: f32, n_dot_l: f32) -> f32const 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 byexamples/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
sparklight::environment
Section titled “sparklight::environment”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>
sparklight::frame
Section titled “sparklight::frame”Bind group 0 of 3D (surface) shaders, filled once per view by the renderer.
struct Lightstruct Framefn 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 whencamera_params.wis 1).fn depth_is_far(d: f32) -> bool- True whendis 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 coordinatesfrag_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 pixelfrag_xyreconstructed from the scene depth.fn sample_scene_color_lod(uv: vec2<f32>, lod: f32) -> vec3<f32>- Opaque scene colour atuv(0..1, y down), mip levellod(levels beyond the copy’s chain clamp to its last).fn sample_scene_color(uv: vec2<f32>) -> vec3<f32>- Opaque scene colour atuv(0..1, y down), level 0.
sparklight::hooks
Section titled “sparklight::hooks”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() -> u32fn hook_light(i: u32) -> Lightfn hook_shadow_visibility(i: u32, world_pos: vec3<f32>, n: vec3<f32>) -> f32fn 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>) -> f32fn hook_finish_color(linear_hdr: vec3<f32>) -> vec3<f32>- Exposure, optional tone mapping (flags bit 0) and optional sRGB encoding (flags bit 1).
sparklight::lighting
Section titled “sparklight::lighting”Light sampling, physically based shading and output encoding.
struct LightSamplefn 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>) -> LightSamplefn shadow_visibility(i: u32, world_pos: vec3<f32>, n: vec3<f32>) -> f32- Shadow visibility of lightiat 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).
sparklight::math
Section titled “sparklight::math”Constants and small helpers shared by every library module.
const PI: f32const TAU: f32const INV_PI: f32const HALF_PI: f32const EPS: f32fn 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 ofxfrom [a, b] to [c, d] (not clamped).fn inverse_lerp(a: f32, b: f32, x: f32) -> f32 { return saturate((x - a) / (b - a)); }- Fraction ofxbetweenaandb, 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 byphi.fn tonemap_aces(x: vec3<f32>) -> vec3<f32>- ACES filmic tone mapping curve fit (Narkowicz 2015, “ACES Filmic Tone Mapping Curve”).
sparklight::noise
Section titled “sparklight::noise”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>) -> f32fn 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.octavesis 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 byjitter(0 = regular grid, 1 = fully random).
sparklight::object
Section titled “sparklight::object”Bind group 1 of 3D (surface) shaders, filled per draw by the renderer.
struct Objectfn object_select(instance: u32) -> u32fn object_select(instance: u32) -> u32fn palette_skin_matrix(base: u32, joints: vec4<u32>, weights: vec4<f32>) -> mat4x4<f32>- Weighted sum of the four palette matrices of a vertex;baseis the index of the first matrix of the pose.
sparklight::sprite_frame
Section titled “sparklight::sprite_frame”Bind groups 0 and 1 of 2D (sprite) shaders.
struct SpriteFrame
sparklight::studio_env
Section titled “sparklight::studio_env”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: u32fn 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>
sparklight::surface
Section titled “sparklight::surface”The contract between a surface shader and the library template.
struct VertexInstruct SurfaceInstruct SurfaceOutfn surface_defaults() -> SurfaceOutfn 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() -> f32fn time_seconds() -> f32
sparklight::template_sprite
Section titled “sparklight::template_sprite”Entry points of a sprite shader (//! kind: sprite).
struct SpriteVertexInstruct SpriteVsOutstruct SpriteInfn sample_sprite(in: SpriteIn, uv: vec2<f32>) -> vec4<f32>- Sprite texture sample atuv, 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) -> SpriteVsOutfn fs_main(in: SpriteVsOut) -> @location(0) vec4<f32>
sparklight::template_surface
Section titled “sparklight::template_surface”Vertex, shadow and fragment entry points of a surface shader.
struct VsOutstruct LocalVertex- Position and normal in the object’s space after skinning (the vertex as it is withoutSKINNED).fn local_vertex(in: VertexIn) -> LocalVertexfn 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) -> VsOutfn vs_shadow(in: VertexIn, @builtin(instance_index) instance: u32) -> @builtin(position) vec4<f32>fn surface_input(in: VsOut, front: bool) -> SurfaceInfn 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 VelocityOutfn vs_velocity(in: VertexIn, @builtin(instance_index) instance: u32) -> VelocityOutfn fs_velocity(in: VelocityOut, @builtin(front_facing) front: bool) -> @location(0) vec2<f32>
Materials
Section titled “Materials”{ "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" }}shadernames a shader id;parentnames another material (an instance: only the overrides are written; chains are resolved, cycles areE_MATERIAL_PARENT_CYCLE). Exactly one of the two.paramsvalues 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).renderoverrides the shader’s default render state field by field:blend,cull,depth_write,depth_test,alpha_mode,queue.alpha_mode: maskcompiles the discard (threshold: the shader’salpha_cutoffparameter, default 0.5),blendpasses alpha to the blend state,premultipliedblending premultiplies the colour in the shader.definesturns 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 areE_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.
Built-in materials
Section titled “Built-in materials”| 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. |
Built-in texture keys
Section titled “Built-in texture keys”Texture keys are strings the renderer resolves. The preview harness provides these procedural ones (deterministic, tileable where it matters):
whiteblackgraynormalbuiltin:checkerbuiltin:noisebuiltin:noise_normalbuiltin:bricks_albedobuiltin:bricks_normalbuiltin:bricks_heightbuiltin:bricks_ormbuiltin:cobble_albedobuiltin:cobble_normalbuiltin:cobble_heightbuiltin:rock_albedobuiltin:rock_normalbuiltin:leafbuiltin:decalbuiltin:sprite_herobuiltin:sprite_hero_normalbuiltin:sprite_hero_indexedbuiltin:palette_abuiltin:palette_b
Shader graphs
Section titled “Shader graphs”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 oneoutputnode. - Types are
float,vec2,vec3,vec4. A float connects to any vector input (it is splatted). Vectors of different sizes never convert implicitly: useswizzle,split,combine. Texture parameters are read withtexture_sample(props.texture). - Only nodes reachable from the output are compiled. Surface graphs set
SurfaceOutfields through theoutputnode; sprite graphs setcolor. - 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 catalogue
Section titled “Node catalogue”| 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 |
Errors
Section titled “Errors”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. |
Hot reload
Section titled “Hot reload”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 inevent.materials(rebind their instances withMaterialInstance::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)(ormaterial_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 asFailedon 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).
GPU adapter
Section titled “GPU adapter”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:
Layouts::new(device, &shader.description)- bind group layouts for groups 0..=2 from the reflected bindings.create_shader_module(device, &compiled)andcreate_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 asShaderError, not as panics.MaterialGpu::new(&ctx, &layouts.groups[2], &material, &mut textures)- uniform buffer + texture/sampler bind group; implementTextureProviderover the asset system (the crate shipsProceduralTexturesfor tests and previews).update_uniformsre-uploads after aset.FrameUniform::new(...),ObjectUniform::new(model, id),LightUniform::{directional, point, spot},SpriteFrameUniform,SpriteInstancemirror the WGSL blocks; a test checks their sizes against the reflected sizes.- Draw with the vertex layout above; draw
instancinginstances whendescription.instancingis set; draw shaders that need the scene colour or depth in a second pass.
Previews and goldens
Section titled “Previews and goldens”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_shaderscompares every built-in preview withgoldens/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 underSPARKLIGHT_UPDATE_GOLDENS=1rewrites the goldens; look at every image before committing.SPARKLIGHT_PREVIEW=water,glasslimits 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 inmaterials/, a preview inpreviews/;build.rsregisters all three. Then update the goldens.
Measured cost
Section titled “Measured cost”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 |
Command line
Section titled “Command line”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 |
Built-in shaders
Section titled “Built-in shaders”builtin::decal - Projected decal
Section titled “builtin::decal - Projected decal”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, shadingcustom, default render state: blendalpha, cullfront, depth writefalse, alpha modeblend, 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 |
builtin::dissolve - Dissolve
Section titled “builtin::dissolve - Dissolve”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, shadingpbr, default render state: blendopaque, cullnone, depth writetrue, alpha modemask, 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 |
builtin::forcefield - Force field
Section titled “builtin::forcefield - Force field”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, shadingunlit, default render state: blendadditive, cullnone, depth writefalse, alpha modeblend, 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) |
builtin::fur - Fur shells
Section titled “builtin::fur - Fur shells”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, shadingpbr, default render state: blendopaque, cullback, depth writetrue, alpha modemask, queue 2450 - instanced: draw
shell_countinstances - 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 |
builtin::glass - Glass / refraction
Section titled “builtin::glass - Glass / refraction”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, shadingpbr, default render state: blendopaque, cullback, depth writefalse, alpha modeopaque, 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) |
builtin::hologram - Hologram
Section titled “builtin::hologram - Hologram”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, shadingunlit, default render state: blendadditive, cullnone, depth writefalse, alpha modeblend, 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 |
builtin::parallax - Parallax occlusion
Section titled “builtin::parallax - Parallax occlusion”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, shadingpbr, default render state: blendopaque, cullback, depth writetrue, alpha modeopaque, 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 |
builtin::pbr - Standard PBR
Section titled “builtin::pbr - Standard PBR”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, shadingpbr, default render state: blendopaque, cullback, depth writetrue, alpha modeopaque, 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, shadingpbr, default render state: blendalpha, cullnone, depth writefalse, alpha modeopaque, 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, shadingpbr, default render state: blendalpha, cullnone, depth writefalse, alpha modeopaque, 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 |
builtin::sprite_outline - Sprite outline
Section titled “builtin::sprite_outline - Sprite outline”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, shadingpbr, default render state: blendalpha, cullnone, depth writefalse, alpha modeopaque, 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, shadingpbr, default render state: blendalpha, cullnone, depth writefalse, alpha modeopaque, 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 |
builtin::toon - Toon / cel
Section titled “builtin::toon - Toon / cel”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, shadingcustom, default render state: blendopaque, cullback, depth writetrue, alpha modeopaque, 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 |
builtin::toon_outline - Toon outline
Section titled “builtin::toon_outline - Toon outline”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, shadingunlit, default render state: blendopaque, cullfront, depth writetrue, alpha modeopaque, 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) |
builtin::triplanar - Triplanar
Section titled “builtin::triplanar - Triplanar”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, shadingpbr, default render state: blendopaque, cullback, depth writetrue, alpha modeopaque, 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 |
builtin::unlit - Unlit
Section titled “builtin::unlit - Unlit”Flat colour with an optional texture and emissive glow; no lighting. For UI in the world, debug views, skies, stylised flat looks.
- kind
surface, shadingunlit, default render state: blendopaque, cullback, depth writetrue, alpha modeopaque, 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 |
builtin::water - Stylised water
Section titled “builtin::water - Stylised water”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, shadingcustom, default render state: blendopaque, cullnone, depth writefalse, alpha modeopaque, 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 |
builtin::wind - Foliage wind
Section titled “builtin::wind - Foliage wind”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, shadingpbr, default render state: blendopaque, cullnone, depth writetrue, alpha modemask, 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 |
graph::lava - Lava
Section titled “graph::lava - Lava”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, shadingpbr, default render state: blendopaque, cullback, depth writetrue, alpha modeopaque, 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 |
graph::marble - Marble
Section titled “graph::marble - Marble”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, shadingpbr, default render state: blendopaque, cullback, depth writetrue, alpha modeopaque, 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 |
graph::rim_glow - Rim glow
Section titled “graph::rim_glow - Rim glow”Fresnel rim light that pulses over time, on top of a plain tinted PBR base.
- kind
surface, shadingpbr, default render state: blendopaque, cullback, depth writetrue, alpha modeopaque, 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) |
Models implemented
Section titled “Models implemented”| 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.
Left for the render pipeline
Section titled “Left for the render pipeline”sparklight-render(2D) does not use the shader adapter yet (examples/adapter.rsis 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 validateinto the editor.