scena
Rust 3D library
scena is an easy-to-use, lightweight 3D library for Rust applications on native and
browser targets. It provides scene graphs, glTF/GLB loading, cameras, lights, materials,
picking, controls, headless rendering, GPU rendering, and deterministic rendered-output
tests through a simple Rust API.
The aim of the project is to make 3D in Rust as straightforward as building a scene, loading a model, adding a camera and light, and rendering the result.
| DamagedHelmet | WaterBottle |
|---|---|
![]() |
![]() |
These are original rendered-output artifacts produced by scena.
Why scena
Rust applications benefit from a focused rendering layer: a library that lets an application say "here is my scene, my assets, my camera, and my surface; draw it predictably."
scena is that layer.
| If you need | scena gives you |
|---|---|
| A Rust replacement for the common Three.js scene workflow | Scene, Assets, and Renderer with typed handles and structured errors |
| glTF/GLB model-viewer behavior | import, instantiate, frame, inspect, animate, pick, and connect authored anchors |
| CAD and industrial visualization | units, axes, handedness repair, connector metadata, labels, helpers, and deterministic placement |
| Native plus browser targets | wgpu/native foundations, WASM packaging, browser WebGPU/WebGL2 proof lanes, and explicit platform capabilities |
| Reliable render loops | explicit prepare() / render() lifecycle that keeps fallible work in predictable host-visible steps |
| Release-quality visual confidence | rendered-output artifacts, browser proof, benchmarks, doctor gates, and release-readiness checks |
scena owns the visual layer: scene graph state, assets, cameras, lights, materials,
interaction data, diagnostics, and rendered-output proof. Host applications keep their
domain model in their own code and drive scena through typed renderer APIs.
Quick start
Clone and run a real viewer example:
Run the deterministic headless render example used by CI-style workflows:
Compile every public example:
Install
From Git:
[]
= { = "https://github.com/johannesPettersson80/scena" }
From a sibling checkout:
[]
= { = "../scena" }
Cargo features:
| Feature | Purpose |
|---|---|
controls |
platform-neutral orbit, pan, zoom, and focus controls |
controls-winit |
native-host control adapter support |
controls-web |
browser-host control adapter support |
browser-probe |
browser/WASM proof entry points used by CI lanes |
inspection |
scene inspection metadata for debugging, docs, and reproducible examples |
icc |
ICC/color-management support through lcms2 |
ktx2 |
KTX2/Basis texture descriptors for KHR_texture_basisu assets |
meshopt |
meshopt-compressed glTF buffer decoding support |
obj |
OBJ import feature path |
Happy Path
Start with the product workflow: load or create assets, build scene state, prepare once, then render prepared frames. The shortest examples are glb_model_viewer, camera_framing, orbit_controls, picking_selection_hover, and headless_ci.
First scene
use ;
The important part is the lifecycle: build scene state, prepare renderer resources, then
render prepared state. If the scene, assets, surface, target, or renderer settings change,
call prepare() again before rendering.
Core workflow
Host app
-> Assets: load/create meshes, materials, textures, environments
-> Scene: create cameras, lights, nodes, imports, labels, animation, picking targets
-> Renderer::prepare*: validate, upload, batch, cache, and build prepared renderer state
-> Renderer::render*: draw prepared state and return frame stats/diagnostics
render() is intentionally predictable. Fetching, parsing, first-use pipeline work,
structural GPU upload, batching, and capability decisions run through prepare(), where
the host receives structured results before drawing frames.
What you can build
Model viewers
- Load and instantiate glTF/GLB assets.
- Frame a model or selected node by bounds.
- Orbit, pan, zoom, focus, hover, select, and pick.
- Preserve asset names, paths, anchors, connectors, clips, pivots, and bounds.
- Run the same viewer logic in native or browser-oriented builds.
CAD-style and industrial visualization
- Convert units and coordinate systems explicitly.
- Repair handedness and axis metadata before placement.
- Snap objects by authored anchors and connectors without raw matrix math.
- Render labels, helper geometry, layers, visibility masks, and helper-on-top views.
- Use deterministic headless output for regression tests and generated documentation.
Visual proof and CI
- Generate rendered-output artifacts for examples and milestone scenes.
- Run browser WebGPU/WebGL2 proof lanes through Rust/WASM probe entry points.
- Record capability JSON, screenshot metadata, benchmark rows, and release-lane artifacts.
- Use
xtask doctorandxtask release-readinessto block silent drift.
Capabilities
| Area | Current surface |
|---|---|
| Scene graph | typed nodes, transforms, cameras, lights, clipping planes, imports, labels, instances, picking targets, animation mixers, and dirty-state tracking |
| Assets | glTF/GLB import, external buffers, cache/dedup/reload, source units, coordinate conversion, anchors, connectors, import-local lookup, retain policy, and stale-handle diagnostics |
| Geometry | primitives, manual buffers, bounds, lines, wire/edge expansion, UV retention, CPU skinning, CPU morph targets, and instance sets |
| Materials | unlit and metallic-roughness paths, texture descriptors, vertex colors, alpha modes, normal/occlusion/emissive/base-color slots, variants, ACES/sRGB output, and FXAA |
| Rendering | headless CPU output, native/headless wgpu foundation, explicit prepare/render lifecycle, render-on-change, offscreen targets, readback, stats, diagnostics, shadows, IBL, and release-lane proof artifacts |
| Interaction | typed picking, hover/selection styling, cursor positions, platform-neutral controls, and independent hover/select/pointer-leave states |
| Browser/WASM | wasm32 compile/package, browser WebGPU/WebGL2 proof lanes, attached-canvas probe paths, surface/context/device-loss event vocabulary, and size gates |
| Quality | unit/integration tests, visual artifacts, browser proof, public API baseline, benchmarks, allocation gates, architecture doctor, release-readiness, and review schemas |
Examples by task
| Task | Examples |
|---|---|
| First render and primitives | first_visible_render.rs, primitive_shapes.rs, headless_ci.rs |
| glTF/model viewer | glb_model_viewer.rs, animation.rs, instancing.rs |
| Camera and controls | camera_framing.rs, orbit_controls.rs, orbit_controls_native_adapter.rs, orbit_controls_browser_adapter.rs |
| Picking and interaction | picking_selection_hover.rs, layers_visibility.rs |
| Anchors, connectors, CAD placement | anchor_alignment.rs, connect_objects.rs, imported_anchor_connection.rs, industrial_connector_assembly.rs, coordinate_connector_repair.rs, coordinate_units.rs |
| Industrial/static scenes | industrial_static_scene.rs, static_batching.rs, labels_helpers.rs |
| Diagnostics and inspection | beginner_diagnostics.rs, scene_inspection.rs |
| Platform setup | native_window.rs, browser_canvas.rs |
All public examples are part of the compile-check surface.
Architecture
flowchart LR
Host[Host application] --> Scene[Scene]
Host --> Assets[Assets]
Host --> Renderer[Renderer]
Assets --> Import[SceneImport]
Import --> Scene
Scene --> Prepare[Renderer prepare]
Assets --> Prepare
Prepare --> Render[Renderer render]
Render --> Output[Frame, stats, diagnostics]
| Owner | Responsibility |
|---|---|
Scene |
graph state, transforms, cameras, lights, labels, imports, animation mixers, picking targets, and dirty tracking |
Assets |
fetchers, parsed/decoded resources, caches, retain/reload policy, and logical handles |
Renderer |
device/surface state, prepared resource tables, render passes, capability reports, diagnostics, stats, and deferred destruction |
SceneImport |
import-local roots, names, paths, anchors, connectors, clips, pivots, bounds, and stale-import checks |
Typed handles such as NodeKey, GeometryHandle, MaterialHandle, TextureHandle,
EnvironmentHandle, AnimationMixerKey, and HitTarget prevent wrong-kind API usage at
compile time. Stale or missing handles return structured errors.
Platform support
| Target | Support |
|---|---|
| Linux native/headless | CI lane with cargo gates, rendered-output tests, examples, doctor, capability artifacts, and release-lane JSON |
| macOS Metal | CI lane with tests, examples, docs, platform proof, capability artifacts, and release-lane JSON |
| Windows DX12 | CI lane with tests, examples, docs, platform proof, capability artifacts, and release-lane JSON |
| Headless CPU | deterministic rendered-output path for tests, docs, and artifact generation |
| Browser WebGPU | WASM/browser proof lane with capability and rendered-output probe artifacts |
| Browser WebGL2 | compatibility proof lane with browser API, context-loss, and rendered-output probe artifacts |
| wasm32-unknown-unknown | compile/package/size-gate lane through wasm-pack |
Surface resize, DPR changes, visibility changes, surface loss, context loss, context
restore, and device loss are explicit SurfaceEvent inputs. Recovery invalidates prepared
state until the host calls prepare() again.
Documentation
| Document | Purpose |
|---|---|
docs/RFC-rust-3d-renderer.md |
renderer charter, scope boundaries, and long-form design narrative |
docs/specs/public-api.md |
public vocabulary, lifecycle signatures, handles, errors, diagnostics, and stats |
docs/specs/render-lifecycle.md |
prepare/render contract, dirty state, retain policy, and resource destruction |
docs/specs/asset-gltf-contract.md |
glTF/GLB import, extension, anchor, lookup, reload, and animation rules |
docs/specs/platform-capabilities.md |
native/browser/WASM capability contracts |
docs/specs/visual-quality-contract.md |
color, environment, screenshot, browser, and tolerance rules |
docs/specs/architecture-contract.md |
SOLID/KISS ownership, dependency direction, public API ownership, and doctor checks |
docs/specs/release-gates.md |
release-readiness gates and required evidence artifacts |
Development
Contributor baseline:
For release validation:
RUSTDOCFLAGS="-D warnings"
The doctor is a source-derived guardrail for known silent-failure families. It complements unit tests, rendered-output proof, browser checks, release gates, and review.
Security
scena parses external asset formats and creates GPU resources, so hosts should apply
normal file, network, size, memory, and timeout policies for untrusted inputs.
The crate uses structured errors and diagnostics for asset, import, prepare, render, and lookup failures. Unsupported required glTF extensions fail explicitly instead of silently rendering wrong output.
FAQ
What is scena? It is a renderer and scene-graph library for Rust applications that need glTF assets, model-viewer workflows, CAD-style inspection, industrial visualization, browser/native targets, and deterministic visual proof.
Can it replace Three.js?
Yes for Rust applications that want the scene-graph/model-viewer workflow in a native Rust
package. scena focuses on typed Rust APIs, explicit lifecycle control, asset ownership,
deterministic rendering, and native/WASM deployment.
Why is prepare() explicit?
Because fetch, parse, upload, pipeline, batching, and capability decisions belong in a
predictable step. render() draws prepared state with host-visible diagnostics.
How does resource cleanup work?
Resource ownership is handle-based and renderer-owned cleanup is explicit. The host works
with typed handles while scena schedules renderer resource cleanup through its lifecycle.
How does application state connect to scena?
Application state stays in the host application. The host maps visual state into Scene,
Assets, and Renderer APIs for rendering, interaction, diagnostics, and proof.
Acknowledgements
scena builds on the Rust graphics ecosystem, especially wgpu, wasm-bindgen,
web-sys, slotmap, glam, image, gltf, meshopt, and the Khronos glTF sample
asset ecosystem used by the tests. The API is intentionally shaped by Three.js' practical
scene-graph ergonomics while using Rust ownership, typed handles, and explicit lifecycle
contracts.
License
Licensed under either of:

