From 526491790e77a1863926f1a18779fbdceeb293d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Garc=C3=ADa=20Li=C3=B1=C3=A1n?= Date: Sat, 31 Jul 2021 14:36:02 +0200 Subject: [PATCH] HDR: Improve envmap pre-filtering --- Compositor/HDR/env-capture-pass.xml | 1 + Compositor/HDR/hdr.xml | 118 +++++++++++++++++++--------- Effects/HDR/envmap-copy.eff | 17 ++++ Effects/HDR/envmap-prefilter.eff | 6 ++ Effects/HDR/envmap-prefilter1.eff | 1 + Effects/HDR/envmap-prefilter2.eff | 1 + Effects/HDR/envmap-prefilter3.eff | 1 + Effects/HDR/envmap-prefilter4.eff | 1 + Shaders/HDR/envmap-copy.frag | 27 +++++++ Shaders/HDR/envmap-prefilter.frag | 66 ++++++++++------ 10 files changed, 177 insertions(+), 62 deletions(-) create mode 100644 Effects/HDR/envmap-copy.eff create mode 100644 Shaders/HDR/envmap-copy.frag diff --git a/Compositor/HDR/env-capture-pass.xml b/Compositor/HDR/env-capture-pass.xml index ff46c701d..2abcdad44 100644 --- a/Compositor/HDR/env-capture-pass.xml +++ b/Compositor/HDR/env-capture-pass.xml @@ -4,6 +4,7 @@ scene depth hdr-envmap + depth 0x800 diff --git a/Compositor/HDR/hdr.xml b/Compositor/HDR/hdr.xml index dcaa1148e..b38719090 100644 --- a/Compositor/HDR/hdr.xml +++ b/Compositor/HDR/hdr.xml @@ -123,6 +123,18 @@ clamp-to-edge 5 + + prefiltered-envmap + cubemap + 128 + 128 + rgb16f + linear-mipmap-linear + linear + clamp-to-edge + clamp-to-edge + 5 + @@ -305,7 +317,6 @@ color0 envmap 0 - 0 @@ -315,7 +326,6 @@ color0 envmap 1 - 0 @@ -325,7 +335,6 @@ color0 envmap 2 - 0 @@ -335,7 +344,6 @@ color0 envmap 3 - 0 @@ -345,7 +353,6 @@ color0 envmap 4 - 0 @@ -355,7 +362,8 @@ color0 envmap 5 - 0 + + true @@ -364,169 +372,207 @@ We convolve the cubemap for five roughness values and store the results on the mipmap levels of the cubemap. Later passes will choose which mipmap level to use for reflections based on the roughness of the surface that's - being lighted/rendered. + being lighted/rendered. The first mipmap level can just be copied from the + original envmap as we'd like it to contain perfect mirror reflections. Diffuse lighting is approximated by using the highest mipmap level (roughness=1). --> + + env-prefilter0 + Effects/HDR/envmap-copy + + color0 + prefiltered-envmap + 0 + 0 + + + color1 + prefiltered-envmap + 1 + 0 + + + color2 + prefiltered-envmap + 2 + 0 + + + color3 + prefiltered-envmap + 3 + 0 + + + color4 + prefiltered-envmap + 4 + 0 + + + color5 + prefiltered-envmap + 5 + 0 + + env-prefilter1 Effects/HDR/envmap-prefilter1 color0 - envmap + prefiltered-envmap 0 1 color1 - envmap + prefiltered-envmap 1 1 color2 - envmap + prefiltered-envmap 2 1 color3 - envmap + prefiltered-envmap 3 1 color4 - envmap + prefiltered-envmap 4 1 color5 - envmap + prefiltered-envmap 5 1 - env-prefilter2 Effects/HDR/envmap-prefilter2 color0 - envmap + prefiltered-envmap 0 2 color1 - envmap + prefiltered-envmap 1 2 color2 - envmap + prefiltered-envmap 2 2 color3 - envmap + prefiltered-envmap 3 2 color4 - envmap + prefiltered-envmap 4 2 color5 - envmap + prefiltered-envmap 5 2 - env-prefilter3 Effects/HDR/envmap-prefilter3 color0 - envmap + prefiltered-envmap 0 3 color1 - envmap + prefiltered-envmap 1 3 color2 - envmap + prefiltered-envmap 2 3 color3 - envmap + prefiltered-envmap 3 3 color4 - envmap + prefiltered-envmap 4 3 color5 - envmap + prefiltered-envmap 5 3 - env-prefilter4 Effects/HDR/envmap-prefilter4 color0 - envmap + prefiltered-envmap 0 4 color1 - envmap + prefiltered-envmap 1 4 color2 - envmap + prefiltered-envmap 2 4 color3 - envmap + prefiltered-envmap 3 4 color4 - envmap + prefiltered-envmap 4 4 color5 - envmap + prefiltered-envmap 5 4 @@ -662,7 +708,7 @@ 9 - envmap + prefiltered-envmap 10 @@ -695,7 +741,7 @@ hdr-forward 9 - envmap + prefiltered-envmap 11 diff --git a/Effects/HDR/envmap-copy.eff b/Effects/HDR/envmap-copy.eff new file mode 100644 index 000000000..646182287 --- /dev/null +++ b/Effects/HDR/envmap-copy.eff @@ -0,0 +1,17 @@ + + + Effects/HDR/envmap-copy + + + + Shaders/HDR/envmap-prefilter.vert + Shaders/HDR/envmap-copy.frag + + + envmap + sampler-cube + 0 + + + + diff --git a/Effects/HDR/envmap-prefilter.eff b/Effects/HDR/envmap-prefilter.eff index 34b7df500..20884a925 100644 --- a/Effects/HDR/envmap-prefilter.eff +++ b/Effects/HDR/envmap-prefilter.eff @@ -3,6 +3,7 @@ Effects/HDR/envmap-prefilter 0.0 + 1 @@ -20,6 +21,11 @@ float roughness + + num_samples + int + num-samples + diff --git a/Effects/HDR/envmap-prefilter1.eff b/Effects/HDR/envmap-prefilter1.eff index 971ea57fa..75bfc5bdb 100644 --- a/Effects/HDR/envmap-prefilter1.eff +++ b/Effects/HDR/envmap-prefilter1.eff @@ -4,5 +4,6 @@ Effects/HDR/envmap-prefilter 0.25 + 8 diff --git a/Effects/HDR/envmap-prefilter2.eff b/Effects/HDR/envmap-prefilter2.eff index 5928cd684..80d81868f 100644 --- a/Effects/HDR/envmap-prefilter2.eff +++ b/Effects/HDR/envmap-prefilter2.eff @@ -4,5 +4,6 @@ Effects/HDR/envmap-prefilter 0.5 + 32 diff --git a/Effects/HDR/envmap-prefilter3.eff b/Effects/HDR/envmap-prefilter3.eff index 6528beb7e..fc12969a5 100644 --- a/Effects/HDR/envmap-prefilter3.eff +++ b/Effects/HDR/envmap-prefilter3.eff @@ -4,5 +4,6 @@ Effects/HDR/envmap-prefilter 0.75 + 64 diff --git a/Effects/HDR/envmap-prefilter4.eff b/Effects/HDR/envmap-prefilter4.eff index 2763f0220..a2a4140be 100644 --- a/Effects/HDR/envmap-prefilter4.eff +++ b/Effects/HDR/envmap-prefilter4.eff @@ -4,5 +4,6 @@ Effects/HDR/envmap-prefilter 1.0 + 128 diff --git a/Shaders/HDR/envmap-copy.frag b/Shaders/HDR/envmap-copy.frag new file mode 100644 index 000000000..9acb8fec4 --- /dev/null +++ b/Shaders/HDR/envmap-copy.frag @@ -0,0 +1,27 @@ +#version 330 core + +layout(location = 0) out vec3 fragColor0; +layout(location = 1) out vec3 fragColor1; +layout(location = 2) out vec3 fragColor2; +layout(location = 3) out vec3 fragColor3; +layout(location = 4) out vec3 fragColor4; +layout(location = 5) out vec3 fragColor5; + +in vec3 cubemapCoord0; +in vec3 cubemapCoord1; +in vec3 cubemapCoord2; +in vec3 cubemapCoord3; +in vec3 cubemapCoord4; +in vec3 cubemapCoord5; + +uniform samplerCube envmap; + +void main() +{ + fragColor0 = textureLod(envmap, cubemapCoord0, 0.0).rgb; + fragColor1 = textureLod(envmap, cubemapCoord1, 0.0).rgb; + fragColor2 = textureLod(envmap, cubemapCoord2, 0.0).rgb; + fragColor3 = textureLod(envmap, cubemapCoord3, 0.0).rgb; + fragColor4 = textureLod(envmap, cubemapCoord4, 0.0).rgb; + fragColor5 = textureLod(envmap, cubemapCoord5, 0.0).rgb; +} diff --git a/Shaders/HDR/envmap-prefilter.frag b/Shaders/HDR/envmap-prefilter.frag index 85183bdbd..7c672409f 100644 --- a/Shaders/HDR/envmap-prefilter.frag +++ b/Shaders/HDR/envmap-prefilter.frag @@ -1,3 +1,6 @@ +// Mostly based on 'Moving Frostbite to Physically Based Rendering' +// https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf + #version 330 core layout(location = 0) out vec3 fragColor0; @@ -16,11 +19,11 @@ in vec3 cubemapCoord5; uniform samplerCube envmap; uniform float roughness; - -uniform int fg_CubemapFace; +uniform int num_samples; const float PI = 3.14159265359; -const uint NUM_SAMPLES = 64u; +const float ENVMAP_SIZE = 128.0; +const float ENVMAP_MIP_COUNT = 4.0; float RadicalInverse_VdC(uint bits) { @@ -37,57 +40,68 @@ vec2 Hammersley(uint i, uint N) return vec2(float(i)/float(N), RadicalInverse_VdC(i)); } -vec3 ImportanceSampleGGX(vec2 Xi, vec3 n, float r) +vec3 ImportanceSampleGGX(vec2 Xi, vec3 n, float a) { - float a = r*r; - float phi = 2.0 * PI * Xi.x; float cosTheta = sqrt((1.0 - Xi.y) / (1.0 + (a*a - 1.0) * Xi.y)); float sinTheta = sqrt(1.0 - cosTheta*cosTheta); - vec3 h; h.x = sinTheta * cos(phi); h.y = sinTheta * sin(phi); h.z = cosTheta; + return h; +} - vec3 up = abs(n.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0); - vec3 tangent = normalize(cross(up, n)); - vec3 bitangent = cross(n, tangent); - - vec3 sampleVec = tangent * h.x + bitangent * h.y + n * h.z; - return normalize(sampleVec); +float D_GGX(float NdotH, float a2) +{ + float f = (NdotH * a2 - NdotH) * NdotH + 1.0; + return a2 / (PI * f * f); } vec3 prefilter(vec3 n) { - n = normalize(n); vec3 v = n; // n = v simplification + float a = roughness*roughness; vec3 prefilteredColor = vec3(0.0); float totalWeight = 0.0; - for (uint i = 0u; i < NUM_SAMPLES; ++i) { - vec2 Xi = Hammersley(i, NUM_SAMPLES); - vec3 h = ImportanceSampleGGX(Xi, n, roughness); + vec3 up = abs(n.z) < 0.999f ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0); + vec3 tangent = normalize(cross(up, n)); + vec3 bitangent = cross(n, tangent); + mat3 tangentToWorld = mat3(tangent, bitangent, n); + + uint sample_count = uint(num_samples); + for (uint i = 0u; i < sample_count; ++i) { + vec2 Xi = Hammersley(i, sample_count); + vec3 h = tangentToWorld * ImportanceSampleGGX(Xi, n, a); vec3 l = normalize(2.0 * dot(v, h) * h - v); float NdotL = max(dot(n, l), 0.0); if (NdotL > 0.0) { - prefilteredColor += textureLod(envmap, l, 0.0).rgb * NdotL; + float NdotH = clamp(dot(n, h), 0.0, 1.0); + float VdotH = clamp(dot(v, h), 0.0, 1.0); + + float pdf = D_GGX(NdotH, a) * NdotH / (4.0 * VdotH); + float omegaS = 1.0 / (float(sample_count) * pdf); + float omegaP = 4.0 * PI / (6.0 * ENVMAP_SIZE * ENVMAP_SIZE); + float mipLevel = clamp(0.5 * log2(omegaS / omegaP) + 1.0, + 0.0, ENVMAP_MIP_COUNT); + + prefilteredColor += textureLod(envmap, l, mipLevel).rgb * NdotL; totalWeight += NdotL; } } - prefilteredColor /= totalWeight; - return prefilteredColor; + return prefilteredColor / totalWeight; } void main() { - fragColor0 = prefilter(cubemapCoord0); - fragColor1 = prefilter(cubemapCoord1); - fragColor2 = prefilter(cubemapCoord2); - fragColor3 = prefilter(cubemapCoord3); - fragColor4 = prefilter(cubemapCoord4); - fragColor5 = prefilter(cubemapCoord5); + fragColor0 = prefilter(normalize(cubemapCoord0)); + fragColor1 = prefilter(normalize(cubemapCoord1)); + fragColor2 = prefilter(normalize(cubemapCoord2)); + fragColor3 = prefilter(normalize(cubemapCoord3)); + fragColor4 = prefilter(normalize(cubemapCoord4)); + fragColor5 = prefilter(normalize(cubemapCoord5)); }