mirage-engine
Mirage is an immediate-mode 3D game engine, built on wgpu and winit,
with egui for the UI. It supports 3D graphics with lights and shadows,
models and animation from Blender, sprites with relief lighting, custom
shaders and post effects, egui UI, sound with spatial mixing, gamepad and
keyboard input with rebinding, parallel work across CPU cores, and save
data. Games run on desktop and in
the browser, packaged for itch.io in one command.
use *;
meshes!
/// Every clip this game plays, named by its file stem.
cargo doc --open is the API reference; every item documents its contract.
Examples
cargo run --example <name> runs one on the desktop and
cargo mirage serve --example <name> in the browser, where these screenshots were
taken.
The crates.io package ships without examples/assets, so check out the
repository to run the examples.
sprite-adventure
A simple two-area world: a pond with a custom water SurfaceStyle, a cave
through a portal in the hedgerow with torch lighting, and progress saved
across runs. The sprites are lit in 3D through relief maps, normals and
depths generated from each sprite sheet.
![]()
animation
A 3D character with basic movement controls, an Animator over ten states
with clips that loop, play once or blend by walking speed. A second elf
scrubs its animation by distance. Lit by a sun, a lamp post's shadowed point
light, a spot light over the damaging red patches, and a colorful butterfly
carrying a shadowed point light.

breakout-game
A paddle bats a ball at a grid of bricks under fixed-step physics, the ball and its spares the only lights in the court, with sound, music and egui menus.

isometric-board
A fixed diagonal view through Projection::orthographic: a click on a unit's
Ray::hit_aabb box selects it, a click on a tile orders it there, and a rock
is built by arithmetic per seed.

ui-fonts
Custom fonts in the UI: startup.set_fonts draws the UI with them, and
clicking a station plays a text animation.

input-lab
A gamepad panel built on the action system: every pad button, axis, stick, mouse button, wheel delta and pointer delta is a row in the table. Each row shows its live bindings, what it reads this frame, and buttons to rebind by capture or reset.

material-playground
Sky, lights, material and post chain controls on panels. Choose between
skyboxes and their lighting. Hold the right mouse button and drag to look,
W/A/S/D to move, Space/Left Shift up and down, and the wheel to
scale each move.

post-effects
A vignette, grain and scanlines over a lit scene, each a custom PostEffect
with live controls, showing how a game writes its own screen-space effects.

sound-lab
A set of sounds and spatial audio: one-shots, sustained cues, and three placed sources you drag around the room. Walk with WASD or a stick to hear the mix change.

flock-parallelism
A flock of butterflies, each one steered by separation, alignment and cohesion read from its last tick's own neighbors, run by a parallel iterator across the engine's own workers. Choose a flock size, then check the sequential box to see how much longer the tick takes without them.

stress-preview
A field of thousands of generated rocks instanced to the horizon. Set the instance count to see what the engine handles, with a report of what each frame costs.

