Skip to content

Commit

Permalink
changes based on community discussion, add spec document
Browse files Browse the repository at this point in the history
  • Loading branch information
msuzuki-nvidia committed Aug 27, 2024
1 parent 3ef0e10 commit 16324b0
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 32 deletions.
37 changes: 37 additions & 0 deletions documents/Specification/MaterialX.PBRSpec.md
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,21 @@ The PBS nodes also make use of the following standard MaterialX types:
* `normal` (vector3): Normal vector of the surface. Defaults to world space normal.
* `mode` (uniform string): Selects between `conty_kulla` and `zeltner` sheen models. Defaults to `conty_kulla`.

<a id="node-hair-chiang-bsdf"> </a>

* **`hair_chiang_bsdf`**: Constructs a hair BSDF based on the Chiang hair shading model[^Chiang2016]. This node does not support vertical layering.
* `tint_R` (color3): Color multiplier for the first R-lobe. Defaults to (1.0, 1.0, 1.0).
* `tint_TT` (color3): Color multiplier for the first TT-lobe. Defaults to (1.0, 1.0, 1.0).
* `tint_TRT` (color3): Color multiplier for the first TRT-lobe. Defaults to (1.0, 1.0, 1.0).
* `ior` (float): Index of refraction. Defaults to 1.55 being the value for keratin.
* `roughness_R` (vector2): Longitudinal and azimuthal roughness (ν, s) for the first R-lobe, range [0.0, ∞). With (0, 0) specifying pure specular scattering. Defaults to (0.1, 0.1).
* `roughness_TT` (vector2): Longitudinal and azimuthal roughness (ν, s) for the first TT-lobe, range [0.0, ∞). With (0, 0) specifying pure specular scattering. Defaults to (0.05, 0.05).
* `roughness_TRT` (vector2): Longitudinal and azimuthal roughness (ν, s) for the first TRT-lobe, range [0.0, ∞). With (0, 0) specifying pure specular scattering. Defaults to (0.2, 0.2).
* `cuticle_angle` (float): Cuticle angle in radians, Values above 0.5 tilt the scales towards the root of the fiber, range [0.0, 1.0]. With 0.5 specifying no tilt. Defaults to 0.5.
* `absorption_coefficient` (vector3): Absorption coefficient normalized to the hair fiber diameter. Defaults to (0.0, 0.0, 0.0).
* `normal` (vector3): Normal vector of the surface. Defaults to world space normal.
* `curve_direction` (vector3): Direction of the hair geometry. Defaults to world space tangent.


## EDF Nodes

