Skip to content

Commit

Permalink
Initial refraction approximation in GLSL (#918)
Browse files Browse the repository at this point in the history
This changelist introduces a rough approximation of refraction in GLSL, allowing transmissive materials such as glass to respond visually to changes in specular_IOR, specular_roughness, and transmission_color.

Because this initial approximation is limited to a single render pass, only the environment radiance map is currently visible in refractions, and opaque objects behind transmissive surfaces are not yet visible.
  • Loading branch information
jstone-lucasfilm authored Apr 25, 2022
1 parent 8c64431 commit f5fc395
Show file tree
Hide file tree
Showing 22 changed files with 224 additions and 219 deletions.
3 changes: 2 additions & 1 deletion javascript/MaterialXView/source/viewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,8 @@ export class Material
u_envRadiance: { value: radianceTexture },
u_envRadianceMips: { value: Math.trunc(Math.log2(Math.max(radianceTexture.image.width, radianceTexture.image.height))) + 1 },
u_envRadianceSamples: { value: 16 },
u_envIrradiance: { value: irradianceTexture }
u_envIrradiance: { value: irradianceTexture },
u_refractionEnv: { value: true }
});

// Create Three JS Material
Expand Down
7 changes: 5 additions & 2 deletions libraries/pbrlib/genglsl/lib/mx_environment_fis.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ vec3 mx_environment_radiance(vec3 N, vec3 V, vec3 X, vec2 alpha, int distributio

// Compute the half vector and incoming light direction.
vec3 H = mx_ggx_importance_sample_NDF(Xi, alpha);
vec3 L = -reflect(V, H);
vec3 L = fd.refraction ? mx_refraction_solid_sphere(-V, H, fd.ior.x) : -reflect(V, H);

// Compute dot products for this sample.
float NdotH = clamp(H.z, M_FLOAT_EPS, 1.0);
Expand All @@ -54,13 +54,16 @@ vec3 mx_environment_radiance(vec3 N, vec3 V, vec3 X, vec2 alpha, int distributio
// Compute the geometric term.
float G = mx_ggx_smith_G2(NdotL, NdotV, avgAlpha);

// Compute the combined FG term, which is inverted for refraction.
vec3 FG = fd.refraction ? vec3(1.0) - (F * G) : F * G;

// Add the radiance contribution of this sample.
// From https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf
// incidentLight = sampleColor * NdotL
// microfacetSpecular = D * F * G / (4 * NdotL * NdotV)
// pdf = D * NdotH / (4 * VdotH)
// radiance = incidentLight * microfacetSpecular / pdf
radiance += sampleColor * F * G * VdotH / (NdotV * NdotH);
radiance += sampleColor * FG * VdotH / (NdotV * NdotH);
}

// Normalize and return the final radiance.
Expand Down
20 changes: 19 additions & 1 deletion libraries/pbrlib/genglsl/lib/mx_microfacet_specular.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ struct FresnelData
// Thin film
float tf_thickness;
float tf_ior;

// Refraction
bool refraction;
};

// https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf
Expand Down Expand Up @@ -217,6 +220,13 @@ float mx_ior_to_f0(float ior)
return mx_square((ior - 1.0) / (ior + 1.0));
}

// Convert normal-incidence reflectivity to real-valued index of refraction.
float mx_f0_to_ior(float F0)
{
float sqrtF0 = sqrt(F0);
return (1.0 + sqrtF0) / (1.0 - sqrtF0);
}

// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/
float mx_fresnel_dielectric(float cosTheta, float ior)
{
Expand Down Expand Up @@ -387,7 +397,7 @@ vec3 mx_fresnel_airy(float cosTheta, vec3 ior, vec3 extinction, float tf_thickne

FresnelData mx_init_fresnel_data(int model)
{
return FresnelData(model, vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0), 0.0, 0.0, 0.0);
return FresnelData(model, vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0), 0.0, 0.0, 0.0, false);
}

FresnelData mx_init_fresnel_dielectric(float ior)
Expand Down Expand Up @@ -462,6 +472,14 @@ vec3 mx_compute_fresnel(float cosTheta, FresnelData fd)
}
}

// Compute the refraction of a ray through a solid sphere.
vec3 mx_refraction_solid_sphere(vec3 R, vec3 N, float ior)
{
R = refract(R, N, 1.0 / ior);
vec3 N1 = normalize(R * dot(R, N) - N * 0.5);
return refract(R, N1, ior);
}

