Skip to content

Commit

Permalink
Energy conservation improvements for generated GLSL
Browse files Browse the repository at this point in the history
- Add mx_microfacet_ggx_directional_albedo for computing a directional albedo for the GGX/Smith/Schlick BRDF.
- Replace mirror Fresnel with directional albedo for BSDF layering.
- Move Fresnel computation into the lighting integral for filtered importance sampling.
- Add mx_average_roughness for converting from anisotropic to isotropic roughness.
  • Loading branch information
jstone-lucasfilm committed Apr 9, 2020
1 parent 2eb238b commit 9ab05b8
Show file tree
Hide file tree
Showing 8 changed files with 85 additions and 80 deletions.
51 changes: 28 additions & 23 deletions libraries/pbrlib/genglsl/lib/mx_bsdfs.glsl
@@ -1,3 +1,10 @@
#include "pbrlib/genglsl/lib/mx_refraction_index.glsl"

float mx_average_roughness(vec2 roughness)
{
return sqrt(roughness.x * roughness.y);
}

float mx_orennayar(vec3 L, vec3 V, vec3 N, float NdotL, float roughness)
{
float LdotV = dot(L, V);
Expand Down Expand Up @@ -52,6 +59,22 @@ float mx_microfacet_ggx_smith_G(float NdotL, float NdotV, float alpha)
return 2.0 / (lambdaL / NdotL + lambdaV / NdotV);
}

// https://www.unrealengine.com/blog/physically-based-shading-on-mobile
vec3 mx_microfacet_ggx_directional_albedo(float NdotV, float roughness, vec3 F0, vec3 F90)
{
const vec4 c0 = vec4(-1, -0.0275, -0.572, 0.022);
const vec4 c1 = vec4( 1, 0.0425, 1.04, -0.04 );
vec4 r = roughness * c0 + c1;
float a004 = min(r.x * r.x, exp2(-9.28 * NdotV)) * r.x + r.y;
vec2 AB = vec2(-1.04, 1.04) * a004 + r.zw;
return F0 * AB.x + F90 * AB.y;
}

float mx_microfacet_ggx_directional_albedo(float NdotV, float roughness, float ior)
{
return mx_microfacet_ggx_directional_albedo(NdotV, roughness, vec3(mx_ior_to_f0(ior)), vec3(1.0)).x;
}

