Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Energy conservation improvements for generated GLSL #402

Merged
merged 1 commit into from Apr 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
}