vec2 mx_latlong_projection(vec3 dir)
{
float latitude = -asin(dir.y) * M_PI_INV + 0.5;
Expand Down
30 changes: 18 additions & 12 deletions libraries/pbrlib/genglsl/mx_dielectric_bsdf.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ void mx_dielectric_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, floa
}
else
{
fd = mx_init_fresnel_dielectric(ior);
fd = mx_init_fresnel_dielectric(ior);
}
vec3 F = mx_compute_fresnel(VdotH, fd);
float D = mx_ggx_NDF(Ht, safeAlpha);
Expand All @@ -44,14 +44,7 @@ void mx_dielectric_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, floa

void mx_dielectric_bsdf_transmission(vec3 V, float weight, vec3 tint, float ior, vec2 roughness, vec3 N, vec3 X, int distribution, int scatter_mode, inout BSDF bsdf)
{
if (scatter_mode == 1)
{
bsdf.response = tint * weight;
bsdf.throughput = bsdf.response;
return;
}

if (weight < M_FLOAT_EPS)
if (weight < M_FLOAT_EPS || scatter_mode == 0)
{
return;
}
Expand All @@ -61,20 +54,29 @@ void mx_dielectric_bsdf_transmission(vec3 V, float weight, vec3 tint, float ior,

FresnelData fd;
if (bsdf.thickness > 0.0)
{
fd = mx_init_fresnel_dielectric_airy(ior, bsdf.thickness, bsdf.ior);
}
else
{
fd = mx_init_fresnel_dielectric(ior);

}
vec3 F = mx_compute_fresnel(NdotV, fd);

vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0);
float avgAlpha = mx_average_alpha(safeAlpha);

float F0 = mx_ior_to_f0(ior);
vec3 comp = mx_ggx_energy_compensation(NdotV, avgAlpha, F);
vec3 dirAlbedo = mx_ggx_dir_albedo(NdotV, avgAlpha, F0, 1.0) * comp;
bsdf.throughput = 1.0 - dirAlbedo * weight;

bsdf.response = (scatter_mode == 2) ? tint * weight * bsdf.throughput : vec3(0.0);
// For now, we approximate the appearance of dielectric transmission as
// glossy environment map refraction, ignoring any scene geometry that
// might be visible through the surface.
fd.refraction = true;
vec3 Li = $refractionEnv ? mx_environment_radiance(N, V, X, safeAlpha, distribution, fd) : $refractionColor;
bsdf.response = Li * tint * weight;
}

void mx_dielectric_bsdf_indirect(vec3 V, float weight, vec3 tint, float ior, vec2 roughness, vec3 N, vec3 X, int distribution, int scatter_mode, inout BSDF bsdf)
Expand All @@ -90,14 +92,18 @@ void mx_dielectric_bsdf_indirect(vec3 V, float weight, vec3 tint, float ior, vec

FresnelData fd;
if (bsdf.thickness > 0.0)
{
fd = mx_init_fresnel_dielectric_airy(ior, bsdf.thickness, bsdf.ior);
}
else
{
fd = mx_init_fresnel_dielectric(ior);

}
vec3 F = mx_compute_fresnel(NdotV, fd);

vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0);
float avgAlpha = mx_average_alpha(safeAlpha);

float F0 = mx_ior_to_f0(ior);
vec3 comp = mx_ggx_energy_compensation(NdotV, avgAlpha, F);
vec3 dirAlbedo = mx_ggx_dir_albedo(NdotV, avgAlpha, F0, 1.0) * comp;
Expand Down
19 changes: 10 additions & 9 deletions libraries/pbrlib/genglsl/mx_generalized_schlick_bsdf.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,7 @@ void mx_generalized_schlick_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlus

void mx_generalized_schlick_bsdf_transmission(vec3 V, float weight, vec3 color0, vec3 color90, float exponent, vec2 roughness, vec3 N, vec3 X, int distribution, int scatter_mode, inout BSDF bsdf)
{
if (scatter_mode == 1)
{
bsdf.response = color0 * weight;
bsdf.throughput = bsdf.response;
return;
}

if (weight < M_FLOAT_EPS)
if (weight < M_FLOAT_EPS || scatter_mode == 0)
{
return;
}
Expand All @@ -56,12 +49,20 @@ void mx_generalized_schlick_bsdf_transmission(vec3 V, float weight, vec3 color0,

vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0);
float avgAlpha = mx_average_alpha(safeAlpha);

vec3 comp = mx_ggx_energy_compensation(NdotV, avgAlpha, F);
vec3 dirAlbedo = mx_ggx_dir_albedo(NdotV, avgAlpha, color0, color90) * comp;
float avgDirAlbedo = dot(dirAlbedo, vec3(1.0 / 3.0));
bsdf.throughput = vec3(1.0 - avgDirAlbedo * weight);

bsdf.response = (scatter_mode == 2) ? color0 * weight * bsdf.throughput : vec3(0.0);
// For now, we approximate the appearance of Schlick transmission as
// glossy environment map refraction, ignoring any scene geometry that
// might be visible through the surface.
float avgF0 = dot(color0, vec3(1.0 / 3.0));
fd.refraction = true;
fd.ior.x = mx_f0_to_ior(avgF0);
vec3 Li = $refractionEnv ? mx_environment_radiance(N, V, X, safeAlpha, distribution, fd) : $refractionColor;
bsdf.response = Li * color0 * weight;
}

