Inherits and Specializes

OpenUSD offers two very similar ways to derive from a prim. Let's review how they work and why you might use them.

Inherits and Specializes

The two most mysterious OpenUSD composition arcs are inherits and specializes. They both offer a pretty similar feature. You can have a prim that inherits or specializes a class or another prim. You'd use this when you have a category of items and you want to be able to set values on the whole category.

Suppose you were making a movie and it has a cast of ants. There are a lot of ants to model, rig, etc. and you need to get started, but the look of the movie and the story is still not totally final (anyone from the film industry will be nodding their heads here; the look and story is never final until it's in theaters).

So, to anticipate artistic and story changes, you want to make your ants flexible. You can make each ant model inherit a base Ant class. In the Ant class you can set things like eye color, exoskeleton color, other shading parameters to hit the movie's look. Then later, when someone decides that all the ants' eyes will be brown, you can change them all in one place. If they also decide the main characters ants' eyes should be blue, you can override that class value for just those models.

That's the motivating idea behind these two composition arcs. I was too young to work at Pixar during the mid nineties, so my ant production story is not first hand. I heard this story when I was first trying to understand inherits. Like me, OpenUSD is too young to have been at Pixar for A Bug's Life, but it sounds like the idea of inherits already existed and was in use, getting the bugs worked out ;)

What's a Class?

The class specifier doesn't come up often, so let's quickly get up to speed on what it is. In text form the class specifier appears where you otherwise might see a def or an over.

A class is a type of prim that doesn't normally show up when you list USD prims, so it's called an "abstract prim". It's usually used for inherits. It offers a way to have a prim that's invisible in most contexts, it doesn't draw, and its name doesn't show up in most editors by default.

class "Ant"
{
    color3f eyecolor = (0.53, 0.31, 0.16)
}

Both inherits and specializes can refer to abstract prims like classes, but they can also refer to normal prims ("concrete prims" as opposed to "abstract prims"). In common practice inherits usually refer to classes and specializes usually refer to concrete prims.

Inherits Example

Seeing inherits in action can be a good way to understand it, so I'll make some dumb little examples setting colors. The full files in text form can be seen on github.

We'll make a simple class called ColorClass first. The class can be as simple as this. I'll put a version in github that demonstrates this, but won't dig in too much here.

class "ColorClass"
{
    color3f[] primvars:theClassColor = [(1.0, 0.0, 1.0)]
}

simplest-class

A primvar is a kind of variable that affects geometry in USD. In this case we'll use one with a single constant magenta color value so we can read it in a UsdPreviewSurface material. There are easier ways to set a color, but I want to use a material so the example is a little more general, you could use this approach for all kinds of material settings.

We need to define a material that uses theClassColor. We could do that in each model that derives from ColorClass, or we could define a material in a common layer that's shared by each model. To keep these examples clean and simple, I'm going to add the material to the class itself. This makes the class more complicated, but that way anything that inherits this class will also get this material already bound. The material details aren't that important to understand, but here it is anyway.

class "ColorClass" (
    apiSchemas = ["MaterialBindingAPI"]
)
{
    color3f[] primvars:theClassColor = [(1.0, 0.0, 1.0)]

    rel material:binding = </ColorClass/ColorMaterial>

    def Material "ColorMaterial"
    {
        token outputs:surface.connect = </ColorClass/ColorMaterial/ColorShader.outputs:surface>

        def Shader "ColorShader"
        {
            uniform token info:id = "UsdPreviewSurface"
            float inputs:roughness = 1
            color3f inputs:diffuseColor.connect = </ColorClass/ColorMaterial/ColorReader.outputs:result>
            token outputs:surface
        }

        def Shader "ColorReader"
        {
            uniform token info:id = "UsdPrimvarReader_float3"
            token inputs:varname = "theClassColor"
            float outputs:result
        }
    }
}

inherits-example-1/color_class.usda

Now let's make two models that inherit from Color. A cube called Box and a sphere called Ball. The Box looks like this, the Ball is similar.

def Xform "Box" (
    inherits = </ColorClass>
)
{
    def Mesh "BoxMesh"
    {
        ... Mesh Contents ...
    }
}

inherits-example-1/box.usda

Notice that we inherit from ColorClass in this file but we don't reference it or get it in any other way. We're declaring that Box inherits from this class but the class itself is not defined here. If you open the box.usda file you will get an unshaded cube. We need to look at Box in a context where the ColorClass is defined for the class to do anything.

Now we'll make a demo_set.usda with a few layers. We'll get the class as a sublayer so it is defined, make a grid of Box and Ball references in a sublayer, and have one stronger layer for demo purposes. The files look like this.

#usda 1.0
(
    subLayers = [
        @stronger_layer.usda@,
        @grid.usda@,
        @color_class.usda@
    ]
)

inherits-example-1/demo_set.usda

