Skip to content

Author a Custom Material ​

Write your own material by dropping small snippets of WGSL into the engine's shading pipeline - a wind-sway vertex, a tinted surface colour, a perturbed normal, or a cutout mask - and assign it to an object.

Awaken materials are hook-based. Rather than writing a whole shader, you fill in up to four well-defined hook slots and the engine composes them into its standard lit shader (composeShader). Each slot is a short piece of WGSL that runs at a specific point in the pipeline, and you expose tunable values as params the hooks read as params.<name>. This tutorial builds one custom material from a fresh asset and assigns it, using the Shader Editor.

What you will build ​

A custom material - for example a gently swaying, tinted surface - authored from WGSL hooks with a couple of Inspector-editable params, then assigned to an object in your scene.

The four hook slots ​

The Shader Editor exposes exactly four slots, in pipeline order. Leave a slot blank and its marker is simply stripped - you pay for only what you use.

The table below is in execution order - the order the GPU runs them, and the order the editor lists them in.

SlotRuns atFill it to…
Vertex Positionvertex stage, after the model transform (before the clip transform)modify world - the world-space vertex position (a vec4<f32>) - e.g. wind sway
Discardfirst statement of the fragment stageearly-exit a fragment: if (…) { discard; } using in.uv / in.worldPos / params.*
Surface Normalfragment stage, before shadingperturb N - the mutable world-space normal - detail, waviness
Surface Colorfragment stage, before lightingset or adjust albedo (a mutable vec3<f32>, rgb) and outAlpha - tints, gradients, transparency

Discard runs before the albedo texture is sampled, so texel is not in scope there - it can't do texture alpha-testing. It doesn't need to: the template already discards at texel.a < 0.5 for every material. Use this slot for cutouts the engine can't guess - UV patterns, world-height clipping, param-driven dissolves.

Params can be float, vec2, vec3, vec4, color, bool, or texture, and every hook reads them as params.<name>. A few engine values are in scope too: globals.time (seconds, handy for animating sway) and globals.anim (0 while editing, 1 during Play - multiply your time by it to freeze animation in the editor), plus - in the fragment hooks - the interpolated varyings on in (in.worldPos, in.uv, in.color).

Hooks or a node graph

The Shader Editor has two create buttons. + New Shader authors the hook slots this tutorial uses; + New Shader Graph opens a node canvas that compiles to those same hooks and params. On a graph, the Tidy button in the Node Graph header auto-arranges the nodes left→right with the Surface node on the right (undoable), and the Fit button beside it frames the whole graph. This tutorial fills the hook slots directly.

Before you start ​

Steps ​

  1. Create the material. Open the Shaders tab (Shader Editor) and click + New Shader in the sidebar footer. Type a memorable name while the rename box is open. The new shader starts from a template that already compiles, so the preview sphere shows it at once.

    The Shaders tab after + New Shader: the shader list, the live preview, and the WGSL hook editors.

  2. Add params. With + Add Param, add the values your hooks will read - for a sway material, a float swaySpeed, a float swayAmount, and a color tint. Each becomes an editable row; hooks reference them as params.swaySpeed, params.tint, and so on.

    The param table after + Add Param: each row's name, type, default and scope, with the per-object box.

  3. Fill the hook slots. Write WGSL into the slots you need. For example, offset world in Vertex Position for sway (world.x = world.x + sin(globals.time * params.swaySpeed + world.z) * params.swayAmount;) and tint in Surface Color with albedo = albedo * params.tint;. Edits mirror live - the Viewport re-renders as you type. You don't have to guess whether the WGSL is valid: the editor compiles it for real as you type and shows any error as a squiggle on the offending line, with a ✓ compiles / ✗ N errors status beside the material name. If you do render a broken hook, the object falls back to the default shader rather than breaking the frame.

  4. Assign it. Select an object, press + Add Component… in the Inspector, add MeshMaterial, and pick your shader in its material field. See Materials for how the assignment is stored.

    A MeshMaterial component with the Wind shader picked in its material field, and the per-object overrides below.

  5. Tune. Adjust the params and watch the surface update. A param with its per-object box ticked can also be overridden on each entity from its MeshMaterial component in the Inspector - so one material can dress many objects with different colours or speeds, without duplicating it. Resetting an override clears it, so that object goes back to following the material's own default.

  6. Play and ship. The composed shader runs identically in Play and in the exported game - see Render Pipeline Overview and Export game.html.

The Shaders tab with a hook shader open: its animating preview and the Vertex Position hook's WGSL in the editor.

A complete example to read

The starter content ships materials/Wind.awakenmat, a hook shader that sways foliage from its Vertex Position hook with per-object phase so a row of identical plants does not move in lock-step. Import it with + Import in the Assets panel and open it in the Shaders tab to read a finished version of steps 2 and 3.

Where to go next ​

See also ​

Awaken: browser-native WebGPU game engine.