@bitruvius/mesh
v0.3.1
Published
Bitruvius mesh renderer: raw-WebGL2 unlit-textured triangle/point rendering of OGC 3D-Tiles / I3S meshes (PackedMeshTile), in the shared ENU frame
Downloads
361
Readme
@bitruvius/mesh
The WebGL2 renderer behind every Bitruvius mesh layer: decoded 3D Tiles and I3S geometry, drawn in the shared ENU frame.
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.
This package does one job. It takes a decoded tile, uploads it to the GPU, and draws it in the frame the host camera hands it. It never fetches a tileset, never parses a container, and never touches a map object.
Why it is separate
Decoding a tileset and drawing it want opposite things. Decoding belongs off the main thread, in
a worker, where no GL context is in reach. Drawing needs the one GL context the page owns and has
to run inside the host's render callback. So the two halves meet at a contract rather than in a
file: PackedMeshTile is defined in @bitruvius/geo-core, the decoders
(@bitruvius/gltf, @bitruvius/i3s, @bitruvius/tiles3d-mesh) produce it, and this package
consumes it. Nothing here knows which format the bytes arrived in.
That buys two things worth the package boundary.
One renderer, several formats. A b3dm tile from a 3D Tiles service and a node from an I3S scene service reach this renderer as the same struct-of-arrays shape, so both get the same shaders, the same alpha handling, the same picking and the same memory accounting. A new container has to reach the packed vocabulary, not write a renderer.
One renderer, several hosts. The renderer takes a placement matrix (ENU to web-mercator
world) and a per-frame mvpWorld, and composes the rest itself. It has two dependencies,
@bitruvius/geo-core and @bitruvius/render-runtime, and MapLibre is not one of them. Six
layers in the SDK drive it, and several more join its shadow pass, without it ever knowing a map
exists.
The shared shadow pass forced the same split from the other direction. ShadowCoordinator lives
here, one per GL context, and builds a single cascade atlas that every registered dataset casts
into and samples, so a building shadows a point cloud and both shadow the terrain from one depth
pass. The caster contract itself sits in @bitruvius/render-runtime, which is why the point
cloud and splat renderers can join that atlas without depending on this package at all.
What is inside
Tiles3DMeshRenderer is the bulk of it: upload and release against the streaming engine's handle
model, an opaque pass and a back-to-front transparent pass, per-tile VAOs and buffers, base-color
texture upload, instancing with ref-counted shared templates, GPU feature-id picking, depth-grid
probes, clipping planes, sun lighting and cascaded shadows. Planet-scale tiles carry a baked
per-vertex mercator offset and are placed from mercator instead of ENU, which is what keeps a
global tileset from swimming far from its anchor.
Around it sit the small, pure pieces that the renderer would otherwise hide inline, each unit tested on its own:
drawStateFormaps a glTF material's alpha mode and sidedness to a WebGL2 draw state, including the rule that an OPAQUE material's alpha must be ignored rather than multiplied in.chooseTextureFormatanddetectTextureCapspick the best KTX2 transcode target the device supports (BC7, ASTC, ETC2, BC3, BC1) and fall back to uncompressed RGBA.gpuBytesOfreports a tile's true resident cost so the engine's memory budget is not fiction.placementFlipsWindingcatches the mirrored placement matrix that would otherwise cull the wrong faces.packFeatureStylesbuilds the one-texel-per-feature buffer that hides, recolors and fades BIM elements without a per-frame cost.featureFootprintsandfeatureTerrainOffsetsseat each building of a sea-level dataset on its own patch of ground.ShadowCoordinator,shadowReceiverGridandTerrainShadowGridReceivercover the shared cascade atlas and the ground that receives it.
Using it directly
You supply the GL context, the placement, and a camera per frame:
import { Tiles3DMeshRenderer } from '@bitruvius/mesh';
const renderer = new Tiles3DMeshRenderer(gl); // a WebGL2RenderingContext
renderer.placement = enuToMercator; // ENU to web-mercator world, column-major Mat4
// on tile ready, from a streaming engine's onTileReady:
const handle = renderer.upload(packedTile); // PackedMeshTile, from @bitruvius/gltf or @bitruvius/i3s
// each frame, inside the host's render callback:
renderer.render({ mvpWorld, viewportWidth, viewportHeight }, [handle], []);
// on eviction, from onTileRelease:
renderer.release(handle);render takes the per-frame camera, the primary handles, and the coarse fallback handles it should
draw only where no finer tile covered the screen. That third argument is why an engine can hand
over a half-loaded tree without holes or coarse-over-fine show-through.
Depend on this package directly only if you are writing your own layer or host integration
against a Bitruvius decoder. If you want 3D Tiles or I3S content on a map, install
@bitruvius/sdk-maplibre, which wires
this renderer to the streaming engines, the codecs and the map for you.
Trademarks
Esri, ArcGIS and I3S are trademarks of Environmental Systems Research Institute, Inc. Cesium and 3D Tiles are trademarks of Cesium GS, Inc. Khronos, glTF, KTX and WebGL are trademarks of The Khronos Group 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.
