Prims, Files and Layers

What is an OpenUSD Layer? How does it relate to Prims and Files?

Prims, Files and Layers

A prim in a USD file is the basic building block of graphics content. Let's take a high level look at prims, see how they interact within a file, and how they combine across layers.

First an aside about files and layers. One of USD's most powerful features is the ability to combine layers. This enables non-destructive editing, easier collaboration, variants with small disk footprints, asset reuse and recombination and just a lot of other features. When combining two files we refer to them as layers. People tend to use the terms USD file and USD layer interchangeably.

USD files are called layers because they can be layered on top of each other. When files are layered there is a weaker layer and a stronger layer. The result of these layered files is the combination of the contents of both layers. If both layers have a different idea about a value, the stronger layer "wins".

For example, consider the simple 100 unit cube from the USD files intro article.

#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"
    }
}

cube.usda

This file contains two prims, RootTransform and Cube. You could imagine it looking like this.

A mental model for nested prims

Please bear with me while I show two more USD layers. I promise this is going somewhere.

#usda 1.0
(
    subLayers = [
        @cubeChanges.usda@,
        @cube.usda@
    ]
)

cubeModel.usda

#usda 1.0

over "RootTransform"
{
    over "Cube"
    {
        token subdivisionScheme = "catmullClark"
        float3[] primvars:displayColor = [(0.0, 0.0, 1.0)] (
            interpolation = "constant"
        )
    }
}

cubeChanges.usda

The cubeModel.usda file defines a "model" (not really, there is more to the idea of Model-hood in USD, but let's ignore that for now). This model has two subLayers, one is the cube.usda we looked at earlier, and the other is this new cubeChanges.usda file.

The cubeChanges.usda file defines some sparse updates to the original cube. Notice that in this file we use over where previously we used def Xform or def Mesh. This declares these prim specifiers as "overrides". This file overrides Cube's subdivisionScheme to make it "catmullClark" instead of "none". It also adds a new property called primvars:displayColor.

Primvars are variables that can apply to geometry in different ways, varying from vertex to vertex, or across faces or uv patches. They're a powerful concept in their own right, but in this case, we're only using one to set a constant blue color across our whole cube.

This diagram attempts to show how these layers are combined when you open cubeModel.usda in a USD-enabled application. I shortened some names to make it more compact.

0:00
/0:17

Composing sublayers

The Cube overrides in cubeChanges.usda apply to the same Cube from cube.usda because they are at the same path. Every prim in a USD scene has a unique path. In this case, the path to Cube is /RootTransform/Cube. If the overs in the cubeChanges.usda file had a different path, like if the Cube wasn't inside RootTransform, or if it was named Cube2, the overs would not have applied to our cube.

Another thing to notice, the earlier a layer is in the subLayers list, the stronger it is. In this model, cube.usda is weaker than cubeChanges.usda. That's why the subdivisionScheme is updated when we overlay them. If the order in the subLayers list were reversed, the "none" value would win.

A common pitfall with layer strength is that cubeModel.usda is strongest. If it had values for a prim at /RootTransform/Cube they would always win. In the animation above it would be layered on last. If you're using sublayers this is good to keep in mind, the root layer (the layer you open) is always strongest.

You can open any of these three files. We haven't changed our original file, so you'd still get the good old cube. You can open the cubeModel and get the blue subdivided version. If you open the cubeChanges.usda file you'll just get nothing. There aren't any errors, but also nothing will draw because no mesh has been defined or referred to.

For some nerdy eye candy 🤓 here's what our catmullClark subdivided blue cube looks like when fully subdivided in usdview.

our lovely blue sphere/cube

Sublayers are not the only way to combine layers! There are six ways, referred to as composition arcs in USD lingo. It's honestly probably too many.

References though, are great. A reference in USD is used to take a prim from one USD layer and place it in another layer at a new path.

Let's say someone loved our two cubes and contracted us to make an animation called Cubes in Space. Let's set up a scene with two cubes in it.

#usda 1.0

def Xform "SpaceRoot"
{
    def "Box" (
        references = [ @cube.usda@ ]
    )
    {
        float3 xformOp:translate = (150.0, 0.0, 0.0)
        token[] xformOpOrder = ["xformOp:translate"]
    }

    def "SpaceBox" (
        references = [ @cubeModel.usda@</RootTransform> ]
    )
    {
    }
}

cubesInSpace.usda

In this file I've defined two prims with references, one at path /SpaceRoot/Box and another at path /SpaceRoot/SpaceBox. These are defined without a type, it's just a named prim. If nothing sets their type they would load as type Prim. In each prim I've set a reference.

def "Box" (
    references = [ @cube.usda@ ]
)
{
    float3 xformOp:translate = (150.0, 0.0, 0.0)
    token[] xformOpOrder = ["xformOp:translate"]
}

The first one references the original cube.usda file we created. Remember that this had a metadata value called defaultPrim set to RootTransform. That prim is what we will reference by default, it gets plucked from the original file with all its contents and placed into our file renamed to /SpaceRoot/Box.

Notice that I also set a translation on it, to move it away from the origin. That overrides any translation that would have been in the source cube.usda file. We can override anything else in the same way.

def "SpaceBox" (
    references = [ @cubeModel.usda@</RootTransform> ]
)

In /SpaceRoot/SpaceBox I referenced the cubeModel.usda file instead, because I think space boxes are probably round. In this case I had to specify which prim to pluck from the file, because it has no defaultPrim set. I went ahead and left SpaceBox at the origin instead of translating it.

Here's a visualization of what happens when we open cubesInSpace.usda

0:00
/0:16

composing references

This is a pretty dumb example, but I hope it's simple enough to show the idea quickly. There are a lot of nice side effects of references.

  • References make it easy to have libraries of models, or lighting rigs, or materials, and choose what you'd like to use in your content
  • References compose with other kinds of composition arcs, so we can modify the referenced content in our scene's layers without having to change the referenced file
  • If the models we're referencing are very large, we could reference that large model as many times as we want and we only need to store one copy of the big data on disk.
  • When USD opens a file that's referenced multiple times it only reads one copy into memory, saving RAM (Renderers might not benefit from this, USD offers instancing primitives to limit renderer memory usage)
  • We don't need to know anything about how the referenced content is assembled. In this example we referenced cubeModel.usda and didn't have to worry about the fact that it has sublayers, that's handled for us. The content you reference can be as complex as it needs to be, and the referencing file doesn't need to worry about it. If another file references cubesInSpace.usda it doesn't need to know that we referenced other files that themselves had sublayers

Here's our dumb example in all its usdview rendered glory.

so lame

The goal here was to give a broad overview of prims and layers, just to explain the big concepts. I've left out tons of things and glossed over some big topics. Still, I hope this shows some of the power of USD.

References let you aggregate content, saving time and memory. Sublayers let you modify content without changing the source, in other words, non-destructive editing. Combining just those two features makes a powerful toolkit for 3d content creation.

USD offers a ton of other functionality. Some of it can be found in other formats and libraries, but I believe this composability and non-destructive editing is unique, at least for now.