An AI-native 3D renderer + lightweight physics simulator — built to be driven by coding agents (e.g. Claude Code via MCP), aimed at robotics and synthetic-data use cases.
Status: 🌿 the native core has landed. One legible op-log is the source of truth; a first-party C++20 core builds it (byte-identical Python + C++ mesh kernels), a from-scratch path tracer (
mirage_render) shoots the ground-truth stills, and a native GL viewport (mirage_viewer) is the realtime preview — no external DCC. Multi-object scenes + physics ride OpenUSD + MuJoCo behind small interfaces. Full design & roadmap: docs/design.md.
Every image below is one op-log replayed through the native mesh kernel and shot with the in-repo path tracer — no external DCC, no fakes.
Beyond primitives — a passenger jet modeled entirely from the engine's own
operators: a surface-of-revolution fuselage (the lathe), lofted swept wings with
winglets, capped-cylinder engines on pylons, all mirrored for symmetry and given
a per-face livery, then path-traced. Reproduce with uv run python examples/airplane.py.
That op-log isn't a static export — it's a sequence of operations you can replay.
Here is that jet being modelled in Mirage's own viewport, assembling operator by
operator — fuselage (a lathe), wings and tailplane (lofted, then mirrored), the fin,
and the podded engines — then a gentle turn to show it off. Every frame is a headless
screenshot of the real native GUI (mirage_viewer) fed a growing op-log, so this is
the tool building the model, not a mock-up. The recorder is a reusable module —
mirage.capture.record_build(stages, …) films any op-log the same way, so every polished
case can be captured — regenerate this one (.mp4 for video, .gif for inline) with
uv run python docs/gallery/render_viewer_build.py.
A whole interior — every object native, and the engine composes it. A furnished
room where each thing is modelled from Mirage's own operators (the lathe turns the vase
and lampshade, bevel rounds the sofa, array stacks the shelves, boolean cuts the
window), assembled by the first-class place operator: the scene is a legible
op-log of place ops, each carrying its object's operators and a transform, so the
op-log stays multi-object and human/AI-editable — not baked geometry, not Python glue.
That op-log builds byte-identically in the Python kernel and the C++ core, and the path
tracer shoots it from a camera inside the room. Reproduce with uv run python examples/cases/18_interior_scene.py --hero.
Here is that room being built in Mirage's own viewport, in the editor's AUTO mode —
when the AI is driving the op-log the tool panel steps aside for a top-left status HUD
(what's being modelled right now), so the frame is all model: the lathe sweeping the vase,
boolean punching the window, bevel rounding the armchair, then each object placed — and
it settles onto a path-traced close-up of the finished scene (the real-time viewport for
the build, the first-party path tracer for the money shot, both off one op-log). Regenerate
with uv run python examples/cases/18_interior_scene.py --film (add ANIM_RAYTRACE=1 for a
fully path-traced promo pass).
And the same build rendered entirely by the first-party path tracer — every frame global
illumination, soft shadows, sky+sun — a promo pass (ANIM_RAYTRACE=1 … --film), kept clean at
low sample counts by the tracer's own edge-avoiding à-trous denoiser (--denoise):
(How large scenes scale, and where the layers used to bottleneck, is measured in
docs/scene-scaling.md — the composition seam that once forced a
manual merge is now closed by the place op.)
The model isn't a bag of geometry to poke at; it's a legible program. Give the op-log a
params block, arithmetic expressions in any numeric field, and a repeat loop, and the
whole form regenerates when you change one number — floors stacks storeys, twist spirals
them, taper pinches the silhouette. Five legible ops resolve to ~100. This is the thing a
puppet-an-app MCP can't do — and it's byte-identical in the C++ core and the Python kernel
(differential-tested), so a parametric op-log path-traces and loads in the GUI natively.
Sweep two parameters over that one program and you get a design space — 16 towers, each
path-traced and denoised (examples/cases/19_parametric_tower.py --grid):
…or animate a parameter and the structure morphs, every frame path-traced (--morph):
And it scales all the way up: the same machinery — params, expressions, nested repeat —
builds a whole classical temple (a stepped stylobate, a peristyle of columns on all four
sides, an entablature, a gabled roof with pediments) from 16 legible ops
(examples/cases/22_parametric_temple.py). Path-traced under a low, art-directed sun
(--sun-dir) so the colonnade rakes long shadows across the stone:
How big can it get? Two ceilings, far apart. Composing separate objects through the
legible place op is O(N²) — a wall around 1–2k objects. But a single mesh op sidesteps
that, and the tracer's BVH eats millions of triangles: this displaced-noise mountain range
is 1.28M triangles in one op, path-traced at 220 spp (measured on a 152-core box, 7.2M tris
render in ~42 s / ~8 GB). Numbers in docs/scene-scaling.md.
And not everything is generated. A large interior can be composed by hand — ~160 objects,
each modelled from operators (the lathe turns the vases and lamp bases, bevel softens every
cushion, boolean cuts the window) and placed into a considered layout — then lit and
surfaced for mood. Materials carry an emission, so the table and floor lamps are real area
lights the tracer samples; they also carry real PBR texture maps — an albedo, a roughness,
and a normal map each — sampled triplanar (projected off the world position, no UVs, no
seams), so the floor reads as planked hardwood with grain that catches the light in relief, the
console as figured walnut, and the upholstery as woven fabric. The starter map library is
generated procedurally (mirage.textures), but any CC0 PBR set drops in as three image files.
Shot at golden hour under a low, art-directed sun that rakes the window-mullion shadows across
the boards, with a thin-lens depth of field and a bloom glow so the window and lamps
read as real light sources rather than flat white (examples/cases/24_grand_interior.py):
And here is that room being built in Mirage's own viewport, in the editor's AUTO mode — a
moving camera through the open corner while the op-log assembles group by group: the boolean
punches the window, the lathe turns the vase, the armchair is blocked out sharp and then
bevel rounds it, and each piece is placed into the layout, the HUD naming the operator at
work. It settles onto the path-traced golden-hour close-up (the real-time viewport for the
build, the first-party tracer + denoiser for the money shot, both off one op-log). Regenerate
with uv run python examples/cases/24_grand_interior.py --film (add ANIM_RAYTRACE=1 for a
fully path-traced pass).
And the same build rendered entirely by the path tracer — every frame global illumination,
the warm sun streaming through the window and raking the mullion shadows across the grain,
the lamps glowing as real area lights — kept clean at low sample counts by the à-trous denoiser
(a fully path-traced promo pass, ANIM_RAYTRACE=1 … --film, rendered headless on a 152-core box):
An icon, read off photographs and published dimensions and pushed into shape by hand:
the Eames Lounge Chair (670 & 671), 130k faces, nothing imported or scanned
(examples/cases/25_eames_lounge.py). It measures within an inch of the real thing on
every published dimension — 31.9″ tall against a 31.5″ spec, a 17.1″ ottoman against
17.25″, 26″ wide against 26″ — because the model is checked against the numbers rather
than eyeballed against the photos.
The op-log has no vertex-addressing grammar — there is no "move vertex 47", by design.
So the control cage is sculpted entirely through re-evaluable queries. Nested box
selections are strict subsets, so a chain of band translates accumulates into a discrete
integral, and choosing each step as a finite difference telescopes the cage exactly onto any
curve. In two dimensions, quadrant boxes give a cumulative sum that the second difference
inverts — so any z(x,y) you can write down lands on the cage, and the shape stays a
function you can read. Three curved plywood shells, three buttoned cushions (the tufting is
real dimpled geometry), a five-star base of tapered blades, the ottoman.
Two engine features carry it. Semi-sharp creases (crease{on, weight}, the
DeRose/Kass/Truong scheme Pixar shipped) hold the plywood rims while subdivide takes each
shell to its limit surface — without them Catmull-Clark rounds every rim into a pillow. A
crease is measured in levels and decays one per subdivision, so a cube creased at weight 3
and subdivided 3× comes back exactly a cube, while a fractional 0.35 on the cushions is
the difference between a stitched leather welt and a foam block.
And smooth shading, which is what stops 130k faces from reading as facets. Each face
corner takes the area-weighted average of the faces meeting there whose normals lie within
--smooth-angle (default 30°) of its own; anything sharper is excluded, so the same vertex
shades smooth on one face and hard on the next. Nothing is authored — curvature is inferred
from the geometry. A cylinder's side goes round while its cap rim stays a crisp line, and a
cube is unchanged to the pixel:
And here is the chair being sculpted in Mirage's own viewport, in the editor's AUTO
mode — a slow dolly around the front quarter while the HUD names each operator as it
lands. A flat 10×10 grid bends under a chain of translate{on: box} queries, mirrors to
exact symmetry, gains its 14 mm of ply — and only then does subdivide take it to the
limit surface, the crease holding the rim crisp while everything else goes round. The rest
is assembly. It settles onto the path-traced beauty frame (real-time viewport for the
build, the first-party tracer + denoiser for the money shot, both off one op-log).
Regenerate with uv run python examples/cases/25_eames_lounge.py --film.
Because the op-log is legible, two versions can be diffed and 3-way merged like source code — a human at the GUI and an AI over MCP editing the same model on separate branches, then reconciling. Disjoint edits to different objects merge automatically; a spot both changed differently surfaces as a conflict (never silently lost). No opaque scene file can do this.
Below: one base scene, a human branch (recolour the vase, move the bowl) and an AI branch
(repaint the floor, add a book) — merge_by_key combines all four edits with zero conflicts,
and the render proves every one landed (examples/cases/20_diff_merge.py):
A puppet-an-app MCP is blind: it fires commands and can't tell what came out. Mirage's agent
can read the op-log, render it (first-party tracer + denoiser), look at the frame,
and edit the op-log to fix what it sees — a closed perception→action loop on its own
creation. Starting from a scene with deliberate, render-only flaws — a floating vase, a book
clipping it, a muddy bowl, a blown-out frame — it converges round by round, each edit derived
from the previous render (examples/cases/21_self_refine.py):
The core operators, one panel each (regenerate with uv run python docs/gallery/render_gallery.py):
| operator | what it is | |
|---|---|---|
| 1 | screw |
the helical sweep — a section revolved while climbing the axis → springs, threads, augers |
| 2 | curvature selector |
selection-as-query by mean dihedral: the flat-ish cap resolves apart from the round body |
| 3 | profile |
a first-class 2D generatrix — an open wire revolved makes a single-walled, hollow vase |
| 4 | boolean |
real BSP mesh-mesh CSG (union / difference / intersection) — here a cube minus a cylinder bore |
Each modeling operator is implemented byte-identically in the C++ core and the Python kernel and pinned by differential tests, so one op-log builds the same mesh in either engine.
Powerful DCC tools (Blender, …) have large, stateful automation surfaces that are awkward for programmatic/agent control. Full robotics simulators are excellent but heavy. Mirage takes the opposite bet:
- Scene = plain data. The whole world is one serializable object (JSON today, USD later). An agent can read it, diff it, edit it, and reproduce it deterministically.
- Tiny, swappable backends. A backend just consumes a
Scene:render(scene, camera)orstep(scene, dt)— MuJoCo behind both, permissively licensed. (Photoreal stills of a model take a different path: the op-log goes straight to Mirage's ownmirage_renderpath tracer.) - AI-native control surface. A first-class MCP server exposes the build/step/render loop as a handful of orthogonal tools, so Claude Code can drive Mirage out of the box.
- Light, fast, permissive. Python conducts; the heavy lifting is native — Mirage's own C++ mesh kernel and
mirage_renderpath tracer, plus OpenUSD and MuJoCo behind small interfaces. Apache-2.0, no GPL entanglement.
git clone https://github.com/saofund/mirage
cd mirage
pip install -e .
python examples/falling_box.pyThis repo ships a project-scoped MCP config (.mcp.json), so Claude Code
picks Mirage up automatically when you open this folder as the workspace:
pip install -e ".[usd,mujoco,mcp,demos]" # full surface: USD scene + MuJoCo physics/render + MCP
cd mirage # the project root, where .mcp.json lives
claude # approve the 'mirage' MCP server when promptedThen /mcp shows mirage connected. The agent can author (add_box,
add_sphere, add_cylinder, add_plane, add_camera, add_light), edit
(move, set_transform, set_material, set_velocity, remove, rename),
inspect & reproduce (get, list_objects, get_scene, set_scene,
diff_scene, save_scene, load_scene, get_log, replay_log), and
simulate & see (step, render). Every tool returns structured JSON;
render returns a PNG the agent can look at.
A portable skill ships with Mirage. skills/mirage/SKILL.md
(a Claude Code skill) and AGENTS.md (which OpenAI Codex reads
natively) teach any agent to set up, connect, and drive the engine — model
authoring, scene composition, rendering, and the performance rules — so a coding
agent is productive in one read.
Run the server standalone (for any other MCP client):
python -m mirage.mcp_serverSee docs/design.md for the v0.1 design & roadmap (and docs/architecture.md for the current scaffold). In one diagram:
agent (Claude Code)
│ MCP tools
▼
┌───────────┐ reads / writes ┌──────────┐
│ Engine │◀───────────────────▶ │ Scene │ (JSON / USD)
└───────────┘ └──────────┘
│ │
step() │ │ render()
▼ ▼
PhysicsBackend RenderBackend
(MuJoCo) (MuJoCo raster · mirage_render path tracer)




















