PixarAnimationStudios / OpenUSD

Universal Scene Description
http://www.openusd.org
Other
6.02k stars 1.19k forks source link

How to distinguish shader types? #1700

Closed zanazakaryaie closed 2 years ago

zanazakaryaie commented 2 years ago

Description of Issue

I'm using USD for loading a .usdc file. I have successfully extracted mesh information (number of vertices, normals, etc.) from UsdGeomMesh. I can also get the texture filepath from UsdShadeShader but can't figure out the texture type (diffuse, normal, ambient occlusion, etc.) of the file. I need to know this for rendering in my own engine.

Steps to Reproduce

  1. Downloaded some files from https://developer.apple.com/augmented-reality/quick-look/
  2. Used the following code to parse data
    
    int main()
    {
    const std::string filepath = ; //file path to .usdc file
    UsdStageRefPtr loadedStage = UsdStage::Open(filepath);
    if (loadedStage)
    {
    auto pseudoRoot = loadedStage->GetPseudoRoot();
    Traverse(pseudoRoot, 0);
    return 0;
    }
    else
    {
    std::err << "Could not load " << filepath << std::endl;
    return -1;
    }
    }

void Traverse(const UsdPrim &prim, int depth) { if (prim.GetTypeName().GetString() == "Xform") { getGeometricTransformations(prim); } else if (prim.GetTypeName().GetString() == "Mesh") { auto mesh = getMesh(prim); //some code for printing mesh } else if (prim.GetTypeName().GetString() == "Material") { material = new Material(); //my own class for represnting material } else if (prim.GetTypeName().GetString() == "Shader") { fillMaterial(prim, material); }

for (UsdPrim const& c : prim.GetChildren()) Traverse(c, depth + 1); }

void fillMaterial(const UsdPrim &prim, Material* material) { UsdShadeShader shader(prim); std::vector inputs = shader.GetInputs();

for (const auto& shadeinput : inputs) { const auto shaderName = shadeinput.GetFullName().GetString();

if (shaderName == "inputs:diffuseColor")
{
  GfVec3f value;
  if (shadeinput.Get<GfVec3f>(&value))
    material->setDiffuseColor(value.data());
}
else if (shaderName == "inputs:emissiveColor")
{
  GfVec3f value;
  if (shadeinput.Get<GfVec3f>(&value))
    material->setEmissiveColor(value.data());
}
else if (shaderName == "inputs:metallic")
{
  float value;
  if (shadeinput.Get<float>(&value))
    material->setMetalnessIntensity(value);
}
else if (shaderName == "inputs:occlusion")
{
  float value;
  if (shadeinput.Get<float>(&value))
    material->setAmbientOcclusionIntensity(value);
}
else if (shaderName == "inputs:roughness")
{
  float value;
  if (shadeinput.Get<float>(&value))
    material->setRoughnessIntensity(value);
}
else if (shaderName == "inputs:clearcoat")
{
  float value;
  if (shadeinput.Get<float>(&value))
    material->setClearcoatLevel(value);
}
else if (shaderName == "inputs:clearcoatRoughness")
{
  float value;
  if (shadeinput.Get<float>(&value))
    material->setClearcoatRoughness(value);
}
else if (shaderName == "inputs:file")
{
  SdfAssetPath path;
  if (shadeinput.Get<SdfAssetPath>(&path))
  {
    auto img = Image::loadFromFile(path.GetString()); //Which texture type (diffuse, normal, roughness, etc.) is this image?!!!        
  }
}

} }



### System Information (OS, Hardware)
Ubuntu 21 Desktop (x64)

### Package Versions
Latest USD

### Build Flags
Just turned off python
spiffmon commented 2 years ago

Hi @zanazakaryaie , In general, there may be no single purpose for a given texture, as it is common to pack three or four scalar signals into an RGB or RGBA texture. To determine intent for a given texture, you will need to follow the connection from an input attribute that consumes it. This implies you need to do a specialized sub-traversal when you see a UsdShadeMaterial prim. So, in your code, dispense with the global variable for accessing a Material, and instead call out to your fillMaterial() which takes a UsdShadeMaterial as its USD-side argument. It will then follow the outputs:surface terminal's connection to find the UsdPreviewSurface Shader prim (and if it doesn't find a shader whose info:id is not "UsdPreviewSurface" you probably can't do anything with the Material). Then you can iterate through the Shader's inputs, looking for connections or values, processing the connected prims if needed.

The are API's to help you do this... I would suggest looking at the simple shading tutorial script, which although Python, shows you the principal API's involved. I think you'll find UsdShadeUtils::GetValueProducingAttributes() quite handy as it embodies all the logic for finding the attribute that will be of interest to you, given an input on the PreviewSurface prim.

There are also facilities for performing traversals, some that even allow pruning (as you would want to do when you hit a Material), e.g. UsdPrimRange::iterator has a PruneChildren() method.

Finally, rather than comparing hardcoded strings to identify prim types, it is both more efficient and much more robust to do:

if (prim.IsA<UsdShadeMaterial>()) ...

Cheers!

zanazakaryaie commented 2 years ago

Hi @spiffmon Thanks a lot for your guide. It was very informative