Getting custom primvars to work inside materials/shaders

Hi all, I’m trying to wrap my head around primvars and how to use them correctly from surface graphs.

All I get is a grey sphere in usdview, but no warnings/errors. My expectation would be:

  1. at least usdview should show me a green sphere (the fallback value for the primvar reader).

  2. at best, usdview should show me a blue sphere (the primvar on the MySphere prim)

Can anyone enlighten me how this is done correctly? :smiley: Thank you!

#usda 1.0
(
    defaultPrim = "Root"
    metersPerUnit = 1
    upAxis = "Y"
)

def Xform "Root"
{
    def Sphere "MySphere" (
        prepend apiSchemas = ["MaterialBindingAPI"]
    )
    {
        rel material:binding = </Root/MyMaterial>
        float3 primvars:myColor = (0, 0, 1) # doesn't work
        uniform token primvars:myColor:interpolation = "constant"
        double radius = 0.5
    }

    def Material "MyMaterial"
    {
        token outputs:mtlx:surface.connect = </Root/MyMaterial/PreviewSurface.outputs:surface>

        def Shader "ColorReader"
        {
            uniform token info:id = "ND_UsdPrimvarReader_vector3"
            float3 inputs:fallback = (0, 1, 0) # doesn't work
            string inputs:varname = "myColor"
            float3 outputs:result
        }

        def Shader "ColorConverter"
        {
            uniform token info:id = "ND_convert_vector3_color3"
            # float3 inputs:in = (1, 0, 0) # this works
            float3 inputs:in.connect = </Root/MyMaterial/ColorReader.outputs:result>
            color3f outputs:out
        }

        def Shader "PreviewSurface"
        {
            uniform token info:id = "ND_UsdPreviewSurface_surfaceshader"
            color3f inputs:diffuseColor.connect = </Root/MyMaterial/ColorConverter.outputs:out>
            float inputs:metallic = 0
            float inputs:roughness = 0.5
            token outputs:surface
        }
    }
}

Things I tried:

Hi @herbst ,

  1. You shouldn’t need to declare interpolation at all since constant is the fallback (but inside parens is correct, not as a separate attribute, if you do)
  2. I see code in Hydra that is trying to handle the primvar/geomprop translation, but our test-cases only cover the _vector2 reader for texture coordinates, not _vector3 or _color3
  3. If you were to change over to native UsdPreviewSurface shader implementations, using a UsdPrimvarReader_float3 directly connected to the UsdPreviewSurface should work in both Storm and Renderman, but I reckon not iOS, so…
  4. Is the reason for going for a mtlx implementation in hopes that it should work even on iOS, or more of a pipeline consistency concern?

We’ll try to have a deeper look when we can - thanks!

1 Like

(and addressing your notes… you can’t mix and match ND_ implementations with “native” implementations - needs to be all one or the other, and if native, connected to outputs:surface, not outputs:mtlx:surface)

1 Like

Thanks for the response and details, @spiff! Well appreciated.

Indeed, I’m trying to get things to work in QuickLook, where such a graph can be authored easily but doesn’t work either (no fallback value and no primvars)… (I reported it to Apple already).

For completeness and to learn more about the differences, I tried to assemble minimal graphs using the native approach (which still renders grey in usdview+Storm for me which I got to work after some trial-and-error):

#usda 1.0
(
    defaultPrim = "Root"
    metersPerUnit = 1
    upAxis = "Y"
)

def Xform "Root"
{
    def Sphere "MySphere" (
        prepend apiSchemas = ["MaterialBindingAPI"]
    )
    {
        rel material:binding = </Root/MyMaterial>
        float3 primvars:myColor = (0, 0, 1) ( interpolation = "constant" ) # overrides the color in the material
        double radius = 0.5
    }

    def Material "MyMaterial"
    {
        token outputs:surface.connect = <PreviewSurface.outputs:surface>

        def Shader "ColorReader"
        {
            uniform token info:id = "UsdPrimvarReader_float3"
            float3 inputs:fallback = (0, 1, 0) # used when primvar not found
            string inputs:varname = "myColor"
            float3 outputs:out
        }

        def Shader "PreviewSurface"
        {
            uniform token info:id = "UsdPreviewSurface"
            color3f inputs:diffuseColor = (1, 0, 0) # used when not connected
            color3f inputs:diffuseColor.connect = <../ColorReader.outputs:out>
            float inputs:metallic = 0
            float inputs:roughness = 0.5
            token outputs:surface
        }
    }
}

