renzora
Game Engine

Material API

Reference for Renzora's material system — the .material node-graph format, material instances, the full node catalog, and the code-shader backends, all backed by the renzora_shader crate.

How materials work

A Renzora material is a node graph, not a fixed list of PBR sliders. You build it in the Material Editor (the Materials workspace), and it is saved to disk as a .material file — JSON-serialized MaterialGraph. At runtime the renzora_shader crate's MaterialResolverPlugin watches every entity that has a MaterialRef component pointing at a .material (or .shader) file, compiles the graph, and applies the result to the mesh.

There are two compile paths, chosen automatically by the resolver:

  • Trivial graphs — a texture/factor wired straight into a PBR output pin compile to a plain Bevy StandardMaterial. Most imported materials land here.
  • Procedural graphs — anything with math, noise, animation, or custom logic compile to a GraphMaterial (an ExtendedMaterial<StandardMaterial, SurfaceGraphExt>) with a generated WGSL fragment shader.

Compiled results are cached per file path in the MaterialCache resource, so editing one material in the editor invalidates and recompiles only that material.

When you save through the editor, codegen also writes the generated WGSL beside the .material file and records its project-relative path in the graph's wgsl_path field. The runtime follows that link to skip codegen entirely. Legacy files without a wgsl_path fall back to live codegen.

Attaching a material to an entity

Materials are assigned with the MaterialRef component (re-exported as renzora::MaterialRef). Its single field is the asset-relative path to a .material or .shader file. Scenes serialize this component, so a material assignment survives save/load.

Per-entity tweaks use the MaterialOverrides component, a map of parameter name → ParamValue applied on top of the material's defaults:

ParamValue::Float(f32)
ParamValue::Vec2([f32; 2])
ParamValue::Vec3([f32; 3])
ParamValue::Vec4([f32; 4])
ParamValue::Color([f32; 4])
ParamValue::Int(i32)
ParamValue::Bool(bool)

The .material file format

A master .material file is a JSON-serialized MaterialGraph:

FieldTypeNotes
namestringDisplay name
domainenumSurface, TerrainLayer, Vegetation, or Unlit
nodesarrayThe graph's nodes (see below)
connectionsarrayWires between pins
next_idu64Next node id the editor will allocate
alpha_modeenumOpaque (default), { "Mask": { "cutoff": 0.5 } }, or Blend
double_sidedboolRender back faces too (default false)
wgsl_pathstringOptional — link to the precompiled .wgsl (omitted when absent)

Each entry in nodes is a MaterialNode:

FieldTypeNotes
idu64Unique within the graph
node_typestringA registered node type, e.g. "math/multiply"
position[f32; 2]Editor canvas position
input_valuesobjectPer-input-pin constant overrides, keyed by pin name

Each entry in connections is a Connection with from_node, from_pin, to_node, to_pin. An input pin accepts one connection; reconnecting it replaces the previous wire.

Pin constants in input_values are PinValues, serialized externally tagged — { "Float": 0.5 }, { "Color": [1.0, 0.0, 0.0, 1.0] }, { "Vec3": [0,0,0] }, { "TexturePath": "textures/brick.png" }, { "String": "BaseColor" }, etc.

{
  "name": "Brick",
  "domain": "Surface",
  "nodes": [
    {
      "id": 1,
      "node_type": "output/surface",
      "position": [300.0, 0.0],
      "input_values": {}
    },
    {
      "id": 2,
      "node_type": "param/color",
      "position": [0.0, 0.0],
      "input_values": {
        "name":    { "String": "BaseColor" },
        "default": { "Color": [0.6, 0.2, 0.15, 1.0] }
      }
    }
  ],
  "connections": [
    { "from_node": 2, "from_pin": "value", "to_node": 1, "to_pin": "base_color" }
  ],
  "next_id": 3,
  "alpha_mode": "Opaque",
  "double_sided": false
}

Pin types

