Phaser AE

3D Mesh Shaders

Pro packv3.0.0-43DFX

A look of its own for one 3D object, written as a short WGSL snippet that replaces the standard material: a cube of molten lava, a hero on fire, an ice statue, a hologram, a shield bubble, a gold idol. Nine recipes work by name, and show and hide cross-fade with the object's own look, so a power-up can come and go.

This is a Pro pack. Upgrade to Phaser Pro to use it in your games.See Phaser Pro →

Released October 14, 2026 · updated October 20, 2026

3D Mesh Shaders: a screenshot of its demo
A note about these graphics

Expansion packs don't come with graphics. This screenshot is one way of drawing what the pack does, made for its demo: none of the sprites, tiles or backgrounds in it are in the pack.

That's by design. A pack is a game's logic, mechanics and effects, and it works with whatever art you give it. Some packs draw effects, backdrops or 3D scenes of their own, but none carries a game's sprites.

So the look is yours. Make the art on phaser.io, bring your own, or let your agent draw it in code: the same pack can look like anything.

The pack

meshShaders

function meshShaders(): Pack<{ meshShaders: MeshShadersApi }>

The mesh-shaders pack: game.meshShaders. Compose it beside render-3d (and so a 3D world); it draws nothing until a shader is put on an object. Supports WebGPU and WebGL2.

Helpers

checkMeshShader

function checkMeshShader(def: MeshShaderDef, opts: { maxCode?: number } = {}): string | null

Check a mesh shader definition the way the runtime takes it, with no GPU — the names, the limits, the shell, while and loop, texture calls, the length (8000 characters unless maxCode says otherwise) and the WebGL2 translation. A snippet that fails here would not draw on WebGL2, so content from a tool or a model goes through this first. Returns the problem in one sentence, or null.

MESH_SHADER_RECIPES

const MESH_SHADER_RECIPES: Readonly<Record<MeshShaderRecipe, MeshShaderRecipeDef>>

The built-in mesh shaders, by name.

Functions

checkMeshShaderWith

function checkMeshShaderWith(def: CheckedMeshShader, translate: SnippetTranslator, opts: { maxCode?: number } = {}): string | null

Check a mesh shader definition the way the runtime takes it, with no GPU: the name, the sixteen-param and four-colour limits, reserved, clashing and redeclared names, while/loop (a loop needs a constant bound or it can hang the GPU), texture calls (the object's own colour is base), the blend, the shell, the length (default 8000 characters) and the WebGL2 translation (translate is render-webgl2's wgslSnippetToGLSL). Returns the problem in one sentence, or null.

meshShaderGLSL

function meshShaderGLSL(code: string, keys: ReadonlyArray<string> = [], colorKeys: ReadonlyArray<string> = []): string

The GLSL fragment stage a mesh shader runs as on WebGL2: the same scope and finish as meshShaderWGSL, with the snippet translated by render-webgl2. Throws, naming the construct, when the snippet will not translate.

meshShaderScope

function meshShaderScope(paramKeys: ReadonlyArray<string>, colorKeys: ReadonlyArray<string> = []): Record<string, string>

What a snippet can see, by GLSL type, as the WebGL2 translator is told it: the template's names, every param and colour.

shellProblem

function shellProblem(shell: unknown): string | null

A shell's problem in one sentence, or null.

shellVertices

function shellVertices(verts: Float32Array): Float32Array

Interleave a mesh's vertices (render-3d's eight floats: position, normal, uv) with each vertex's shell offset — the direction a push of 1 moves it, the same for every vertex at one position. Pure; no GPU.

Constants

MESH_SHADER_HELPERS

const MESH_SHADER_HELPERS: Readonly<Record<string, { args: string[]; ret: string }>>

The helper functions the template declares beyond the shared hash21, noise2 and fbm2, as the translator is told them.

MESH_SHADER_RESERVED

const MESH_SHADER_RESERVED: ReadonlySet<string>

The names a param or colour may not take: the template's own (its scope, its locals, its uniforms and functions in both dialects), the built-in functions and types a snippet calls, and the reserved words of WGSL and GLSL ES 3.0 — a param called length would hide the function, and one called sample would not compile on one backend.

MESH_SHADER_VERT_GLSL

const MESH_SHADER_VERT_GLSL: string

The GLSL vertex stage: render-3d's GL instance transform (rotate3d, verbatim) from the object record, and the push.

meshShaderWGSL

const meshShaderWGSL = (code: string, keys: ReadonlyArray<string> = [], colorKeys: ReadonlyArray<string> = []): string=> …