// http://blog.selfshadow.com/publications/s2017-shading-course/imageworks/s2017_pbs_imageworks_sheen.pdf (Equation 2)
float mx_microfacet_sheen_NDF(float cosTheta, float roughness)
{
Expand Down Expand Up @@ -117,37 +140,19 @@ vec3 mx_fresnel_schlick(float cosTheta, vec3 F0, vec3 F90, float exponent)

vec3 mx_fresnel_schlick(float cosTheta, vec3 F0)
{
if (cosTheta < 0.0)
return vec3(1.0);
float x = 1.0 - cosTheta;
float x2 = x*x;
float x5 = x2*x2*x;
float x = clamp(1.0 - cosTheta, 0.0, 1.0);
float x5 = mx_pow5(x);
return F0 + (1.0 - F0) * x5;
}

float mx_fresnel_schlick(float cosTheta, float ior)
{
if (cosTheta < 0.0)
return 1.0;
float F0 = (ior - 1.0) / (ior + 1.0);
F0 *= F0;
float x = 1.0 - cosTheta;
float x2 = x*x;
float x5 = x2*x2*x;
float x = clamp(1.0 - cosTheta, 0.0, 1.0);
float x5 = mx_pow5(x);
float F0 = mx_ior_to_f0(ior);
return F0 + (1.0 - F0) * x5;
}

float mx_fresnel_schlick_roughness(float cosTheta, float ior, float roughness)
{
cosTheta = abs(cosTheta);
float F0 = (ior - 1.0) / (ior + 1.0);
F0 *= F0;
float x = 1.0 - cosTheta;
float x2 = x*x;
float x5 = x2*x2*x;
return F0 + (max(1.0 - roughness, F0) - F0) * x5;
}

// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/
float mx_fresnel_dielectric(float cosTheta, float ior)
{
Expand Down
15 changes: 7 additions & 8 deletions libraries/pbrlib/genglsl/lib/mx_environment_fis.glsl
Expand Up @@ -30,7 +30,7 @@ vec3 mx_latlong_map_lookup(vec3 dir, mat4 transform, float lod, sampler2D sample
}

// Only GGX is supported for now and the distribution argument is ignored
vec3 mx_environment_radiance(vec3 N, vec3 V, vec3 X, vec2 roughness, int distribution)
vec3 mx_environment_radiance(vec3 N, vec3 V, vec3 X, vec2 roughness, vec3 F0, vec3 F90, int distribution)
{
vec3 Y = normalize(cross(N, X));
X = cross(Y, N);
Expand Down Expand Up @@ -60,20 +60,19 @@ vec3 mx_environment_radiance(vec3 N, vec3 V, vec3 X, vec2 roughness, int distrib
float lod = mx_latlong_compute_lod(L, pdf, $envRadianceMips - 1, $envRadianceSamples);
vec3 sampleColor = mx_latlong_map_lookup(L, $envMatrix, lod, $envRadiance);

// Compute the Fresnel term.
vec3 F = mx_fresnel_schlick(VdotH, F0, F90, 5.0);

// Compute the geometric term.
float G = mx_microfacet_ggx_smith_G(NdotL, NdotV, max(roughness.x, roughness.y));
float G = mx_microfacet_ggx_smith_G(NdotL, NdotV, mx_average_roughness(roughness));

// Fresnel is applied outside the lighting integral for now.
// TODO: Move Fresnel term into the lighting integral.
float F = 1.0;

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

// Normalize and return the final radiance.
Expand Down
2 changes: 1 addition & 1 deletion libraries/pbrlib/genglsl/lib/mx_environment_none.glsl
@@ -1,4 +1,4 @@
vec3 mx_environment_radiance(vec3 N, vec3 V, vec3 X, vec2 roughness, int distribution)
vec3 mx_environment_radiance(vec3 N, vec3 V, vec3 X, vec2 roughness, vec3 F0, vec3 F90, int distribution)
{
return vec3(0.0);
}
Expand Down
9 changes: 6 additions & 3 deletions libraries/pbrlib/genglsl/lib/mx_environment_prefilter.glsl
Expand Up @@ -27,10 +27,13 @@ vec3 mx_latlong_map_lookup(vec3 dir, mat4 transform, float lodBias, sampler2D sa
return vec3(0.0);
}

vec3 mx_environment_radiance(vec3 N, vec3 V, vec3 X, vec2 roughness, int distribution)
vec3 mx_environment_radiance(vec3 N, vec3 V, vec3 X, vec2 roughness, vec3 F0, vec3 F90, int distribution)
{
vec3 dir = reflect(-V, N);
return mx_latlong_map_lookup(dir, $envMatrix, max(roughness.x, roughness.y), $envRadiance);
vec3 L = reflect(-V, N);
float NdotV = dot(N, V);

vec3 dirAlbedo = mx_microfacet_ggx_directional_albedo(NdotV, mx_average_roughness(roughness), F0, F90);
return mx_latlong_map_lookup(L, $envMatrix, mx_average_roughness(roughness), $envRadiance) * dirAlbedo;
}

vec3 mx_environment_irradiance(vec3 N)
Expand Down
6 changes: 6 additions & 0 deletions libraries/pbrlib/genglsl/lib/mx_refraction_index.glsl
@@ -1,3 +1,9 @@
// Convert a real-valued index of refraction to normal-incidence reflectivity.
float mx_ior_to_f0(float ior)
{
return mx_square((ior - 1.0) / (ior + 1.0));
}

// "Artist Friendly Metallic Fresnel", Ole Gulbrandsen, 2014
// http://jcgt.org/published/0003/04/03/paper.pdf

Expand Down
16 changes: 6 additions & 10 deletions libraries/pbrlib/genglsl/mx_conductor_brdf.glsl
@@ -1,5 +1,4 @@
#include "pbrlib/genglsl/lib/mx_bsdfs.glsl"
#include "pbrlib/genglsl/lib/mx_refraction_index.glsl"

void mx_conductor_brdf_reflection(vec3 L, vec3 V, float weight, vec3 reflectivity, vec3 edge_color, vec2 roughness, vec3 N, vec3 X, int distribution, out BSDF result)
{
Expand All @@ -21,19 +20,17 @@ void mx_conductor_brdf_reflection(vec3 L, vec3 V, float weight, vec3 reflectivit

vec3 H = normalize(L + V);
float NdotH = dot(N, H);

float D = mx_microfacet_ggx_NDF(X, Y, H, NdotH, roughness.x, roughness.y);
float G = mx_microfacet_ggx_smith_G(NdotL, NdotV, max(roughness.x, roughness.y));
float VdotH = dot(V, H);

vec3 ior_n, ior_k;
mx_artistic_to_complex_ior(reflectivity, edge_color, ior_n, ior_k);

float VdotH = dot(V, H);
float D = mx_microfacet_ggx_NDF(X, Y, H, NdotH, roughness.x, roughness.y);
vec3 F = mx_fresnel_conductor(VdotH, ior_n, ior_k);
F *= weight;
float G = mx_microfacet_ggx_smith_G(NdotL, NdotV, mx_average_roughness(roughness));

// Note: NdotL is cancelled out
result = F * D * G / (4 * NdotV);
result = D * F * G * weight / (4 * NdotV);
}

void mx_conductor_brdf_indirect(vec3 V, float weight, vec3 reflectivity, vec3 edge_color, vec2 roughness, vec3 N, vec3 X, int distribution, out vec3 result)
Expand All @@ -47,8 +44,7 @@ void mx_conductor_brdf_indirect(vec3 V, float weight, vec3 reflectivity, vec3 ed
vec3 ior_n, ior_k;
mx_artistic_to_complex_ior(reflectivity, edge_color, ior_n, ior_k);

vec3 Li = mx_environment_radiance(N, V, X, roughness, distribution);
vec3 Li = mx_environment_radiance(N, V, X, roughness, vec3(1.0), vec3(1.0), distribution);
vec3 F = mx_fresnel_conductor(dot(N, V), ior_n, ior_k);
F *= weight;
result = Li * F;
result = Li * F * weight;
}
30 changes: 14 additions & 16 deletions libraries/pbrlib/genglsl/mx_dielectric_brdf.glsl
Expand Up @@ -20,17 +20,17 @@ void mx_dielectric_brdf_reflection(vec3 L, vec3 V, float weight, vec3 tint, floa

vec3 H = normalize(L + V);
float NdotH = dot(N, H);
float VdotH = dot(V, H);

float D = mx_microfacet_ggx_NDF(X, Y, H, NdotH, roughness.x, roughness.y);
float G = mx_microfacet_ggx_smith_G(NdotL, NdotV, max(roughness.x, roughness.y));

float VdotH = dot(V, H);
float F = mx_fresnel_schlick(VdotH, ior);
F *= weight;
float G = mx_microfacet_ggx_smith_G(NdotL, NdotV, mx_average_roughness(roughness));

float dirAlbedo = mx_microfacet_ggx_directional_albedo(NdotV, mx_average_roughness(roughness), ior);

// Note: NdotL is cancelled out
result = tint * D * G * F / (4 * NdotV) // Top layer reflection
+ base * (1.0 - F); // Base layer reflection attenuated by top fresnel
result = D * F * G * tint * weight / (4 * NdotV) // Top layer reflection
+ base * (1.0 - dirAlbedo * weight); // Base layer reflection attenuated by top directional albedo
}

void mx_dielectric_brdf_transmission(vec3 V, float weight, vec3 tint, float ior, vec2 roughness, vec3 N, vec3 X, int distribution, BSDF base, out BSDF result)
Expand All @@ -46,11 +46,10 @@ void mx_dielectric_brdf_transmission(vec3 V, float weight, vec3 tint, float ior,
// inverse of top layer reflectance.

// Abs here to allow transparency through backfaces
float NdotV = abs(dot(N,V));
float F = mx_fresnel_schlick(NdotV, ior);
F *= weight;
float NdotV = abs(dot(N, V));
float dirAlbedo = mx_microfacet_ggx_directional_albedo(NdotV, mx_average_roughness(roughness), ior);

result = base * (1.0 - F); // Base layer transmission attenuated by top fresnel
result = base * (1.0 - dirAlbedo * weight); // Base layer transmission attenuated by top directional albedo
}

void mx_dielectric_brdf_indirect(vec3 V, float weight, vec3 tint, float ior, vec2 roughness, vec3 N, vec3 X, int distribution, BSDF base, out BSDF result)
Expand All @@ -61,12 +60,11 @@ void mx_dielectric_brdf_indirect(vec3 V, float weight, vec3 tint, float ior, vec
return;
}

vec3 Li = mx_environment_radiance(N, V, X, roughness, distribution);
vec3 Li = mx_environment_radiance(N, V, X, roughness, vec3(mx_ior_to_f0(ior)), vec3(1.0), distribution);

float NdotV = dot(N,V);
float F = mx_fresnel_schlick_roughness(NdotV, ior, max(roughness.x, roughness.y));
F *= weight;
float NdotV = dot(N, V);
float dirAlbedo = mx_microfacet_ggx_directional_albedo(NdotV, mx_average_roughness(roughness), ior);

result = Li * tint * F // Top layer reflection
+ base * (1.0 - F); // Base layer reflection attenuated by top fresnel
result = Li * tint * weight // Top layer reflection
+ base * (1.0 - dirAlbedo * weight); // Base layer reflection attenuated by top directional albedo
}
36 changes: 17 additions & 19 deletions libraries/pbrlib/genglsl/mx_generalized_schlick_brdf.glsl
Expand Up @@ -20,18 +20,18 @@ void mx_generalized_schlick_brdf_reflection(vec3 L, vec3 V, float weight, vec3 c

vec3 H = normalize(L + V);
float NdotH = dot(N, H);
float VdotH = dot(V, H);

float D = mx_microfacet_ggx_NDF(X, Y, H, NdotH, roughness.x, roughness.y);
float G = mx_microfacet_ggx_smith_G(NdotL, NdotV, max(roughness.x, roughness.y));

float VdotH = dot(V, H);
vec3 F = mx_fresnel_schlick(VdotH, color0, color90, exponent);
F *= weight;
float avgF = dot(F, vec3(1.0 / 3.0));
float G = mx_microfacet_ggx_smith_G(NdotL, NdotV, mx_average_roughness(roughness));

vec3 dirAlbedo = mx_microfacet_ggx_directional_albedo(NdotV, mx_average_roughness(roughness), color0, color90);
float avgDirAlbedo = dot(dirAlbedo, vec3(1.0 / 3.0));

// Note: NdotL is cancelled out
result = D * G * F / (4 * NdotV) // Top layer reflection
+ base * (1.0 - avgF); // Base layer reflection attenuated by top fresnel
result = D * F * G * weight / (4 * NdotV) // Top layer reflection
+ base * (1.0 - avgDirAlbedo * weight); // Base layer reflection attenuated by top directional albedo
}

void mx_generalized_schlick_brdf_transmission(vec3 V, float weight, vec3 color0, vec3 color90, float exponent, vec2 roughness, vec3 N, vec3 X, int distribution, BSDF base, out BSDF result)
Expand All @@ -47,12 +47,11 @@ void mx_generalized_schlick_brdf_transmission(vec3 V, float weight, vec3 color0,
// inverse of top layer reflectance.

// Abs here to allow transparency through backfaces
float NdotV = abs(dot(N,V));
vec3 F = mx_fresnel_schlick(NdotV, color0, color90, exponent);
F *= weight;
float avgF = dot(F, vec3(1.0 / 3.0));
float NdotV = abs(dot(N, V));
vec3 dirAlbedo = mx_microfacet_ggx_directional_albedo(NdotV, mx_average_roughness(roughness), color0, color90);
float avgDirAlbedo = dot(dirAlbedo, vec3(1.0 / 3.0));

result = base * (1.0 - avgF); // Base layer transmission attenuated by top fresnel
result = base * (1.0 - avgDirAlbedo * weight); // Base layer transmission attenuated by top directional albedo
}

void mx_generalized_schlick_brdf_indirect(vec3 V, float weight, vec3 color0, vec3 color90, float exponent, vec2 roughness, vec3 N, vec3 X, int distribution, BSDF base, out BSDF result)
Expand All @@ -63,13 +62,12 @@ void mx_generalized_schlick_brdf_indirect(vec3 V, float weight, vec3 color0, vec
return;
}

vec3 Li = mx_environment_radiance(N, V, X, roughness, distribution);
vec3 Li = mx_environment_radiance(N, V, X, roughness, color0, color90, distribution);

float NdotV = dot(N,V);
vec3 F = mx_fresnel_schlick(NdotV, color0, color90, exponent);
F *= weight;
float avgF = dot(F, vec3(1.0 / 3.0));
float NdotV = dot(N, V);
vec3 dirAlbedo = mx_microfacet_ggx_directional_albedo(NdotV, mx_average_roughness(roughness), color0, color90);
float avgDirAlbedo = dot(dirAlbedo, vec3(1.0 / 3.0));

result = Li * F // Top layer reflection
+ base * (1.0 - avgF); // Base layer reflection attenuated by top fresnel
result = Li * weight // Top layer reflection
+ base * (1.0 - avgDirAlbedo * weight); // Base layer reflection attenuated by top directional albedo
}

0 comments on commit 9ab05b8

Please sign in to comment.