USD File Basics

OpenUSD file structure explained at a high level

USD File Basics

Pixar's OpenUSD is often referred to as a file format. It does offer a few graphics file formats, but USD also provides a lot of additional functionality and software. In this article we're just going to look at the file formats.

USD ships with a few built-in file formats and more can be added with software plugins. The built-in formats use these extensions: *.usd, *.usda, *.usdc and *.usdz.

ℹ️
*.usd Contains either usda or usdc content
*.usda Text formatted file
*.usdc Binary formatted file
*.usdz Bundle of USD and other files

Core Content Files

I think of files with extensions *.usd, *.usda and *.usdc as core content files. They're the lowest common denominator building blocks of a USD scene.

The *.usd extension is an umbrella container for either a *.usda or a *.usdc file. The correct format will be detected at runtime based on header bytes. This extension can be used for all your basic USD files if you don't want to deal with setting different extensions based on the file format.

The *.usda and *.usdc file formats are interchangeable. A single file can store an entire graphics scene including multiple models, lights, cameras, materials and other data. Typically the scene is broken up into more manageable parts and each part is stored in one or more of these files. The files can then refer to each other in a few different ways to aggregate that data into a final scene. This is the real magic of USD, but I'll dig into it in another article. For now, let's focus on what the files are.

The basic building block of a USD file is called a primitive. People often refer to these as prims for short. Meshes, transforms, materials, lights are all types of prims in USD's mental model. Prims are hierarchical, so a mesh prim might be contained inside a transform prim, indicating that the mesh is moved by that transform. You'd call the mesh a child of the parent transform.

A file with a collection of prims could be stored either as text, in a usda file, or as binary content in a usdc file. Here's an example in usda.

#usda 1.0

def Xform "RootTransform"
{
    def Mesh "Cube"
    {
    }
}

sample minimal USD file

This is a pretty minimal file, but it is a functional one. If we were to open it in usdview we'd get prims but nothing would draw in the viewport. Let's break down what's here.

#usda 1.0

This is the required set of header bytes for a usda file. The version 1.0 is also required. At the time of writing (2024) it is the only version, and it has been the only version for as long as USD has been publicly available (since 2016 or so). It's only there to guard against the future in case breaking changes are needed for some reason.

def Xform "RootTransform"

This line defines a new primitive, which is a transform called RootTransform. The def stands for define. The other option is over which stands for override. Overrides are for combining files where you want to override content from another file without changing it.

Xform is the type of the new primitive being defined, and "RootTransform" is the name. This is followed by a set of curly braces { ... }. Inside these braces are the child prims and any properties of the RootTransform. In this case there is a child prim and no properties.

def Mesh "Cube"

Inside the RootTransform we define this Mesh prim and name it Cube. It also has some empty curly braces beneath it. Notice that Cube is nested inside RootTransform, which means that transform will move it.

The RootTransform and Cube have no properties set in the file, which means they only have fallback values. The mesh has no faces, and the transform is positioned at the origin. If we set some properties we can draw something. Here's the same file with some properties set.

#usda 1.0
(
    defaultPrim = "RootTransform"
)

def Xform "RootTransform"
{
    def Mesh "Cube"
    {
        int[] faceVertexCounts = [4, 4, 4, 4, 4, 4]
        int[] faceVertexIndices = [0, 1, 3, 2, 0, 4, 5, 1, 1, 5, 6, 3, 2, 3, 6, 7, 0, 2, 7, 4, 4, 7, 6, 5]
        point3f[] points = [(-50, -50, -50), (50, -50, -50), (-50, -50, 50), (50, -50, 50), (-50, 50, -50), (50, 50, -50), (50, 50, 50), (-50, 50, 50)]
        token subdivisionScheme = "none"
    }
}

example with metadata and properties

There are two changes. I've added metadata to the file and set some of the properties of the Cube so it's actually a cube instead of an empty mesh.

(
    defaultPrim = "RootTransform"
)

This section in parenthesis ( ... ) is metadata. In usda file format all metadata is stated in parenthesis following the item it pertains to. This metadata is placed at the top of the file because it refers to the entire file, but you may also see parenthesis after prims or properties. In this case I set the defaultPrim metadata of the file to RootTransform. This isn't really needed for the example, but it could help in the future if we were to add this file to another scene. It means that unless the user specifies otherwise, the primary content of the file is the RootTransform and everything inside it.

