Drop-in integration for Nuclideon's udSDK — render UDS point clouds inside an
ArcGIS <arcgis-scene> with depth correctly interleaving against the
ArcGIS-rendered basemap, terrain, and 3D scene layers. The bridge hooks into
the ArcGIS managed render pipeline via a RenderNode, so udSDK composites as
just another stage of the SceneView's frame rather than as a detached overlay.
Language: Javascript / HTML
Type: Integration
Contributor: Nuclideon Development Team <info@nuclideon.com>
Organization: Nuclideon, https://nuclideon.com
Date: 2026-06-16
ArcGIS Versions: Maps SDK for JavaScript 5.x (ESM build)
udSDK Version: 2.6.1
<script type="module" src="https://js.arcgis.com/5.0/"></script>
<script src="https://nuclideon.com/cdn/integrations/esri/1.0.0/udsdk-arcgis.js"></script>
<arcgis-scene id="scene" basemap="hybrid" ground="world-topobathymetry"
viewing-mode="global" style="width:100%;height:100vh"></arcgis-scene>
<script>
udSDKArcGIS.attach(document.getElementById("scene"), {
clientName: "Acme Portal",
models: ["https://my-bucket.s3.amazonaws.com/site.uds"],
});
</script>A worked example lives in index.html.
- The ArcGIS Maps SDK for JavaScript 5.x on the page as the
<script type="module" src="https://js.arcgis.com/5.0/">ESM build.attach()polls for the$arcgisglobal, so the script tags can sit in the obvious order even though the module script is implicitly deferred. - Host page must be cross-origin isolated — udSDK is a threaded
emscripten build and needs
SharedArrayBuffer. Set these on the page response:Verify at runtime:Cross-Origin-Opener-Policy: same-origin Cross-Origin-Embedder-Policy: require-corp (or credentialless)window.crossOriginIsolated === true. - The CDN serving the udSDK siblings (
udSDKjs.js,udSDKjs.wasm,easyudSDKjs.js) must sendAccess-Control-Allow-OriginandContent-Type: application/wasmon the.wasm. UnderCOEP: require-corpit must also sendCross-Origin-Resource-Policy: cross-originon those files, and your.udshosts must do the same. - A Nuclideon udCloud account to sign in.
udSDKArcGIS.attach(sceneEl, opts) returns an EventTarget controller.
sceneEl is the <arcgis-scene> element.
| Option | Default | Notes |
|---|---|---|
clientName |
"arcgis-<hostname>" |
OAuth consent app name |
models |
[] |
array of url strings or {url, resolution, slot} |
signInUI |
"auto" |
"auto" shows the built-in box; false to BYO |
signInLabel |
"Sign in to udCloud" |
button text |
autoEnable |
true |
attach the RenderNode (make udSDK visible) immediately |
tryDomainLogin |
"auto" |
try silent SSO before falling back to OAuth popup |
serverAddress |
(default udCloud) | override for staging / self-hosted / regional |
baseUrl |
pinned SDK CDN base | where to fetch the udSDKjs.* siblings |
arcgisTimeoutMs |
30000 |
how long to wait for $arcgis to appear |
onStatus |
null |
emscripten loader status callback |
signIn()→Promise. Must be triggered from a user gesture.loadModel(spec)→Promise<handle>.specis a url string or{url, resolution, slot}(resolutiondefault2.5,slotdefault-1).enabled— show/hide udSDK by attaching/detaching theRenderNode.signedIn,view,renderNode.destroy()— remove theRenderNode+ UI.
| Event | detail |
|---|---|
ready |
— |
signed-in |
{ method: "oauth" | "domain" } |
model-loaded |
{ url, handle } |
model-error |
{ url, code, error } |
sign-in-error |
{ code, error } |
A tiny static server is included that issues the required COOP/COEP headers so the demo runs locally without setting up nginx/Caddy/etc.:
python httpserver.py
# http://localhost:8080/index.html
-
Bootstrap. udSDK is a threaded emscripten build, which means three non-obvious things have to happen when loading it cross-origin:
Module.locateFilepoints emscripten at the CDN base so it fetchesudSDKjs.wasmfrom there instead of the page's origin.- The pthread pool spawns workers via
new Worker(scriptUrl), which browsers refuse for cross-origin URLs regardless of CORS. The wrapper fetchesudSDKjs.jsas text, wraps it in aBlob, and hands the resulting same-origin Blob URL to emscripten viaModule.mainScriptUrlOrBlob.easyudSDKjs.jsstays a regular cross-origin<script>since it spawns no workers. - The page has to be cross-origin isolated (see Requirements) so
SharedArrayBufferis exposed. The wrapper checkscrossOriginIsolatedup front and fails loudly if it's false.
-
Sign-in. Tries
udSDKJS_CreateSharedFrom_udCloud(name, null)first — a silent domain auth that succeeds if the browser already has a session with the configured udCloud server (typically via corporate SSO). If that fails, the built-in sign-in box opens an OAuth popup from a user gesture and finishes viaudSDKJS_ConnectStart+udSDKJS_ConnectComplete. -
The
RenderNode. Rather than overlaying a separate canvas, the bridge subclasses ArcGIS'sRenderNodeand inserts itself into the SceneView's managed render pipeline. It declaresconsumes: { required: ["composite-color"] }andproduces: "composite-color"— so ArcGIS hands it the fully composited scene color (and its depth-stencil attachment) and takes back the node's output as the new composite. Togglingenabledflipsproducesbetween"composite-color"andnull, which detaches the node from the pipeline. -
Per-frame render. Each frame the node's
render(inputs)runs two screen-space passes into the output framebuffer (a full-screen triangle, no geometry):- It first re-attaches the input's depth-stencil to the output so udSDK fragments depth-test against everything ArcGIS already drew.
- Pass 1 blits the incoming
composite-colorstraight through, so the ArcGIS scene is preserved. - Pass 2 (only once udSDK is ready) resizes udSDK's offscreen scene if
the viewport changed, pushes the camera's
viewMatrixandprojectionMatrixinto udSDK viaudSDKJS_SetMatrix, callsudSDKJS_RenderQueue(), then copies udSDK's colour and depth buffers into two GL textures and draws them. The fragment shader writesgl_FragDepthfrom the udSDK depth sample so udSDK fragments interleave correctly with the ArcGIS depth buffer. - The node calls
requestRender()every frame so udSDK's streaming/LOD keeps progressing even when the camera is still.
-
Colour buffer details. udSDK delivers colour as BGRA, so the shader swizzles
col.bgron output. The wrapper also tags the buffer's top-left pixel magenta before upload; the shader checks that pixel to detect whether udSDK delivered the frame vertically flipped and flips the sample UV to compensate. -
Depth buffer. udSDK's depth comes back as float32, uploaded directly into an
R32Ftexture and sampled as-is, then remapped into clip range (depth * 0.5 + 0.5) when written togl_FragDepth.
- Single-instance only. udSDK's render state is process-global, so one
attach()per page. - No WebGL context-loss recovery. Colour/depth textures aren't recreated after a lost context. Reload the page.
- WebGL2 /
R32Frequired. The shaders are GLSL 3.00 ES and the depth path relies on a renderable/sampleableR32Ftexture. The ArcGIS Maps SDK 5.x runs WebGL2, so this holds in practice.