SplatLoader
Streams 3D Gaussian splats THROUGH the world's deferred PostproductionRenderer as a first-class citizen of the pipeline.
The splats are drawn by the in-house WebGL2 GSplat renderer (EWA covariance billboards,
a worker-driven global depth sort, a 10Β³ cell grid for distance LOD + frustum culling). They
are rendered into the deferred occlusion seam in LAYER mode (premultiplied over the finished
frame); critically, every splat writes its CENTRE's reversed-Z depth and the material depth-
TESTS (GreaterEqualDepth, no depth write) against the BIM depth borrowed by the seam β so BIM
geometry occludes the splats with NO shader patching, exactly as a real mesh would.
This replaces the previous Spark + 3d-tiles-renderer implementation; those optional peer dependencies are no longer needed.
Extendsβ
Component
Implementsβ
Disposable_2
Propertiesβ
listβ
readonlylist:Map<string,LoadedSplat>
Loaded splats, keyed by id.
lodQualityβ
lodQuality:
number=0.55
LOD-distance QUALITY factor (Γ dataset radius) applied to every splat's lodBase
each frame. Higher = LOD stays high-detail farther from the camera (more quality,
slower); lower = detail drops off sooner (faster). 0.55 is the tuned default (the
value each renderer initialises lodBase to). Live: read per-frame in _occlusion.
minPixelSizeβ
minPixelSize:
number=3
Minimum projected splat size (px). Splats below it are skipped in the vertex stage, but their opacity now ramps down over the band just above the threshold, so they FADE out instead of blinking out β no hard boundary travelling with the camera. ~2-3 px is a common default and a real perf lever on dense scenes: larger = cheaper and blurrier at distance. With subPixelDetail on, this becomes a size FLOOR instead and larger values cost MORE.
onDisposedβ
readonlyonDisposed:Event_2<unknown>
OBC.Disposable.onDisposed
Implementation ofβ
OBC.Disposable.onDisposed
onSplatLoadedβ
readonlyonSplatLoaded:Event_2<LoadedSplat>
Fires once a splat source has finished loading + been added to the splat scene.
shMaxDistanceβ
shMaxDistance:
number=0
Distance, in world units, past which the view-dependent term fades out. 0 (the default) means no limit. Specular response only reads as such on a surface you are close enough to resolve; beyond that it is per-splat work paying for nothing, and a splat past the limit skips its texture fetch entirely. Ramped over the last 20% so nothing changes colour as it crosses.
Left at 0 deliberately: this is an ABSOLUTE distance, and no absolute value is right for both a room scan and a 500 m site. Guidance from the FCC capture (radius 365 m), where savings and visual cost both ordered monotonically with the limit: ~0.3x the dataset radius (110 m) saved 0.6 ms and moved 5% of pixels by >8/255; 0.14x (50 m) saved 1.3 ms and moved 14%.
shStrengthβ
shStrength:
number=1
How much VIEW-DEPENDENT colour to apply, 0..1. Gaussian splats store colour as spherical
harmonics; the flat DC term alone gives a splat the same colour from every angle, and the
higher bands carry the specular response β wet asphalt, glass, polished stone β that reads as
"detail" next to a viewer which renders them. 1 = as captured, 0 = flat (identical to the
pre-SH look). Applies only to datasets converted WITH .sh sidecars; others ignore it.
Live: a uniform write, not a re-stream.
subPixelDetailβ
subPixelDetail:
boolean=false
Keep sub-pixel splats instead of dropping them: each is grown to minPixelSize and has its opacity scaled by the area it gained, so its contribution is preserved.
This is the only mode in which extra streamed detail actually reaches the screen. Measured on the FCC capture: with it OFF, raising lodQuality from 0.55 to 2.0 drew 20% more splats and produced a bit-identical image β everything the finer LOD added fell under the size threshold and was culled on arrival. With it ON, the same change alters the image.
Off by default because it is the expensive path: +18.8 ms at 4.07M drawn (0/15 paired trials β it lost every one). subPixelMinAlpha claws back roughly half of that. Turn it on for stills, inspection and close work.
subPixelMinAlphaβ
subPixelMinAlpha:
number=0.1
With subPixelDetail on, the alpha at which a grown splat is finally dropped.
This is the cost dial for that mode, and it is safe in a way a size threshold is not: opacity decays CONTINUOUSLY with distance, so raising this only makes splats disappear once they have already faded to nothing. It cannot recreate the hard edge the whole change exists to remove.
0.1 by default, chosen from measurement rather than taste: against a 1/255 cut it recovers 8.7 ms of the mode's 18.8 ms (14/15 paired trials, p = 0.0005) while the rendered frame moves by 0.875/255 on average β about 0.3%. 0.05 was not worth it (-2.3 ms, p = 0.06). Drop it to 1/255 for maximum fidelity in a still.
Accessorsβ
enabledβ
setenabled(value):void
When false, splats stop drawing (they stay loaded).
Parametersβ
| Parameter | Type |
|---|---|
value | boolean |
gaussianScaleβ
getgaussianScale():number
Uniform scale of every splat dataset (sizes + spacing together) β fits the reconstruction to BIM real-world units.
Returnsβ
number
splatCountβ
getsplatCount():number
Live total of gaussians currently drawn across every dataset.
Returnsβ
number
worldβ
setworld(value):void
The world this loader draws into. Setting it registers the occlusion hook on the world's deferred renderer (LAYER mode); clearing it unregisters it.
Parametersβ
| Parameter | Type |
|---|---|
value | null | World |
Methodsβ
dispose()β
dispose():
void
OBC.Disposable.dispose
Returnsβ
void
Implementation ofβ
OBC.Disposable.dispose
getTransform()β
getTransform(
id?):null|Matrix4
The current placement matrix (localβworld, WITHOUT the global gaussianScale) of a dataset (defaults to the first loaded), or null. Symmetric with setTransform.
Parametersβ
| Parameter | Type |
|---|---|
id? | string |
Returnsβ
null | Matrix4
load()β
load(
source,options?):Promise<LoadedSplat>
Load a gaussian splat source and add it to the splat scene.
options.anchor (or the full options.transform) places it for BIM co-location.
Parametersβ
| Parameter | Type | Description |
|---|---|---|
source | string | SplatSource | - |
options? | object | - |
options.anchor? | Vector3 | - |
options.id? | string | - |
options.transform? | Matrix4 | Full TRS placement (dataset-local β world), e.g. a BIM-alignment matrix. Supersedes |
anchor. Update later via setTransform. |
Returnsβ
Promise <LoadedSplat>
remove()β
remove(
id):void
Remove a loaded splat source and free it.
Parametersβ
| Parameter | Type |
|---|---|
id | string |
Returnsβ
void
removeCollisionProxy()β
removeCollisionProxy(
id):void
Detach + dispose a splat's crisp pick proxy (falls back to gaussian depth).
Parametersβ
| Parameter | Type |
|---|---|
id | string |
Returnsβ
void
setCollisionProxy()β
setCollisionProxy(
id,source):Promise<void>
Attach a CRISP pick proxy to a loaded splat: a low-poly collision mesh (GLB,
from splat-transform -K smooth, in the splat's SOG frame). While a proxy is
attached, the FastModelPicker depth pass draws these opaque surfaces instead of
the fuzzy gaussian depth, so orbit anchors and measurements snap to a hard
surface. The proxy inherits the splat's placement Γ gaussianScale automatically.
Parametersβ
| Parameter | Type | Description |
|---|---|---|
id | string | a loaded splat id. |
source | object | the GLB as a URL or raw bytes (e.g. a platform hidden file the app has resolved to a signed URL / fetched). |
source.bytes? | ArrayBuffer | - |
source.url? | string | - |
Returnsβ
Promise<void>
setTransform()β
setTransform(
matrix,id?):void
Set a dataset's full TRS placement (dataset-local β world), e.g. a BIM-alignment matrix.
With no id, applies to every loaded dataset. Updates the handle box too.
Parametersβ
| Parameter | Type |
|---|---|
matrix | Matrix4 |
id? | string |
Returnsβ
void