void mx_generalized_schlick_bsdf_indirect(vec3 V, float weight, vec3 color0, vec3 color90, float exponent, vec2 roughness, vec3 N, vec3 X, int distribution, int scatter_mode, inout BSDF bsdf)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0"?>
<materialx version="1.38" colorspace="lin_rec709">
<standard_surface name="SR_glass_tinted" type="surfaceshader">
<input name="base" type="float" value="0" />
<input name="specular" type="float" value="1" />
<input name="specular_color" type="color3" value="1, 1, 1" />
<input name="specular_roughness" type="float" value="0.15" />
<input name="specular_IOR" type="float" value="1.54" />
<input name="transmission" type="float" value="1" />
<input name="transmission_color" type="color3" value="0.2, 0.1, 1" />
</standard_surface>
<surfacematerial name="GlassTinted" type="material">
<input name="surfaceshader" type="surfaceshader" nodename="SR_glass_tinted" />
</surfacematerial>
</materialx>
121 changes: 21 additions & 100 deletions resources/Materials/TestSuite/pbrlib/bsdf/dielectric.mtlx
Original file line number Diff line number Diff line change
@@ -1,122 +1,43 @@
<?xml version="1.0"?>
<materialx version="1.38">
<!-- Test dielectric_bsdf in various scatter modes -->
<nodegraph name="dielectric_bsdf_R">
<dielectric_bsdf name="bsdf1" type="BSDF">
<nodegraph name="dielectric_bsdf">
<dielectric_bsdf name="dielectric_R" type="BSDF">
<input name="weight" type="float" value="1.0" />
<input name="tint" type="color3" value="0.7, 0.7, 0.7" />
<input name="ior" type="float" value="1.7" />
<input name="scatter_mode" type="string" value="R" />
</dielectric_bsdf>
<surface name="surface1" type="surfaceshader">
<input name="bsdf" type="BSDF" nodename="bsdf1" />
<surface name="surface_R" type="surfaceshader">
<input name="bsdf" type="BSDF" nodename="dielectric_R" />
</surface>
<output name="out" type="surfaceshader" nodename="surface1" />
</nodegraph>
<nodegraph name="dielectric_bsdf_T">
<dielectric_bsdf name="bsdf1" type="BSDF">
<output name="R_out" type="surfaceshader" nodename="surface_R" />

<dielectric_bsdf name="dielectric_T" type="BSDF">
<input name="weight" type="float" value="1.0" />
<input name="tint" type="color3" value="0.7, 0.7, 0.7" />
<input name="ior" type="float" value="1.7" />
<input name="scatter_mode" type="string" value="T" />
</dielectric_bsdf>
<surface name="surface1" type="surfaceshader">
<input name="bsdf" type="BSDF" nodename="bsdf1" />
<surface name="surface_T" type="surfaceshader">
<input name="bsdf" type="BSDF" nodename="dielectric_T" />
</surface>
<output name="out" type="surfaceshader" nodename="surface1" />
</nodegraph>
<nodegraph name="dielectric_bsdf_RT">
<dielectric_bsdf name="bsdf1" type="BSDF">
<output name="T_out" type="surfaceshader" nodename="surface_T" />

