Voxita asks one systems question: what becomes possible when rendering, collision, destruction and world generation all operate on the same mutable voxel world?
I designed and implemented the prototype in four weeks. The project covers the complete path from packed CPU voxel data and GPU traversal to dynamic destruction, rigid-body promotion, first-person interaction and seeded dungeon generation.
- Solo
- engine prototype
- 4 weeks
- design to playable build
- 2 bytes
- per voxel
- 8³
- voxels per background brick
- 60 Hz
- simulation authority
The whole system in motion
The complete demo shows traversal, custom lighting, destruction and generated rooms operating on one live voxel world. It appears first because the individual systems below were built to work together, not as isolated rendering tests.
One voxel contract, four systems
The background uses a two-level brickmap: a dense top-level index points to allocated 8 × 8 × 8 voxel bricks. Detached objects move into exact-size local voxel arrays with a 4 × 4 × 4 block-occupancy map for acceleration. Both residency states preserve the same two-byte material and auxiliary-state contract.
Storage changes when voxels become dynamic, but their material and occupancy semantics do not. The renderer resolves both representations through one scene-query interface. Damage changes the same matter model, the player controller queries the same occupancy rules, and the generator writes the format consumed at runtime. Background topology can therefore change without rebuilding a triangle mesh for visual rendering.
Rendering directly from voxel data
I built a compute-based 3D-DDA renderer and integrated it into URP through Render Graph. The renderer exposes ANY_HIT queries for visibility tests and CLOSEST_HIT queries when full intersection data is required. A shared TraverseSceneRay foundation serves camera rays, true shadow rays, GI bounce rays, irradiance-probe refresh, reflection and refraction.
Background rays traverse the 8³ brickmap and skip empty bricks. Dynamic objects first pass a world-space bounds test, transform the ray into local space, and skip empty 4³ occupancy blocks before stepping their exact-size voxel arrays. The nearest hit is compared in world-space distance so objects can occlude the background and remain correctly hidden behind walls. This is a custom compute traversal rather than a DXR integration.
The renderer owns its lighting and material path. Custom point, spot and area lights enter a registry instead of being rediscovered from the scene each frame. A shared material table keeps optical properties such as roughness, metalness, emission and refraction beside physical properties such as density and strength, all addressed by the voxel's material ID.
GPU data movement and rendering validation
Packed GPU contract
Keep two-byte voxels across a four-byte StructuredBuffer boundary
The CPU world stores each voxel as a packed ushort: one material byte and one auxiliary byte. Unity requires four-byte StructuredBuffer elements, so the upload path reinterprets each pair of voxels as one uint. This preserves the two-byte world format without expanding every voxel for the GPU. Dirty background bricks upload independently; a high dirty count falls back to one full-buffer transfer.
Rendering verification
Treat feature switches as regression boundaries
I checked a traversal refactor across 140 frame-MD5 cases: seven feature combinations, ten debug views and two scene configurations. Every case matched its reference output. A separate shadow-off comparison also returned zero changed pixels across 4,479,000 pixels. These are targeted regression results, not a general performance claim.
Light as a scene query
Once visibility became a reusable voxel ray query, lighting could build on the same foundation. Direct lights cast actual voxel visibility rays, and soft shadows use four deterministic cone samples to avoid unstable per-frame noise. The current light record packs point, spot and area lights into one 64-byte GPU contract; the default active-light budget is 16 and remains configurable.
For indirect light, I explored irradiance and distance probes stored in 8 × 8 octahedral tiles, with 36 interior directions per probe. The default refresh traces six directions per frame and completes one probe cycle over six frames. An optional stochastic path adds one true indirect bounce through TraverseSceneRay.
The screen-space result uses world-space temporal reprojection followed by variance-guided à-trous filtering. Reprojection is especially important here because destroyed walls and promoted voxel objects can invalidate previous visibility. The GI system is an exploratory implementation rather than a production-complete solution.
Destruction and a script-driven PhysX cycle
Destruction changes world state instead of spawning a visual effect. Damage writes into the background field, then connectivity analysis finds solid regions that no longer reach an anchor. Promotion removes those voxels from the background, constructs an exact-size local voxel object, rebuilds its render and collision data, derives mass from its materials, and hands the resulting rigid body to PhysX.
- 01Background voxelsCheap world-resident storage
- 02Damage and connectivityMutate the field and find detached regions
- 03Promoted voxel objectTransfer voxels into accelerated local storage
- 04PhysX motionIntegrate only the active rigid body
- 05Sleep or settle-bakeKeep the object dormant or merge it into the world
Physics scheduling
PhysX is the motion backend, not the simulation authority
I disabled Unity's automatic physics stepping and call Physics.Simulate from an explicit 60 Hz authority. Each tick reveals queued damage, completes a valid structural-solver slice, promotes disconnected matter, advances the voxel player, runs PhysX, processes collision-driven fracture, and finally applies sleep, settle-bake or eviction decisions. This order prevents physics from observing half-applied topology changes.
Large voxel objects use an approximate convex decomposition capped at four convex collider pieces; small debris and solid cuboids deliberately fall back to a single AABB. The lifecycle limits active runtime fractures, allows dormant objects to sleep, and can merge settled matter back into the background. The default active-fracture cap is 20, an adjustable budget rather than a benchmark result.
The first-person controller does not use a Rigidbody or CapsuleCollider. It runs C# AABB sweeps directly against voxel occupancy, including step-up, wall sliding, sprint, crouch, jump and slide. Removed voxels also seed a GPU debris pipeline: reset, spawn and simulation stay in compute, followed by one indirect draw with no live particle-count readback to the CPU.
Rewriting the structural solver
Failure analysis
An 812-voxel false detachment exposed the wrong abstraction
An early parent-tree solver could mark an otherwise stable column as detached after a one-voxel bottleneck changed its traversal order. The failure involved 812 voxels, so a shape-specific exception would only have hidden the underlying problem.
I replaced that formulation with three independent checks: layered load redistribution, anchor-region connectivity and a center-of-mass test against the support polygon. The rewrite separated load failure, topological support and tipping instead of asking one traversal tree to represent all three.
Boundary: the advanced tier-B solver is implemented and tested, but the recorded runtime demo uses the simpler connectivity solver.
Generating space by carving solid volumes
The dungeon generator starts from a solid voxel volume and carves playable space through four world-level stages and five floor-level stages. The room graph decides whether an edge becomes a door or a one-brick destructible boundary, so a hidden room is generated topology rather than a separate trigger volume.
An early Poisson, Delaunay and MST layout failed to converge for about half of tested seeds when linking floors. I replaced it with four-neighbor grid growth, which turns every expansion into a bounded and reproducible decision. A separate Mask × Field × Action styling system describes where a treatment applies, how it varies and what it writes. Seven room styles and two corridor styles reuse the same painter without adding style-specific generation code.
The seven images below are seven generated dungeon style variations. They remain in one large visual sequence, following the way the project presentation compared them.

Engineering decisions behind the prototype
Correctness under load
Slice work only at valid boundaries
Structural analysis can span multiple fixed steps when a large region changes. The budget system yields between complete work slices, never in the middle of a query that callers might mistake for a finished answer.
System ownership
Use PhysX for motion while Voxita owns the world model
PhysX integrates promoted rigid bodies. Voxita still owns voxel occupancy, connectivity, object classification, fracture decisions and settle-bake transitions. This keeps the engine boundary explicit and lets the changing world remain authoritative.
Scope and next integration work
Voxita is a four-week solo technical prototype. The systems above are implemented unless a section explicitly marks them as exploratory. The next integration targets are structural labeling for generated spaces, runtime activation of the advanced solver and stronger ownership between lights and the voxels that emit them.
Built with: Unity 6000.5, URP Render Graph, C#, HLSL compute shaders and PhysX.