Every pin has a PinType. Numeric, vector, and color types are freely inter-connectable — the codegen inserts the right WGSL coercion (widening copies the scalar across components; narrowing takes the leading components).

PinTypeWGSL typeNotes
Floatf32
Vec2vec2<f32>
Vec3vec3<f32>
Vec4vec4<f32>
Colorvec4<f32>Treated as Vec4 for casting
Boolbool
Texture2Dtexture_2d<f32>Texture asset path
Samplersampler
StringEditor-only (e.g. parameter names); never reaches WGSL

Material domains and output nodes

Every graph has exactly one output node, fixed by its domain. The output node cannot be deleted. Its input pins are the channels you drive.

output/surface — Surface

Full PBR surface that maps 1:1 onto Bevy's StandardMaterial. Disconnected pins keep StandardMaterial defaults.

PinTypeDefaultPurpose
base_colorColor[0.8, 0.8, 0.8, 1.0]Albedo
metallicFloat0.0Dielectric ↔ metal
roughnessFloat0.5Smooth ↔ matte
normalVec3Tangent-space normal
emissiveVec3[0,0,0]Self-illumination
aoFloat1.0Ambient occlusion
alphaFloat1.0Opacity
reflectanceVec3[0.5,0.5,0.5]Specular reflectance
specular_transmissionFloat0.0Refraction (glass, water)
diffuse_transmissionFloat0.0Foliage / skin
thicknessFloat0.0Volume thickness
iorFloat1.5Index of refraction
attenuation_distanceFloat1.0e37Volume attenuation
clearcoatFloat0.0Second specular layer (car paint)
clearcoat_roughnessFloat0.5Clearcoat roughness
anisotropy_strengthFloat0.0Directional specular (brushed metal, hair)
anisotropy_rotationFloat0.0Anisotropy direction

Other output nodes

Node typeDomainInput pins
output/terrain_layerTerrain Layer (blended via splatmap)base_color, metallic, roughness, normal, height
output/vegetationPBR + vertex displacementbase_color, metallic, roughness, normal, emissive, ao, alpha, vertex_offset
output/unlitUnlit (no lighting)color, alpha

Material instances

A material instance is a derived .material file: instead of a full graph it carries a master path and an overrides map. Many visually distinct materials can share one compiled master shader — the same idea as Unreal's material instances.

Masters and instances share the .material extension; the resolver content-detects which is which (a derived file has a non-empty master). Legacy .material_instance files still load.

{
  "master": "models/Wood/materials/Wood.material",
  "overrides": {
    "BaseColor": { "Color": [0.45, 0.22, 0.10, 1.0] },
    "Roughness": { "Float": 0.85 }
  }
}
  • overrides keys are the names authored on the master's param/* nodes (an unnamed node falls back to FloatParam, ColorParam, …).
  • Unknown keys are ignored at resolve time, so renaming a master parameter won't hard-fail every instance.
  • For a trivial master the override values are spliced into the matching param/* defaults and re-classified into a fresh StandardMaterial. For a procedural master the master compiles once and each instance only overwrites the relevant slots of the parameter uniform buffer, so wgpu reuses the same specialized pipeline.

Expose a value for instances (or for MaterialOverrides) by adding a Parameter node (param/float, param/color, …) and giving its name pin a stable identifier. Anything wired from a parameter becomes overridable by name.

Node catalog

Node types are identified by a category/name string. Categories appear in the editor's node menu in the order below.

For the per-node reference — every node's inputs, outputs, and exactly what it computes — see Material Node Reference. The tables below are the quick index.

Input

Node typeNameDescription
input/uvUVTexture coordinates (0–1); also u, v
input/uv_scaleUV ScaleScale/offset UVs for tiling
input/uv_polarPolar UVCartesian → polar (angle, radius)
input/uv_rotatorUV RotatorRotate UVs around a center
input/uv_pannerUV PannerTime-driven UV pan
input/world_positionWorld PositionFragment world-space position
input/world_normalWorld NormalFragment world-space normal
input/view_directionView DirectionFragment → camera direction
input/timeTimetime, sin_time, cos_time
input/vertex_colorVertex ColorPer-vertex color attribute
input/camera_positionCamera PositionWorld-space camera position
input/object_positionObject PositionObject pivot world position

Parameter

Named graph-boundary inputs that material instances and MaterialOverrides can replace by name.

Node typeName
param/floatFloat Parameter
param/colorColor Parameter
param/vec2Vec2 Parameter
param/vec3Vec3 Parameter
param/vec4Vec4 Parameter
param/boolBool Parameter

Texture

Node typeNameDescription
texture/sampleSample TextureSample a 2D texture; outputs color, rgb, r, g, b, a
texture/sample_normalSample Normal MapSample + decode a normal map (with strength)
texture/triplanarTriplanar SampleWorld-projected sampling, no UV seams
texture/sample_lodSample Texture LODSample at an explicit mip (textureSampleLevel)
texture/sample_gradSample Texture GradSample with explicit UV derivatives
texture/sample_cubemapSample CubemapMaterial-local cubemap along a direction
texture/sample_2d_arraySample 2D ArrayLayered array; layer picks the slice
texture/sample_3dSample 3D TextureVolumetric (UVW) sampling

All texture slots in a graph material share one sampler. Every slot — 2D, cubemap, array, and 3D — samples with the first 2D texture's sampler settings (or a default linear sampler when there is none). Per-image filter and wrap modes on the other textures are not honored. This keeps the fragment stage under the 16-samplers-per-stage limit that Metal and baseline WebGPU/Vulkan impose; per-slot samplers made graph materials fail pipeline creation on macOS.

Math

All operate component-wise on the inferred type. Each has a result output.

Node typeOpNode typeOp
math/addA + Bmath/saturateclamp 0–1
math/subtractA − Bmath/moduloA mod B
math/multiplyA × Bmath/sign−1 / 0 / +1
math/divideA / Bmath/atan2atan2(y, x)
math/powerbase ^ expmath/trunctruncate toward zero
math/absabsolute valuemath/roundround to nearest
math/negate−valuemath/expe^x
math/one_minus1 − valuemath/logln(x)
math/fractfractional partmath/sqrtsquare root
math/floorround downmath/reciprocal1 / value
math/ceilround upmath/tantangent
math/minmin(A, B)math/asinarcsine
math/maxmax(A, B)math/acosarccosine
math/clampclamp(value, min, max)math/radiansdegrees → radians
math/lerpmix(A, B, T)math/degreesradians → degrees
math/smoothstepHermite(edge0, edge1, x)math/sinsine
math/step0 / 1 stepmath/coscosine
math/remapremap range

Vector

Node typeNameDescription
vector/split_vec2Split Vec2Vec2 → x, y
vector/split_vec3Split Vec3Vec3 → x, y, z
vector/combine_vec2Combine Vec2Components → Vec2
vector/combine_vec3Combine Vec3Components → Vec3
vector/combine_vec4Combine Vec4Components → Vec4
vector/dotDot ProductScalar
vector/crossCross ProductVec3
vector/normalizeNormalizeUnit vector
vector/distanceDistanceDistance between points
vector/lengthLengthMagnitude
vector/reflectReflectReflect about a normal
vector/refractRefractRefract with IOR ratio
vector/swizzleSwizzleReorder vec4 channels (0=X … 4=zero, 5=one)

Color

Node typeNameDescription
color/constantColorConstant color (color, rgb, r, g, b)
color/floatFloatConstant scalar
color/vec2Vec2Constant Vec2
color/vec3Vec3Constant Vec3
color/lerpColor LerpBlend two colors
color/cosine_paletteCosine PaletteIQ-style procedural palette
color/fresnelFresnelView-angle rim factor
color/srgb_to_linearsRGB → LinearColor space convert
color/linear_to_srgbLinear → sRGBColor space convert
color/rgb_to_hsvRGB → HSV
color/hsv_to_rgbHSV → RGB
color/hue_shiftHue ShiftRotate hue
color/luminanceLuminancePerceptual brightness
color/gammaGammaGamma curve
color/brightness_contrastBrightness/Contrast
color/saturationSaturationAdjust saturation
color/blendBlendPhotoshop-style blend modes

Procedural

Node typeNameNode typeName
procedural/noise_perlinPerlin Noiseprocedural/gradient_radialRadial Gradient
procedural/noise_simplexSimplex Noiseprocedural/gradient_linearLinear Gradient
procedural/noise_voronoiVoronoiprocedural/gradient_angularAngular Gradient
procedural/noise_fbmFBMprocedural/gradient_diamondDiamond Gradient
procedural/checkerboardCheckerboardprocedural/bump_offsetBump Offset (parallax)
procedural/gradientGradientprocedural/noise_ridgedRidged Noise
procedural/brickBrickprocedural/noise_turbulenceTurbulence
procedural/normal_from_heightNormal from Heightprocedural/noise_billowBillow Noise
procedural/world_normal_from_heightWorld Normal from Heightprocedural/noise_whiteWhite Noise
procedural/domain_warpDomain Warpprocedural/noise_curlCurl Noise
procedural/hex_tileHex Tile
procedural/noise_triplanar_fbmTriplanar FBMprocedural/noise_triplanar_billowTriplanar Billow
procedural/noise_triplanar_ridgedTriplanar Ridgedprocedural/noise_triplanar_voronoiTriplanar Voronoi
procedural/noise_triplanar_turbulenceTriplanar Turbulence

Animation

Node typeNameDescription
animation/uv_scrollUV ScrollTime-driven UV scroll
animation/flow_mapFlow MapFlow-map distortion
animation/sine_waveSine WaveAnimated sine
animation/ping_pongPing PongBack-and-forth oscillation
animation/windWindVertex sway for vegetation
animation/flipbook_uvFlipbook UVSprite-sheet frame UVs

Utility

Node typeNameDescription
utility/world_pos_maskWorld Position MaskMask by world position
utility/slope_maskSlope MaskMask by surface slope
utility/depth_fadeDepth FadeDistance-based fade
utility/dpdxDDXScreen-space x derivative
utility/dpdyDDYScreen-space y derivative
utility/fwidthFwidthabs(ddx) + abs(ddy)
utility/ditherDitherOrdered dithering
utility/hashHashDeterministic pseudo-random

Control

Node typeNameDescription
control/ifIfSelect A/B on a condition
control/static_switchStatic SwitchCompile-time branch
control/component_maskComponent MaskPick vector channels
control/greater_thanGreater ThanComparison → bool
control/less_thanLess ThanComparison → bool
control/equalEqualComparison → bool
control/not_equalNot EqualComparison → bool
control/andAndBoolean and
control/orOrBoolean or
control/notNotBoolean not

Scene

These read Bevy prepass buffers, so the camera must have the matching prepass enabled.

Node typeNameDescription
scene/pixel_depthPixel DepthLinear view-space depth of this fragment
scene/scene_depthScene DepthOpaque depth buffer (needs DepthPrepass)
scene/depth_fadeScene Depth FadeProximity fade to nearest surface (foam, soft intersection)
scene/scene_normalScene NormalWorld normal from the normal prepass
scene/motion_vectorMotion VectorScreen-space velocity (needs MotionVectorPrepass)
scene/refraction_uv_offsetRefraction UV OffsetScreen-UV offset for refraction
scene/screen_uvScreen UVFragment screen-space UV
scene/scene_colorScene ColorNot implemented — returns magenta (no grab-pass)
scene/env_map_sampleEnvironment Map SampleSample the scene env cubemap along a direction
scene/env_map_reflectEnvironment Map ReflectMirror/glossy reflection off the env cubemap

scene/scene_color is a placeholder: Bevy doesn't expose a grab-pass texture to custom-material shaders without a dedicated render-graph node, so this node currently outputs magenta.

Custom

Node typeNameDescription
custom/codeCustom CodeInline WGSL escape hatch — write shader logic the node library can't express

custom/code takes four vec4 inputs (a, b, c, d) plus a code string pin, and exposes result (vec4) and its rgb/x/y/z/w channels. The snippet runs in a generated helper function with the inputs in scope and assigns the result (pre-seeded to opaque black):

result = a * b + vec4<f32>(sin(c.x), 0.0, 0.0, 1.0);

It's the in-graph counterpart to a full code shader: reach for it when you only need a few lines of WGSL inside an otherwise node-based material.

Material functions (subgraphs)

A reusable subgraph is stored as a .material_function JSON file (a MaterialFunction: a name plus a MaterialGraph). The resolver loads *.material_function files sibling to a material. Inside a function graph you use the bracket nodes instead of an output node:

Node typeRole
function/input_pointOutputs the call site's four Vec4 inputs (in_0in_3)
function/output_pointReceives the four Vec4 returns (out_0out_3)
function/callInvokes a function by name; set input_values["function"] to the function name

At compile time each function/call inlines the function's WGSL helper at module scope and invokes it at the call site, so functions compose like ordinary nodes while staying visually encapsulated.

Code shaders

Besides node graphs, MaterialRef can point at a .shader file — a hand-written shader. renzora_shader ships four transpiling backends, all targeting WGSL for Bevy's pipeline:

Backend (language)HandlesNotes
BevyWGSL using #import bevy_*Bevy-flavored WGSL (naga_oil); default
WGSLraw WGSL@fragment, var<uniform>, …
GLSL.glsl, .frag, .vert#version, void main(), uniform/varying
ShaderToyShaderToy-style fragment codemainImage, iTime, iResolution

A .shader file is a JSON ShaderFile:

FieldTypeNotes
languagestringOne of the backend names above
shader_typeenumFragment (default), Material, or PostProcess
shader_sourcestringRaw source code
paramsobjectParameters parsed from @param annotations

The compiler transpiles to WGSL, injects a ShaderUniforms bind group (time, delta_time, resolution, mouse, frame) at @group(3) @binding(0) when the source references uniforms.*, and turns @param comment annotations into WGSL const declarations so parameter names resolve as variables. A Fragment shader previews on a mesh via CodeShaderMaterial; Material and PostProcess types are compile-checked.

#import bevy_pbr::forward_io::VertexOutput

// @param speed float 1.0 0.1 5.0 Animation speed
// @param tint  color 1.0 0.8 0.6 1.0

@fragment
fn fragment(in: VertexOutput) -> @location(0) vec4<f32> {
    let t = uniforms.time * speed;
    let r = 0.5 + 0.5 * cos(t + in.uv.x * 6.283);
    let g = 0.5 + 0.5 * cos(t + in.uv.y * 6.283 + 2.094);
    let b = 0.5 + 0.5 * cos(t + (in.uv.x + in.uv.y) * 3.141 + 4.189);
    return vec4<f32>(r * tint.r, g * tint.g, b * tint.b, 1.0);
}

@param syntax is // @param <name> <type> <default> [min max] [description], where <type> is float, vec2, vec3, vec4, color, int, or bool. Author and preview code shaders in the Shader Editor (the asset browser routes .wgsl/.glsl/.vert/.frag there).

Scripting

Material authoring is done in the graph, not in scripts. Lua registers exactly one material-related global:

-- Set the base color of the material on this entity. r, g, b, a are 0.0–1.0
-- floats; a is optional and defaults to 1.0.
set_material_color(1.0, 0.0, 0.0)        -- red
set_material_color(0.0, 0.4, 1.0, 0.5)   -- semi-transparent blue

set_material_color is Lua-only — it is not part of the Rhai subset. There are no set_material_property, set_material_emissive, or swap_material functions. In this release set_material_color emits a script action that the renderer does not yet consume, so for reliable runtime variation use material instances / MaterialOverrides or edit the graph directly.

To add your own node types to the graph, see Custom Material Nodes.