Eight design ideas in Radix. Each has a small interactive example and a link to the page that covers it in full.
About eight minutes. For the classes and how they connect, see how the classes fit together.
Radix stores all its data in tables: rows of elements, and columns of typed-array channels grouped into named bundles. The GPU holds the same table in textures, one per channel, so row i on the CPU is row i on the GPU, with the same bits.
A table can be sent to a worker without a copy, saved to a file chunk by chunk, and uploaded to the GPU unchanged. Code on each side reads it with twin classes: Splats on the CPU and GpuSplats in shaders have the same method names and the same bits.
See also: ArrayData GPU arrays The data
A splat is a center, three scales, a rotation, a color, and an opacity, plus a normal, roughness, and metallic for lighting. Radix's ext layout stores all of these in two 128-bit words.
The center is float32. The rotation is stored as an axis and an angle, which keeps small rotations precise. Color has a shared exponent, so it can hold HDR values and the negative values that merged LoD nodes produce. For large files, the packed layout uses 16 bytes per splat.
The projection shader gets each splat i from a generator. Stored splats from a file are read by one generator; a subset of a file through an index list uses the same generator with different data; a formula is another generator.
Programs are cached by generator, and data is bound per object, so a new file or a new subset compiles no new program. A generator can also read a simulation's particle buffers directly, without copying them into a splat array.
See also: Generators and modifiers A subset per camera
Each render projects every splat for the camera into a few 32-bit words, holding what the draw needs to place and shade its quad, and a sort key. The draw only decodes these words.
The values both eyes of an XR frame share are computed once from the head, and each eye adds only its 2D bound. Stereo adds words per splat, but there is still one projection pass and one sort for both eyes.
See also: Words Head and eyes
Splats blend back to front, so each gets a sort key: its distance from the head as the bits of a float, inverted so that ascending keys run from far to near. Distances from the head stay valid as the head turns, which depths along the view direction do not.
The key width is a setting. 24 bits (the default) orders splats more than about 0.0015% of their distance apart, in three GPU radix passes. 32 bits keeps the full float, in four passes. 16 bits ties splats within 0.1%, in two passes.
See also: Sort keys The pipeline
Two splat objects drawn in two draw calls, each sorted on its own, blend wrongly where they overlap: one is painted over the other even where their splats interleave in depth.
A renderer projects all objects of its collection into one set, each with its own transform, and sorts them as one list. Any mix of files, subsets, and generated splats then blends correctly, in one draw call.
See also: SplatCollection Drawing many objects
A renderer holds the settings that all viewpoints share. A view holds only what one viewpoint changes, such as its own subset of objects, its own blur, or no modifier. Each field the view leaves unset comes from the renderer.
On WebGPU no state depends on the camera between renders, so one renderer can draw any number of cameras without views. On WebGL2 each camera's view also holds that camera's sort order.
See also: Views Thumbnails with different settings
Every shader in Radix is TSL, which three.js compiles to WGSL for WebGPU and GLSL for WebGL2. On WebGPU the projection runs in compute and the sort on the GPU; on WebGL2 the same projection runs as a fragment pass and the sort moves to a worker.
The app code is the same on both: the renderer picks the backend, and the splat renderer picks the sort. On WebGL2 a view draws its last order until a new one arrives, so a late order still draws the right splats.
See also: The CPU sort One code path