def Xform "World"
{
    def "Item_1_1" (
        references = @box.usda@
    )
    {
        matrix4d xformOp:transform = ...
        uniform token[] xformOpOrder = ["xformOp:transform"]
    }
    ... Same for Item_1_2, etc. ...

inherits-example-1/grid.usda

inherits-example-1/demo_set.png

If you look at this in a USD editor like usdview you'll see that each Item_M_N has the magenta class color and its mesh and material inside it, combining the referenced prim and the class. Now in any layer we choose we can set opinions on the class, and those will affect all the instances of both models. If I set theClassColor in stronger_layer.usda it will turn everything green.

over "ColorClass"
{
    color3f[] primvars:theClassColor = [(0.0, 1.0, 0.2)]
}

inherits-example-2/stronger_layer.usda

inherits-example-2/demo_set.png

In addition to setting opinions on the class, we can set them on individual items and override the class opinion.

over "ColorClass"
{
    color3f[] primvars:theClassColor = [(0.0, 1.0, 0.2)]
}

over "World"
{
    over "Item_1_2"
    {
        color3f[] primvars:theClassColor = [(1.0, 0.4, 1.0)]
    }
}

inherits-example-3/stronger_layer.usda

inherits-example-3/demo_set.png

Specializes

Specializes are often used differently than inherits, but they are just like inherits with one important distinction. If we keep the examples above but change the inherits to specializes everything is the same. You get all the same output.

There is no difference unless the theClassColor value is changed across a reference. So, if we change the model definition in box.usda to make boxes have a cyan color instead of the class default magenta, we can see the difference between an inherits and a specializes arc.

def Xform "Box" (
    inherits = </ColorClass>
)
{
    # This is the change, now in Box we're setting a cyan color for
    # the class color.
    color3f[] primvars:theClassColor = [(0.0, 1.0, 1.0)]
    
    def Mesh "BoxMesh"
    {
        ... Mesh contents ...
    }
}

inherits-compare-to-specializes/box.usda

If we use inherits = </ColorClass> we'll get this image in the demo_set. Notice that it's the same image we had when the Box didn't set cyan as its default color. In this case setting theClassColor to green overrode Box's cyan setting.

inherits-compare-to-specializes/demo_set.png

If we change the inherits to specializes = </ColorClass> in box.usda and ball.usda we'll get this image in the demo_set.

specializes/demo_set.png

When the model is loaded over a reference, a specialized model keeps its opinions, sometimes called specializations. So the Box models stay cyan even when the class is set to green. As we saw in the previous example, if these were inherits the model could still be overridden by class opinions.

Specializing a prim is basically saying, "I want the values from the class (or prim) for everything except those things I set explicitly in my model."

You can still set each specialized Box's color by setting it on the referenced Item_M_N. There's no way to change all the specialized boxes at once though, unless we change or remove the theClassColor override in the model.

Something a little less theoretical

I mentioned earlier that specializes are used differently from inherits. Specializes were added to USD to address a specific need related to materials.

A classic example is a base Aluminum material and a BrushedAluminum derived material. If the Aluminum base material changes we only want that to show up in BrushedAluminum if BrushedAluminum hasn't changed those values. That way an artist working on the "look" on a stronger layer can make changes to all kinds of Aluminum in one go without worrying about breaking derived materials.

With inherits the stronger Aluminum opinions would win if the material library was loaded from a reference. With specializes the specialized BrushedAluminum opinions win over Aluminum, even if the Aluminum opinions are stronger.

Pixar wrote up a nice example of this, and it shows how specializes is often used in actual practice.

USD Terms and Concepts — Universal Scene Description 24.08 documentation

You could use specializes for anything where it fits your needs, it isn't just for materials. I just wanted to mention the most common usage.

Practical Considerations

Chances are your editing tool doesn't offer inherits or specializes workflows. Can you use this at all?

Anything that reads USD files will get correct inherits and specializes support even if it doesn't know what those are. So, if you create your content with these composition arcs you can at least read them in your renderer, game engine, editing tool. Creating them in the first place, and authoring values on them, is the sticky part.

SideFX Houdini and NVIDIA Omniverse are the only public tools I'm aware of that do allow editing inherits and specializes, at least as of July 2024.

If you're able to write scripts to create/edit it can be done that way, possibly as part of an asset build script or pipeline. It's not too tough to take the USD files output from your editor and sublayer them in a Model.usd file that doesn't change them, but adds inherits, specializes, or other things you'd like.

Outside of that though, I think a non-technical user in most USD capable editors probably can't use these features. I'm sorry if that's you and you read this far 😅 I hope there's some consolation in at least understanding how it works and what it takes to use.

Notes and Details

  • You might see classes named like _class_myclass. The leading underscores and class are not necessary, just a common convention. It can make it easier to tell what's a class just by looking at text names.
  • To avoid weird recursive definitions a prim can't have an inherits or specializes from a prim that's one of its own parents or children. For this reason class prims are often put at the root scope, with no parents.
  • I have heard the argument that USD should get rid of specializes and make inherits work the way specializes does now. Basically, specializes is how inheritance should always work, and the inherits version is not useful enough to justify keeping. I imagine making that change would break too much existing content, but I think it's interesting to think about.