// Copyright 2025 DeepMind Technologies Limited // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "material_parsing.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Using to satisfy TF_DEFINE_PRIVATE_TOKENS macro below and avoid operating in // PXR_NS. using pxr::TfToken; template using TfStaticData = pxr::TfStaticData; // clang-format off TF_DEFINE_PRIVATE_TOKENS(kTokens, ((auto_, "auto")) ((diffuseColor, "diffuseColor")) ((file, "file")) ((metallic, "metallic")) ((r, "r")) ((raw, "raw")) ((rgb, "rgb")) ((roughness, "roughness")) ((sourceColorSpace, "sourceColorSpace")) ((srgb, "sRGB")) ((UsdPreviewSurface, "UsdPreviewSurface")) ((UsdUVTexture, "UsdUVTexture")) ); // clang-format on pxr::GfVec3f AsVec3f(pxr::GfVec3f val) { return val; } pxr::GfVec3f AsVec3f(float val) { return pxr::GfVec3f(val, val, val); } pxr::GfVec4f AsVec4f(pxr::GfVec4f val) { return val; } pxr::GfVec4f AsVec4f(float val) { return pxr::GfVec4f(val, val, val, 1.0f); } pxr::GfVec4f AsVec4f(pxr::GfVec3f vec3) { return pxr::GfVec4f(vec3[0], vec3[1], vec3[2], 1.0f); } pxr::TfToken GetShaderId(const pxr::UsdShadeShader& shader) { pxr::TfToken shader_id; shader.GetShaderId(&shader_id); return shader_id; } // Data read from a shader input. template struct ResolvedShaderInput { // The value of the input if it is a constant. std::optional value; // The value of the input if it is a texture. std::optional sampler; }; // Attempts to read an input from a USD ShadeInput as a specific type. Returns // nullopt if the input is not found or is not of the given type. template std::optional ReadInput(pxr::UsdShadeInput usd_input) { T val; if (!usd_input.Get(&val)) { return std::nullopt; } return val; } // Attempts to read a UsdUVTexture shader and return a sampler. template ResolvedShaderInput ReadUsdUVTexture(mjSpec* spec, const pxr::UsdShadeShader shader, unsigned nchannels) { ResolvedShaderInput out; mjsTexture* texture = mjs_addTexture(spec); texture->type = mjtTexture::mjTEXTURE_2D; mjs_setName(texture->element, shader.GetPath().GetAsString().c_str()); pxr::TfToken source_color_space = kTokens->auto_; if (auto color_space_input = shader.GetInput(kTokens->sourceColorSpace)) { color_space_input.Get(&source_color_space); } if (source_color_space == kTokens->raw) { texture->colorspace = mjtColorSpace::mjCOLORSPACE_LINEAR; } else if (source_color_space == kTokens->srgb) { texture->colorspace = mjtColorSpace::mjCOLORSPACE_SRGB; } else if (source_color_space == kTokens->auto_) { texture->colorspace = mjtColorSpace::mjCOLORSPACE_AUTO; } pxr::SdfAssetPath resolved_texture_asset_path; if (auto file_input = shader.GetInput(kTokens->file)) { // Use GetValueProducingAttributes to follow all connections including // material interfaces. auto value_attrs = pxr::UsdShadeUtils::GetValueProducingAttributes(file_input); for (const auto& attr : value_attrs) { if (attr.Get(&resolved_texture_asset_path) && !resolved_texture_asset_path.GetAssetPath().empty()) { break; } } } else { mju_error("UsdUVTexture missing inputs:file."); return out; } // Use the resolved path from USD, fall back to asset path if not resolved std::string resolved_path_str = resolved_texture_asset_path.GetResolvedPath(); if (resolved_path_str.empty()) { resolved_path_str = resolved_texture_asset_path.GetAssetPath(); } if (resolved_path_str.empty()) { mju_warning("UsdUVTexture %s: No texture file path specified.", shader.GetPath().GetAsString().c_str()); return out; } if (pxr::UsdShadeUdimUtils::IsUdimIdentifier(resolved_path_str)) { mju_error("MuJoCo does not support UDIM textures: %s", resolved_path_str.c_str()); return out; } if (!std::filesystem::exists(resolved_path_str)) { mju_warning( "USD decoder only supports assets that are available on the file " "system. Could not open: %s", resolved_path_str.c_str()); return out; } texture->nchannel = nchannels; mjs_setString(texture->file, resolved_path_str.c_str()); out.sampler = texture; return out; } // Reads UsdShadeInput as a value of type T or a texture sampler. // Uses GetValueProducingAttributes to follow all connections including // material interfaces. template ResolvedShaderInput ReadShaderInput(mjSpec* spec, pxr::UsdShadeInput input) { ResolvedShaderInput out; if (!input.GetPrim().IsValid()) { return out; } // GetValueProducingAttributes follows connections recursively, including // through material interfaces, and returns the attributes that produce // values. auto value_attrs = pxr::UsdShadeUtils::GetValueProducingAttributes(input); for (const auto& attr : value_attrs) { // Check if this attribute belongs to a UsdUVTexture shader (for textures) pxr::UsdPrim prim = attr.GetPrim(); pxr::UsdShadeShader shader(prim); if (shader) { pxr::TfToken shader_id = GetShaderId(shader); if (shader_id == kTokens->UsdUVTexture) { // Determine channel count from the output name pxr::TfToken attr_name = attr.GetBaseName(); unsigned nchannels = 3; // default if (attr_name == kTokens->rgb) { nchannels = 3; } else if (attr_name == kTokens->r) { nchannels = 1; } out = ReadUsdUVTexture(spec, shader, nchannels); if (out.sampler.has_value()) { return out; } } // For other shader types, fall through to try reading as a value } // Try to read as a direct value out.value = ReadInput(pxr::UsdShadeInput(attr)); if (out.value.has_value()) { return out; } } // If no value-producing attributes found, try reading the input directly // (for non-connected inputs with authored values) out.value = ReadInput(input); return out; } template void AssignShaderInput(mjSpec* spec, mjsMaterial* material, T* val, mjtTextureRole texrole, ResolvedShaderInput resolved_input) { if (resolved_input.value.has_value()) { if constexpr (std::is_same_v) { *val = resolved_input.value.value(); } else if constexpr (std::is_same_v) { const pxr::GfVec3f resolved_value = AsVec3f(resolved_input.value.value()); (*val)[0] = resolved_value[0]; (*val)[1] = resolved_value[1]; (*val)[2] = resolved_value[2]; } else if constexpr (std::is_same_v) { const pxr::GfVec4f resolved_value = AsVec4f(resolved_input.value.value()); (*val)[0] = resolved_value[0]; (*val)[1] = resolved_value[1]; (*val)[2] = resolved_value[2]; (*val)[3] = resolved_value[3]; } } else if (resolved_input.sampler.has_value()) { mjsTexture* texture = resolved_input.sampler.value(); auto name = mjs_getName(texture->element); mjs_setInStringVec(material->textures, texrole, name->c_str()); } else { mju_warning("No value or texture for shader input."); } } void ParsePreviewSurface(mjSpec* spec, mjsMaterial* material, const pxr::UsdShadeShader& shader) { auto diffuse = ReadShaderInput( spec, shader.GetInput(kTokens->diffuseColor)); AssignShaderInput(spec, material, &material->rgba, mjTEXROLE_RGB, diffuse); auto metallic = ReadShaderInput(spec, shader.GetInput(kTokens->metallic)); AssignShaderInput(spec, material, &material->metallic, mjTEXROLE_METALLIC, metallic); auto roughness = ReadShaderInput(spec, shader.GetInput(kTokens->roughness)); AssignShaderInput(spec, material, &material->roughness, mjTEXROLE_ROUGHNESS, roughness); } mjsMaterial* ParseMaterial(mjSpec* spec, const pxr::UsdShadeMaterial& material) { pxr::UsdShadeShader surface_shader = material.ComputeSurfaceSource(); if (!surface_shader.GetPrim().IsValid()) { mju_warning("Material %s has no surface output.", material.GetPath().GetAsString().c_str()); return nullptr; } pxr::TfToken surface_shader_id = GetShaderId(surface_shader); if (surface_shader_id != kTokens->UsdPreviewSurface) { mju_warning("Mujoco only supports UsdPreviewSurface as surface output."); return nullptr; } mjsMaterial* mj_mat = mjs_addMaterial(spec, nullptr); mjs_setName(mj_mat->element, material.GetPath().GetAsString().c_str()); ParsePreviewSurface(spec, mj_mat, surface_shader); return mj_mat; }