How do I intantiate MaterialX(and other) shaders?

I was debating if I should tag this beginner or not, but I’ve been googling a bunch and haven’t been able to figure it out. But I’m pretty new to Usd Shaders and Materials.

Basically: I want to add a UsdShade.Shader that’s an instantiation of a MaterialX Standard Surface. (Or also non-MaterialX shaders - but this originally came up because I was trying to wrap my head around MaterialX.)
The basic shader tutorials make it look like I would do something that’s essentially

shader = UsdShade.Shader.Define(stage, shader_primpath)
shader.CreateIdAttr("ND_standard_surface_surfaceshader")
# hunt down the MaterialX docs and shader.CreateInput(...) each of the inputs
# and the outputs.

but I keep thinking… that can’t be right.
Though that is basically how the tutorial puts together a preview shader.

I know there’s a UsdMtlx, but I can only find an overview doc, but not an api description, and am having a heck of a time trying to figure out what I can do with it.

I found some code - I think in the USD source for parsing materialx, and found out about Sdr.Registry(). With that, it looks like maybe I can:

>>> sdr = Sdr.Registry()
>>> sdn = sdr.GetShaderNodeByIdentifier("ND_standard_surface_surfaceshader")
>>> sdn.GetInputNames()
['base', 'base_color', 'diffuse_roughness', 'metalness', 'specular', 'specular_color', 'specula
r_roughness', 'specular_IOR', 'specular_anisotropy', 'specular_rotation', 'transmission', 'tran
smission_color', 'transmission_depth', 'transmission_scatter', 'transmission_scatter_anisotropy
', 'transmission_dispersion', 'transmission_extra_roughness', 'subsurface', 'subsurface_color',
 'subsurface_radius', 'subsurface_scale', 'subsurface_anisotropy', 'sheen', 'sheen_color', 'she
en_roughness', 'coat', 'coat_color', 'coat_roughness', 'coat_anisotropy', 'coat_rotation', 'coa
t_IOR', 'coat_normal', 'coat_affect_color', 'coat_affect_roughness', 'thin_film_thickness', 'th
in_film_IOR', 'emission', 'emission_color', 'opacity', 'thin_walled', 'normal', 'tangent']
# then loop through those, getting the shader properties like
>>> base_color_input = sdn.GetShaderInput("base_color")
>>> base_color_input.GetDefaultValue()
Gf.Vec3f(0.800000011920929, 0.800000011920929, 0.800000011920929)
>>> base_color_input.GetType()
'float'

And use the properties to shader.CreateInput(…)
Though… in the above example, I notice that the default is a Vec3f, but the type is only given as “float” - not even “float3”, so I was also wondering if there’s another function I can use to find out that it is a vector?
It feels like this is a little bit better, but still feels like there would be an easier way like:

  • Can I more directly CreateInput(some pxr.Sdr.ShaderProperty) ?

  • Better yet, can I more directly do something like sdn.Define(shader_primpath) where sdn is a pxr.Sdr.ShaderNode that would set up all the inputs and outputs, maybe even with their default values?

  • Did the “ND_standard_surface_surfaceshader” come from Usd? I’m in HoudiniSolaris (in a python shell pane), so I wasn’t sure if it came from Solaris, or if it’s natively defined in Usd? In some of the random code I found googling, it looked like the ND_ was maybe a standard in USD? Also, here it looked like though it is materialx, it considered its render context to be “karma”. I guess what I’m getting at is: do I get these shaders outside of houdini - like in usdview? Who populates the registry and when?

I’m not an expert on the API here, but to your first question you can do

base_color_input.GetTypeAsSdfType()[0].type

and you’d get it as a GfVec3f. I’m sure there’s a better way, but I think that should unblock you at least on that front?

I do not believe there are convenience APIs to make shader creation as easy as you want here, but I think that’s a really good idea to have something like that. It might be worth filing an issue against the OpenUSD GitHub, or even a PR if you’re comfortable with the C++ API to do so.

When I run that, it returns
Tf.Type.FindByName('float')

From looking at the shaderProperty source code

I would have expected the type to be a Color or Color3f.

So I feel like the answer “float” was completely wrong.
BTW, I’m in a Solaris/Houdini python shell, looking for this.
On top of that, I also tried the registry to look up “vray:BRDFVRayMtl” and it returned a shader node. But when I asked for GetInputNames(), I got an empty list. So I am getting skeptical about the Sdr.Registry being the reference for shaders.

So I am still unclear about: What is the Sdr.Registry intended for?
The VOP nodes in Houdini have all the parns for the Mtlx Standard Shader and the BRDFVRayMtl shaders, which suggests to me SideFX/Houdini is not using the Sdr.Registry to get the shader definitions - so are all the DCCs basically doing my first guess - research the docs for the shaders and set it up manually? (And independent renderers like VRay would be registering their VOP plugins, but not necessarily registering complete definitions in the Sdr.Registry?)

Hmm… maybe I’ll write up the request - I have an embarassingly old newbie username there… I occasionally dive in to read the C++ source code to try to understand something, but so far, my only interaction with USD has been through python.

You’ve made a ton of good deductions here, @SteveHwan . Sdr.REgistry is absolutely supposed to be the definitive reference for shader node definitions for all renderer/language implementations of each shader. I also suspect that Vray is not properly feeding the registry with its shader definitions from its USD/Hydra plugins - and this is an area where our documentation could be vastly improved.

I also love the idea of leveraging Sdr more in UsdShade, however the suggestion of initializing a Shader from an SdrSHaderNode and having it add specs for all the inputs, with default values will actually be somewhat counter-productive because it will bloat the scene and eliminate one of the important (in large shading networks) optimizations Hydra and other renderers apply, which is to only process inputs that have scene description (either connections or values) authored.

The path-of-least-friction for UsdShade would be for Shader publishers to use our usdgenschemafromsdrnode utility to generate actual Usd schemas for each shader type. Then when you add a ND_standard_surface_surfaceshader prim, you would see all the possible inputs as builtins with fallbacks, which does not defeat the scalability optimization I mentioned. We do this for the shaders and shader-like objects that ship with RenderMan, as part of hdPrman. But it adds an extra authoring/pipeline step after changing (e.g.) any given OSL shader, and the pattern has yet to catch on more broadly.

So today a user-friendly UsdShade-based DCC must consult the Sdr.Registry itself for presentation and help authoring.

I am not sure what’s going on with the base_color input… based on the definition in the MaterialX library it sure does seem like it should be a Color3f. Possibly something is going wrong in the UsdMtlx translation.

So it sounds like if everything was working as intended, I should be able to just create a UsdShade.Shader, set the id, and that would be enough. And anything I didn’t explicitly declare would fall back to the default, which sounds great to me.
Though I would still need an accurate Sdr.Registry so when I did want to set values, I could consult it for the names/types. Or are you saying from the id, there’s a way I can access the schema, and look it up there? I thought all the shaders just had a “Shader” schema, and didn’t know where to find more specific ones.

A couple more notes on what I was seeing in houdini (Maybe I should mention I’m looking in an older 19.5. And for that matter, my VRay is actually in the 5.2 series):

  • With the ND_standard_surface_surfaceshader, when I get the Shader Properties, ALL of the inputs are coming back “float”. And there are several others that should have been a color. However, when I output the usd, everything is correct from both vray and mtlx - there are floats, color3f, color4f, etc.
  • In the Solaris “Material Library” node, in the VOP network, if I instantiate a BRDFVRayMtl and just leave them on their defaults and then write out the .usda file, the vray shader is fully populated with int, float, color4f parameters. Though the types aren’t really relevant here - what I mean is that by default, everything is output.
  • Also in the VOP, if I instantiate the MaterialX Standard Surface and write it out, I get around a third of them with floats and color3f. and it’s kind of weird, it looks like it’s not actually setting a value, but has customData for houdini.