embedded-3dgfx
A no_std 3D graphics and physics engine for embedded systems, optimized for resource-constrained devices. Features real-time rendering, rigid body dynamics, soft body physics, skeletal animation, and visual effects.
This is a fork of embedded-gfx by Kezii. This fork adds texture mapping, fog/dithering effects, DMA rendering, Z-buffer improvements, anti-aliasing, a complete physics engine, and Quake-style environmental effects.
What's new in 0.4.0
- Breaking:
fixed_math.rsandlut.rswere removed. Q16.16 fixed-point arithmetic and the sin/cos lookup table now live inembedded-dsp'sfixed-point/lutmodules (identical algorithms, ported verbatim, now shared withembedded-guiinstead of duplicated).Q16,to_q16,from_q16,ScanlineInterp, and friends are now only exported when thefixed-transformfeature is enabled (previously unconditional).fixed-transformnow impliesdsp.- The legacy
to_fp/from_fp/mul_fp/div_fpaliases are gone — use the canonicalto_q16/from_q16/mul_q16/div_q16names instead. - No rendering behavior change — verified bit-identical output against the prior implementation.
What's new in 0.3.0
- Perspective-correct textures — clip-space W is propagated through the rasterizer; UV coordinates are now divided by W per pixel, eliminating the affine swim on non-axis-aligned surfaces
- Sector lighting — Doom-style per-sector brightness scaling applied at mesh level, zero per-vertex cost
- UV ray-casting —
mesh_ray_castnow returns barycentric-interpolated UV at the hit point alongside distance, world position, and face normal - HUD overlay —
hudmodule provides a fixed-capacity overlay for text and icon elements drawn after the scene pass - record/execute pipeline (introduced in 0.2, stabilized in 0.3) — scene traversal and rasterization are explicit separate phases; command buffers can be replayed without re-traversing the scene graph
Recent additions
- Textured meshes via record/execute —
RenderMode::Textured+K3dengine::execute_with_textureslet the normal mesh pipeline draw texture-mapped triangles (previouslyexecute()silently dropped them; textures required bypassingrecord/executeentirely). Textured and flat-colored/lit meshes can be recorded together in onerecord()call. Seeexamples/mesh_texture_demo.rs. - Particle system — fixed-capacity, no-alloc billboard emitter (
ParticleSystem<N>). Supports color and size interpolation over particle lifetime, gravity/acceleration, and camera-facing billboards. Integrates with the record/execute pipeline viasys.record(&engine, &mut cmd_buf). - Runtime depth fog —
K3dengine::set_fog(FogConfig)enables per-pixel linear depth fog applied during rasterization across both mesh and BSP paths. - Dynamic point lights —
K3dengine::add_point_light(PointLight)registers runtime point lights with squared-distance falloff that are composited as an additive RGB565 tint on top of baked/directional lighting at face granularity. - BSP room-strip builder (std) —
bsp::builder::build_room_stripconverts high-level room specs into valid BSP lumps for quick tooling/tests/examples.
Features
3D Rendering
- Full MVP pipeline with perspective projection and frustum/backface culling
- Z-buffering with 16.16 fixed-point depth testing
- Flat and Gouraud shading, directional lighting, Blinn-Phong specular
- Perspective-correct UV texture mapping with multi-texture support (RGB565)
- Sector lighting (Doom-style per-mesh brightness scaling)
- Runtime depth fog (
K3dengine::set_fog) - Dynamic point lights with additive tint (
K3dengine::add_point_light) - Particle system — no-alloc billboard emitters with color/size over lifetime (
ParticleSystem<N>) - Bayer 4×4 dithering, billboards, vertex animation
- Anti-aliased lines and triangles (heuristic and per-pixel coverage modes)
- LOD system with distance-based mesh switching
- DMA double-buffer rendering via
swapchain - HUD overlay with text and icon elements
Record/Execute Pipeline
engine.record(meshes, &mut cmd_buf, telemetry)— traverse scene graph, emit draw commandsengine.execute(fb, &mut frame, &cmd_buf, telemetry)— rasterize commands to framebufferengine.execute_tiled(...)— tile-binned execution for partial display updates
Physics Engine (feature: physics, opt-in)
- Rigid body dynamics — linear/angular motion, forces, torques
- Sphere, AABB, and capsule colliders with impulse-based collision response
- Distance, ball-socket, and fixed joint constraints
- Ray casting with hit point, face normal, and UV coordinate
Skeletal Animation
- Parent-child bone hierarchies with linear blend skinning (LBS/SSD)
- Up to 4 bone influences per vertex
Soft Body Physics (feature: physics, opt-in)
- Mass-spring systems for cloth, jelly, and deformable objects
- Volume preservation via pressure simulation
UI Animation
- Rigid transform animation tracks for splash screens and menus
- Tweening with easing functions (
Tween3,Easing)
Screenshots
To regenerate all screenshots and GIFs:
Installation
[]
# Embedded (no_std)
= { = "0.4", = false }
# With physics
= { = "0.4", = ["physics"] }
# Desktop / simulator with all features
= { = "0.4", = ["std", "physics"] }
Basic Example
use ;
use Vector3;
let mut engine = new;
engine.camera.set_position;
let geometry = Geometry ;
let mut mesh = new;
mesh.set_render_mode;
// Record draw commands, then rasterize to framebuffer
let mut commands = new;
engine.record.unwrap;
engine.execute.unwrap;
Particle System
use ;
use ;
// Fixed capacity — no heap allocation
let mut sys: = new;
// Spawn a burst of sparks from the origin
sys.spawn;
// Per-frame: integrate physics, then append billboard quads to the command buffer
let gravity = new;
sys.update;
sys.record;
// engine.execute(...) renders particles alongside meshes
Dynamic Point Lights
use PointLight;
use Point3;
let mut engine = new;
// Register up to MAX_POINT_LIGHTS per frame
engine.add_point_light;
// Lights are automatically applied during engine.record()
// as additive RGB565 tint at face granularity — no extra draw pass needed.
engine.record.unwrap;
// Clear each frame to update light positions
engine.clear_point_lights;
Depth Fog
use FogConfig;
let fog_color = new; // dark blue haze
engine.set_fog; // near, far
// Fog is applied per-pixel during rasterization across all render modes.
// Clear the display to the fog color so distant surfaces blend seamlessly:
display.clear.unwrap;
engine.record.unwrap;
engine.execute.unwrap;
// Disable fog
engine.clear_fog;
Physics Example
use ;
use Vector3;
let mut world = new;
world.set_gravity;
// Dynamic sphere
let sphere_id = world.add_body.unwrap;
// Static floor
world.add_body.unwrap;
// Advance simulation (8 constraint-solver iterations)
world.;
// UV-aware ray cast
let ray = new;
if let Some = world.ray_cast
Skeletal Animation
use ;
use Vector3;
let mut skeleton = new;
let root = skeleton.add_bone.unwrap;
let arm = skeleton.add_bone.unwrap;
skeleton.update_transforms;
skeleton.compute_inverse_bind_poses;
let mut skinning = new;
skinning.add_vertex.unwrap;
// Animate, then deform mesh
skeleton.get_bone_mut.unwrap.set_rotation;
skeleton.update_transforms;
apply_skinning;
Soft Body
use SoftBody;
// Pre-built cloth pinned at the top edge
let mut cloth = create_cloth.unwrap;
cloth.set_gravity;
cloth.ground_plane = Some;
cloth.step;
let mut positions = ;
cloth.get_vertex_positions;
Available pre-built shapes: create_cloth, create_jelly_cube, create_soft_sphere.
Examples
31 interactive examples — run any with:
Rendering
| Example | Description |
|---|---|
basic_rendering |
Render mode cycling: points, lines, solid |
rotating_cube |
Animated 3D transformations |
scene_viewer |
Interactive multi-mesh scene |
lighting_demo |
Directional lighting with ambient |
gouraud_demo |
Smooth per-vertex color interpolation |
blinn_phong_demo |
Specular highlights |
fog_dithering_demo |
Atmospheric fog + Bayer dithering |
texture_mapping_demo |
Perspective-correct UV textures |
retro_presets_demo |
Toggle Doom/PSX/Modern retro rendering presets |
bsp_builder_demo |
Runtime BSP room-strip building + textured BSP render |
dma_rendering_demo |
Double-buffer DMA performance |
billboard_demo |
Camera-facing quads |
lod_demo |
Distance-based mesh LOD switching |
vertex_animation_demo |
Keyframe vertex morphing |
painters_algorithm_demo |
Painter's algorithm ordering |
boot_menu |
Boot splash + menu transitions (96×64, tweens) |
stl_viewer |
Load and view STL models |
Physics (requires physics feature)
| Example | Description |
|---|---|
physics_rolling_ball |
Beginner intro — gravity, friction, ramps |
physics_bouncing_balls |
Restitution coefficients compared |
physics_pendulum |
Constraint-based swinging |
physics_newtons_cradle |
Momentum conservation via distance constraints |
physics_stack_tower |
Stacking stability with friction |
physics_domino_chain |
Chain-reaction angular dynamics |
physics_wrecking_ball |
Heavy ball vs light boxes |
physics_demo |
Comprehensive physics showcase |
capsule_physics_demo |
Capsule collider interactions |
skeletal_animation_demo |
Bone hierarchy and linear blend skinning |
cloth_simulation |
Hanging cloth with wind |
jelly_cube_demo |
Deformable cube with volume preservation |
raycast_demo |
Ray casting, hit detection, UV lookup |
Physics Reference
Creating bodies
// Dynamic body
let id = physics.add_body.unwrap;
// Static body (floor, wall)
physics.add_body.unwrap;
Distance constraints
physics.add_distance_constraint.unwrap;
Stepping and sync
// Step with 8 constraint-solver iterations
physics.;
// Sync physics state to render meshes
for &id in &body_ids
Capacity
new // 16 bodies, 8 constraints (const generics, no heap)
Troubleshooting
| Symptom | Fix |
|---|---|
| Bodies fall through floor | Increase solver iterations; check collider types match |
| Constraints are stretchy | Increase solver iterations; set compliance to 0.0 |
| Simulation explodes | Reduce timestep; add damping; verify inertia tensors |
| Poor performance | Reduce max contacts; fewer substeps; prefer sphere colliders |
Feature Flags
| Flag | Default | Description |
|---|---|---|
physics |
off | Rigid body dynamics, soft body, and ray casting (physics + softbody modules) |
std |
on | Enables painter's algorithm (Vec) and includes perfcounter |
perfcounter |
off | FPS/timing measurements (requires std or embassy-time) |
embassy-time |
off | Timing source for embedded targets |
aa-heuristic |
on | Heuristic edge AA for triangles, no extra buffer |
aa-coverage |
on | Per-pixel coverage AA; eliminates shared-edge seam at cost of a W×H byte buffer |
row_width_96 |
— | Optimize row buffers for 96 px wide displays |
row_width_160 |
— | Optimize row buffers for 160 px wide displays |
row_width_240 |
— | Optimize row buffers for 240 px wide displays (default) |
row_width_320 |
— | Optimize row buffers for 320 px wide displays |
dsp |
off | Pulls in embedded-dsp for shared numerics (currently: quaternion normalization in skeleton.rs) |
fixed-transform |
off | Fixed-point screen-space projection path. Implies dsp — Q16.16 arithmetic lives in embedded-dsp's fixed-point feature (shared with embedded-gui) rather than a copy in this crate |
dwt-profiler |
off | DWT cycle-counter profiling hooks |
triple-buffering |
off | Triple-buffered swapchain APIs |
row_width_* flags are mutually exclusive. aa is an internal flag enabled automatically by either AA feature.
Caps and Telemetry
Technical reference docs in docs/:
| Doc | Topic |
|---|---|
caps-and-telemetry.md |
Record/execute pipeline, profile caps, telemetry API, CI budget enforcement |
backend-integration.md |
Board bring-up checklist, memory sizing, hardware profiling, smoke tests, compatibility matrix |
asset-pipeline.md |
Offline converter CLI, chunked scene format, cooperative streaming loader, CI budget reporting |
System Requirements
Minimum:
- ARM Cortex-M4F with FPU
- 128 KB RAM (single buffer, small scenes)
- 256 KB Flash
Recommended:
- ARM Cortex-M33 with FPU (e.g. STM32WBA)
- 512 KB+ RAM (double buffer + Z-buffer + physics)
- 512 KB+ Flash
Memory at 240×135:
| Feature | Cost |
|---|---|
| Single framebuffer | 65 KB |
| Double framebuffer | 130 KB |
| Z-buffer | 130 KB |
| Physics (16 bodies) | ~4 KB |
| Soft body (64 particles) | ~2 KB |
| Skeleton (8 bones) | ~1 KB |
| Particle system (256 particles) | ~6 KB |
| Point light set (8 lights) | <1 KB |
Budget at 240×135 @ 60 FPS:
- Rendering: ~10–13 ms/frame
- Physics (16 bodies): ~2–3 ms/frame
- DMA display transfer: ~3 ms (parallel)
Performance
Desktop benchmarks at 320×240 (cargo run --release --example screenshots --features std):
| Scene | Triangles | p50 release | p50 debug |
|---|---|---|---|
| Wireframe cube | 12 | 0.05 ms | 3.6 ms |
| Blinn-Phong Suzanne | ~960 | 0.09 ms | 6.6 ms |
| Physics (5 balls + floor) + render | ~800 | 0.08 ms | 8.2 ms |
Desktop figures are on a modern x86-64 CPU. On ARM Cortex-M33 @ 64 MHz expect roughly 10–13 ms/frame for mid-complexity scenes at 240×135.
Key optimizations in the record/execute pipeline:
- micromath transcendentals —
sin/cos/atan2routed through micromath onno_std, replacing soft-float libm; ~15–25% reduction in per-frame transform cost on Cortex-M4 - 16.16 fixed-point z-buffer — depth values stored as
u32, cutting memory bandwidth on 32-bit bus targets - Separate record/execute phases — command-buffer replay avoids re-traversing the scene graph on unchanged frames
Architecture
src/
lib.rs # Engine entry point: K3dengine, record/execute API
camera.rs # View/projection matrices
mesh.rs # Geometry, LOD, render modes
draw.rs # Rasterization, shading, fog, effects
renderer.rs # FrameCtx, execute_commands, tiled execution
command_buffer.rs # Fixed-capacity command buffer
particles.rs # No-alloc billboard particle system
lights.rs # Dynamic point lights, PointLight, PointLightSet
sector_lights.rs # Doom-style per-sector brightness scaling
config.rs # ProfileCaps, QualityTier, DegradationPolicy
error.rs # RenderError, BudgetKind
physics.rs # Rigid body dynamics (feature: physics)
character.rs # Character controller
skeleton.rs # Skeletal animation, linear blend skinning
softbody.rs # Mass-spring soft body physics (feature: physics)
texture.rs # Texture management, RGB565
billboard.rs # Camera-facing quads
animation.rs # Keyframe vertex animation
transform_anim.rs # Rigid transform animation tracks
tween.rs # Tweening and easing functions
swapchain.rs # DMA double/triple buffering
display_backend.rs # Display abstraction layer
bridge.rs # embedded-graphics bridge
input.rs # Input event handling
painters.rs # Painter's algorithm (std only)
retro.rs # Retro rendering presets (Doom/PSX-style)
hud.rs # HUD overlay elements
scene_format.rs # Serialized scene chunk format
scene_stream.rs # Cooperative chunk streaming
hardware_profile.rs # Target hardware profile definitions
perfcounter.rs # FPS/timing measurements
telemetry.rs # Record/execute telemetry types
tilebin.rs # Tile-bin stats and config
bsp/ # BSP tree builder, PVS, and traversal (Doom-style levels)
load_stl/ # STL file embedding macro
examples/ # 31 interactive demos
tests/ # 320 library unit tests + integration/regression suites (~400 total)
Q16.16 fixed-point math (fixed-transform feature) and the sin/cos lookup table formerly in this crate's own fixed_math.rs/lut.rs now live in embedded-dsp, shared with embedded-gui; see the dsp/fixed-transform rows in Feature Flags.
Testing
Git Hooks (Rustfmt Guardrails)
Versioned hooks live in .githooks/ and can be installed into .git/hooks:
Installed behavior:
pre-commit: runscargo fmt --alland re-stages formatted staged Rust files.pre-push: runscargo fmt --all --checkand blocks push on formatting drift.
Contributing
Contributions welcome. Priority areas:
- Hardware-specific display backends (ESP32, STM32, RP2040)
- Spatial partitioning (octree/BVH for broad-phase collision)
- Additional joint types (hinge, slider, prismatic)
- Mesh colliders (convex hulls)
References
Graphics:
- Tricks of the 3D Game Programming Gurus
- Michael Abrash's Graphics Programming Black Book
- PSX Graphics Programming
Physics:
License
The contents of this repository are dual-licensed under the MIT OR Apache 2.0
License. That means you can choose either the MIT license or the Apache 2.0
license when you re-use this code. See LICENSE-MIT or
LICENSE-APACHE for more information on each specific
license. Our Apache 2.0 notices can be found in NOTICE.