A 3D engine written in Aether, built to be driven by programs. One scene API over Vulkan and OpenGL; a rigid body engine of its own in the loop; skinned crowds of half a million; a Blender-to-engine pipeline; a scene editor; and a control channel through which a program, a test or an AI agent builds a scene, reads back what was drawn and checks the frame by number rather than by eye.
Unedited Vulkan frames from the current engine: examples/caustics.ae, a sea floor lit through the moving surface above it; examples/black_hole.ae, the disk and its jets ray-marched through the hole's curved space; examples/smooth_terrain.ae under AE3D_WEATHER=storm, rain over surface-nets terrain in a Gerstner sea; and the editor (editor/), an object as its components.
ae3d is the successor to Gopher3D, the same author's Go engine, rebuilt in Aether and taken further. The engine is Aether from the loop to the crowd's kernels; what remains in C is there for one stated reason each (native/README.md).
| Two renderers, one interface | Vulkan by default, OpenGL 4.1 at parity: one test draws the same scene through both and holds them to 0.7% of channels. DirectX 12 and Metal are the next implementations of the same vtable. |
| Physically based shading | Metallic/roughness materials, sixteen directional, point or spot lights a frame from any number, normal mapping, texel-snapped shadow maps, SSAO, screen-space reflections, ray-traced shadows with penumbrae and ambient occlusion by ray (Vulkan ray query), MSAA, TAA, DLSS, FXAA, bloom, ACES with an exposure that follows the frame, fog, wet surfaces. |
| A sky by the hour | engine_set_time_of_day(hours) places the sun and derives the light, the fog and a procedural sky. Volumetric clouds from baked Perlin-Worley textures, lit through a sun march and shadowing the ground, in about 1.5 ms. |
| Weather and water | Rain, snow, dust and storm over any scene: a hundred thousand particles stepped over the job pool, the sky gone overcast, lightning. A Gerstner sea with dispersion, fresnel, whitecaps and caustics from underneath. |
| Physics | aephysics, a rigid body engine written in Aether on Box3D's design -- hulls, meshes, joints of every kind, ragdolls that wear a skinned figure, vehicles, continuous collision, a parallel step that is the same to the bit at any thread count -- in the scene as a Rigidbody component and colliders on game objects. A character controller walks, runs and jumps up kerbs and stairs and off slopes: ten thousand random moves through the street end no deeper than the solver's 5 mm slop. |
| Crowds | A figure's walk baked into a pose bank, every instance posed in the vertex shader from its own phase, three tiers by distance ending in an impostor lit by the scene's lights, the sort on the device and the draws indirect. Half a million zombies in a handful of draws at 79 fps, feet planted, the simulation over every core. |
| Navigation | A flow field over the ground: one flood from the target, a direction per cell, read by every figure every frame. |
| Models from anywhere | glTF 2.0 with skins and animations, OBJ, and the engine's own Blender export with a manifest; any glTF figure is a horde in one call. Skies, sand and palettes painted from the engine's own noise. |
| Voxels and terrain | Voxel worlds as only the faces that show; surface nets over a distance field for smooth terrain. |
| An engine you can ask | AE3D_AGENT=port opens a JSON channel: read and change the scene, hold a frame, read its pixels, trace a model from its Blender object to the pixels it landed on. |
| Natural motion | ae3d.motion, an active ragdoll on any dressed figure: the animation played by joint motors within an adult's torque budget, a blow that knocks it off its pose and back or down, a struck limb that goes weak and recovers, a fall with the hands out and the head tucked, and getting up again face up or face down, handed back to the animation with nothing to jump (docs/motion.md). |
| Multiplayer in the engine | ae3d.net: host or join over UDP -- a handshake, acknowledgements, a reliable ordered channel of its own and fragments on the one socket -- TCP, or an in-process loopback, any of them with simulated latency, jitter and loss; networked objects replicated and interpolated, held to within a millimetre and a half of the host over a perfect link; players that walk the instant their client asks, predicted and reconciled against the host; snapshots quantised to a tenth of a millimetre and sent as changes against the last one each client acknowledged, a still scene 510 bytes a second where whole snapshots cost 24 KB; only what is near each client, within its budget of bytes, the most urgent first; a horde every peer simulates, its struck zombies handed to ragdolls the host simulates and every client draws; and the editor playing a scene as a host and its clients (docs/networking.md). |
| Input as a game names it | Actions and axes bound once to keys, mouse and gamepad, read by name from any script, injectable from a test or an agent. The camera they fly keeps out of the scene -- swept and slid, never through a wall or under the ground -- and one call frames a model of any size. |
| An editor | Objects as their components and the scene as one of them, as in Unity: a panel a component, drawn from what the component's kind declares, Add Component, edits across a selection (components). Hierarchy, gizmos, terrain sculpting, physics bodies and a Simulate button, Play as a host and up to four clients over a simulated link, each world shown in turn and its player walked from the keys, undo, scene files; opens any program's scene (AE3D_SCENE_OUT); one dark theme on every platform. |
Each row is a page in docs/ with the reasoning and the measurement behind it.
Requirements: the Aether toolchain
(ae, aetherc) on PATH, a C compiler, GLFW 3, zlib, pkg-config and
the Vulkan headers. The Vulkan loader is opened at run time; a driver is
optional.
brew install glfw molten-vk vulkan-loader # macOS
sudo apt install libglfw3-dev libvulkan-dev mesa-vulkan-drivers # Debian/Ubuntu
pacman -S mingw-w64-ucrt-x86_64-{gcc,glfw,zlib,pkgconf,vulkan-headers,vulkan-loader} # Windows, MSYS2 UCRT64git submodule update --init # deps/aephysics, the physics engine
ae run examples/spinning_cube.ae # build and run, as any Aether project
ae build examples/street_drive.ae -o build/street_drive # or a binary to keepThe toolchain alone builds every example: aether.toml names the physics
engine, and each module brings the C files and libraries it needs. build.sh
builds the same programs against the engine as one shared library, which is
what a program that loads scripts (the editor) needs, and the DLSS shim when
AE3D_STREAMLINE_ROOT names the Streamline SDK:
./build.sh examples/spinning_cube.ae && ./build/spinning_cube
./build.sh examples/street_drive.ae && ./build/street_drive # W/S A/D space shift; drives itself if left alone
./build.sh examples/zombie_city.ae && AE3D_CROWD=100000 ./build/zombie_city
AE3D_API=opengl ./build/zombie_city # the other rendererEvery program honours the same environment: AE3D_FRAMES=n to stop after
n frames, AE3D_HIDDEN=1 for no window, AE3D_SNAPSHOT=frame.png for
the last frame, AE3D_PERF=1 for the frame's cost by stage, AE3D_RAYS=1
for ray-traced shadows, AE3D_DLSS=n, AE3D_AGENT=port for the channel.
The full list, the build's options and the CI gate are in
docs/building.md. ./ci.sh is the whole gate: every
module type-checked, every suite and benchmark run, every example driven
hidden, the demo scene critiqued on both backends and held to its recorded
frame cost.
A program is written the way a Unity or gopher3D game is: game objects in the engine's scene, scripts on them with Unity's phases under Unity's names, and the engine calling the phases.
import ae3d.core
import ae3d.engine
import ae3d.behaviour
import ae3d.loader
struct Spinner { pitch: float, yaw: float } // the script's own state
start(state: ptr, go: *GameObject) { // once, first frame
cam = engine.engine_camera(engine.of(go))
core.camera_look_at(cam, behaviour.object_position(go))
}
update(state: ptr, go: *GameObject, delta: float) { // every frame
spin = state as *Spinner
core.model_rotate(behaviour.object_model(go), spin.pitch * delta, spin.yaw * delta, 0.0)
}
main() {
e = engine.engine_new()
cube = loader.cube(1.0)
core.model_set_scale(cube, 20.0, 20.0, 20.0)
spinner = Spinner { pitch: 18.0, yaw: 30.0 }
object = engine.object(e, "Cube", cube) // a game object with a mesh
engine.script(object, "Spinner", &spinner, start, update) // AddComponent
engine.engine_run(e)
engine.engine_free(e) // the scene and its models go with it
}
The phases each frame: fixed_update as many times as the fixed step
fits, update once, late_update after the draw; start once on the
first frame the object is in the scene. Physics is one more component:
p = physics.attach(e)
physics.rigidbody(crate, physics.DYNAMIC)
physics.box_collider(crate, core.vec3(0.5, 0.5, 0.5), physics.material(0.6, 0.0))
How the loop, the behaviours, the renderers and the job pool fit together: docs/architecture.md.
| Example | What it shows |
|---|---|
street_drive.ae |
The street driven, and walked: mesh and hull colliders, a car on wheel joints, skinned figures worn by sprung ragdolls, hit events; E gets out of the car and walks the street first-person on a character controller; the bystanders are active ragdolls the car knocks back or down, and they get up again (docs/physics.md) |
physics.ae |
AE3D_PHYSICS_SCENE=pyramid|pile|ragdolls|cloth, the reference's scenes |
net_walk.ae, net_cars.ae |
Multiplayer: ./build/net_walk hosts, AE3D_NET=join:127.0.0.1 ./build/net_walk joins; players walk a plaza, predicted, or eight cars round a ring, interpolated (docs/networking.md) |
zombie_city.ae |
The street in a city -- the block instanced around it to the horizon -- and its horde hunting the camera; AE3D_CROWD sets the count, AE3D_WEATHER the weather, AE3D_HUNT=0 lets the horde wander |
gltf_crowd.ae |
Any glTF figure as a horde, laid out in the figure's own measure and framed from a raised three-quarter view: AE3D_CROWD=100000 ./build/gltf_crowd figure.glb Walk |
gltf_viewer.ae |
Any glTF on a floor under a sun, playing one of its animations, framed whatever its size |
caustics.ae, smooth_terrain.ae, voxel_world.ae, sand.ae |
Water, terrain, voxels, a million point instances |
black_hole.ae |
Kerr geodesics per pixel (docs/black-hole.md) |
lights.ae, models.ae, blender_pipeline.ae, backend_switch.ae, spinning_cube.ae |
Materials and lights, OBJ, the Blender export, the two renderers, the smallest program |
Every scene is verified the way the engine is: from a sweep of camera positions and by numbers read back over the channel, not from one still (docs/testing.md).
The zombie street is not a downloaded asset: tools/blender/make_zombie_street.py
builds the buildings, the road, the rigged and textured zombie and its
walk in a headless Blender, and ae3d_export.py writes a deterministic,
manifested export the engine loads. Every build then runs a critique that
holds the scene to a standard (texel density, normal maps, planted feet,
lit windows) and a frame budget that fails on one extra triangle
(docs/pipeline.md).
editor/ is a scene editor whose chrome is
aether-ui: a hierarchy, an
asset browser, an inspector, a viewport you orbit and click in, a gizmo, a
sculpting brush, behaviours on objects, bodies and colliders with a
Simulate button that runs the physics in the viewport and puts the scene
back, undo (docs/editor.md). Any program's scene opens
in it: AE3D_SCENE_OUT=path writes the scene a program built, bodies
included, and the editor takes the file as its argument.
git clone https://github.com/aether-lang-dev/aether-ui.git ../aether-ui
./editor/build_editor.sh && ./build/ae3d_editor
AE3D_SCENE_OUT=build/street.json ./build/street_drive && ./build/ae3d_editor build/street.jsonsrc/ae3d/ the engine: core, engine, behaviour, input, jobs, vulkan, gl, shaders, physics,
crowd, horde, nav, ecs, gltf, assets, weather, water, sky, voxel, terrain,
agent, probe, script, scene, ... one module a directory
native/ the C that remains, by role: gpu/, image/, platform/, dlss/
deps/ aephysics, the physics engine, as a submodule
examples/ runnable scenes tests/ one program a suite
benchmarks/ per-frame cost, no window tools/ the agent client, viewer, critique, bench, bakers
scripts/ export, critique, perf editor/ the scene editor
docs/ the documentation, docs/images/ its pictures
Architecture · Building · Rendering · Physics · Crowds and navigation · Pipeline · Agent channel · Networking · Natural motion · Editor · Game UI · Performance · Testing · Writing Aether · Changelog
The engine is built toward an open-world game it has to carry -- hordes of the real animated zombie, extreme weather, the best picture the hardware gives, multiplayer -- and the map of what that still needs is #326: every line becomes an issue when it is next and lands as a measured, tested pull request. Done on that map:
- the crowd's sort on the device and its indirect draws;
- motion vectors, render scale, DLSS, and ray-traced shadows with the crowd in them;
- weather, physics and navigation;
- a character controller;
- natural motion: active ragdolls that fall the way a person does and get up (#414);
- multiplayer in the engine: UDP with a reliable channel of its own, snapshots quantised and as deltas against what each client acknowledged, relevance and a budget a client, predicted players, events and objects made mid-game, a horde simulated on every peer with its struck zombies handed to ragdolls, and the editor playing a scene as a host and its clients (#413);
- animated figures in scenes and the editor (#439);
- input mapping;
- the OpenGL renderer in Aether.
Next:
- the rest of the Vulkan renderer in Aether, a part at a time on Aether's own bindings (#402);
- the prompt-to-scene pipeline through the engine (#416);
- audio (#415), clustered lighting and streaming tiles;
- DirectX 12 (#311) and Metal (#312) beside Vulkan.
ae3d continues Gopher3D (MIT), the same author's earlier Go engine: the architecture, the GLSL programs, the material and lighting model and the OBJ loader's behaviour carry over; the Go served as the reference and none of it was copied. Built with GLFW and Vulkan (MoltenVK on macOS); the image decoders are Aether ports of stb_image's. See NOTICE and THIRD_PARTY_LICENSES.md.
MIT, see LICENSE.








