# Getting custom primvars to work inside materials/shaders

**URL:** <https://forum.aousd.org/t/getting-custom-primvars-to-work-inside-materials-shaders/2734>\
**Category:** USD\
**Created:** [October 13, 2025, 6:04pm UTC](https://forum.aousd.org/t/getting-custom-primvars-to-work-inside-materials-shaders/2734 "2025-10-13T18:04:51Z")\
**Posts on this page:** 6\
**Page:** 1

<div class="post-metadata">

**Author:** ![herbst](https://sea2.discourse-cdn.com/flex016/user_avatar/forum.aousd.org/herbst/32/905_2.png) [@herbst](https://forum.aousd.org/u/herbst)\
**Post date:** [October 13, 2025, 6:04pm UTC](https://forum.aousd.org/t/getting-custom-primvars-to-work-inside-materials-shaders/2734/1 "2025-10-13T18:04:51Z")

</div>

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? 😃 Thank you!

```auto
#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:

- following this example [https://github.com/usd-wg/assets/blob/main/intent-vfx/scenes/teapotScene\_layoutOverrides.usd](https://github.com/usd-wg/assets/blob/main/intent-vfx/scenes/teapotScene_layoutOverrides.usd) but no luck

- various combinations of float3 / color3f / color3f / float3 for the primvar

- not using the colorconverter inbetween (as the teapot directly passes color3f

- UsdPrimvarReader\_vector3 instead of ND\_UsdPrimvarReader\_vector3 as I saw that a few times

- moving interpolation = “constant” after the primvar declaration inside ( … )

---

<div class="post-metadata">

**Author:** ![spiff](https://sea2.discourse-cdn.com/flex016/user_avatar/forum.aousd.org/spiff/32/22_2.png) [@spiff](https://forum.aousd.org/u/spiff)\
**Post date:** [October 13, 2025, 11:54pm UTC](https://forum.aousd.org/t/getting-custom-primvars-to-work-inside-materials-shaders/2734/2 "2025-10-13T23:54:25Z")

</div>

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!

---

<div class="post-metadata">

**Author:** ![spiff](https://sea2.discourse-cdn.com/flex016/user_avatar/forum.aousd.org/spiff/32/22_2.png) [@spiff](https://forum.aousd.org/u/spiff)\
**Post date:** [October 13, 2025, 11:58pm UTC](https://forum.aousd.org/t/getting-custom-primvars-to-work-inside-materials-shaders/2734/3 "2025-10-13T23:58:15Z")

</div>

(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`)

---

<div class="post-metadata">

**Author:** ![herbst](https://sea2.discourse-cdn.com/flex016/user_avatar/forum.aousd.org/herbst/32/905_2.png) [@herbst](https://forum.aousd.org/u/herbst)\
**Post date:** [October 14, 2025, 8:12am UTC](https://forum.aousd.org/t/getting-custom-primvars-to-work-inside-materials-shaders/2734/4 "2025-10-14T08:12:48Z")

</div>

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):

```c
#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):

```c
#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)

```c
#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
        }
    }
}

```

 ![image](https://us1.discourse-cdn.com/flex016/uploads/aousd/original/2X/3/3cae9960650355e19c5c3c4791dff3b345a303a8.jpeg)

---

<div class="post-metadata">

**Author:** ![spiff](https://sea2.discourse-cdn.com/flex016/user_avatar/forum.aousd.org/spiff/32/22_2.png) [@spiff](https://forum.aousd.org/u/spiff)\
**Post date:** [October 15, 2025, 6:51pm UTC](https://forum.aousd.org/t/getting-custom-primvars-to-work-inside-materials-shaders/2734/5 "2025-10-15T18:51:20Z")

</div>

I haven’t looked at your mtlx examples yet, but the PreviewSurface one… [per the spec](https://openusd.org/release/spec_usdpreviewsurface.html#primvar-reader), 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.

---

<div class="post-metadata">

**Author:** ![spiff](https://sea2.discourse-cdn.com/flex016/user_avatar/forum.aousd.org/spiff/32/22_2.png) [@spiff](https://forum.aousd.org/u/spiff)\
**Post date:** [October 15, 2025, 6:56pm UTC](https://forum.aousd.org/t/getting-custom-primvars-to-work-inside-materials-shaders/2734/6 "2025-10-15T18:56:34Z")

</div>

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!
