@bitruvius/render-runtime
v0.3.1
Published
Bitruvius SDK WebGL2 render runtime: GL context, programs, buffers, data textures, blend and depth presets, splat depth sorting, and the shadow, clip and drape contracts every Bitruvius renderer shares
Downloads
354
Readme
@bitruvius/render-runtime
The WebGL2 layer every Bitruvius renderer stands on, so that none of them has to stand on another.
Internal building block. This package exists so that
@bitruvius/sdk-maplibreand the Bitruvius codecs can resolve their dependencies on npm. It has no standalone product story. Unless you are deliberately building against it, install the SDK instead.
Why it is a separate package
Bitruvius ships several renderers: mesh, point cloud, Gaussian splat, point symbol and terrain. They are separate packages on purpose, because a map that draws only LiDAR should not pull in a splat renderer.
But the interesting features cut across all of them. One sun casts into one shadow atlas that a mesh, a point cloud and a splat cloud all write into and read back. One slice plane cuts every dataset at exactly the same place. One vector drape composites over every surface in the scene. Those features only work if the renderers agree, to the sign bit, on the state they share.
If that shared state lived in any one renderer, the others would have to depend on it, and rendering a point cloud would drag in the mesh renderer. So it lives here instead, in the common leaf below all of them, and the renderers stay independent of each other.
The rule holds throughout the package. The shadow coordinator stays in
@bitruvius/mesh; only the contract every caster implements lives here. The clip
test is a single GLSL string that mesh (fragment discard), points and splats
(vertex cull) all interpolate into their shaders, so a slice plane cannot drift
between datasets. The drape contract sits here so the MapLibre-side coordinator
that produces the atlas never has to know about renderers, and the renderers never
have to know about MapLibre.
Every shared block is also off by default and a strict no-op when off. Uniform defaults of zero disable clipping, shadows and draping, so a program that never touches them renders byte-identically.
What is in it
GL plumbing. getWebGL2 for standalone viewers that do not already own a
context, compileShader / createProgram / uniformLocations (compile and link
failures arrive as the GL info log, not a silent black screen), createBuffer and
updateBuffer, and createDataTexture with texDimsFor for the integer data
textures that splats read with texelFetch.
const gl = getWebGL2(canvas, { antialias: false, premultipliedAlpha: true });
const prog = createProgram(gl, vertexSrc, fragmentSrc);
const u = uniformLocations(gl, prog, ['uMVP'] as const);Blend and depth presets. setPremultipliedBlend, disableBlend,
setSplatDepthState (depth test on, depth write off, because sorted transparent
splats would otherwise corrupt each other) and setOpaqueDepthState. Viewer
adapters call these and then restore the host's expected state.
Splat depth sort. radixSortIndices and cameraSpaceZ, the pure core of the
back-to-front ordering splats need for correct premultiplied "over" blending. A
comparison sort is too slow at millions of splats, so this is a stable O(n) LSD
radix sort; the f32 keys are reinterpreted as order-preserving unsigned ints so a
single unsigned pass handles negative depths correctly. Runs off the main thread
in the splat worker.
const depth = cameraSpaceZ(positions, count, viewModel);
const drawOrder = radixSortIndices(depth); // ascending z = back-to-frontShared shadow contract. SharedShadow (one cascade atlas plus its matrices)
and ShadowCastRenderer, discriminated into MeshShadowCaster (triangle VAOs
drawn with the coordinator's depth program) and FootprintShadowCaster (points and
splats drawn as depth discs with their own). The GLSL comes with it:
FOOTPRINT_SIZE_GLSL, ROUND_POINT_DEPTH_FRAG, SAMPLE_SUN_SHADOW_GLSL and a
cheaper one-tap variant for high-overdraw receivers.
Shared clip state. ClipState, clipStateFromPlanes, MAX_CLIP_PLANES and
bindClipUniforms, with CLIP_UNIFORMS_GLSL and CLIP_TEST_GLSL as the one copy
of the plane test. Planes arrive already resolved into the renderer's anchor-ENU
frame in metres; converting from geographic coordinates is the layer's job.
Shared drape contract. SharedDrape, DrapeReceiver, bindDrapeUniforms and
DRAPE_UNIFORMS_GLSL / DRAPE_BLEND_GLSL, the single premultiplied over-blend
that keeps a draped road looking the same on a mesh, a point cloud and a splat.
Debug. Tiles3DBoundsRenderer, a small line renderer for tile bounding
volumes, shared by the mesh, splat and point-cloud tile layers for the same reason
as everything else here.
Who should depend on it
Almost nobody directly. @bitruvius/mesh, @bitruvius/ptcloud,
@bitruvius/splats, @bitruvius/point-symbols, @bitruvius/raster and
@bitruvius/sdk-maplibre depend on it, and installing the SDK brings it along.
Take a direct dependency only if you are writing your own renderer that has to join
the shared passes: implement ShadowCastRenderer to cast into the same atlas,
include the clip and drape GLSL so your geometry slices and drapes with everyone
else's, and use the GL helpers so your programs report failures the same way.
npm i @bitruvius/render-runtimeRequires WebGL2. The math (sort, texture sizing, clip test) is unit-tested under
bun test; the thin GL glue is exercised by the SDK examples.
Trademarks
Cesium and 3D Tiles are trademarks of Cesium GS, Inc. MapLibre is a trademark of the MapLibre organization. All other marks are the property of their respective owners.
These names are used solely to describe the data formats this software interoperates with. Bitruvius is not affiliated with, sponsored by, or endorsed by any of them, and no such relationship is implied.
License
Proprietary. The full terms ship as LICENSE inside this package, and are readable
before installing at cdn.bitruvius.com/legal/sdk-license-v1.txt.
© Bitruvius, Inc.
