Skip to content

specular_color tinting of internal reflections is inconsistent in the reference MaterialX graph (TIR untinted, partial internal reflection tinted) #311

Description

@portsmouth

Summary

The reference MaterialX graph for open_pbr_surface does not distinguish between external and internal reflection when applying specular_color, and as a side effect it tints the two kinds of internal reflection inconsistently:

  • partial internal reflection (below the critical angle) is tinted by specular_color;
  • total internal reflection (TIR) (above the critical angle) is not tinted by specular_color.

This came up in the working-group discussion around specular_color / back-face behavior; filing it so it can be resolved in the spec and reflected in the reference graph (and noted for the ASWF/OpenUSD normative shading-model docs).

What the graph does

The dielectric base is built from two independent single-mode lobes:

Node scatter_mode tint
dielectric_reflection (+ _tf thin-film variant) "R" specular_color
dielectric_transmission "T" if_transmission_tint (white; becomes transmission_color only when transmission_depth == 0)
dielectric_base = layer(
    top  = dielectric_reflection_tf_mix   # the R lobe, tinted by specular_color
    base = mix(dielectric_transmission(+ volume), opaque_base, transmission_weight)
)

specular_color is wired straight into tint of a single dielectric_bsdf with scatter_mode="R". That node reflects on whichever side the interface is hit (the dielectric_bsdf decides entering-vs-exiting internally from the ior), and tint multiplies the whole lobe. There is no backfacing node, geompropvalue facing test, or any view-direction conditional in the graph — so the tint applies uniformly to reflections from both hemispheres.

Verified in both:

  • the OpenPBR reference graph (reference/open_pbr_surface.mtlx), and
  • the shipped MaterialX library (libraries/bxdf/open_pbr_surface.mtlx, MaterialX 1.39.5).

The inconsistency

Because TIR is modeled inside the transmission lobe (scatter_mode="T") rather than the reflection lobe:

  • partial internal reflection → produced by the R lobe → tinted by specular_color;
  • TIR → produced by the T lobe → never tinted by specular_color.

So whether an internal reflection is tinted depends on whether the ray is above or below the critical angle — which was never intended to be an observable distinction of specular_color. There is effectively no single, well-defined behavior for "internal reflection."

Spec ambiguity

The graph matches neither interpretation cleanly:

  • Literal current specspecular_color tints the BRDF (light reflected back into the same hemisphere, from upper or lower incidence), transmission unaffected. Under this reading TIR reflections should also be tinted, but they are not (they live in the T lobe).
  • "External-only" intent (raised in the working group) — specular_color should tint only the external reflection. Under this reading partial internal reflections should not be tinted, but they are (the R lobe).

Root cause / MaterialX limitation

With the current MaterialX node set, a single dielectric_bsdf(scatter_mode="R") does not expose entering-vs-exiting, so the tint cannot be gated on facing. Cleanly implementing an external-only (or a consistent internal) behavior would require either additional MaterialX functionality or a graph restructuring.

Proposed next steps

  1. Decide the intended semantics: does specular_color tint (a) external reflection only, (b) all reflections including internal (both partial and TIR), or (c) something else?
  2. Clarify the spec text accordingly (the current wording is being read inconsistently).
  3. Once decided, correct the reference MaterialX graph — and, if it needs facing/entering-exiting information that MaterialX cannot currently express, raise the corresponding MaterialX feature request.
  4. Note the current graph↔spec difference for the ASWF/OpenUSD normative shading-model documentation.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions