Spark examples on Radix
Ports of the SparkJS 2.3.1 examples to Radix (@sparkjsdev/radix) as it is,
without changes to the renderer: what it can do today, and what it lacks. Each page runs on WebGPU, or on the
WebGL2 backend with ?gpu=webgpu-gl (the footer of each page switches). The sources are TypeScript
in examples/spark/; npm run build builds the library and these
pages (types stripped) into dist/ and build/examples/, and npm run serve serves the repo.
Assets load from sparkjs.dev and the other original hosts.
What Radix lacks for the rest
- LoD selection and LoD trees. Radix loads trees from SPZ and RAD files but neither builds them nor chooses which nodes to draw (the app passes index lists), so the LoD, streaming and foveation examples have no port.
- Paged RAD loading over HTTP range requests, and a resident-page budget across objects.
- SOG / SOGS (
.sog,.zip): Radix reads PLY, SPZ, RAD and RDX. - Per-object opacity and tint: the ports set them through a generator's
bind(js/objects.js); an object param would remove the wrapper. - Splat raycasting: the ports test ellipsoids on the CPU from the loaded data (
js/raycast.js). - SDF edits: written as a world-space TSL modifier (
js/sdf.js). - Skinning, portals, and a custom splat fragment shader (the portal examples).
Small additions the ports suggest
Each is worked around in js/ today; the workaround shows how often it is needed.
- Per-object
opacityandtintinSplatObjectParams: the carousel, interactivity, raycasting, procedural text and editor ports wrapfromSplatsin a generator whosebindsets two uniforms (js/objects.js). - Per-object values and world matrices for generators: Spark gives each mesh its own world modifier;
splat-flow and the transitions effects bind each object's index and world matrices so one generator can run a
world-space effect per object (
js/worldEffect.js). Radix now hands every generator the object's matrices and the view origin (itscontext, which also places the SH); a per-object id would remove the rest. - A CPU splat raycast in
core/:js/raycast.jsaccumulates each splat's Gaussian opacity along the ray and stops at 50% coverage; taking the first splat over an opacity threshold misses the visible surface in grass and foliage. - LoD selection over a loaded
lodTree: LoD files draw their leaves through an index list (loadSplatFileinjs/files.js); a budgeted selection would unblock the LoD examples for files that already carry trees.
Notes from porting: pages with lit three.js meshes need createRenderer({ directCanvas: false })
(the splats adapt to the output pass); a CubeCamera renders splats on the CPU sort once each face
camera's view is sorted (js/cube.js); animated modifiers on the CPU sort call
requestSort() every frame.
Shared helpers in js/
Each port is a complete program: it makes its scene, camera, renderer, CameraControls and
animation loop itself. Simple ports draw through Radix's SplatRenderer and
SplatCollection directly; larger ones use SplatSet / SplatModel. What
they have in common is written once on Radix's public API (each file's header says more). The links open the
built JavaScript; the TypeScript sources are in examples/spark/js/.
- page.js
windowRenderer():createRenderer(WebGPU, or WebGL2 with?gpu=webgpu-gl) sized to the window, added to the page and resized with it;examplePage(): status line, legend, load progress, footer links, error capture, and the signal that tells the smoke check the page has drawn.- files.js
- Splat files on the GPU with their spherical harmonics, which Radix loads and draws seen from each view:
loadSplatFile()(a file on the GPU, a LoD file's leaves as an index list),fileOf()andfileObject()(aSplatObjectfor a loaded file, with optional modifiers). - objects.js
- Spark's
SplatMeshon Radix, for the larger ports:SplatModel(anObject3Dplacing a file or a generator, with opacity, tint and raycasts; SH from Radix) andSplatSet(oneSplatRendererwhose collection follows the models shown);raycastSplats()for files drawn without models. - raycast.js
SplatPicker: CPU ray picking against Gaussian ellipsoids, accumulating opacity along the ray.- worldEffect.js
- Spark's per-mesh world modifiers: one generator that binds each object's index and world matrices.
- construct.js
- Splats built on the CPU:
SplatBuilder, text, image and grid splats. - sdf.js
- Spark's SDF edits (spheres that tint, light or displace splats) as a world-space TSL modifier.
- snow.js
- Spark's
snowBoxsnow and rain particles as a TSL generator. - cube.js
- Cube maps of splats with a
CubeCamera(each face sorted on the CPU sort) and their readback. - xr.js
- WebXR on either backend: the Enter VR button, reference space, one view for the head, emulator fixes.
- assets.js
- Spark's asset names to their hosted URLs (
?assets=points at a local copy), and GLB loading with optional unlit materials. - examples.js
- Every Spark example with its status, what it uses and what blocks it: this page and the smoke check
(
scripts/check-examples.mjs) read it. - example.css
- The status line, legend and footer styles.