The scene & material file format for the Hyperion and Theia renderers — a small C++23 library that parses TOML scene/material files and OBJ geometry into plain CPU data structures.
Aether — primordial deity of the bright upper air and pure light the gods breathe; the medium through which light travels. Aether is the medium: it carries a scene's description, untied to any renderer or GPU.
flowchart LR
SM["slang-math<br/>math"] --> A["<b>Aether</b><br/>file format"]
SM --> H
A --> H["Harmonia<br/>shared Vulkan lib"]
H --> Hy["Hyperion<br/>path tracer · ground truth"]
H --> T["Theia<br/>real-time renderer"]
Aether is fully independent: it has no Vulkan, no GPU, and no renderer dependencies (only slang-math for math and toml++ for parsing). It turns text files into plain CPU structs; consumers (Harmonia and the renderers) handle all GPU upload.
| Parses | Into |
|---|---|
<name>.scene.toml (TOML scene description) |
aether::SceneDesc (camera, render settings, env, tonemapper, post-tonemap renderer, geometry blocks with TRS + material refs, node graph with compounded TRS, material-library references) |
<name>.materials.toml (TOML OpenPBR material library) |
aether::MaterialDesc (OpenPBR Surface v1.1.1 parameters + texture references) |
| Wavefront OBJ (geometry only — triangles only; OBJ material directives ignored) | aether::MeshData / aether::MeshGroup (deduplicated vertices + indices, local space) |
Geometry stays in OBJ (bulk vertex/index data); every other element — scene, camera,
render settings, tonemapping, materials — is TOML, so files are small, single-purpose,
and easy to edit (by hand or by an agent). TOML is the best compromise between human
readability and token efficiency: compared to equivalent pretty-printed JSON, the TOML files
save up to one third of the token usage when foreign files are read by an LLM/agent — less
syntactic noise (no braces, fewer quotes), more content per token. Minified JSON would be
denser still, but is no longer readable or editable by humans — and unlike JSON, TOML
supports comments. Geometry is returned in object/local space;
world placement lives on the scene's geometry blocks (TRS). Colors are kept in their declared
input color space, tagged with MaterialColorSpace, so the consumer performs any color-space
conversion.
OpenUSD and glTF are both excellent formats — but for different jobs than Aether's.
glTF is a runtime delivery format: binary buffers optimized for engines to load, not
for humans or agents to read and edit. Its 2.0 core material is metallic-roughness;
OpenPBR is not yet fully supported — it is only partially reachable through material
extensions (KHR_materials_*, still evolving) — and glTF 2.0 does not yet cover complex
scenes. OpenUSD is a powerful composition and interchange system (layering,
references, variants) but is heavyweight and complex — far more than a small hobby
renderer needs, and not something you hand-edit in a text box.
Aether deliberately takes the best of both and stays small and agentic-friendly:
- Human- and agent-readable/editable text (like neither format's binary payloads) — small, single-purpose TOML files with comments, cheap for an LLM to read and generate.
- OBJ for bulk geometry — the one thing that genuinely needs a compact vertex/index container (glTF's buffers, but simpler and ubiquitous), kept separate from the text.
- Lightweight composition — scene/camera/render/tonemap presets referenced and overridden (a small nod to USD layering) without USD's machinery.
- OpenPBR verbatim — material parameters use the exact OpenPBR Surface 1.1.1 names, rather than glTF's or USD's own material models, so nothing is lost in translation.
The result: files small enough to diff and hand-edit, structured enough to compose, and faithful to OpenPBR — the sweet spot glTF and USD each sit to one side of.
-
Scene (
<name>.scene.toml): top-levelmaterial_librariesarray;[render],[camera],[tonemap]and[post_tonemap]tables; a[[mesh]]array of unique geometry declarations and an ordered[[instance]]array that places a mesh by name with a TRS and material. A[[mesh]]entry usestype = "box"(half_extents) ortype = "sphere"(radius), or is omitted /"object"for an OBJpath; an optionalbounds = { min=[...], max=[...] }table authors the object-space AABB (otherwise the renderer derives it from the vertices; generate it withtools/obj_bounds.py). Keys are spelled in full words for clarity (samples_per_pixel,environment_map,vertical_field_of_view,mesh,half_extents,material, …). The[render]table may select the scene-referred working color space viaworking_color_space = "lin_rec2020_scene" | "lin_rec709_scene"(absent → consumer default, Rec.2020); consumers convert all assets to it on load. -
Node graph (optional
[[node]]array): parent/child nodes that compound transforms. A node has a uniqueid, an optionalparent(absent/empty = root), the same TRS keys as an instance (translate,rotatequaternion orrotate_x/rotate_y/rotate_zEuler,scale), and optionalmesh+material/materialsbindings — a node without a mesh is a pure transform grouping node. A node also carries optional agent metadata:description(prose: what this node/subtree is) andtags(categorization keywords) — pure AI context, never affecting the resolved instance list. The parser validates the graph (duplicate id, unknown parent, cycle are errors naming the offending id) and resolves it into the flat instance list: world = parent_world × local (T × R × S), one instance per meshed node, so several nodes may reference the same mesh (instanced subtrees). Flat[[instance]]blocks keep parsing exactly as before and stay first in the instance list; node-derived instances are appended after them — both may coexist in one scene.[[mesh]] name = "ball" type = "sphere" radius = 0.5 [[node]] id = "arm" translate = [0.0, 1.0, 0.0] rotate_y = 30.0 [[node]] id = "grip" parent = "arm" mesh = "ball" translate = [0.5, 0.0, 0.0] # world placement compounds the arm's TRS material = "Ceramic"
-
Agent metadata (optional
[meta]block): pure AI context — the renderer never reads it, and absent or wrong-typed keys parse leniently (never an error). Scene-level:schema,version,model,title,description,units,generator, plusassembly(prose: how the scene fits together — what the parts are and how they connect). Per linked file:[meta.link."<path>"]summaries (kind,description,bounds,triangles,materials) so an agent can understand the scene without opening the links. One compact example:[meta] title = "Shaderball rig" assembly = "The arm node parents the grip; the grip places the ball mesh." [[node]] id = "grip" parent = "arm" mesh = "ball" description = "Grip tip of the arm - this subtree is the end effector." tags = ["rig", "end-effector"]
-
Camera preset (
<name>.camera.toml): a small standalone preset file withtranslate, rotation (rotatequaternion orrotate_x/rotate_y/rotate_zEuler),vertical_field_of_viewandev100keys. It is meant to be referenced from the scene file's[camera]section. -
Setting presets (
presets/<name>.<group>.toml): the[render],[camera]and[tonemap]and[post_tonemap]sections may carry areference = "presets/…"key pointing at a standalone preset file (top-level keys, same shape as the inline section). The referenced file is applied first as a base, then any inline keys override it (local wins). Settings shared across scenes therefore live in a single deduplicated preset file (e.g. all Cornell scenes sharepresets/cornell.camera.tomlandpresets/preview.render.toml). References resolve relative to the scene directory. -
Materials (
<name>.materials.toml): one TOML table per material (keyed by name), using OpenPBR Surface v1.1.1 parameter names verbatim; optional top-levelcolorspaceandmodel(default"openpbr"— the material model tag, anticipating additional material models in the future). Texture bindings use Aether'smap_*keys. -
OBJ: geometry only. Non-triangle faces are skipped and all
usemtl/mtllibdirectives are ignored — materials are assigned exclusively from the scene file.
See tools/README.md for the Blender scene exporter and the MaterialX-to-TOML
converter. The Blender addon exports a .scene.toml, a matching .camera.toml
and OBJ files; OpenPBR materials stay in TOML and can be authored separately or
generated from MaterialX OpenPBR libraries.
Tip: in VS Code, install the Even Better TOML extension for syntax highlighting, formatting, and (with a JSON Schema) live validation of the
.tomlfiles.
Requirements: CMake 3.28+, a C++23 compiler, vcpkg (provides toml++; slang-math is pulled via CMake FetchContent).
cmake -S . -B build -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_TOOLCHAIN_FILE="<vcpkg-root>/scripts/buildsystems/vcpkg.cmake"
cmake --build buildGoogleTest-based unit tests (pure CPU — no GPU required):
cd build && ctest --output-on-failureHyperion, Theia and Harmonia pull Aether via CMake FetchContent and link the
aether::aether target.
| Library | Purpose |
|---|---|
| slang-math | Vector / quaternion math (header-only, via FetchContent) |
| toml++ | TOML parsing for the .scene.toml / .materials.toml formats |
| GoogleTest | Unit testing (tests only) |
The material model follows the OpenPBR Surface specification; MaterialX is the
canonical node reference used by the tools/ MaterialX→TOML converter.
- OpenPBR Surface v1.1.1 — Academy Software Foundation. https://academysoftwarefoundation.github.io/OpenPBR/ · reference repo
- MaterialX — specification and node library (OpenPBR
open_pbr_surface). https://materialx.org/ · https://github.com/AcademySoftwareFoundation/MaterialX - Wavefront OBJ — geometry container (triangles only; material directives ignored).