26 Apr 2026

3DGS meet BIM: Rendering Gaussian Splats inside APS Viewer

What if you could overlay a photorealistic reality capture directly onto your BIM model — right in the browser, with no plugins, no server-side rendering, and no separate viewport?

Now, with 3DGS and APS Viewer, you can.  With the recent announcement of WorldLabs and XGRIDS new PortalCam, it was time to update the DevCon2025 code
 

Try it Live: wallabyway / Gaussian-splats-lmv

This demo decodes LCC, then renders 3D Gaussian Splats on top of a Revit model - all inside the web based APS Viewer.
The entire thing is roughly 600 lines of JavaScript.


Quick Start

Download the source code (wallabyway/gaussian-splats-lmv) and serve the files with any static HTTP server:

# Python
python3 -m http.server 8000

# Node (npx)
npx serve .

Then open http://localhost:8000 in a modern browser or load a custom LCC model like this:

http://localhost:8000/?lcc=https://d2pqszqfxcodwz.cloudfront.net/lcc-model/showroom+level+2/showroom2.lcc

Try loading other Sample LCC scenes (see references) 


Five Technical Highlights

Click the link for full technical details:

1. From Point Clouds to Gaussian Splats: The Shader

If you’ve ever rendered a point cloud (previously), you’re halfway there. The leap from fixed-size dots to oriented Gaussian ellipses is smaller than it looks — each splat is just an instanced quad whose “texture” is a mathematically computed Gaussian falloff, shaped by projecting a 3D covariance matrix through the camera.

Point cloud vs Gaussian splat

Point cloud vs Gaussian splat

 

2. The LCC Format and the XGRIDS LiDAR Pipeline

How a single Gaussian splat gets packed into 32 bytes of binary data — and why a LiDAR scanner on your phone can skip the most expensive step in the traditional 3DGS pipeline. XGRIDS uses hardware depth sensing instead of COLMAP’s hours-long photogrammetry reconstruction, making splat capture viable on a handheld device.

32-byte Gaussian splat record binary layout
32-byte splat record layout (LCC data.bin)
XGRIDS LiDAR vs COLMAP pipeline
XGRIDS LiDAR vs COLMAP pipeline

 

3. Why Splats Need Sorting

Transparent primitives can’t hide behind a Z-buffer. Every camera movement means re-sorting millions of splats back-to-front (without using OIT techniques or ray-tracing).

Why the Sort Order matters for Transparency

A 65536-bucket counting sort runs in O(n) time inside a dedicated Web Worker, keeping the main thread free for rendering.

Counting sort and Web Worker architecture
Counting sort and Web Worker architecture

 

 

4. Section Planes: Making Splats Play Nice with BIM Tools

The BIM viewer’s section tool cuts through both the Revit model and the splat overlay at exactly the same plane. The vertex shader evaluates the same half-space equation that LMV uses internally, so the cut aligns perfectly with no coordinate transforms or sign flips.

 


Add 3DGS to Your Own APS Viewer

Already have an APS Viewer app? You can drop Gaussian splats into it with four steps.

1. Import the loader and renderer

import { LCCLoader } from './lcc-loader.mjs';
import { GaussianSplatRenderer } from './splat-renderer.mjs';

2. Load the LCC file and initialize the renderer

const loader = new LCCLoader({ targetLOD: 4 });
const data = await loader.load('https://your-cdn.com/path/to/model.lcc');

const splatRenderer = new GaussianSplatRenderer();
await splatRenderer.init(data);

// Scale and rotate to align with your model
const METERS_TO_FEET = 3.28084;
splatRenderer.mesh.scale.set(METERS_TO_FEET, METERS_TO_FEET, METERS_TO_FEET);

3. Add the splat mesh as an overlay

viewer.impl.createOverlayScene('splats');
viewer.impl.addOverlay('splats', splatRenderer.mesh);

4. Drive the render loop on camera changes

viewer.addEventListener(Autodesk.Viewing.CAMERA_CHANGE_EVENT, () => {
    splatRenderer.update(viewer.impl.camera);
    viewer.impl.invalidate(false, false, true);
});

That’s it. The splats will sort, render, and composite automatically. If you also want section plane support, add one more listener:

viewer.addEventListener(Autodesk.Viewing.CUTPLANES_CHANGE_EVENT, () => {
    splatRenderer.setCutPlanes(viewer.getCutPlanes() || []);
    viewer.impl.invalidate(false, false, true);
});

The Result

Realistic looking 3D Gaussian Splats (XGRIDS LCC) composited on top of a Revit model inside the APS Viewer, in the browser. Pan, orbit, and section the BIM model; the splat overlay respects the same camera, the same section planes, and the same compositing stack as the native geometry. The entire implementation is roughly 600 lines of JavaScript across three modules.

Try it live: wallabyway.github.io/gaussian-splats-lmv.


Source

The complete source code for this demo is in the repository wallabyway/gaussian-splats-lmv.  The implementation lives in three files:

File Lines Role
lcc-loader.mjs ~210 Decodes the XGRIDS LCC binary format (see part 1 below for the spec details)
splat-renderer.mjs ~375 vertex/fragment shaders, Web Worker depth sort, cut-plane support
app.mjs ~115 Viewer init, Revit model loading, splat overlay

 


References


Related Article