The WGSL a mesh shader runs as: the template around a definition's snippet, with its params and colours named by keys and colorKeys in order. Group 0 is render-3d's shared camera uniform, the atlas and its sampler, and the sun's cascades; group 1 is the draw's object record (dynamic offset). For tools and tests; the runtime builds it once per definition.

SHELL_MAX_LAYERS

const SHELL_MAX_LAYERS: 8

The most shell layers.

SHELL_MAX_PUSH

const SHELL_MAX_PUSH: 1

The largest shell push, as a fraction of the object's size.

Types

CheckedMeshShader

interface CheckedMeshShader

The shape a check reads: a mesh shader definition.

  • name: string — Its name.
  • code: string — The WGSL snippet.
  • params?: Record<string, number> — Its params and defaults.
  • colors?: Record<string, string> — Its colours.
  • blend?: MeshShaderBlend — How its surface composes.
  • shell?: MeshShaderShell | null — Its shell.
  • shadow?: boolean — Whether it casts the sun's shadow.
  • twoSided?: boolean — Whether back faces draw too.

MeshShaderBlend

type MeshShaderBlend = 'opaque' | 'alpha' | 'add'

How a mesh shader's surface is composed into the scene.

MeshShaderDef

interface MeshShaderDef

One mesh shader as data: a WGSL snippet and what it needs. The snippet sees, in scope:

name what
uv the mesh's uv, vec2
pos object-space position, vec3: the mesh's own coordinates before the object's scale and turn (−0.5..0.5 across a primitive); on a shell, pushed out with it
wpos, center, size world-space position; the object's centre; its scale per axis, vec3
normal, view, fresnel world normal (unit; facing the camera on a back face); unit vector to the camera; 1 − saturate(dot(normal, view))
base the object's own colour × its texture at uv, premultiplied, vec4
light the scene's diffuse light here as a colour, vec3: hemisphere ambient plus the sun with its shadow (0..1+; base.rgb * light is the plain matte look)
toSun, sunColor unit vector toward the sun; its colour × intensity, vec3
shell 0 on the surface; on a shell layer, layer / layers (1 on a single shell)
time, TAU seconds (the sim clock, plus the handle's own offset); a full turn
hash21, noise2, fbm2 the backdrops' 2D hash and noise, 0..1
hash31, noise3, fbm3 the same in 3D (fbm3 is four octaves, 0..0.94)
params, p0..p15 every param by name and by position, f32
colours, c0..c3 every colour by name and by position, vec4

It writes color, premultiplied.

  • name: string — The name add finds it by. A letter, then letters, digits, - or _.
  • description?: string — One line for a menu: what it looks like.
  • code: string — WGSL: the fragment body. color starts transparent black; leave the premultiplied result in it. No fn, while, loop or texture calls.
  • params?: Record<string, number> — The params and their defaults, in p0..p15 order. At most sixteen.
  • colors?: Record<string, string> — The colours and their default hex strings, in c0..c3 order. At most four.
  • blend?: MeshShaderBlend — How the surface composes. 'opaque' (the default): alpha is ignored, it writes depth among the opaque meshes. 'alpha': premultiplied over what is behind, in the blended pass, sorted back to front with the transparent meshes. 'add': color.rgb is added to the scene (alpha ignored), in the blended pass.
  • shell?: MeshShaderShell | null — A shell standing off the surface — see MeshShaderShell. The snippet runs there with shell above 0.
  • shadow?: boolean — Cast the sun's shadow — as the plain solid mesh, not as what the shader draws. Default true.
  • twoSided?: boolean — Draw back faces too, with the normal turned to face the camera (a dissolve sees into the object). Default false.

MeshShaderEase

type MeshShaderEase = EaseName | `${'sine' | 'quad' | 'cubic' | 'quart' | 'quint' | 'expo' | 'circ' | 'back' | 'elastic' | 'bounce'}${'In' | 'Out' | 'InOut'}` | EaseFn

An easing for tween, show and hide: a maths EASE name ('easeOutCubic', 'linear', 'sine'), the title file's spelling ('cubicOut'), or a function of 0..1.

MeshShaderHandle

interface MeshShaderHandle

One object wearing a shader, returned by add. Every write after remove (or the object's death) is ignored.

  • readonly name: string — The shader's name.
  • readonly entity: Handle — The object it is on.
  • set(param: string | MeshShaderValues, value?: number): void — Set one param live, or params and colours together. Throws on a name the shader does not declare or a value that is not finite. Stops a tween of that param.
  • setColor(name: string, css: string): void — Set one colour live from a CSS colour. Throws on an unknown name.
  • tween(params: Record<string, number>, opts?: MeshShaderTweenOptions): Promise<void> — Ease params to new values on the sim clock. Resolves when they arrive (or are set over, or the handle goes).
  • show(opts?: MeshShaderTweenOptions): Promise<void> — Ease the shader in over the object's own look (fade to 1).
  • hide(opts?: MeshShaderTweenOptions): Promise<void> — Ease it back out to the object's own look (fade to 0). The handle stays: show brings it back.
  • readonly fade: number — 0..1: how much of the shader is showing. At 0 the object is drawn as if it had none.
  • time: number — The shader's own clock, seconds: the sim clock plus an offset. Set it to scrub or to start a look at a moment.
  • readonly alive: boolean — False after remove, a scene change or the object's death.
  • remove(): void — Take the shader off; the object goes back to its own look. Idempotent.

MeshShaderOptions

interface MeshShaderOptions

What add takes beside the object and the shader.

  • params?: Record<string, number> — Params over the definition's defaults.
  • colors?: Record<string, string> — Colours over the definition's defaults.
  • fade?: number — Start at this fade, 0..1; default 1 (on at once). Pass 0 and call show() to ease it in.

MeshShaderRecipe

type MeshShaderRecipe = 'lava' | 'burning' | 'ice' | 'hologram' | 'forceField' | 'gold' | 'chrome' | 'dissolve' | 'toon'

A built-in mesh shader's name.

MeshShaderRecipeDef

interface MeshShaderRecipeDef

A built-in mesh shader: a MeshShaderDef-shaped record.

  • name: string — Its name.
  • description: string — One line: what it looks like.
  • code: string — The WGSL snippet.
  • params: Record<string, number> — Its params and defaults.
  • colors: Record<string, string> — Its colours.
  • blend?: MeshShaderBlend — How its surface composes (default opaque).
  • shell?: MeshShaderShell — Its shell.
  • shadow?: boolean — Whether it casts the sun's shadow (default true).
  • twoSided?: boolean — Whether back faces draw too.

MeshShadersApi

interface MeshShadersApi

game.meshShaders.

  • add(entity: Handle, def: MeshShaderDef | string, opts?: MeshShaderOptions): MeshShaderHandle — Put a shader on a 3D object: a definition, or the name of one this game defined or of a built-in recipe ('lava', 'burning', 'ice', 'hologram', 'forceField', 'gold', 'chrome', 'dissolve', 'toon'). An object wears one shader; adding another replaces it. Scene-scoped. Throws on an object that is not a 3D mesh, an unknown name, or a definition checkMeshShader refuses (the message says why).
  • define(def: MeshShaderDef): void — Define a shader this game owns, so add finds it by name (a built-in recipe's name takes the recipe's place). Defining a name again REPLACES it, live: objects that put it on by name take the new definition, keeping their values by param name. An identical definition changes nothing. Throws when the check refuses it.
  • get(entity: Handle): MeshShaderHandle | null — The handle on an object, or null.
  • clear(): void — Take every shader off.
  • readonly count: number — How many objects wear a shader.
  • readonly available: ReadonlyArray<string> — Every name add takes: this game's definitions, then the built-in recipes.
  • failure(name: string): string | null — Why the shader named name is not drawn (its object back in its own look): its compile error (WGSL on WebGPU, GLSL on WebGL2), or null. It arrives a frame or two after the shader is first drawn. For tools that show a shader's errors.

MeshShaderShell

interface MeshShaderShell

A SHELL: the mesh drawn again around itself, pushed out along its smoothed normals and added on top, for flames, glows and force fields that stand off the surface. The snippet runs again there with shell above 0.

  • push: number — How far out the outermost layer sits, as a fraction of the object's size on each axis (0.25 is a quarter). 0 < push ≤ 1.
  • layers?: number — How many layers, 1 to 8, evenly spaced out to push; layer i of n has shell = i / n. Default 1. Each is weighted 1 / n, so the sum keeps its brightness whatever the count.

MeshShaderTweenOptions

interface MeshShaderTweenOptions

How long a tween, show or hide takes and along which curve.

  • duration?: number — Seconds of sim time; default 0.5. 0 is at once. Throws when negative or not finite.
  • ease?: MeshShaderEase — The curve; default 'easeInOutQuad'. An unknown name throws.

MeshShaderValues

interface MeshShaderValues

Params and colours to set together: handle.set({ params: { heat: 2 }, colors: { flame: '#4af' } }).

  • params?: Record<string, number> — Params by name.
  • colors?: Record<string, string> — Colours by name, as CSS colours.

SnippetTranslator

type SnippetTranslator = (code: string, scope: Record<string, string>, helpers: Record<string, { args: string[]; ret: string }>) => string

The translator a check runs, as render-webgl2's wgslSnippetToGLSL: it throws naming what it cannot translate.