And the MaterialX ND_ version (this one I can’t get to work, always stays grey):

#usda 1.0
(
    defaultPrim = "Root"
    metersPerUnit = 1
    upAxis = "Y"
)

def Xform "Root"
{
    def Sphere "MySphere" (
        prepend apiSchemas = ["MaterialBindingAPI"]
    )
    {
        rel material:binding = </Root/MyMaterial>
        float3 primvars:myColor = (0, 0, 1) (
            interpolation = "constant"
        )
        double radius = 0.5
    }

    def Material "MyMaterial"
    {
        token outputs:mtlx:surface.connect = <PreviewSurface.outputs:surface>

        def Shader "ColorReader"
        {
            uniform token info:id = "ND_UsdPrimvarReader_vector3"
            float3 inputs:fallback = (0, 1, 0)
            string inputs:varname = "myColor"
            float3 outputs:out
        }

        def Shader "PreviewSurface"
        {
            uniform token info:id = "ND_UsdPreviewSurface_surfaceshader"
            float3 inputs:diffuseColor.connect = </Root/MyMaterial/ColorReader.outputs:out>
            float inputs:metallic = 0
            float inputs:roughness = 0.5
            token outputs:surface
        }
    }
}

And a geomprop version that I believe should also be equivalent.
This one actually works in usdview+Storm!
(but sadly not in Reality Composer / QuickLook)

#usda 1.0
(
    defaultPrim = "Root"
    metersPerUnit = 1
    upAxis = "Y"
)

def Xform "Root"
{
    def Sphere "MySphere" (
        prepend apiSchemas = ["MaterialBindingAPI"]
    )
    {
        rel material:binding = </Root/MyMaterial>
        float3 primvars:myColor = (0, 0, 1) (
            interpolation = "constant"
        )
        double radius = 0.5
    }

    def Material "MyMaterial"
    {
        token outputs:mtlx:surface.connect = <PreviewSurface.outputs:surface>

        def Shader "PreviewSurface"
        {
            uniform token info:id = "ND_UsdPreviewSurface_surfaceshader"
            color3f inputs:diffuseColor = (0.18, 1, 0.18)
            color3f inputs:diffuseColor.connect = </Root/MyMaterial/Convert.outputs:out>
            float inputs:metallic = 0
            float3 inputs:normal
            float inputs:opacity
            float inputs:roughness = 0.5
            token outputs:out
            token outputs:surface
        }

        def Shader "GeometricProperty"
        {
            uniform token info:id = "ND_geompropvalue_vector3"
            float3 inputs:default = (0, 1, 0)
            string inputs:geomprop = "myColor"
            float3 outputs:out
        }

        def Shader "Convert"
        {
            uniform token info:id = "ND_convert_vector3_color3"
            float3 inputs:in.connect = </Root/MyMaterial/GeometricProperty.outputs:out>
            color3f outputs:out
        }
    }
}

I haven’t looked at your mtlx examples yet, but the PreviewSurface one… per the spec, UsdPrimvarReader does not have an out output, but rather a result output. Apologies on that, these nodes were designed before we were really synced up with the MaterialX folks, and adopted studio conventions.

In your first mtlx example, I can’t find anything that looks incorrect.. wondering if that works in a pure mtlx environment outside of USD? If so, that could be Hydra-integration problem, as the scene description looks correct according to how the Mtlx spec defines ND_UsdPrimvarReader_vector3 . But glad the last example works also!