MaterialX Support in LightUSD

September 3, 2026 · View on GitHub

LightUSD provides MaterialX integration including parsing .mtlx files, color space conversions, OpenPBR/StandardSurface shader support, and a JavaScript MaterialX pipeline for Three.js.

Source Files

FileDescription
src/usdShade.hhMaterialXConfigAPI, OpenPBRSurface, UsdPreviewSurface, Material structs
src/usdMtlx.{hh,cc}MtlxOpenPBRSurface, MtlxAutodeskStandardSurface, MtlxUsdPreviewSurface; USD<->mtlx graph conversion
src/usdMtlx-write.cc.mtlx writer
src/color-space.{hh,cc}ColorSpace enum and token/utility functions
src/image-util.{hh,cc}Color space conversion functions
src/mtlx-dom.{hh,cc}MaterialX document object model
src/mtlx-xml-parser.{hh,cc}, src/mtlx-xml-tokenizer.{hh,cc}MaterialX XML parser/tokenizer
src/mtlx-simple-parser.{hh,cc}Simplified MaterialX parser
src/mtlx-usd-adapter.hhUSD-MaterialX integration
src/prim-reconstruct-shader.ccShader/Material/NodeGraph reconstruction (OpenPBRSurface, MtlxAutodeskStandardSurface, UsdPreviewSurface)
src/tydra/render-data-shader.hhOpenPBRSurfaceShader, PreviewSurfaceShader, RenderMaterial structs
src/tydra/render-data-material.ccConvertMaterial / OpenPBR + UsdPreviewSurface shader conversion
src/tydra/render-data-material-mtlx.ccMaterialX NodeGraph traversal (ExtractMtlxNodeGraphInfo)
src/tydra/materialx-to-json.{hh,cc}MaterialX to JSON conversion
web/js/src/lightusd/LightUSDMaterialX.jsJS OpenPBR to Three.js conversion

Supported Shader Types

ShaderC++ Structinfo:id
OpenPBR SurfaceOpenPBRSurface / MtlxOpenPBRSurfaceND_open_pbr_surface_surfaceshader
Standard SurfaceMtlxAutodeskStandardSurfaceND_standard_surface_surfaceshader
USD Preview SurfaceUsdPreviewSurface / MtlxUsdPreviewSurfaceUsdPreviewSurface

(info:id constants: kNdOpenPbrSurfaceSurfaceshader, kNdStandardSurfaceSurfaceshader in src/usdMtlx.hh; kUsdPreviewSurface in src/usdShade.hh. Note lightusd uses the bare UsdPreviewSurface id, not ND_UsdPreviewSurface_surfaceshader.)

MaterialXConfigAPI

struct MaterialXConfigAPI {
    TypedAttributeWithFallback<std::string> mtlx_version{"1.38"};
    TypedAttributeWithFallback<std::string> mtlx_namespace{""};
    TypedAttributeWithFallback<std::string> mtlx_colorspace{"lin_rec709"};
    TypedAttributeWithFallback<std::string> mtlx_sourceUri{""};
};

Stored as Material::materialXConfig (optional) and applied via config:mtlx:version, config:mtlx:namespace, config:mtlx:colorspace, config:mtlx:sourceUri.


Color Space Support

ColorSpace Enum (src/color-space.hh)

TokenEnumDescription
lin_rec709_sceneLinRec709SceneLinear Rec.709/sRGB (default)
lin_ap0_sceneLinAp0SceneACES 2065-1 (AP0)
lin_ap1_sceneLinAp1SceneACES CG (AP1)
lin_p3d65_sceneLinP3D65SceneLinear P3-D65
lin_rec2020_sceneLinRec2020SceneLinear Rec.2020
lin_adobergb_sceneLinAdobeRGBSceneLinear Adobe RGB
lin_ciexyzd65_sceneLinCieXyzD65SceneCIE XYZ-D65
srgb_rec709_sceneSrgbRec709ScenesRGB Rec.709
srgb_ap1_sceneSrgbAp1ScenesRGB AP1
srgb_p3d65_sceneSrgbP3D65ScenesRGB P3-D65
g22_rec709_sceneG22Rec709SceneGamma 2.2 Rec.709
g22_ap1_sceneG22Ap1SceneGamma 2.2 AP1
g22_adobergb_sceneG22AdobeRGBSceneGamma 2.2 Adobe RGB
g18_rec709_sceneG18Rec709SceneGamma 1.8 Rec.709
dataDataNon-color data (normals, displacement)
rawRawLegacy equivalent of data (distinct enum; is_data() returns true)
unknownUnknownUnspecified
identityIdentityLegacy equivalent of unknown (distinct enum)

