Purpose. This file is the canonical entry point for anyone (human or AI agent) opening this repository cold. Read it first; it tells you what shipped, what is in progress, what conventions to follow and where everything lives. Keep it in sync with reality — every PR that changes scope should touch this file.
Last updated: 2026-05-24.
This is a fork of Phaser 4 that adds a minimal, web-first 3D
rendering pipeline alongside the existing 2D renderer. The 3D side
lives in its own folders so syncing with upstream Phaser stays cheap
(see rewrite.md for the exact list of patched 2D files).
- Engine fork: in
src/, with the 3D engine isolated in six new folders (see §3). - Reference sandbox (Vite + React + Monaco):
examples/vite-3d/. ~30 scenes that exercise every shipped feature. - Comparative sandbox:
examples/three/(Vite + Three.js). The same stress-test and instancing demos rebuilt on Three.js for honest cross-engine draw-call comparison. - glTF samples (Khronos):
examples/gltf-samples/(read-only reference; copy individual models intoexamples/vite-3d/public/assets/gltf/as needed).
The renderer ships behind a webpack build flag (WEBGL3D_RENDERER),
so a Phaser build without the flag behaves like vanilla upstream.
# from the repo root
npm install
npm run build # produces build/phaser.js (~9.9 MB UMD)The examples/vite-3d sandbox consumes the freshly built
build/phaser.js via its dev plugin, so iterating on the engine is
"rebuild the engine, refresh the sandbox tab".
cd examples/vite-3d
npm install
npm run dev # http://localhost:5173 (or next free port)
# or for a static drop you can host anywhere:
npm run build && npx serve distThe Vite plugin also copies build/phaser.js into dist/phaser-build.js
on vite build, so the resulting dist/ is fully self-contained.
cd examples/three
npm install
npm run dev # http://localhost:5273Two pages: / (stress, one mesh per cube) and /instanced.html (one
THREE.InstancedMesh). Used for benchmarking against the equivalent
Phaser scenes in examples/vite-3d/#/stress-test and #/instanced-cubes.
Six folders, all new — they do not collide with upstream Phaser.
| Folder | What it contains |
|---|---|
src/renderer/webgl3d/ |
WebGL3DRenderer, MaterialManager, Material, GLSL shaders (8 base + 4 instanced + 4 skinned variants). |
src/cameras/3d/ |
Camera3D, PerspectiveCamera, OrthographicCamera, Frustum, Ray3D, CameraManager3D. |
src/gameobjects3d/ |
Object3D, Mesh3D, SkinnedMesh3D, InstancedMesh3D, Billboard3D, BlobShadow3D, primitives (Cube, Plane), GameObjectFactory3D. |
src/lights3d/ |
Light3D base + Ambient, Directional, Point, Spot subclasses + LightManager3D. |
src/loader3d/ |
GLTFFile, GLTFParser, GLTFAsset (glTF 2.0 loader). |
src/animation3d/ |
AnimationMixer3D (linear blend skinning + crossfade). |
Patched 2D files (9 of them) are listed in rewrite.md. That is
the file to consult when syncing with a new upstream Phaser.
This is the closed list of what the engine can do today. Every entry
maps to documentation in docs/WEBGL3D.md and at least one example
in the sandbox.
- Render type
Phaser.WEBGL3D = 9(build-flagged). - WebGL2 only; throws at
CreateRenderertime if the device lacks WebGL2. - 8 base shader programs:
unlit_color,unlit_textured,lit_color,lit_textured+ their:skinnedvariants for linear blend skinning. - 4 additional
:instancedshader programs for GPU instancing (color × textured, lit and unlit; no skinned-instanced yet). - Per-camera linear fog, vertex snapping (
u_snap) and affine UV mapping (u_affine) for stylized looks. - Colour space toggle:
webgl3d.colorSpace: 'linear' | 'srgb'Game config flag. - Frustum culling per mesh (bounding sphere test against camera frustum planes).
renderer.statswithdrawCalls,meshesDrawn,meshesCulled.
- Lit / unlit families × color / textured shapes. Smooth or flat
shading toggle (
shading: 'smooth' | 'flat'). - Emissive (
emissive,emissiveIntensity,emissiveTexture). - Normal maps (
normalTexture,normalScale), TBN derived in the fragment shader from screen-space derivatives (no TANGENT attribute required). - Ambient occlusion (
occlusionTexture,occlusionStrength). - Specular (Blinn-Phong) (
specular,shininess). - Texture transform (
textureTransform: { offset, scale, rotation }), glTFKHR_texture_transformcompatible. - TEXCOORD_1 (
texCoord: 0 | 1). - Alpha modes:
transparent,alphaTest,depthWrite,blendMode: 'normal' | 'additive' | 'multiply'. - Vertex colours (
vertexColors: true).
- Slots:
ambient(1),directional(1),points[](up to 4),spots[](up to 4). All consumed by everylit_*shader. - Presets:
lights3d.preset('studio' | 'moody' | 'none'). - Spot lights with inner / outer cone half-angles; cone cosines are precomputed on upload.
PerspectiveCameraandOrthographicCamera(inPhaser.Cameras.ThreeD).- High-level helpers on
Camera3D:fixed(position, target),follow(target, { offset, lookAtOffset }),orbit(target, { distance, yaw, pitch }),firstPerson(position, yaw, pitch),clearControl(). getRay(x, y, width, height, out?)for picking.setFog(color, near, far)linear fog.
Object3Dbase class with parent / children / world matrix.Object3D#lookAt(target, yawOnly?).add3D.cube,add3D.plane,add3D.material,add3D.gltf,add3D.existing,add3D.remove.add3D.raycast(ray, roots?)andadd3D.raycastFromPointer(pointer)for picking (bounding-sphere precision).add3D.billboard(camera-facing plane) andadd3D.blobShadow(contact shadow projected on a horizontal plane).root.findNode,root.findNodes,root.findMesh,root.findMesheson glTF instances.add3D.instancedMesh,add3D.instancedCube,add3D.instancedPlanefor GPU instancing viagl.drawElementsInstanced.InstancedMesh3DexposessetMatrixAt,setPositionAt,setPositionScaleAt,setColorAt,commitInstances().
.glband.gltf+ external buffers / images.- Mesh primitives:
POSITION,NORMAL,TEXCOORD_0,TEXCOORD_1,COLOR_0,JOINTS_0,WEIGHTS_0, indices. - Node hierarchy (TRS or 4×4 matrix), skins (up to 64 joints),
animations (
LINEARandSTEPsamplers on TRS channels;CUBICSPLINEdowngrades to LINEAR with a warning). - PBR metallic-roughness →
lit_color/lit_texturedwith downgrade notes for the ignored bits (metallic, roughness, etc.). - Extensions honoured:
KHR_lights_punctual(directional, point, spot),KHR_materials_unlit,KHR_texture_transform. alphaMode: OPAQUE / BLEND / MASKall handled (MASK via shader- side alpha test).
AnimationMixer3Dwithplay(clipName, opts),stop,stopAll,crossFade, weight blending, looping. Auto-subscribes to sceneUPDATEevent by default.
Stable URL pattern: #/<id>. Each example has a "Code" tab driven by
@monaco-editor/react that shows the source verbatim. Source code
lives in examples/vite-3d/src/examples/<id>.js.
hello-cube,primitives,stylization
camera-fixed,camera-follow-orbit,camera-firstperson
material-lit-vs-unlit,emissive-pulse,normal-maps,occlusion-map,specular,alpha-modes,texture-transform,texcoord1
lights-punctual,spot-lights,spot-flashlight,light-presets,color-space
scene-graph-orbits,lookat-tracker,tweens
gltf-static(DamagedHelmet),gltf-vertex-colors,gltf-unlit,gltf-animated,gltf-skinned(Fox)
picking-raycast,blob-shadows-billboards,find-mesh
hero(synthwave skyline + WebP foreground overlay + mouse parallax),mini-game-arena,stress-test(1 mesh per cube, CPU draw-call ceiling),instanced-cubes(singleInstancedMesh3D, 100 k cap, 1 draw call).
Full registry: examples/vite-3d/src/examples/registry.js.
| File | Purpose |
|---|---|
STATE.md (this file) |
Project entry point + status. |
rewrite.md |
List of upstream 2D files we modified, with patches and a sync procedure. Required reading before pulling a new Phaser version. |
docs/WEBGL3D.md |
Primary API reference for the 3D pipeline. |
docs/WEBGL3D_GAME_OPPORTUNITIES.md |
Honest matrix of game genres viable with the current renderer (Spanish). The English version of the same data lives in examples/vite-3d/src/data/game-ideas.js and surfaces on the gallery home page. |
changelog/v4/4.0/CHANGELOG-v4.NEXT.md |
All 3D-related entries appended over the development of this fork. |
examples/vite-3d/README.md |
Sandbox-specific guide: how to add a new example, where assets live, how the Game ↔ Code toggle works. |
examples/three/README.md |
What is and is not identical between the Phaser and Three.js stress / instancing ports. |
feature-to-implement/webgl3dmix-renderer.md |
Full plan for the next feature: see §8. |
.cursor/skills/webgl3d-api/SKILL.md |
Agent-facing skill: keeps Cursor / Codex / Claude in line with the project's conventions when editing 3D code. |
- No emojis in code, comments, file names, console output. The project is intentionally plain-text.
- Tabs for indentation in source, 4-space inside docstrings and Markdown.
- JSDoc on every public method / class / property of the 3D engine, mirroring the upstream Phaser style.
- Module pattern: every public class lives in its own file under
the right
src/<area>/folder and is re-exported from the area'sindex.js. - All edits outside
src/{renderer/webgl3d, cameras/3d, gameobjects3d, lights3d, loader3d, animation3d}/must be gated behindif (typeof WEBGL3D_RENDERER)so a build without the flag stays upstream-shaped. Seerewrite.mdfor the full disciplines list. - No reformats of upstream 2D files — only the surgical changes required to wire the 3D path in.
These were settled during the development sessions; revisit only with strong reason:
- WEBGL3D is a separate renderer, not an addition to the 2D
WebGLRenderer. This is what keepsrewrite.mdshort. InstancedMesh3Dis the answer to "too many draw calls", not automatic batching. State sorting / batching is on the backlog but optional.- Skinning + instancing in the same shader is out of scope today.
We ship
*_skinnedand*_instancedas separate shader families; combining them adds complexity not justified by current demand. - Frustum culling is per-mesh, not per-instance.
InstancedMesh3Dpasses the whole batch as one drawable; spatial subdivision is the user's job. - Foreground / HUD layering for v1 of the sandbox uses DOM
overlays. The "Phaser-native" path is the proposed
WEBGL3DMIXrenderer (see §8). - 2D Phaser GameObjects (
add.image,add.text, etc.) do not render underWEBGL3Dtoday. Same reason: the 2D pipeline is a different renderer.WEBGL3DMIXexists precisely to close that gap. - Stylization knobs (
vertexSnap,affineUV,shading: 'flat') are off by default. Only the#/stylizationexample shows them on purpose. Earlier demos had them enabled on the floor and read as "broken UV mapping" to the casual observer.
Read .cursor/skills/webgl3d-api/SKILL.md. It is the contract for
how an agent should edit / extend the 3D code.
A new render type that lets a single Phaser.Game host both 3D
(add3D.*) and the full 2D GameObject toolkit (add.image,
add.text, add.particles, …) on the same canvas frame.
- Status: proposed, not started.
- Plan document:
feature-to-implement/webgl3dmix-renderer.md. - Approach chosen: two
<canvas>stacked withz-index, onePhaser.Game, two renderers that each create their own context. Zero modifications toWebGLRenderer.js(so the 2D core stays syncable from upstream with no extra conflicts). - Estimated effort: 1–2 weeks of focused work.
- Why it matters: it is the feature that turns this project from "Phaser with an experimental 3D renderer next to it" into "Phaser that draws 3D, with the full 2D toolkit available on top".
The plan's own Sprint 1 is the keystone (the composer + new render type); if that compiles and renders one frame correctly, the rest is plumbing.
- Material batching / state sorting in the 3D render loop. Today
it walks the display list in insertion order; sorting by
(program, texture, material)would cut state changes ~30 % on large scenes. - Skinned + instanced shader variants. Useful for crowds of animated NPCs. Multi-week work; defer until real demand surfaces.
- Particle system 3D (
ParticleEmitter3D). The instancing path is the right substrate; add a CPU / GPU integrator on top. - Area lights (disc / line). Ambient + directional + point + spot ship today; area lights are the next lighting tier.
- glTF morph targets. Currently dropped at load with a warning. Required for face animation in character viewers / VNs.
- Draco / KTX2 / meshopt via peer dependency. Out of scope for the core repo; document the third-party path instead.
- Cross-platform polish: smoke-test the gallery on Safari / iOS WebKit. Today's CI is desktop Chromium.
add.imagebridge that secretly creates aPlane3D. Rejected in favour ofWEBGL3DMIX(see §8). The bridge would lose pixel-perfect 2D rendering and most of the 2D GameObject niceties.- Bundled Rapier3D physics. Physics will be a peer dependency or a separate package, never bundled into Phaser core.
- Full WebGPU rewrite. Out of scope.
@monaco-editor/react4.7 (sandbox only) — Code tab in examples.react/react-dom/react-router-dom— sandbox shell.three0.169 (examples/three/only, never linked into the Phaser core) — comparative benchmark.- No new runtime dependencies in the engine itself. It still ships with the same set as upstream Phaser.
When a new session opens, do the following before touching code:
- Read this file (top to bottom). It is short on purpose.
- Read
rewrite.md. Knowing which 2D files are patched prevents accidental edits that break upstream sync. - Read
.cursor/skills/webgl3d-api/SKILL.md. Style + conventions. - Skim
docs/WEBGL3D.mdTable of Contents to find which section covers the area you are about to change. - If asked to add a new example, follow the recipe in
examples/vite-3d/README.md(create aSceneclass, register inregistry.js, document any new asset underpublic/assets/). - If asked to add a feature to the engine: drop new files in the
relevant
src/<area>/folder; add the export in that folder'sindex.js; add a JSDoc block; add at least one example in the gallery; add an entry inchangelog/v4/4.0/CHANGELOG-v4.NEXT.md; only edit upstream 2D files if absolutely required and document the change inrewrite.mdin the same PR. - Run
npm run buildat the repo root andnpm run buildinexamples/vite-3dbefore signing off. Lints clean.
Welcome.