Expand Down Expand Up @@ -374,6 +389,21 @@ Note that the standard library includes definitions for [**`displacement`**](./M
* `ior` (**output**, vector3): Computed index of refraction.
* `extinction` (**output**, vector3): Computed extinction coefficient.

* **`hair_roughness`**: Converts the artistic parameterization hair roughness to roughness for R, TT and TRT lobes. Output type `multioutput`, `roughness_R`, `roughness_TT` and `roughness_TRT`, `vector2` type.
* `longitudinal` (float): Longitudinal roughness, range [0.0, 1.0]. Defaults to 0.1.
* `azimuthal` (float): Azimuthal roughness, range [0.0, 1.0]. Defaults to 0.2.
* `scale_TT` (float): Roughness scale for TT lobe. Defaults to 0.5[^Marschner2003].
* `scale_TRT` (float): Roughness scale for TRT lobe. Defaults to 2.0[^Marschner2003].

* **`hair_absorption_from_melanin`** : Converts the hair melanin parameterization to absorption coefficient based on pigments eumelanin and pheomelanin using the mapping method described in [^d'Eon2011]. The default of `eumelanin_color` and `pheomelanin_color` are `lin_rec709` color converted from the constants[^d'Eon2011] via `exp(-c)`. They may be transformed to scene-linear rendering color space. `Output type `vector3`.
* `melanin_concentration` (float): Amount of melanin affected to the output, range [0.0, 1.0]. Defaults to 0.25.
* `melanin_redness` (float): Amount of redness affected to the output, range [0.0, 1.0]. Defaults to 0.5.
* `eumelanin_color` (color3): Eumelanin color. Defaults to (0.657704, 0.498077, 0.254107)
* `pheomelanin_color` (color3): Pheomelanin color. Defaults to (0.829444, 0.67032, 0.349938)

* **`hair_absorption_from_color`** : Coverts the hair scattering color to absorption coefficient using the mapping method described in [^Chiang2016]. Output type `vector3`.
* `color` (color3): Scattering color. Defaults to (1.0, 1.0, 1.0).
* `azimuthal_roughness` (float): Azimuthal roughness, range [0.0, 1.0]. Defaults to 0.2.


# Shading Model Examples
Expand Down Expand Up @@ -449,3 +479,10 @@ The MaterialX PBS Library includes a number of nodegraphs that can be used to ap
[^Walter2007]: Bruce Walter et al., **Microfacet Models for Refraction through Rough Surfaces**, <https://www.graphics.cornell.edu/~bjw/microfacetbsdf.pdf>, 2007

[^Zeltner2022]: Tizian Zeltner et al., **Practical Multiple-Scattering Sheen Using Linearly Transformed Cosines**, <https://tizianzeltner.com/projects/Zeltner2022Practical/>, 2022

[^d'Eon2011]: Eugene d'Eon et al., **An Energy-Conserving Hair Reflectance Model**, <https://eugenedeon.com/pdfs/egsrhair.pdf>, 2011

[^Chiang2016]: Matt Jen-Yuan Chiang et al., **A Practical and Controllable Hair and Fur Model for Production
Path Tracing**, <https://media.disneyanimation.com/uploads/production/publication_asset/152/asset/eurographics2016Fur_Smaller.pdf>, 2016

[^Marschner2003]: Stephen R. Marschner et al., **Light Scattering from Human Hair Fibers**, <http://www.graphics.stanford.edu/papers/hair/hair-sg03final.pdf>, 2003
66 changes: 36 additions & 30 deletions libraries/pbrlib/genglsl/mx_hair_bsdf.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,20 @@


// d’Eon et al. (2011)
void mx_hair_absorption_from_melanin(float melanin_concentration, float melanin_redness, out vec3 absorption)
void mx_hair_absorption_from_melanin(
float melanin_concentration,
float melanin_redness,
// constants converted to color via exp(-c). the defaults are lin_rec709 colors, they may be
// transformed to scene-linear rendering color space.
vec3 eumelanin_color, // default: (0.657704, 0.498077, 0.254106) == exp(-(0.419, 0.697, 1.37))
vec3 pheomelanin_color, // default: (0.829443, 0.670320, 0.349937) == exp(-(0.187, 0.4, 1.05))
out vec3 absorption)
{
float melanin = -log(max(1.0 - melanin_concentration, 0.0001));
float eumelanin = melanin * (1.0 - melanin_redness);
float pheomelanin = melanin * melanin_redness;
absorption = max(
eumelanin * vec3(0.419, 0.697, 1.37) + pheomelanin * vec3(0.187, 0.4, 1.05),
eumelanin * -log(eumelanin_color) + pheomelanin * -log(pheomelanin_color),
vec3(0.0)
);
}
Expand All @@ -34,6 +41,8 @@ void mx_hair_absorption_from_color(vec3 color, float betaN, out vec3 absorption)
void mx_hair_roughness(
float longitudinal,
float azimuthal,
float scale_TT, // empirical roughenss scale from Marschner et al. (2003).
float scale_TRT, // default: scale_TT = 0.5, scale_TRT = 2.0
out vec2 roughness_R,
out vec2 roughness_TT,
out vec2 roughness_TRT
Expand All @@ -46,14 +55,11 @@ void mx_hair_roughness(
float v = 0.726 * lr + 0.812 * lr * lr + 3.7 * pow(lr, 20);
v = v * v;

// azimuthal scale
// the constant can be found in Appendix A, eq (12)
const float sqrtPiOver8 = 0.626657;
float s = sqrtPiOver8 * (0.265 * ar + 1.194 * ar * ar + 5.372 * pow(ar, 22));
float s = 0.265 * ar + 1.194 * ar * ar + 5.372 * pow(ar, 22);

roughness_R = vec2(v, s);
roughness_TT = vec2(v * 0.25, s); // empirical roughenss scale from Marschner et al. (2003)
roughness_TRT = vec2(v * 4.0, s); // values are squared here
roughness_TT = vec2(v * scale_TT * scale_TT, s);
roughness_TRT = vec2(v * scale_TRT * scale_TRT, s);
}

float mx_hair_transform_sin_cos(float x)
Expand Down Expand Up @@ -101,6 +107,8 @@ float mx_hair_logistic_cdf(float x, float s)

float mx_hair_trimmed_logistic(float x, float s, float a, float b)
{
// the constant can be found in Chiang et al. (2016) Appendix A, eq. (12)
s *= 0.626657; // sqrt(M_PI/8)
return mx_hair_logistic(x, s) / (mx_hair_logistic_cdf(b, s) - mx_hair_logistic_cdf(a, s));
}

Expand Down Expand Up @@ -157,7 +165,7 @@ void mx_hair_alpha_angles(
// 0:R, 1:TT, 2:TRT, 3:TRRT+
for (int i = 0; i <= 3; ++i)
{
if (alpha == 0.0)
if (alpha == 0.0 || i == 3)
angles[i] = vec2(sinThetaI, cosThetaI);
else
{
Expand All @@ -182,10 +190,10 @@ void mx_hair_attenuation(float f, vec3 T, out vec3 Ap[4]) // Ap
vec3 mx_hair_chiang_bsdf(
vec3 L,
vec3 V,
float ior,
vec3 tint_R,
vec3 tint_TT,
vec3 tint_TRT,
float ior,
vec2 roughness_R,
vec2 roughness_TT,
vec2 roughness_TRT,
Expand All @@ -195,7 +203,6 @@ vec3 mx_hair_chiang_bsdf(
vec3 X
)
{
vec3 Ng = N;
N = mx_forward_facing_normal(N, V);
X = normalize(X - dot(X, N) * N);
vec3 Y = cross(N, X);
Expand Down Expand Up @@ -236,28 +243,24 @@ vec3 mx_hair_chiang_bsdf(
float alpha = cuticle_angle * M_PI - (M_PI / 2.0); // remap [0, 1] to [-PI/2, PI/2]
mx_hair_alpha_angles(alpha, sinThetaI, cosThetaI, angles);

tint_R = clamp(tint_R, 0.0, 1.0);
tint_TT = clamp(tint_TT, 0.0, 1.0);
tint_TRT = clamp(tint_TRT, 0.0, 1.0);
vec3 tint[4] = vec3[](tint_R, tint_TT, tint_TRT, tint_TRT);

roughness_R = clamp(roughness_R, 0.001, 1.0);
roughness_TT = clamp(roughness_TT, 0.001, 1.0);
roughness_TRT = clamp(roughness_TRT, 0.001, 1.0);
vec2 vs[4] = vec2[](roughness_R, roughness_TT, roughness_TRT, roughness_TRT);

// R, TT, TRT
// R, TT, TRT, TRRT+
vec3 F = vec3(0.0);
for (int i = 0; i < 3; ++i)
for (int i = 0; i <= 3; ++i)
{
F += mx_hair_longitudinal_scattering(angles[i].x, angles[i].y, sinThetaO, cosThetaO, vs[i].x)
* mx_hair_azimuthal_scattering(phi, i, vs[i].y, gammaO, gammaT)
* tint[i] * Ap[i];
if (all(lessThanEqual(tint[i], vec3(0.0))))
continue;

float Mp = mx_hair_longitudinal_scattering(angles[i].x, angles[i].y, sinThetaO, cosThetaO, vs[i].x);
float Np = (i == 3) ? (1.0 / 2.0 * M_PI) : mx_hair_azimuthal_scattering(phi, i, vs[i].y, gammaO, gammaT);
F += Mp * Np * tint[i] * Ap[i];
}
// TRRT+
F += mx_hair_longitudinal_scattering(cosThetaO, angles[3].y, angles[3].x, sinThetaO, vs[3].x)
* (1.0 / 2.0 * M_PI)
* tint[3] * Ap[3];

return F;
}
Expand All @@ -267,10 +270,10 @@ void mx_hair_chiang_bsdf_reflection(
vec3 V,
vec3 P,
float occlusion,
float ior,
vec3 tint_R,
vec3 tint_TT,
vec3 tint_TRT,
float ior,
vec2 roughness_R,
vec2 roughness_TT,
vec2 roughness_TRT,
Expand All @@ -284,10 +287,10 @@ void mx_hair_chiang_bsdf_reflection(
vec3 F = mx_hair_chiang_bsdf(
L,
V,
ior,
tint_R,
tint_TT,
tint_TRT,
ior,
roughness_R,
roughness_TT,
roughness_TRT,
Expand All @@ -297,16 +300,16 @@ void mx_hair_chiang_bsdf_reflection(
X
);

bsdf.throughput = 1.0 - F;
bsdf.throughput = vec3(0.0);
bsdf.response = F * occlusion * M_PI_INV;
}

void mx_hair_chiang_bsdf_indirect(
vec3 V,
float ior,
vec3 tint_R,
vec3 tint_TT,
vec3 tint_TRT,
float ior,
vec2 roughness_R,
vec2 roughness_TT,
vec2 roughness_TRT,
Expand All @@ -326,7 +329,8 @@ void mx_hair_chiang_bsdf_indirect(
FresnelData fd = mx_init_fresnel_dielectric(ior, 0.0, 1.0);
vec3 F = mx_compute_fresnel(NdotV, fd);

vec2 safeAlpha = clamp(roughness_R, M_FLOAT_EPS, 1.0); // ?
vec2 roughness = (roughness_R + roughness_TT + roughness_TRT) / vec2(3.0); // ?
vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0);
float avgAlpha = mx_average_alpha(safeAlpha);

// use ggx because the environment map for FIS is preintegrated with ggx
Expand All @@ -335,6 +339,8 @@ void mx_hair_chiang_bsdf_indirect(
vec3 dirAlbedo = mx_ggx_dir_albedo(NdotV, avgAlpha, F0, 1.0) * comp;

vec3 Li = mx_environment_radiance(N, V, X, safeAlpha, 0, fd);
bsdf.throughput = 1.0 - dirAlbedo;
bsdf.response = Li * comp;
vec3 tint = (tint_R + tint_TT + tint_TRT) / vec3(3.0); // ?

bsdf.throughput = vec3(0.0);
bsdf.response = Li * comp * tint;
}
8 changes: 6 additions & 2 deletions libraries/pbrlib/pbrlib_defs.mtlx
Original file line number Diff line number Diff line change
Expand Up @@ -141,17 +141,17 @@
A BSDF node for Chaing hair shading model.
-->
<nodedef name="ND_hair_chiang_bsdf" node="hair_chiang_bsdf" bsdf="R" nodegroup="pbr" doc="A BSDF node for Chiang hair shading model.">
<input name="ior" type="float" value="1.55" />
<input name="tint_R" type="color3" value="1, 1, 1" />
<input name="tint_TT" type="color3" value="1, 1, 1" />
<input name="tint_TRT" type="color3" value="1, 1, 1" />
<input name="ior" type="float" value="1.55" />
<input name="roughness_R" type="vector2" value="0.1, 0.1" />
<input name="roughness_TT" type="vector2" value="0.05, 0.05" />
<input name="roughness_TRT" type="vector2" value="0.2, 0.2" />
<input name="cuticle_angle" type="float" value="0.5" />
<input name="absorption_coefficient" type="vector3" value="0.0, 0.0, 0.0" />
<input name="normal" type="vector3" defaultgeomprop="Nworld" />
<input name="tangent" type="vector3" defaultgeomprop="Tworld" />
<input name="curve_direction" type="vector3" defaultgeomprop="Tworld" />

This comment has been minimized.

Copy link
@BrianSharpe

BrianSharpe Aug 28, 2024

Contributor

I guess we'll need to add a default geometry property to pbrlib_defs.mtlx (similar to Vworld in nprlib_defs.mtlx)
something like this?
<geompropdef name="CDworld" type="vector3" geomprop="curvedirection" space="world" />

This comment has been minimized.

Copy link
@msuzuki-nvidia

msuzuki-nvidia Sep 6, 2024

Author Contributor

According to the discussion on Slack, we want to leave adding the new geomprop for later updates due to it requires a change in core library.

<output name="out" type="BSDF" />
</nodedef>

Expand Down Expand Up @@ -430,6 +430,8 @@
<nodedef name="ND_hair_absorption_from_melanin" node="hair_absorption_from_melanin" nodegroup="pbr" doc="Calculates hair absorption from melanin parameters.">
<input name="melanin_concentration" type="float" value="0.25" />
<input name="melanin_redness" type="float" value="0.5" />
<input name="eumelanin_color" type="color3" value="0.657704, 0.498077, 0.254107" colorspace="lin_rec709" doc="constant from d'Eon et al. 2011, converted to color via exp(-c)" uiadvanced="true"/>
<input name="pheomelanin_color" type="color3" value="0.829444, 0.67032, 0.349938" colorspace="lin_rec709" doc="constant from d'Eon et al. 2011, converted to color via exp(-c)" uiadvanced="true"/>
<output name="absorption" type="vector3" />
</nodedef>

Expand All @@ -450,6 +452,8 @@
<nodedef name="ND_hair_roughness" node="hair_roughness" nodegroup="pbr" doc="Calculates hair roughness for R, TT and TRT component.">
<input name="longitudinal" type="float" value="0.1" />
<input name="azimuthal" type="float" value="0.2" />
<input name="scale_TT" type="float" value="0.5" />
<input name="scale_TRT" type="float" value="2.0" />
<output name="roughness_R" type="vector2" />
<output name="roughness_TT" type="vector2" />
<output name="roughness_TRT" type="vector2" />
Expand Down

0 comments on commit 16324b0

Please sign in to comment.