Your CesiumJS scene. Multi-model point clouds. Streamed on CPU.
Two tags. Full-resolution datasets, as many as you want to load.
The Unlimited Detail engine renders point clouds on the CPU, leaving your GPU budget free for Cesium's terrain, 3D Tiles, imagery, and glTF. Protected under Unlimited Detail patents.
Cesium handles terrain, 3D Tiles, imagery, and glTF beautifully. udSDK slots in as another primitive alongside them: stream multiple original-resolution point cloud datasets, rendered on the CPU, with no preprocessing pipeline and no draw on the GPU memory you've budgeted for the rest of the scene.
A drop-in bridge. Not a rewrite.
udsdk-cesium.js adds a custom Primitive to your viewer's primitive collection. The primitive runs udSDK each frame, copies its colour + depth buffers into Cesium-managed GL textures, and submits a screen-space DrawCommand that writes depth through czm_writeLogDepth — so point cloud pixels share Cesium's log-depth buffer and interleave correctly with terrain, 3D Tiles, and other primitives. Not painted on top.
A complete, runnable page
Two script tags (plus the Cesium widgets CSS) and one function call. Opens on a LiDAR scan of Japan, with the camera auto-flown to the dataset's bounding sphere and correct occlusion against the OSM basemap. The live demo on the right runs exactly this code.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>udSDK + CesiumJS</title>
<script src="https://cesium.com/downloads/cesiumjs/releases/1.141/Build/Cesium/Cesium.js"></script>
<link href="https://cesium.com/downloads/cesiumjs/releases/1.141/Build/Cesium/Widgets/widgets.css" rel="stylesheet" />
<script src="https://nuclideon.com/cdn/integrations/cesium/1.0.0/udsdk-cesium.js"></script>
<style> html, body, #cesiumContainer { height: 100%; margin: 0; } </style>
</head>
<body>
<div id="cesiumContainer"></div>
<script>
const viewer = new Cesium.Viewer("cesiumContainer", {
scene3DOnly: true,
baseLayer: new Cesium.ImageryLayer(
new Cesium.OpenStreetMapImageryProvider({ url: "https://tile.openstreetmap.org/" })
),
baseLayerPicker: false,
geocoder: false,
});
udSDKCesium.attach(viewer, {
models: ["https://nucl-demo.s3.amazonaws.com/Japan/JapanALS_Split/1_0_0.uds"],
});
</script>
</body>
</html>
Pin to a versioned path. The CDN host prefix may change — the version segment will not.
Five things the wrapper does for you
The hard parts of the integration — handled by the wrapper, so they don't surface in your code.
A script tag and one function call. No build tooling, no framework lock-in, no bundler config. Works in Sandcastle-style script-tag environments.
Point clouds interleave with Cesium content via `czm_writeLogDepth`. Terrain, 3D Tilesets, glTF models, and other primitives correctly occlude and are occluded — point clouds are not a flat overlay painted on top of the scene.
Two paths to udCloud: a Nuclideon-issued domain license signs all visitors in silently on that domain (no popup, no account), or individual users sign in with their own udCloud accounts. `attach()` tries the silent domain path first and falls back to OAuth.
The `serverAddress` option points the SDK at a private udCloud tenant if you run your own infrastructure.
Immutable versioned paths. Pin your version; upgrade on your schedule.
Three things your host page must provide
Before you wire up `attach()`, the page serving your HTML needs to satisfy these. The cross-origin isolation requirement has real deployment implications — read it carefully.
udSDK uses SharedArrayBuffer for its worker pool, which requires the host document to be cross-origin isolated. Send these headers on the HTML document. credentialless is the easier choice — require-corp forces every cross-origin resource (Cesium CDN, basemap tiles, udCloud, your .uds hosts) to send its own CORP header, which is brittle across origins you don't control. These headers are per-document, not site-wide — scope them to the viewer path in your CDN or reverse proxy.
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: credentialless
Use the CDN UMD bundle, which exposes a global Cesium. Tested against 1.141; the wrapper depends on the WebGL2-only shader path Cesium added in 1.95. The ES module build (@cesium/engine via a bundler) isn't supported by the drop-in — a custom integration is needed for that case.
<script src="https://cesium.com/downloads/cesiumjs/releases/1.141/Build/Cesium/Cesium.js"></script>
<link href="https://cesium.com/downloads/cesiumjs/releases/1.141/Build/Cesium/Widgets/widgets.css" rel="stylesheet" />
Two options, pick one. Domain license: Nuclideon issues a license bound to your hosting domain. Any page served from that domain signs visitors in silently — no popup, no per-user account. Talk to us to arrange one. Per-user udCloud accounts: each end user signs in with their own account via the OAuth popup at udcloud.nuclideon.com. .uds point cloud files can be hosted anywhere the browser can fetch from with CORS — they don't need to live in udCloud.
attach() options and controller
udSDKCesium.attach(viewer, options) returns a controller. Defaults are sensible; override only what you need.
All options are optional. Pass a bare `{}` to take defaults.
udSDKCesium.attach(viewer, {
// Name shown on the udCloud OAuth consent screen.
// Default: "cesium-<window.location.hostname>"
clientName: "Acme Portal",
// Models to auto-load after sign-in. Strings or {url, zOffset, zone, classification, flyTo} objects.
// zone defaults to 4978 (WGS84 ECEF) — Cesium's native world frame.
models: [
"https://my-bucket.example.com/site.uds",
{ url: "https://.../offset.uds", zOffset: 150, zone: 4978 },
{ url: "https://.../classified.uds", classification: true },
],
// "auto" shows a built-in sign-in button when needed; false = customer drives it
signInUI: "auto",
signInLabel: "Sign in to udCloud",
// Point at a self-hosted or regional udCloud. Default: https://udcloud.nuclideon.com/
serverAddress: "https://udcloud.acme.internal/",
// "auto" tries silent domain-license sign-in first; false always shows the OAuth popup
tryDomainLogin: "auto",
// Make the primitive visible immediately. Set false to start hidden.
autoEnable: true,
// Fly the camera to the first loaded model's bounding sphere (from header data)
autoFlyTo: true,
// Override the SDK CDN base. Defaults to the udSDK release this
// wrapper version was pinned to at build time
// (https://nuclideon.com/cdn/udsdk/2.6.1/js/).
baseUrl: null,
// Callback for emscripten loader progress strings
onStatus: (text) => console.log(text),
});
`attach()` returns a controller exposing sign-in state, model management, a rendering toggle, and an EventTarget for lifecycle events.
const ud = udSDKCesium.attach(viewer, { /* ... */ });
await ud.ready; // bootstrap + initial sign-in flow complete
// Sign-in state
ud.signedIn; // boolean
await ud.signIn(); // manual OAuth (must be from user gesture)
// Model management
await ud.loadModel("https://.../extra.uds");
await ud.loadModel({ url: "...", zOffset: 50, zone: 4978, classification: true });
// Rendering toggle
ud.enabled = false; // hide point clouds without unloading
ud.enabled = true;
// Underlying objects, if you want to reach in
ud.viewer; // the Cesium.Viewer
ud.primitive; // the Primitive added to viewer.scene.primitives
// Events (EventTarget)
ud.addEventListener("ready", () => {});
ud.addEventListener("signed-in", (e) => {
// e.detail.method is "domain" (silent domain-license sign-in) or "oauth" (popup)
});
ud.addEventListener("sign-in-error", (e) => {});
ud.addEventListener("model-loaded", (e) => { /* e.detail.url, e.detail.handle, e.detail.slot */ });
ud.addEventListener("model-error", (e) => { /* e.detail.url, e.detail.code */ });
// Teardown
ud.destroy();
How the bridge actually works
A walk through the render pipeline the wrapper installs. Skip this section if you just want to ship; it's here for the integration leads who need to know what's running inside their scene before they sign off.
The wrapper builds a Cesium Primitive and adds it to viewer.scene.primitives. Each frame, the primitive's update() is called from inside Cesium's render loop. It runs udSDK, copies the colour + depth output into two Cesium-managed GL textures, and pushes a screen-space DrawCommand onto the frame's command list. Cesium then dispatches it alongside its own commands.
The fragment shader unprojects each pixel's udSDK depth value back to world space, computes distance from the camera, and writes the result through Cesium's built-in czm_writeLogDepth. This shares Cesium's log-depth buffer, so the hardware depth test handles occlusion against terrain, 3D Tiles, glTF models, and other primitives automatically — no z-fighting, no ordering tricks.
View and projection matrices from Cesium's frame state (frameState.camera.viewMatrix and frameState.camera.frustum.projectionMatrix) are fed into udSDKJS_SetMatrix("view", ...) and udSDKJS_SetMatrix("projection", ...) every frame, so udSDK renders from the same viewpoint as the rest of the scene.
udSDK is a threaded emscripten build. Since the wrapper loads cross-origin from the CDN, it fetches udSDKjs.js as text, wraps it in a Blob, and passes the Blob URL to Module.mainScriptUrlOrBlob so the pthread pool can spawn same-origin workers. Browsers refuse new Worker(crossOriginURL), so this detour is unavoidable.
udSDK delivers depth as raw IEEE-754 little-endian float32 bytes. The wrapper uploads them as an RGBA8 texture (4 bytes per pixel = 1 float per pixel) and decodes back to float in the fragment shader. This avoids depending on R32F renderable-texture support, which keeps the integration working across the wider set of devices Cesium itself supports.
udSDKJS_ConnectStart returns an approve URL. The wrapper navigates an already-opened popup to it from inside the click handler — popups opened after an await get blocked. udSDKJS_ConnectComplete polls udCloud for completion.
Before showing any UI, the wrapper tries udSDKJS_CreateSharedFrom_udCloud(clientName, null). The null key argument tells udCloud to authenticate against a domain license bound to the serving origin. If the domain is licensed, no popup appears and every visitor is signed in. If it isn't, the built-in OAuth button renders and the user signs in with their own udCloud account.
udSDK's wasm uses emscripten Asyncify and yields by throwing the literal string "unwind". If that escaped into Cesium's render loop, Cesium would catch it as a fatal renderError and stop rendering for good. The primitive's update() catches it locally and drops the frame instead — the last good colour/depth buffer stays bound on the textures, so it's visually unnoticeable.
Things to know before you ship
The rough edges in the current build. Workarounds are noted where they exist.
Emscripten's Module is a global. You can't run two independent udSDK bridges on one document.
Domain login ignores the serverAddress override in the current udSDK build — a fix is in progress. If you need a custom tenant today, set tryDomainLogin: false to force the OAuth path.
A page that can't set the COOP/COEP headers — for example, an existing Cesium app hosted on a domain you don't control — can't use the drop-in as-is. You'll need a reverse proxy, an iframe into a page you do control, or a non-threaded udSDK build (on the roadmap).
The wrapper looks up window.Cesium. If you're consuming Cesium as ES modules via a bundler, either also load the UMD build for the wrapper to find, or fork the wrapper to pass Cesium in via an option. We're considering a published @nuclideon/udsdk-cesium npm package — talk to us if that would unblock you.
Colour and depth textures aren't recreated after a lost WebGL context. The page needs reloading. Uncommon in practice but worth flagging.
Why the Unlimited Detail engine runs on the CPU — the architecture behind the Cesium wrapper, explained.
How the same engine powering this SDK ran in production at enterprise scale.
Licensing options, platform support, and integration documentation.
Ship point clouds in your CesiumJS viewer.
The script is live at the CDN. Wire it up in an afternoon. If you need a private tenant or a self-hosted udCloud, talk to us first.