Skip to content

Commit f6dc35f

Browse files
Add retroreflective to specular BSDFs (#2783)
Add a `retroreflective` boolean input to the specular BSDF nodes (`dielectric_bsdf`, `conductor_bsdf`, `generalized_schlick_bsdf`). * Update the PBR specification with the new input * Add `retroreflective` input to node definitions in `pbrlib_defs.mtlx` * Add GLSL implementation. OSL and MDL have interface-only changes (the parameter is ignored) Also includes minor test infrastructure fixes: * Make `FilePath::createDirectory()` recursive to support nested output directories * Resolve relative paths to absolute in `OslRenderer` before working directory changes * Skip `blur.mtlx` in MDL render tests due to material name mismatch
1 parent bb3e736 commit f6dc35f

26 files changed

Lines changed: 84 additions & 28 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
build
22
dist
33
.DS_Store
4+
CMakeUserPresets.json

documents/Specification/MaterialX.PBRSpec.md

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -200,16 +200,19 @@ Implementations are expected to preserve energy as the roughness of the surface
200200

201201
The `tint` input colors the reflected and transmitted light but should be left at white (1,1,1) for physically correct results. Setting the `ior` input to zero disables the Fresnel curve, allowing reflectivity to be controlled purely by weight and tint.
202202

203-
The `scatter_mode` controls whether the surface reflects light (`R`), transmits light (`T`), or both (`RT`). In `RT` mode, reflection and transmission occur both when entering and leaving a surface, with their respective intensities controlled by the Fresnel curve. Depending on the IOR and incident angle, total internal reflection may occur even when transmission modes are selected.
203+
Setting `retroreflective` to true switches the BSDF to retroreflection mode, where light is reflected back toward the incoming direction rather than the mirror reflection direction[^Raab2025].
204204

205205
Thin-film iridescence effects[^Belcour2017] may be enabled by setting `thinfilm_thickness` to a non-zero value.
206206

207+
The `scatter_mode` controls whether the surface reflects light (`R`), transmits light (`T`), or both (`RT`). In `RT` mode, reflection and transmission occur both when entering and leaving a surface, with their respective intensities controlled by the Fresnel curve. Depending on the IOR and incident angle, total internal reflection may occur even when transmission modes are selected.
208+
207209
|Port |Description |Type |Default |Accepted Values|
208210
|--------------------|---------------------------------------------------------------|-------|-------------|---------------|
209211
|`weight` |Weight of the BSDF contribution |float |1.0 |[0, 1] |
210212
|`tint` |Color weight to tint the reflected and transmitted light |color3 |1.0, 1.0, 1.0| |
211213
|`ior` |Index of refraction of the surface |float |1.5 | |
212214
|`roughness` |Surface roughness along the tangent and bitangent |vector2|0.05, 0.05 |[0, 1] |
215+
|`retroreflective` |Enable retroreflection mode for the BSDF |boolean|false | |
213216
|`thinfilm_thickness`|Thickness of the iridescent thin-film layer in nanometers |float |0.0 | |
214217
|`thinfilm_ior` |Index of refraction of the thin-film layer |float |1.5 | |
215218
|`normal` |Normal vector of the surface |vector3|Nworld | |
@@ -227,6 +230,8 @@ Implementations are expected to preserve energy as the roughness of the surface
227230

228231
The default values for `ior` and `extinction` represent approximate values for gold.
229232

233+
Setting `retroreflective` to true switches the BSDF to retroreflection mode, where light is reflected back toward the incoming direction rather than the mirror reflection direction[^Raab2025].
234+
230235
Thin-film iridescence effects[^Belcour2017] may be enabled by setting `thinfilm_thickness` to a non-zero value.
231236

232237
|Port |Description |Type |Default |Accepted Values|
@@ -235,6 +240,7 @@ Thin-film iridescence effects[^Belcour2017] may be enabled by setting `thinfilm_
235240
|`ior` |Index of refraction |color3 |0.183, 0.421, 1.373 | |
236241
|`extinction` |Extinction coefficient |color3 |3.424, 2.346, 1.770 | |
237242
|`roughness` |Surface roughness |vector2|0.05, 0.05 |[0, 1] |
243+
|`retroreflective` |Enable retroreflection mode for the BSDF |boolean|false | |
238244
|`thinfilm_thickness`|Thickness of the iridescent thin-film layer in nanometers|float |0.0 | |
239245
|`thinfilm_ior` |Index of refraction of the thin-film layer |float |1.5 | |
240246
|`normal` |Normal vector of the surface |vector3|Nworld | |
@@ -251,10 +257,12 @@ Implementations are expected to preserve energy as the roughness of the surface
251257

252258
The `color82` input provides a multiplier on reflectivity at 82 degrees, useful for capturing the characteristic "dip" in the reflectance curve of metallic surfaces. Setting it to (1,1,1) effectively disables this feature for backward compatibility.
253259

254-
The `scatter_mode` behavior matches that of `dielectric_bsdf`: in `RT` mode, reflection and transmission occur both when entering and leaving a surface, with intensities controlled by the Fresnel curve. Total internal reflection may occur depending on the incident angle.
260+
Setting `retroreflective` to true switches the BSDF to retroreflection mode, where light is reflected back toward the incoming direction rather than the mirror reflection direction[^Raab2025].
255261

256262
Thin-film iridescence effects[^Belcour2017] may be enabled by setting `thinfilm_thickness` to a non-zero value.
257263

264+
The `scatter_mode` behavior matches that of `dielectric_bsdf`: in `RT` mode, reflection and transmission occur both when entering and leaving a surface, with intensities controlled by the Fresnel curve. Total internal reflection may occur depending on the incident angle.
265+
258266
|Port |Description |Type |Default |Accepted Values|
259267
|--------------------|---------------------------------------------------------------|-------|-------------|---------------|
260268
|`weight` |Weight of the BSDF contribution |float |1.0 |[0, 1] |
@@ -263,6 +271,7 @@ Thin-film iridescence effects[^Belcour2017] may be enabled by setting `thinfilm_
263271
|`color90` |Reflectivity per color component at grazing angles |color3 |1.0, 1.0, 1.0| |
264272
|`exponent` |Exponent for Schlick blending between color0 and color90 |float |5.0 | |
265273
|`roughness` |Surface roughness along the tangent and bitangent |vector2|0.05, 0.05 |[0, 1] |
274+
|`retroreflective` |Enable retroreflection mode for the BSDF |boolean|false | |
266275
|`thinfilm_thickness`|Thickness of the iridescent thin-film layer in nanometers |float |0.0 | |
267276
|`thinfilm_ior` |Index of refraction of the thin-film layer |float |1.5 | |
268277
|`normal` |Normal vector of the surface |vector3|Nworld | |
@@ -702,6 +711,8 @@ Path Tracing**, <https://media.disneyanimation.com/uploads/production/publicatio
702711

703712
[^Portsmouth2025]: Portsmouth et al., **EON: A practical energy-preserving rough diffuse BRDF**, <https://www.jcgt.org/published/0014/01/06/>, 2025.
704713

714+
[^Raab2025]: Matthias Raab et al., **The Minimal Retroreflective Microfacet Model**, to appear, 2025
715+
705716
[^Turquin2019]: Emmanuel Turquin, **Practical multiple scattering compensation for microfacet models**, <https://blog.selfshadow.com/publications/turquin/ms_comp_final.pdf>, 2019.
706717

707718
[^Walter2007]: Bruce Walter et al., **Microfacet Models for Refraction through Rough Surfaces**, <https://www.graphics.cornell.edu/~bjw/microfacetbsdf.pdf>, 2007

libraries/pbrlib/genglsl/mx_conductor_bsdf.glsl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#include "lib/mx_closure_type.glsl"
22
#include "lib/mx_microfacet_specular.glsl"
33

4-
void mx_conductor_bsdf(ClosureData closureData, float weight, vec3 ior_n, vec3 ior_k, vec2 roughness, float thinfilm_thickness, float thinfilm_ior, vec3 N, vec3 X, int distribution, inout BSDF bsdf)
4+
void mx_conductor_bsdf(ClosureData closureData, float weight, vec3 ior_n, vec3 ior_k, vec2 roughness, bool retroreflective, float thinfilm_thickness, float thinfilm_ior, vec3 N, vec3 X, int distribution, inout BSDF bsdf)
55
{
66
bsdf.throughput = vec3(0.0);
77

@@ -13,6 +13,7 @@ void mx_conductor_bsdf(ClosureData closureData, float weight, vec3 ior_n, vec3 i
1313
vec3 V = closureData.V;
1414
vec3 L = closureData.L;
1515

16+
V = retroreflective ? reflect(-V, N) : V;
1617
N = mx_forward_facing_normal(N, V);
1718
float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0);
1819

libraries/pbrlib/genglsl/mx_dielectric_bsdf.glsl

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#include "lib/mx_closure_type.glsl"
22
#include "lib/mx_microfacet_specular.glsl"
33

4-
void mx_dielectric_bsdf(ClosureData closureData, float weight, vec3 tint, float ior, vec2 roughness, float thinfilm_thickness, float thinfilm_ior, vec3 N, vec3 X, int distribution, int scatter_mode, inout BSDF bsdf)
4+
void mx_dielectric_bsdf(ClosureData closureData, float weight, vec3 tint, float ior, vec2 roughness, bool retroreflective, float thinfilm_thickness, float thinfilm_ior, vec3 N, vec3 X, int distribution, int scatter_mode, inout BSDF bsdf)
55
{
66
if (weight < M_FLOAT_EPS)
77
{
@@ -15,6 +15,10 @@ void mx_dielectric_bsdf(ClosureData closureData, float weight, vec3 tint, float
1515
vec3 V = closureData.V;
1616
vec3 L = closureData.L;
1717

18+
// Retroreflective mode is only supported for reflection and indirect
19+
if (retroreflective && (closureData.closureType != CLOSURE_TYPE_TRANSMISSION))
20+
V = reflect(-V, N);
21+
1822
N = mx_forward_facing_normal(N, V);
1923
float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0);
2024

libraries/pbrlib/genglsl/mx_generalized_schlick_bsdf.glsl

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#include "lib/mx_closure_type.glsl"
22
#include "lib/mx_microfacet_specular.glsl"
33

4-
void mx_generalized_schlick_bsdf(ClosureData closureData, float weight, vec3 color0, vec3 color82, vec3 color90, float exponent, vec2 roughness, float thinfilm_thickness, float thinfilm_ior, vec3 N, vec3 X, int distribution, int scatter_mode, inout BSDF bsdf)
4+
void mx_generalized_schlick_bsdf(ClosureData closureData, float weight, vec3 color0, vec3 color82, vec3 color90, float exponent, vec2 roughness, bool retroreflective, float thinfilm_thickness, float thinfilm_ior, vec3 N, vec3 X, int distribution, int scatter_mode, inout BSDF bsdf)
55
{
66
if (weight < M_FLOAT_EPS)
77
{
@@ -15,6 +15,10 @@ void mx_generalized_schlick_bsdf(ClosureData closureData, float weight, vec3 col
1515
vec3 V = closureData.V;
1616
vec3 L = closureData.L;
1717

18+
// Retroreflective mode is only supported for reflection and indirect
19+
if (retroreflective && (closureData.closureType != CLOSURE_TYPE_TRANSMISSION))
20+
V = reflect(-V, N);
21+
1822
N = mx_forward_facing_normal(N, V);
1923
float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0);
2024

libraries/pbrlib/genmdl/pbrlib_genmdl_impl.mtlx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,13 @@
1111
<implementation name="IM_translucent_bsdf_genmdl" nodedef="ND_translucent_bsdf" sourcecode="materialx::pbrlib_{{MDL_VERSION_SUFFIX}}::mx_translucent_bsdf(mxp_weight:{{weight}}, mxp_color:{{color}}, mxp_normal:{{normal}})" target="genmdl" />
1212

1313
<!-- <dielectric_bsdf> -->
14-
<implementation name="IM_dielectric_bsdf_genmdl" nodedef="ND_dielectric_bsdf" sourcecode="materialx::pbrlib_{{MDL_VERSION_SUFFIX}}::mx_dielectric_bsdf(mxp_weight:{{weight}}, mxp_tint:{{tint}}, mxp_ior:{{ior}}, mxp_roughness:{{roughness}}, mxp_thinfilm_thickness:{{thinfilm_thickness}}, mxp_thinfilm_ior:{{thinfilm_ior}}, mxp_normal:{{normal}}, mxp_tangent:{{tangent}}, mxp_distribution:{{distribution}}, mxp_scatter_mode:{{scatter_mode}}, mxp_base:{{base}}, mxp_top_weight:{{top_weight}})" target="genmdl" />
14+
<implementation name="IM_dielectric_bsdf_genmdl" nodedef="ND_dielectric_bsdf" sourcecode="materialx::pbrlib_{{MDL_VERSION_SUFFIX}}::mx_dielectric_bsdf(mxp_weight:{{weight}}, mxp_tint:{{tint}}, mxp_ior:{{ior}}, mxp_roughness:{{roughness}}, mxp_retroreflective:{{retroreflective}}, mxp_thinfilm_thickness:{{thinfilm_thickness}}, mxp_thinfilm_ior:{{thinfilm_ior}}, mxp_normal:{{normal}}, mxp_tangent:{{tangent}}, mxp_distribution:{{distribution}}, mxp_scatter_mode:{{scatter_mode}}, mxp_base:{{base}}, mxp_top_weight:{{top_weight}})" target="genmdl" />
1515

1616
<!-- <conductor_bsdf> -->
17-
<implementation name="IM_conductor_bsdf_genmdl" nodedef="ND_conductor_bsdf" sourcecode="materialx::pbrlib_{{MDL_VERSION_SUFFIX}}::mx_conductor_bsdf(mxp_weight:{{weight}}, mxp_ior:{{ior}}, mxp_extinction:{{extinction}}, mxp_roughness:{{roughness}}, mxp_thinfilm_thickness:{{thinfilm_thickness}}, mxp_thinfilm_ior:{{thinfilm_ior}}, mxp_normal:{{normal}}, mxp_tangent:{{tangent}}, mxp_distribution:{{distribution}})" target="genmdl" />
17+
<implementation name="IM_conductor_bsdf_genmdl" nodedef="ND_conductor_bsdf" sourcecode="materialx::pbrlib_{{MDL_VERSION_SUFFIX}}::mx_conductor_bsdf(mxp_weight:{{weight}}, mxp_ior:{{ior}}, mxp_extinction:{{extinction}}, mxp_roughness:{{roughness}}, mxp_retroreflective:{{retroreflective}}, mxp_thinfilm_thickness:{{thinfilm_thickness}}, mxp_thinfilm_ior:{{thinfilm_ior}}, mxp_normal:{{normal}}, mxp_tangent:{{tangent}}, mxp_distribution:{{distribution}})" target="genmdl" />
1818

1919
<!-- <generalized_schlick_bsdf> -->
20-
<implementation name="IM_generalized_schlick_bsdf_genmdl" nodedef="ND_generalized_schlick_bsdf" sourcecode="materialx::pbrlib_{{MDL_VERSION_SUFFIX}}::mx_generalized_schlick_bsdf(mxp_weight:{{weight}}, mxp_color0:{{color0}}, mxp_color82:{{color82}}, mxp_color90:{{color90}}, mxp_exponent:{{exponent}},mxp_roughness:{{roughness}}, mxp_thinfilm_thickness:{{thinfilm_thickness}}, mxp_thinfilm_ior:{{thinfilm_ior}}, mxp_normal:{{normal}}, mxp_tangent:{{tangent}}, mxp_distribution:{{distribution}}, mxp_scatter_mode:{{scatter_mode}}, mxp_base:{{base}}, mxp_top_weight:{{top_weight}})" target="genmdl" />
20+
<implementation name="IM_generalized_schlick_bsdf_genmdl" nodedef="ND_generalized_schlick_bsdf" sourcecode="materialx::pbrlib_{{MDL_VERSION_SUFFIX}}::mx_generalized_schlick_bsdf(mxp_weight:{{weight}}, mxp_color0:{{color0}}, mxp_color82:{{color82}}, mxp_color90:{{color90}}, mxp_exponent:{{exponent}},mxp_roughness:{{roughness}}, mxp_retroreflective:{{retroreflective}}, mxp_thinfilm_thickness:{{thinfilm_thickness}}, mxp_thinfilm_ior:{{thinfilm_ior}}, mxp_normal:{{normal}}, mxp_tangent:{{tangent}}, mxp_distribution:{{distribution}}, mxp_scatter_mode:{{scatter_mode}}, mxp_base:{{base}}, mxp_top_weight:{{top_weight}})" target="genmdl" />
2121

2222
<!-- <subsurface_bsdf> -->
2323
<implementation name="IM_subsurface_bsdf_genmdl" nodedef="ND_subsurface_bsdf" sourcecode="materialx::pbrlib_{{MDL_VERSION_SUFFIX}}::mx_subsurface_bsdf(mxp_weight:{{weight}}, mxp_color:{{color}}, mxp_radius:{{radius}}, mxp_anisotropy:{{anisotropy}}, mxp_normal:{{normal}})" target="genmdl" />

libraries/pbrlib/genosl/mx_dielectric_bsdf.osl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
void mx_dielectric_bsdf(float weight, color tint, float ior, vector2 roughness, float thinfilm_thickness, float thinfilm_ior, normal N, vector U, string distribution, string scatter_mode, output BSDF bsdf)
1+
void mx_dielectric_bsdf(float weight, color tint, float ior, vector2 roughness, int retroreflective, float thinfilm_thickness, float thinfilm_ior, normal N, vector U, string distribution, string scatter_mode, output BSDF bsdf)
22
{
33
color reflection_tint = (scatter_mode == "T") ? color(0.0) : tint;
44
color transmission_tint = (scatter_mode == "R") ? color(0.0) : tint;

libraries/pbrlib/genosl/mx_generalized_schlick_bsdf.osl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
void mx_generalized_schlick_bsdf(float weight, color color0, color color82, color color90, float exponent, vector2 roughness, float thinfilm_thickness, float thinfilm_ior, normal N, vector U, string distribution, string scatter_mode, output BSDF bsdf)
1+
void mx_generalized_schlick_bsdf(float weight, color color0, color color82, color color90, float exponent, vector2 roughness, int retroreflective, float thinfilm_thickness, float thinfilm_ior, normal N, vector U, string distribution, string scatter_mode, output BSDF bsdf)
22
{
33
color reflection_tint = (scatter_mode == "T") ? color(0.0) : color(1.0);
44
color transmission_tint = (scatter_mode == "R") ? color(0.0) : color(1.0);

libraries/pbrlib/pbrlib_defs.mtlx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
<input name="tint" type="color3" value="1.0, 1.0, 1.0" />
6565
<input name="ior" type="float" value="1.5" />
6666
<input name="roughness" type="vector2" value="0.05, 0.05" />
67+
<input name="retroreflective" type="boolean" value="false" uniform="true" />
6768
<input name="thinfilm_thickness" type="float" value="0" unittype="distance" unit="nanometer" />
6869
<input name="thinfilm_ior" type="float" value="1.5" />
6970
<input name="normal" type="vector3" defaultgeomprop="Nworld" />
@@ -82,6 +83,7 @@
8283
<input name="ior" type="color3" value="0.183, 0.421, 1.373" />
8384
<input name="extinction" type="color3" value="3.424, 2.346, 1.770" />
8485
<input name="roughness" type="vector2" value="0.05, 0.05" />
86+
<input name="retroreflective" type="boolean" value="false" uniform="true" />
8587
<input name="thinfilm_thickness" type="float" value="0" unittype="distance" unit="nanometer" />
8688
<input name="thinfilm_ior" type="float" value="1.5" />
8789
<input name="normal" type="vector3" defaultgeomprop="Nworld" />
@@ -101,6 +103,7 @@
101103
<input name="color90" type="color3" value="1.0, 1.0, 1.0" />
102104
<input name="exponent" type="float" value="5.0" />
103105
<input name="roughness" type="vector2" value="0.05, 0.05" />
106+
<input name="retroreflective" type="boolean" value="false" uniform="true" />
104107
<input name="thinfilm_thickness" type="float" value="0" unittype="distance" unit="nanometer" />
105108
<input name="thinfilm_ior" type="float" value="1.5" />
106109
<input name="normal" type="vector3" defaultgeomprop="Nworld" />

python/Scripts/creatematerial.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@ def main():
256256
if options.outputFilename:
257257
# Write the document to disk.
258258
if not mtlxFile.getParentPath().exists():
259-
mtlxFile.getParentPath().createDirectory()
259+
mtlxFile.getParentPath().createDirectory(True)
260260
mx.writeToXmlFile(doc, mtlxFile.asString())
261261
print('Wrote MaterialX document to disk:', mtlxFile.asString())
262262
else:

0 commit comments

Comments
 (0)