Utility functions (src/color-space.{hh,cc}): to_token(), from_token(), is_linear(), is_data(), get_default() (= LinRec709Scene). Default color space is Linear Rec.709.

Conversion Functions (src/image-util.{hh,cc})

// sRGB
bool srgb_8bit_to_linear_f32(const std::vector<uint8_t> &in, ...);
bool srgb_f32_to_linear_f32(const std::vector<float> &in, ...);

// Rec.2020
bool linear_rec2020_to_linear_sRGB(const std::vector<float> &in, ...);
bool linear_sRGB_to_linear_rec2020(const std::vector<float> &in, ...);

// ACEScg (AP1)
bool linear_sRGB_to_ACEScg(const std::vector<float> &in, ...);
bool ACEScg_to_linear_sRGB(const std::vector<float> &in, ...);

// Display P3
bool linear_displayp3_to_linear_sRGB(const std::vector<float> &in, ...);
bool linear_sRGB_to_linear_displayp3(const std::vector<float> &in, ...);

// Gamma
bool gamma22_f32_to_linear_f32(const std::vector<float> &in, ...);
bool gamma18_f32_to_linear_f32(const std::vector<float> &in, ...);

Performance: sRGB 8-bit -> linear conversion uses a 256-entry lookup table (a static SRGB_8BIT_TO_LINEAR_DOUBLE table for the f32 path; a per-call 256-entry table for the 8bit->8bit path).


Tydra MaterialX Conversion Pipeline

Conversion Flow

These conversion functions live in src/tydra/render-data-material.cc. ConvertMaterial, ConvertOpenPBRSurfaceShader, and ConvertPreviewSurfaceShader are RenderSceneConverter methods; the ConvertMtlx* helpers are file-local static functions:

USD Stage -> Material with shaders -> ConvertMaterial() -> RenderMaterial
  ├── OpenPBRSurface             -> ConvertOpenPBRSurfaceShader()                  -> RenderMaterial.openPBRShader
  ├── MtlxOpenPBRSurface         -> ConvertMtlxOpenPBRSurfaceToOpenPBRSurface()    -> same path
  ├── MtlxAutodeskStandardSurface-> ConvertMtlxStandardSurfaceToOpenPBRSurface()   -> same path
  └── UsdPreviewSurface          -> ConvertPreviewSurfaceShader()                  -> RenderMaterial.surfaceShader

ConvertMtlxStandardSurfaceToOpenPBRSurface maps StandardSurface params to OpenPBR equivalents. Key type differences handled:

  • opacity: StandardSurface color3f -> OpenPBR float (Rec.709 luminance extraction)
  • normal / tangent: copied only when authored
  • StandardSurface transmission_extra_roughness has no OpenPBR equivalent (dropped)

OpenPBR Surface Fields

CategoryFields
Basebase_weight, base_color, base_roughness, base_metalness, base_diffuse_roughness
Specularspecular_weight, specular_color, specular_roughness, specular_ior, specular_ior_level, specular_anisotropy, specular_rotation, specular_roughness_anisotropy
Transmissiontransmission_weight, transmission_color, transmission_depth, transmission_scatter, transmission_scatter_anisotropy, transmission_dispersion, transmission_dispersion_abbe_number, transmission_dispersion_scale
Subsurfacesubsurface_weight, subsurface_color, subsurface_radius, subsurface_radius_scale, subsurface_scale, subsurface_anisotropy, subsurface_scatter_anisotropy
Coatcoat_weight, coat_color, coat_roughness, coat_anisotropy, coat_rotation, coat_ior, coat_affect_color, coat_affect_roughness, coat_roughness_anisotropy, coat_darkening
Sheen/Fuzzsheen_weight, sheen_color, sheen_roughness, fuzz_weight, fuzz_color, fuzz_roughness
Thin Filmthin_film_weight, thin_film_thickness, thin_film_ior
Emissionemission_luminance, emission_color
Geometryopacity, normal, tangent, coat_normal, coat_tangent

Material Tag Classification

ShaderOpaqueTranslucentMasked
OpenPBRDefaulttransmission_weight > 0 or opacity < 1--
UsdPreviewSurfaceDefaultopacity < 1opacityThreshold > 0

NodeGraph Traversal (ExtractMtlxNodeGraphInfo)

Follows inputs:in connections through node chains (max depth 15):

Node TypeActionExtracted Info
ND_normalmap*Follow inputs:innormal_map_scale, has_normal_map
ND_rotate3d_vector3Follow inputs:intangent_rotation
ND_image_*Terminalnormal_map_texture
ND_tiledimage_*Terminaltexture path, uvtiling, uvoffset
ND_texcoord_*Terminaltexcoord_index
ND_geompropvalue_*Terminalgeomprop_name (primvar)
ND_separate* / ND_extract_*Follow inputs:inMulti-output / channel extraction
ND_convert_*Follow inputs:inType conversion passthrough
ND_constant_*TerminalConstant value
Math/color opsFollow inputs:inPassthrough traversal

Texture Colorspace Handling

Parameter TypesourceColorSpace
Color params (base_color, emission_color, specular_color, coat_color, sheen_color, subsurface_color, transmission_color, fuzz_color)sRGB
Non-color params (roughness, metalness, normal, weight, IOR, etc.)Raw

Texture Wrap Modes

MaterialXUSD UsdUVTexture::Wrap
periodicRepeat
clampClamp
mirrorMirror
constantBlack

Mesh Attribute Resolution (UVs and Normals)

UVs: Tydra checks ListUVNames(material) for shader-referenced UV sets, falls back to primvars:st (configurable via MeshConverterConfig::default_texcoords_primvar_name). This mirrors OpenUSD's defaultgeomprop="UV0" -> primvars:st mapping.

Normals: Loaded unconditionally from mesh geometry:

  1. primvars:normals (primvar)
  2. normals (legacy attribute)
  3. Auto-compute smooth normals

Blender MaterialX Export

Principled BSDF to OpenPBR Surface Mapping

Blender 4.5+ exports Principled BSDF as OpenPBR Surface (ND_open_pbr_surface_surfaceshader). Both MaterialX and UsdPreviewSurface are exported on the same Material.

Base / Specular

Blender Principled BSDFOpenPBR SurfaceNotes
Base Colorbase_colorDirect
Metallicbase_metalnessDirect
Diffuse Roughnessbase_diffuse_roughnessOren-Nayar (0 = Lambertian)
Roughnessspecular_roughnessDirect
IORspecular_iorDirect
IOR Levelspecular_weightMultiply by 2.0 (Blender 0.5 = neutral, OpenPBR 1.0 = neutral)
Specular Tintspecular_colorDirect
Anisotropicspecular_roughness_anisotropyDirect
Anisotropic Rotation(tangent vector)Tangent rotated around normal

Subsurface / Transmission

BlenderOpenPBRNotes
Subsurface Weightsubsurface_weightDirect
Subsurface Scalesubsurface_radiusMean free path
Subsurface Radiussubsurface_radius_scalePer-channel RGB multiplier
Subsurface Anisotropysubsurface_scatter_anisotropyDirect
Transmission Weighttransmission_weightDirect

Coat / Sheen / Thin Film / Emission

BlenderOpenPBRNotes
Coat Weightcoat_weightDirect
Coat Tintcoat_colorDirect
Coat Roughnesscoat_roughnessDirect
Coat IORcoat_iorDirect
Coat Normalgeometry_coat_normalDirect
Sheen Weightfuzz_weightRenamed: sheen -> fuzz
Sheen Tintfuzz_colorRenamed
Sheen Roughnessfuzz_roughnessDirect
Thin Film Thicknessthin_film_thicknessDirect
Thin Film IORthin_film_iorDirect
Emission Coloremission_colorDirect
Emission Strengthemission_luminanceDirect
Alphageometry_opacityDirect
Normalgeometry_normalDirect

Blender Node to MaterialX Mapping

Blender shader nodes translate to MaterialX standard library nodes. Machine-readable data files:

  • doc/blender_shader_nodes.json — every Blender shader node (98) with inputs/outputs/socket types/defaults.
  • doc/blender_to_materialx_node_mapping.json — Blender node -> MaterialX node translation (per-node materialx_nodes + formula).

Note: rotation sockets (NodeSocketRotation) store Euler angles in radians internally (the Blender UI displays degrees); convert with math.radians() / math.degrees() when authoring or reading these values.

Key patterns:

Color Nodes:

Blender NodeMaterialX Translation
InvertND_subtract_color3 (1-color) + ND_mix_color3 (Fac blend)
Hue/Saturation/ValueND_combine3_vector3 + ND_hsvadjust_color3 + ND_mix_color3
Brightness/ContrastND_multiply + ND_add + ND_subtract + ND_max chain
GammaND_power_color3
RGB to BWND_luminance_color3 + ND_extract_color3
MixND_mix_color3
Separate ColorND_extract_color3 (per channel)
Combine ColorND_combine3_color3

Math/Vector Nodes:

Blender OperationMaterialX Node
ADD/SUBTRACT/MULTIPLY/DIVIDEND_{op}_float or ND_{op}_vector3
DOT_PRODUCTND_dotproduct_vector3
NORMALIZEND_normalize_vector3
CLAMPND_clamp_float
MAP_RANGEND_remap_float
POWER/SQRT/ABSND_power_float / ND_sqrt_float / ND_absval_float
Trig functionsND_sin_float / ND_cos_float / ND_tan_float

Geometry (auto-generated):

ND_normal_vector3(world) -> ND_normalize_vector3 -> Normal
ND_tangent_vector3(world) -> ND_normalize_vector3 -> ND_rotate3d_vector3(-90) -> Tangent

Limitations:

  • RGB Curves, ColorRamp: only pre-computed with constant inputs
  • OSL scripts, custom groups: not supported
  • MixRGB blend modes: only MIX fully translated
  • Noise/Voronoi: approximations via ND_noise3d_float / ND_cellnoise3d_float

Blender Export Options

bpy.ops.wm.usd_export(
    filepath="output.usda",
    export_materials=True,
    generate_materialx_network=True,
    export_textures=True,
)

Test files: tests/feat/node-mtlx/*.usda


Three.js / WebGL Integration

Material Implementations

ImplementationClassUse Case
MeshPhysicalMaterialTHREE.MeshPhysicalMaterialStandard PBR, broad compatibility
OpenPBRMaterialCustom ShaderMaterialFull OpenPBR BRDF (Oren-Nayar, coat IOR, fuzz)

OpenPBR Parameters and Three.js MeshPhysicalMaterial Mapping

LightUSD parses and converts the full OpenPBR parameter set (struct OpenPBRSurfaceShader in src/tydra/render-data-shader.hh; defaults below). The Three.js MeshPhysicalMaterial target supports only a subset. The MaterialX input names are the OpenPBR inputs:<name> attributes; Blender v4.5+ emits the same names.

Support legend: Y full, ~ partial / workaround, N no Three.js equivalent.

OpenPBR paramTypeDefaultMeshPhysicalMaterialSupport
base_weightfloat1.0(folded into opacity)~
base_colorcolor3f(0.8, 0.8, 0.8)color / mapY
base_roughness (diffuse)float0.0(Oren-Nayar; no direct slot)~
base_metalnessfloat0.0metalness / metalnessMapY
specular_weightfloat1.0reflectivity (r170+)~
specular_colorcolor3f(1, 1, 1)specularColor~
specular_roughnessfloat0.3roughness / roughnessMapY
specular_iorfloat1.5iorY
specular_ior_levelfloat0.5N
specular_anisotropyfloat0.0anisotropy (r170+)~
specular_rotationfloat0.0anisotropyRotation (r170+)~
transmission_weightfloat0.0transmissionY
transmission_colorcolor3f(1, 1, 1)— (Three.js assumes white)N
transmission_depthfloat0.0thickness (approx)~
transmission_scatter / _anisotropy / _dispersion0— (volume effects)N
subsurface_* (weight, color, radius, scale, anisotropy)see struct— (no core SSS)N
sheen_weightfloat0.0sheenY
sheen_colorcolor3f(1, 1, 1)sheenColorY
sheen_roughnessfloat0.3sheenRoughnessY
coat_weightfloat0.0clearcoatY
coat_roughnessfloat0.0clearcoatRoughnessY
coat_colorcolor3f(1, 1, 1)— (clearcoat is white)N
coat_iorfloat1.5ior (shared)~
coat_anisotropy / coat_rotation / coat_affect_color / coat_affect_roughness / coat_darkeningfloat0.0N
thin_film_weightfloat0.0iridescence~
emission_colorcolor3f(1, 1, 1)emissive / emissiveMapY
emission_luminancefloat0.0emissiveIntensityY
geometry_opacityfloat1.0opacity + transparentY
geometry_normalnormal3f(0, 0, 1)normalMapY
geometry_tangentvector3f(1, 0, 0)(computed by Three.js)~

Three.js limitations: no subsurface scattering, no colored/volumetric transmission or dispersion, clearcoat is always white and isotropic, and anisotropy is experimental (r170+). Approximate unsupported features (e.g. SSS via albedo darkening) or warn and drop. The WebGPU MaterialX node path can cover more of these.

API

import {
    convertOpenPBRToMeshPhysicalMaterial,      // Immediate (textures load async)
    convertOpenPBRToMeshPhysicalMaterialLoaded  // Await all textures
} from 'lightusd/LightUSDMaterialX.js';

const material = await convertOpenPBRToMeshPhysicalMaterialLoaded(matData, usdScene, options);

HDR/EXR textures supported via LightUSD WASM decoder (HDR) and Three.js EXRLoader.

NodeGraph Optimizer

Location: web/js/src/lightusd/LightUSDMaterialX.js

Simplifies MaterialX node graphs from Blender export:

import { optimizeNodeGraph, NodeGraphOptimizationLevel } from 'lightusd/LightUSDMaterialX.js';
const optimized = optimizeNodeGraph(nodeGraph, NodeGraphOptimizationLevel.STANDARD);

Optimization Levels: NONE (0), BASIC (1, identity removal), STANDARD (2, patterns + identity), AGGRESSIVE (3, + constant folding)

Pattern Categories:

  • Blender-specific: Invert (subtract+mix -> invert), Brightness/Contrast, HSV adjust
  • Channel ops: Swizzle detection, separate/combine passthrough, single channel modification
  • Math: Add/subtract inverse cancellation, multiply/divide inverse, idempotent chains
  • Conversion: Type roundtrips (color3<->vector3), colorspace roundtrips, chained normalize
  • Identity removal: multiply by 1, add 0, mix factor=0/1, etc.

pxrUSD MaterialX Parser Reference

Reference for pxrUSD's pxr/usd/usdMtlx/parser.cpp which converts MaterialX node definitions to USD Sdr shader nodes.

Key Components

  1. ShaderBuilder: Accumulates data for SdrShaderNode construction (properties, metadata, name remapping)
  2. AddProperty(): Maps MaterialX typed elements to SdrShaderProperty with type resolution via UsdMtlxGetUsdType(), default values via UsdMtlxGetUsdValue(), UI metadata, colorspace, and primvar tracking
  3. ParseElement(): Main NodeDef parsing - determines context (shader/pattern), collects primvars from geometry nodes, iterates inputs/outputs
  4. UsdMtlxParserPlugin: Loads MaterialX documents, looks up NodeDef by identifier, returns SdrShaderNode

Environment Settings

USDMTLX_PRIMARY_UV_NAME: Override primary UV set name (default: UsdUtilsGetPrimaryUVSetName() -> "st")


USD NodeGraph Structure

def Material "OpenPBRMaterial" {
    token outputs:surface.connect = </OpenPBRMaterial/Shader.outputs:surface>

    def Shader "Shader" {
        uniform token info:id = "ND_open_pbr_surface_surfaceshader"
        color3f inputs:base_color.connect = </OpenPBRMaterial/NG.outputs:base_color>
        float inputs:specular_roughness = 0.5
        token outputs:surface
    }

    def NodeGraph "NG" {
        def Shader "texcoord" {
            uniform token info:id = "ND_texcoord_vector2"
            int inputs:index = 0
            float2 outputs:out
        }
        def Shader "diffuse_tex" {
            uniform token info:id = "ND_image_color3"
            asset inputs:file = @textures/diffuse.png@
            float2 inputs:texcoord.connect = </OpenPBRMaterial/NG/texcoord.outputs:out>
            color3f outputs:out
        }
        color3f outputs:base_color.connect = </OpenPBRMaterial/NG/diffuse_tex.outputs:out>
    }
}

Implementation Status

Completed

  • MaterialX XML parsing. MaterialXParser::ValidateVersion() accepts v1.36/1.37/1.38; newer versions parse with a warning.
  • Color space conversions (all MaterialX spaces; see table above)
  • MaterialXConfigAPI struct and config:mtlx:* attributes
  • OpenPBRSurface, MtlxAutodeskStandardSurface, UsdPreviewSurface shader structs
  • Prim reconstruction (USDA/USDC read) of Shader / Material / NodeGraph, incl. OpenPBRSurface, MtlxAutodeskStandardSurface, UsdPreviewSurface (src/prim-reconstruct-shader.cc)
  • Tydra: OpenPBR / MtlxOpenPBR / MtlxAutodeskStandardSurface -> OpenPBRSurfaceShader conversion
  • Tydra: UsdPreviewSurface -> PreviewSurfaceShader conversion
  • Tydra: NodeGraph traversal with texture/normal/tangent extraction
  • .mtlx writer (src/usdMtlx-write.cc)
  • JS: OpenPBR to MeshPhysicalMaterial / OpenPBRMaterial conversion
  • JS: NodeGraph optimizer

Partial / In Progress

  • MaterialX file import via references (basic, no full composition)
  • Displacement/volume shader evaluation (connections tracked, not evaluated)

Not Yet Implemented

  • MaterialX <xi:include> / standard-library resolution
  • Geometry assignments and collections
  • Unit system support

References