USD Comes to Babylon.js: Preliminary Runtime Loading Support

USD Comes to Babylon.js: Preliminary Runtime Loading Support

I’m excited to share that initial USD loading support has been merged into Babylon.js!

You can now load .usd, .usda, .usdc, and .usdz assets directly into a Babylon.js scene.

This support is preliminary. USD is a broad ecosystem, and this first implementation focuses on bringing common geometry, materials, instancing, and animation. We plan to expand support as the community shares assets, reports issues, and requests additional USD features.

How it works

The loader uses **OpenUSD compiled to WebAssembly**, running inside a Web Worker. OpenUSD opens and composes the stage, then the C++ extractor produces two binary buffers: a command queue describing the scene and a data buffer containing geometry, textures, transforms, and animation samples.

Babylon.js reads those buffers and creates the corresponding scene objects directly:

USD asset -> OpenUSD/WASM worker -> Binary command buffers -> Babylon.js scene

What’s supported?

The initial implementation includes:

File formats and composition: USD, USDA, USDC, and USDZ, including layers, references, and payloads when their dependencies are supplied.

Scene hierarchy: transforms, affine matrices, stage units, Y-up/Z-up conversion, mesh orientation, and default-time visibility.

Geometry: polygon meshes, triangulation, indexed geometry, normals, vertex colors/opacity, and material subsets.

Analytic primitives: UsdGeomCube, UsdGeomSphere, UsdGeomCylinder, and UsdGeomCone.

Instancing: repeated USD instance/prototype geometry is shared and represented using Babylon.js instances.

Materials: common UsdPreviewSurface PBR inputs, including base color, metallic/roughness, emission, normal maps, opacity, alpha blending, and cutouts.

Textures: PNG, JPEG, BMP, and WebP; supported packed metallic/roughness/occlusion layouts; UV transforms and texture wrapping.

Skinning and animation: skeletons, up to eight joint influences per vertex, skeletal animation, and transform-node animation.

Loading a USDZ from a URL

Register the loader, then use the regular Babylon.js loading API:


import { AppendSceneAsync } from "@babylonjs/core/Loading/sceneLoader.js";
import "@babylonjs/loaders/USD/index.js";

await AppendSceneAsync("https://example.com/models/robot.usdz", scene);

Loading a multi-file USD stage

A .usd file may depend on other layers, payloads, or textures. Unlike a self-contained USDZ package, those dependencies must currently be supplied explicitly.

For example, suppose your asset has this layout:

asset/
|-- scene.usd
    -- layers/
     -- geometry.usda
     -- materials.usda

If scene.usd references layers/geometry.usda and layers/materials.usda, load those files and provide them through pluginOptions.usd.files:

import { AppendSceneAsync } from "@babylonjs/core/Loading/sceneLoader.js";
import "@babylonjs/loaders/USD/index.js";

const baseUrl = "https://example.com/asset/";

async function getBytes(path: string): Promise<ArrayBuffer> {
    const response = await fetch(baseUrl + path);
    if (!response.ok) {
        throw new Error(`Could not load ${path}: ${response.status}`);
    }
    return response.arrayBuffer();
}

const [geometry, materials] = await Promise.all([
   getBytes("layers/geometry.usda"),
   getBytes("layers/materials.usda"),
]);

await AppendSceneAsync(baseUrl + "scene.usd", scene, {
    pluginOptions: {
        usd: {
            rootFileName: "scene.usd",
            files: {
            "layers/geometry.usda": geometry,
            "layers/materials.usda": materials,
            },
        },
    },
});

The file-map keys must preserve the paths and casing used by the USD asset. Include any additional referenced layers, payloads, and textures in the same map. rootFileName specifies where the root layer sits in that virtual directory structure.

The loader does not automatically discover and download external dependencies from the root file’s URL.

Loading USDZ from simple URL

Try it in the Sandbox

The integration also adds USD support to the Babylon.js Sandbox, once the updated build is deployed.

You can drop a single USD/USDZ file, multiple supporting files, or a folder. Folder paths are preserved, and when several files could be the root layer, the Sandbox lets you choose which one to open.

What isn’t supported yet?

This is not yet a complete USD renderer. Important limitations include:

  • USD cameras, lights, and UsdGeomPointInstancer.
  • Curves, points, NURBS, volumes, and additional primitive schemas.
  • Subdivision-surface evaluation: control cages are currently loaded as polygon meshes.
  • Blend shapes, vertex-cache animation, animated topology, and animated material bindings.
  • Tangent generation and multiple UV sets used simultaneously by one material.
  • MaterialX, MDL, arbitrary shader graphs, and advanced material inputs such as transmission, clearcoat, displacement, and IOR.
  • Separate metallic/roughness textures, separate occlusion textures, and unsupported channel layouts.
  • Runtime variant switching and automatic external-dependency downloading.

Transform and skeletal animation are supported, but that should not be confused with support for every kind of time-varying USD data.

Help shape what comes next

We want this initial implementation to grow around real community needs.

Please share the USD workflows you’d like to bring to Babylon.js, the features your assets rely on, and any problems you encounter. Small, shareable reproduction assets are especially helpful.

We plan to add support for more and more USD features as they are requested by the community. This is the beginning, and your feedback will help determine what comes next.

Implementation details: Babylon.js PR #18882.

17 Likes

This is GREAT.

I’ve tried it with an own scene in USDz and found that Snadbox duplicates all materials, although the ones used in the scene are the ones Sandbox names as “(Double-Sided)”.

Is this normal behaviour?

I need to check… can you provide a sample Playground?