The browser
The browser build needs a nightly toolchain with the standard library's
sources (rustup toolchain install nightly --component rust-src), so run the
command as cargo +nightly mirage serve or set nightly for the game's folder;
under stable it stops before it starts and says so. cargo-mirage and the
engine are released together: a build under a cargo-mirage of another minor
stops before it starts and names the release to install. Inside this repository, the tree's own is the one that matches:
cargo run --package cargo-mirage --release -- mirage serve --example breakout-game.
cargo mirage web alone writes dist/ and the zip that itch.io accepts as an HTML
game, with the page, the asset folders and a check against itch's upload
limits. That upload needs SharedArrayBuffer support turned on in the itch
project's embed options, or the game starts no worker and the page says so.
Every example above runs in the browser at
https://mirage-engine.pages.dev, the site site/build.sh writes.
PUBLISHING.md writes out the full process for a
game in its own repository, from the build through to the itch.io project
settings. Players need a browser with WebGPU: on Linux, Firefox still ships
it disabled (dom.webgpu.enabled in about:config), and Chromium-based
browsers work out of the box.
Assets
Export a .glb from Blender, list it in Config::with_assets, and pull each
model by name inside build(). Paths in with_assets are relative to the
working directory, which is why the examples name examples/assets/....
Every model is built at startup, so a missing model, texture or material is
a startup error rather than a blank draw.
The engine uses the names from Blender's outliner. Below are two example files, as the engine reads them:
examples/assets/breakout.glb
└── paddle object, the root node assets.mesh("paddle")
└── paddle mesh
├── Paddle Frame material draws as authored
└── Paddle Face material #[part("Paddle Face")]
examples/assets/elf.glb
├── Elf armature, the root node assets.mesh("Elf")
│ ├── ElfBody skinned mesh
│ │ └── elf_body_textures material
│ └── root bone hierarchy
│ └── pelvis …
└── actions one clip each
├── idle #[clip("idle")]
├── walk #[clip("walk")]
└── jog … #[clip("jog")]
A model is pulled by its root node's name, the object or the armature. When
two files share a root name, put the file stem in front: props#Ship.
Materials are named in a #[derive(Part)] enum so a draw can recolor them
one at a time; a material the enum does not name draws as authored. Clips
are named in a #[derive(Clip)] enum the same way. The paddle above:
use *;
/// The paddle in `examples/assets/breakout.glb`, pulled by its root node's name.
;
/// The material a draw can repaint, named as Blender names it.
meshes!
tools/hello_fixture.py is a scripted, reproducible Blender export to start
from.
Images: a spritesheet or any standalone .png is pulled with
assets.texture by its file stem; .pixelated() keeps its pixels crisp.
Sounds: .ogg files, one clip each, pulled with assets.sound by file
stem; .streamed() marks a long track to decode during playback.
Fonts: .ttf or .otf files, one font each. startup.font("pixel-operator")
reads one as an egui::FontData, and startup.set_fonts(fonts) makes the UI
draw in them from the first frame.
Known limits
- Textures hold no mip chain, so a textured model far from the camera sparkles.
Future work, in order
- WebGL2 fallback, only if itch players report coverage gaps.
- Directional sound sources: a placed sound gains a facing and a cone, the spot light's shape applied to audio, in the same shared level arithmetic.
doubleSidedimport: an unculled slot. Culling is pipeline state, so it forks pipelines the way cutout does.- Vertex colors, multiplied into the tint, common in low-poly found art.
KHR_materials_emissive_strength: one factor multiplied into the emissive lane.Colorbeside egui's colors: a conversion pair between the engine's linear channels, whose values go past1.0, and the sRGB-encoded color a picker edits, where the picker holds the tone and an intensity value scales that tone's light.
Out of scope
Decided against, so games can rely on the boundary:
- Physics simulation. Arcade physics is a few lines of game code, and a
real solver (Rapier) slots into the fixed-step
tickas ordinary game state. - Inverse kinematics.
- Terrain systems. A heightmap mesh can be constructed with
build(). Chunked streaming worlds are a different class of engine. - A scene graph, ECS, or editor. Games own their state in plain Rust. The engine draws what a frame submits.
- Networking.
- Other glTF material extensions (transmission, clearcoat, sheen, specular). The material set is closed; simple games do not need them.
License
Zlib OR Apache-2.0 OR MIT, at your option, for all three crates. The example
assets are CC0, cropped from ArMM1998's "Zelda-like tilesets and sprites"
(https://opengameart.org/content/zelda-like-tilesets-and-sprites); the
-relief.png maps beside them are derived from that art by
tools/spritemaps, and share its license. The animal models are CC0 from
Gobkit's "Free Animal Pack"
(https://gobkit.itch.io/gobkit-free-animal-pack); the chest is
CC0 from 3darknight's "3D Low Poly Chest"
(https://3darknight.itch.io/3d-low-poly-chest). The elf is p4ss0's
"PSX Red Elf" (https://p4ss0.itch.io/psx-red-elf), offered at no cost
and royalty free, bound by tools/elf_fixture.py to the CC0 skeleton and
clips of Quaternius's "Universal Animation Library"
(https://quaternius.itch.io/universal-animation-library) and weight
painted by hand in examples/assets/elf.blend, whose default export is
the .glb. The butterfly is Čestmír Dammer's ("CDmir") "Butterfly
(animated)" (https://opengameart.org/content/butterfly-animated), CC0,
its wings doubled by tools/butterfly_fixture.py in
examples/assets/butterfly.blend, whose default export is the .glb; its
texture is derived from photo documentation by Rick Hoppmann, as the
archive's own license file states. The six sky images are CC0:
sky-clear.png, sky-classic.png, sky-dawn.png and sky-sinister.png
from vladislavzh's "Retro Skyboxes Pack"
(https://opengameart.org/content/retro-skyboxes-pack), and
sky-stars-lightblue.png and sky-stars-blue.png from StumpyStrust's
"Space Skyboxes" (https://opengameart.org/content/space-skyboxes-0);
tools/sky_fixture.py stitches each set's six cube faces into the
equirectangular image an example loads. The example fonts are CC0 from
Jayvee Enaguas's "Pixel Operator"
(https://fontlibrary.org/en/font/pixel-operator), the proportional font
and the Mono font of that family. The display font is CC0 from Sora
Sagano's "Ferrum" (https://fontlibrary.org/en/font/ferrum). The
input-prompt glyphs are CC0 from Kenney's "Input Prompts"
(https://kenney.nl/assets/input-prompts), the Keyboard & Mouse font of
that pack. One model under tests/assets comes from elsewhere, and the
decoder's tests alone read it: robot-expressive.glb is the CC0
RobotExpressive of the three.js examples
(https://github.com/mrdoob/three.js). Every other file under
tests/assets is the repository's own, built by the tools/*_fixture.py
scripts.