<dielectric_bsdf name="dielectric_RT" type="BSDF">
<input name="weight" type="float" value="1.0" />
<input name="tint" type="color3" value="0.7, 0.7, 0.7" />
<input name="ior" type="float" value="1.7" />
<input name="scatter_mode" type="string" value="RT" />
</dielectric_bsdf>
<surface name="surface1" type="surfaceshader">
<input name="bsdf" type="BSDF" nodename="bsdf1" />
</surface>
<output name="out" type="surfaceshader" nodename="surface1" />
</nodegraph>
<nodegraph name="dielectric_bsdf_layeredRT">
<dielectric_bsdf name="bsdf1" type="BSDF">
<input name="weight" type="float" value="1.0" />
<input name="tint" type="color3" value="0.7, 0.7, 0.7" />
<input name="ior" type="float" value="1.7" />
<input name="scatter_mode" type="string" value="R" />
</dielectric_bsdf>
<dielectric_bsdf name="bsdf2" type="BSDF">
<input name="weight" type="float" value="1.0" />
<input name="tint" type="color3" value="0.7, 0.7, 0.7" />
<input name="ior" type="float" value="1.7" />
<input name="scatter_mode" type="string" value="T" />
</dielectric_bsdf>
<layer name="layer1" type="BSDF">
<input name="top" type="BSDF" nodename="bsdf1" />
<input name="base" type="BSDF" nodename="bsdf2" />
</layer>
<surface name="surface1" type="surfaceshader">
<input name="bsdf" type="BSDF" nodename="layer1" />
<surface name="surface_RT" type="surfaceshader">
<input name="bsdf" type="BSDF" nodename="dielectric_RT" />
</surface>
<output name="out" type="surfaceshader" nodename="surface1" />
</nodegraph>
<output name="RT_out" type="surfaceshader" nodename="surface_RT" />

<!-- Test generalized_schlick_bsdf in various scatter modes -->
<nodegraph name="generalized_schlick_bsdf_R">
<generalized_schlick_bsdf name="bsdf1" type="BSDF">
<input name="weight" type="float" value="1.0" />
<input name="color0" type="color3" value="0.7, 0.7, 0.7" />
<input name="color90" type="color3" value="1.0, 1.0, 1.0" />
<input name="scatter_mode" type="string" value="R" />
</generalized_schlick_bsdf>
<surface name="surface1" type="surfaceshader">
<input name="bsdf" type="BSDF" nodename="bsdf1" />
</surface>
<output name="out" type="surfaceshader" nodename="surface1" />
</nodegraph>
<nodegraph name="generalized_schlick_bsdf_T">
<generalized_schlick_bsdf name="bsdf1" type="BSDF">
<input name="weight" type="float" value="1.0" />
<input name="color0" type="color3" value="0.7, 0.7, 0.7" />
<input name="color90" type="color3" value="1.0, 1.0, 1.0" />
<input name="scatter_mode" type="string" value="T" />
</generalized_schlick_bsdf>
<surface name="surface1" type="surfaceshader">
<input name="bsdf" type="BSDF" nodename="bsdf1" />
</surface>
<output name="out" type="surfaceshader" nodename="surface1" />
</nodegraph>
<nodegraph name="generalized_schlick_bsdf_RT">
<generalized_schlick_bsdf name="bsdf1" type="BSDF">
<input name="weight" type="float" value="1.0" />
<input name="color0" type="color3" value="0.7, 0.7, 0.7" />
<input name="color90" type="color3" value="1.0, 1.0, 1.0" />
<input name="scatter_mode" type="string" value="RT" />
</generalized_schlick_bsdf>
<surface name="surface1" type="surfaceshader">
<input name="bsdf" type="BSDF" nodename="bsdf1" />
</surface>
<output name="out" type="surfaceshader" nodename="surface1" />
</nodegraph>
<nodegraph name="generalized_schlick_bsdf_layeredRT">
<generalized_schlick_bsdf name="bsdf1" type="BSDF">
<input name="weight" type="float" value="1.0" />
<input name="color0" type="color3" value="0.7, 0.7, 0.7" />
<input name="color90" type="color3" value="1.0, 1.0, 1.0" />
<input name="scatter_mode" type="string" value="R" />
</generalized_schlick_bsdf>
<generalized_schlick_bsdf name="bsdf2" type="BSDF">
<input name="weight" type="float" value="1.0" />
<input name="color0" type="color3" value="1.0, 1.0, 1.0" />
<input name="color90" type="color3" value="1.0, 1.0, 1.0" />
<input name="scatter_mode" type="string" value="T" />
</generalized_schlick_bsdf>
<layer name="layer1" type="BSDF">
<input name="top" type="BSDF" nodename="bsdf1" />
<input name="base" type="BSDF" nodename="bsdf2" />
<layer name="layer_RT" type="BSDF">
<input name="top" type="BSDF" nodename="dielectric_R" />
<input name="base" type="BSDF" nodename="dielectric_T" />
</layer>
<surface name="surface1" type="surfaceshader">
<input name="bsdf" type="BSDF" nodename="layer1" />
<surface name="surface_layer_RT" type="surfaceshader">
<input name="bsdf" type="BSDF" nodename="layer_RT" />
</surface>
<output name="out" type="surfaceshader" nodename="surface1" />
<output name="layer_RT_out" type="surfaceshader" nodename="surface_layer_RT" />
</nodegraph>
</materialx>
Loading

0 comments on commit f5fc395

Please sign in to comment.