{
    int[] faceVertexCounts = [4, 4, 4, 4, 4, 4]
    int[] faceVertexIndices = [0, 1, 3, 2, 0, 4, 5, 1, 1, 5, 6, 3, 2, 3, 6, 7, 0, 2, 7, 4, 4, 7, 6, 5]
    point3f[] points = [(-50, -50, -50), (50, -50, -50), (-50, -50, 50), (50, -50, 50), (-50, 50, -50), (50, 50, -50), (50, 50, 50), (-50, 50, 50)]
    token subdivisionScheme = "none"
}

In this block I set several of the properties of the Cube. The points, faceVertexCounts and faceVertexIndices define a mesh with six square faces and set the points to the corners of a 100 unit cube centered at the origin. Each of these properties follows the pattern type name = value.

The property subdivisionScheme is also set to "none". USD meshes use this property to indicate whether the mesh is a subdivision surface, and if so, how to subdivide the faces. In our case we want a purely polygonal mesh with no subdivision, so we say "none". Notice that the type is token, a token in USD is a text string that usually comes from a predefined list of options.

Here's a screenshot of this file in usdview

This exact content can be stored in a usda file or a usdc file. In either case you could give it the extension usd and anything that supports USD would be able to open and work with that file. USD ships with a command line tool called usdcat that can switch the file types back and forth.

Which format you'd choose depends on your use case. If you want to be able to read the files and tell what's in them without converting to text, usda might be the right choice for storing them. If the files are large, or you need better performance, usdc (also called a crate file) is likely the better choice. Crate files are optimized for good compression and fast lookup speeds with typical computer graphics workloads. On platforms that support it they also keep as much data out of core as possible, so that you can have very large files without needing to read them all into memory. I've seen crate files in the hundreds of GB that were readable and renderable.

USDZ Files

USD also ships with support for *.usdz files. These are a special kind of zip file that can be opened by USD like a single file. A typical use case is to have a main usdc file that defines the content and materials of an object, and to wrap that up with any texture files it needs all into one single usdz archive. Then the usdz file has everything it needs to render and can be passed around more easily. I think of this as a publishing format.

Apple has a lot of great examples of usdz files, and supports them in quicklook on Mac, in Keynote and other applications. Augmented reality and VR platforms also often support usdz files.

Let's take a look at the content of Apple's Toy Drummer example. Here's a screenshot in usdview, expanded to show some prims and properties.

If I extract the contents of the toy_drummer_idle.usdz file I get this set of files.

├── toy_drummer_idle.usdc
├── 0
│   ├── accessories_realistic_ao.png
│   ├── accessories_realistic_bc.png
│   ├── accessories_realistic_m.png
│   ├── accessories_realistic_n.png
│   ├── accessories_realistic_r.png
│   ├── drummer_realistic_ao.png
│   ├── drummer_realistic_m.png
│   ├── drummer_realistic_n.png
│   ├── drummer_realistic_r.png
│   └── toy_drummer_realistic_bc.png

We can see that the usdz file contains one usdc file, which is the main content. That file then refers to the png texture images in the folder named 0 and uses them for the materials in the toy drummer.

Because all of this is in a single archive it can be downloaded or dragged and dropped in an OS window without losing any of the needed textures.

Other File Formats

USD's built-in file formats are provided using a plugin system, and software plugins can add support for other file formats. Some optional but common ones are Alembic, Draco and MaterialX files. If built with support USD can read and write these files, and refer to them in the same ways it refers to the built-in USD file formats.

It's not uncommon for film studios to write their own file formats and add new features. Dreamworks did this with their usdat file format, which they've been good enough to open source.

GitHub - dreamworksanimation/dwa_usd_plugins: DreamWorks Animation USD Plugins
DreamWorks Animation USD Plugins. Contribute to dreamworksanimation/dwa_usd_plugins development by creating an account on GitHub.

TTFN

And that's it! This is a whirlwind overview of what a USD file is, but it barely scratches the surface of what's available. In the future I'll be writing more articles that dive deeper on these topics and cover more aspects of USD. For instance, why would I want to use it in the first place? 😆