1
0
Fork 0

HDR: Improve envmap pre-filtering

This commit is contained in:
Fernando García Liñán 2021-07-31 14:36:02 +02:00
parent 5c1c28c536
commit 526491790e
10 changed files with 177 additions and 62 deletions

View file

@ -4,6 +4,7 @@
<type>scene</type> <type>scene</type>
<implicit-attachment-mask>depth</implicit-attachment-mask> <implicit-attachment-mask>depth</implicit-attachment-mask>
<effect-scheme>hdr-envmap</effect-scheme> <effect-scheme>hdr-envmap</effect-scheme>
<clear-mask>depth</clear-mask>
<!-- Only render the skydome and terrain --> <!-- Only render the skydome and terrain -->
<!-- TODO: Explicitly select the LOD level --> <!-- TODO: Explicitly select the LOD level -->
<cull-mask>0x800</cull-mask> <cull-mask>0x800</cull-mask>

View file

@ -123,6 +123,18 @@
<wrap-t>clamp-to-edge</wrap-t> <wrap-t>clamp-to-edge</wrap-t>
<mipmap-levels>5</mipmap-levels> <mipmap-levels>5</mipmap-levels>
</buffer> </buffer>
<buffer>
<name>prefiltered-envmap</name>
<type>cubemap</type>
<width>128</width>
<height>128</height>
<format>rgb16f</format>
<min-filter>linear-mipmap-linear</min-filter>
<mag-filter>linear</mag-filter>
<wrap-s>clamp-to-edge</wrap-s>
<wrap-t>clamp-to-edge</wrap-t>
<mipmap-levels>5</mipmap-levels>
</buffer>
<!-- Shadow map atlas --> <!-- Shadow map atlas -->
<buffer> <buffer>
@ -305,7 +317,6 @@
<component>color0</component> <component>color0</component>
<buffer>envmap</buffer> <buffer>envmap</buffer>
<face>0</face> <face>0</face>
<level>0</level>
</attachment> </attachment>
</pass> </pass>
<pass include="env-capture-pass.xml"> <pass include="env-capture-pass.xml">
@ -315,7 +326,6 @@
<component>color0</component> <component>color0</component>
<buffer>envmap</buffer> <buffer>envmap</buffer>
<face>1</face> <face>1</face>
<level>0</level>
</attachment> </attachment>
</pass> </pass>
<pass include="env-capture-pass.xml"> <pass include="env-capture-pass.xml">
@ -325,7 +335,6 @@
<component>color0</component> <component>color0</component>
<buffer>envmap</buffer> <buffer>envmap</buffer>
<face>2</face> <face>2</face>
<level>0</level>
</attachment> </attachment>
</pass> </pass>
<pass include="env-capture-pass.xml"> <pass include="env-capture-pass.xml">
@ -335,7 +344,6 @@
<component>color0</component> <component>color0</component>
<buffer>envmap</buffer> <buffer>envmap</buffer>
<face>3</face> <face>3</face>
<level>0</level>
</attachment> </attachment>
</pass> </pass>
<pass include="env-capture-pass.xml"> <pass include="env-capture-pass.xml">
@ -345,7 +353,6 @@
<component>color0</component> <component>color0</component>
<buffer>envmap</buffer> <buffer>envmap</buffer>
<face>4</face> <face>4</face>
<level>0</level>
</attachment> </attachment>
</pass> </pass>
<pass include="env-capture-pass.xml"> <pass include="env-capture-pass.xml">
@ -355,7 +362,8 @@
<component>color0</component> <component>color0</component>
<buffer>envmap</buffer> <buffer>envmap</buffer>
<face>5</face> <face>5</face>
<level>0</level> <!-- Generate the mips after writing to the last face -->
<mipmap-generation>true</mipmap-generation>
</attachment> </attachment>
</pass> </pass>
@ -364,169 +372,207 @@
We convolve the cubemap for five roughness values and store the results 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 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 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 Diffuse lighting is approximated by using the highest mipmap level
(roughness=1). (roughness=1).
--> -->
<pass include="env-prefilter-pass.xml">
<name>env-prefilter0</name>
<effect>Effects/HDR/envmap-copy</effect>
<attachment>
<component>color0</component>
<buffer>prefiltered-envmap</buffer>
<face>0</face>
<level>0</level>
</attachment>
<attachment>
<component>color1</component>
<buffer>prefiltered-envmap</buffer>
<face>1</face>
<level>0</level>
</attachment>
<attachment>
<component>color2</component>
<buffer>prefiltered-envmap</buffer>
<face>2</face>
<level>0</level>
</attachment>
<attachment>
<component>color3</component>
<buffer>prefiltered-envmap</buffer>
<face>3</face>
<level>0</level>
</attachment>
<attachment>
<component>color4</component>
<buffer>prefiltered-envmap</buffer>
<face>4</face>
<level>0</level>
</attachment>
<attachment>
<component>color5</component>
<buffer>prefiltered-envmap</buffer>
<face>5</face>
<level>0</level>
</attachment>
</pass>
<pass include="env-prefilter-pass.xml"> <pass include="env-prefilter-pass.xml">
<name>env-prefilter1</name> <name>env-prefilter1</name>
<effect>Effects/HDR/envmap-prefilter1</effect> <effect>Effects/HDR/envmap-prefilter1</effect>
<attachment> <attachment>
<component>color0</component> <component>color0</component>
<buffer>envmap</buffer> <buffer>prefiltered-envmap</buffer>
<face>0</face> <face>0</face>
<level>1</level> <level>1</level>
</attachment> </attachment>
<attachment> <attachment>
<component>color1</component> <component>color1</component>
<buffer>envmap</buffer> <buffer>prefiltered-envmap</buffer>
<face>1</face> <face>1</face>
<level>1</level> <level>1</level>
</attachment> </attachment>
<attachment> <attachment>
<component>color2</component> <component>color2</component>
<buffer>envmap</buffer> <buffer>prefiltered-envmap</buffer>
<face>2</face> <face>2</face>
<level>1</level> <level>1</level>
</attachment> </attachment>
<attachment> <attachment>
<component>color3</component> <component>color3</component>
<buffer>envmap</buffer> <buffer>prefiltered-envmap</buffer>
<face>3</face> <face>3</face>
<level>1</level> <level>1</level>
</attachment> </attachment>
<attachment> <attachment>
<component>color4</component> <component>color4</component>
<buffer>envmap</buffer> <buffer>prefiltered-envmap</buffer>
<face>4</face> <face>4</face>
<level>1</level> <level>1</level>
</attachment> </attachment>
<attachment> <attachment>
<component>color5</component> <component>color5</component>
<buffer>envmap</buffer> <buffer>prefiltered-envmap</buffer>
<face>5</face> <face>5</face>
<level>1</level> <level>1</level>
</attachment> </attachment>
</pass> </pass>
<pass include="env-prefilter-pass.xml"> <pass include="env-prefilter-pass.xml">
<name>env-prefilter2</name> <name>env-prefilter2</name>
<effect>Effects/HDR/envmap-prefilter2</effect> <effect>Effects/HDR/envmap-prefilter2</effect>
<attachment> <attachment>
<component>color0</component> <component>color0</component>
<buffer>envmap</buffer> <buffer>prefiltered-envmap</buffer>
<face>0</face> <face>0</face>
<level>2</level> <level>2</level>
</attachment> </attachment>
<attachment> <attachment>
<component>color1</component> <component>color1</component>
<buffer>envmap</buffer> <buffer>prefiltered-envmap</buffer>
<face>1</face> <face>1</face>
<level>2</level> <level>2</level>
</attachment> </attachment>
<attachment> <attachment>
<component>color2</component> <component>color2</component>
<buffer>envmap</buffer> <buffer>prefiltered-envmap</buffer>
<face>2</face> <face>2</face>
<level>2</level> <level>2</level>
</attachment> </attachment>
<attachment> <attachment>
<component>color3</component> <component>color3</component>
<buffer>envmap</buffer> <buffer>prefiltered-envmap</buffer>
<face>3</face> <face>3</face>
<level>2</level> <level>2</level>
</attachment> </attachment>
<attachment> <attachment>
<component>color4</component> <component>color4</component>
<buffer>envmap</buffer> <buffer>prefiltered-envmap</buffer>
<face>4</face> <face>4</face>
<level>2</level> <level>2</level>
</attachment> </attachment>
<attachment> <attachment>
<component>color5</component> <component>color5</component>
<buffer>envmap</buffer> <buffer>prefiltered-envmap</buffer>
<face>5</face> <face>5</face>
<level>2</level> <level>2</level>
</attachment> </attachment>
</pass> </pass>
<pass include="env-prefilter-pass.xml"> <pass include="env-prefilter-pass.xml">
<name>env-prefilter3</name> <name>env-prefilter3</name>
<effect>Effects/HDR/envmap-prefilter3</effect> <effect>Effects/HDR/envmap-prefilter3</effect>
<attachment> <attachment>
<component>color0</component> <component>color0</component>
<buffer>envmap</buffer> <buffer>prefiltered-envmap</buffer>
<face>0</face> <face>0</face>
<level>3</level> <level>3</level>
</attachment> </attachment>
<attachment> <attachment>
<component>color1</component> <component>color1</component>
<buffer>envmap</buffer> <buffer>prefiltered-envmap</buffer>
<face>1</face> <face>1</face>
<level>3</level> <level>3</level>
</attachment> </attachment>
<attachment> <attachment>
<component>color2</component> <component>color2</component>
<buffer>envmap</buffer> <buffer>prefiltered-envmap</buffer>
<face>2</face> <face>2</face>
<level>3</level> <level>3</level>
</attachment> </attachment>
<attachment> <attachment>
<component>color3</component> <component>color3</component>
<buffer>envmap</buffer> <buffer>prefiltered-envmap</buffer>
<face>3</face> <face>3</face>
<level>3</level> <level>3</level>
</attachment> </attachment>
<attachment> <attachment>
<component>color4</component> <component>color4</component>
<buffer>envmap</buffer> <buffer>prefiltered-envmap</buffer>
<face>4</face> <face>4</face>
<level>3</level> <level>3</level>
</attachment> </attachment>
<attachment> <attachment>
<component>color5</component> <component>color5</component>
<buffer>envmap</buffer> <buffer>prefiltered-envmap</buffer>
<face>5</face> <face>5</face>
<level>3</level> <level>3</level>
</attachment> </attachment>
</pass> </pass>
<pass include="env-prefilter-pass.xml"> <pass include="env-prefilter-pass.xml">
<name>env-prefilter4</name> <name>env-prefilter4</name>
<effect>Effects/HDR/envmap-prefilter4</effect> <effect>Effects/HDR/envmap-prefilter4</effect>
<attachment> <attachment>
<component>color0</component> <component>color0</component>
<buffer>envmap</buffer> <buffer>prefiltered-envmap</buffer>
<face>0</face> <face>0</face>
<level>4</level> <level>4</level>
</attachment> </attachment>
<attachment> <attachment>
<component>color1</component> <component>color1</component>
<buffer>envmap</buffer> <buffer>prefiltered-envmap</buffer>
<face>1</face> <face>1</face>
<level>4</level> <level>4</level>
</attachment> </attachment>
<attachment> <attachment>
<component>color2</component> <component>color2</component>
<buffer>envmap</buffer> <buffer>prefiltered-envmap</buffer>
<face>2</face> <face>2</face>
<level>4</level> <level>4</level>
</attachment> </attachment>
<attachment> <attachment>
<component>color3</component> <component>color3</component>
<buffer>envmap</buffer> <buffer>prefiltered-envmap</buffer>
<face>3</face> <face>3</face>
<level>4</level> <level>4</level>
</attachment> </attachment>
<attachment> <attachment>
<component>color4</component> <component>color4</component>
<buffer>envmap</buffer> <buffer>prefiltered-envmap</buffer>
<face>4</face> <face>4</face>
<level>4</level> <level>4</level>
</attachment> </attachment>
<attachment> <attachment>
<component>color5</component> <component>color5</component>
<buffer>envmap</buffer> <buffer>prefiltered-envmap</buffer>
<face>5</face> <face>5</face>
<level>4</level> <level>4</level>
</attachment> </attachment>
@ -662,7 +708,7 @@
</binding> </binding>
<binding> <binding>
<unit>9</unit> <unit>9</unit>
<buffer>envmap</buffer> <buffer>prefiltered-envmap</buffer>
</binding> </binding>
<binding> <binding>
<unit>10</unit> <unit>10</unit>
@ -695,7 +741,7 @@
<effect-scheme>hdr-forward</effect-scheme> <effect-scheme>hdr-forward</effect-scheme>
<binding> <binding>
<unit>9</unit> <unit>9</unit>
<buffer>envmap</buffer> <buffer>prefiltered-envmap</buffer>
</binding> </binding>
<binding> <binding>
<unit>11</unit> <unit>11</unit>

View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<PropertyList>
<name>Effects/HDR/envmap-copy</name>
<technique n="1">
<pass>
<program>
<vertex-shader>Shaders/HDR/envmap-prefilter.vert</vertex-shader>
<fragment-shader>Shaders/HDR/envmap-copy.frag</fragment-shader>
</program>
<uniform>
<name>envmap</name>
<type>sampler-cube</type>
<value type="int">0</value>
</uniform>
</pass>
</technique>
</PropertyList>

View file

@ -3,6 +3,7 @@
<name>Effects/HDR/envmap-prefilter</name> <name>Effects/HDR/envmap-prefilter</name>
<parameters> <parameters>
<roughness type="float">0.0</roughness> <roughness type="float">0.0</roughness>
<num-samples type="int">1</num-samples>
</parameters> </parameters>
<technique n="1"> <technique n="1">
<pass> <pass>
@ -20,6 +21,11 @@
<type>float</type> <type>float</type>
<value><use>roughness</use></value> <value><use>roughness</use></value>
</uniform> </uniform>
<uniform>
<name>num_samples</name>
<type>int</type>
<value><use>num-samples</use></value>
</uniform>
</pass> </pass>
</technique> </technique>
</PropertyList> </PropertyList>

View file

@ -4,5 +4,6 @@
<inherits-from>Effects/HDR/envmap-prefilter</inherits-from> <inherits-from>Effects/HDR/envmap-prefilter</inherits-from>
<parameters> <parameters>
<roughness type="float">0.25</roughness> <roughness type="float">0.25</roughness>
<num-samples type="int">8</num-samples>
</parameters> </parameters>
</PropertyList> </PropertyList>

View file

@ -4,5 +4,6 @@
<inherits-from>Effects/HDR/envmap-prefilter</inherits-from> <inherits-from>Effects/HDR/envmap-prefilter</inherits-from>
<parameters> <parameters>
<roughness type="float">0.5</roughness> <roughness type="float">0.5</roughness>
<num-samples type="int">32</num-samples>
</parameters> </parameters>
</PropertyList> </PropertyList>

View file

@ -4,5 +4,6 @@
<inherits-from>Effects/HDR/envmap-prefilter</inherits-from> <inherits-from>Effects/HDR/envmap-prefilter</inherits-from>
<parameters> <parameters>
<roughness type="float">0.75</roughness> <roughness type="float">0.75</roughness>
<num-samples type="int">64</num-samples>
</parameters> </parameters>
</PropertyList> </PropertyList>

View file

@ -4,5 +4,6 @@
<inherits-from>Effects/HDR/envmap-prefilter</inherits-from> <inherits-from>Effects/HDR/envmap-prefilter</inherits-from>
<parameters> <parameters>
<roughness type="float">1.0</roughness> <roughness type="float">1.0</roughness>
<num-samples type="int">128</num-samples>
</parameters> </parameters>
</PropertyList> </PropertyList>

View file

@ -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;
}

View file

@ -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 #version 330 core
layout(location = 0) out vec3 fragColor0; layout(location = 0) out vec3 fragColor0;
@ -16,11 +19,11 @@ in vec3 cubemapCoord5;
uniform samplerCube envmap; uniform samplerCube envmap;
uniform float roughness; uniform float roughness;
uniform int num_samples;
uniform int fg_CubemapFace;
const float PI = 3.14159265359; 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) float RadicalInverse_VdC(uint bits)
{ {
@ -37,57 +40,68 @@ vec2 Hammersley(uint i, uint N)
return vec2(float(i)/float(N), RadicalInverse_VdC(i)); 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 phi = 2.0 * PI * Xi.x;
float cosTheta = sqrt((1.0 - Xi.y) / (1.0 + (a*a - 1.0) * Xi.y)); float cosTheta = sqrt((1.0 - Xi.y) / (1.0 + (a*a - 1.0) * Xi.y));
float sinTheta = sqrt(1.0 - cosTheta*cosTheta); float sinTheta = sqrt(1.0 - cosTheta*cosTheta);
vec3 h; vec3 h;
h.x = sinTheta * cos(phi); h.x = sinTheta * cos(phi);
h.y = sinTheta * sin(phi); h.y = sinTheta * sin(phi);
h.z = cosTheta; 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); float D_GGX(float NdotH, float a2)
vec3 tangent = normalize(cross(up, n)); {
vec3 bitangent = cross(n, tangent); float f = (NdotH * a2 - NdotH) * NdotH + 1.0;
return a2 / (PI * f * f);
vec3 sampleVec = tangent * h.x + bitangent * h.y + n * h.z;
return normalize(sampleVec);
} }
vec3 prefilter(vec3 n) vec3 prefilter(vec3 n)
{ {
n = normalize(n);
vec3 v = n; // n = v simplification vec3 v = n; // n = v simplification
float a = roughness*roughness;
vec3 prefilteredColor = vec3(0.0); vec3 prefilteredColor = vec3(0.0);
float totalWeight = 0.0; float totalWeight = 0.0;
for (uint i = 0u; i < NUM_SAMPLES; ++i) { vec3 up = abs(n.z) < 0.999f ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0);
vec2 Xi = Hammersley(i, NUM_SAMPLES); vec3 tangent = normalize(cross(up, n));
vec3 h = ImportanceSampleGGX(Xi, n, roughness); 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); vec3 l = normalize(2.0 * dot(v, h) * h - v);
float NdotL = max(dot(n, l), 0.0); float NdotL = max(dot(n, l), 0.0);
if (NdotL > 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; totalWeight += NdotL;
} }
} }
prefilteredColor /= totalWeight; return prefilteredColor / totalWeight;
return prefilteredColor;
} }
void main() void main()
{ {
fragColor0 = prefilter(cubemapCoord0); fragColor0 = prefilter(normalize(cubemapCoord0));
fragColor1 = prefilter(cubemapCoord1); fragColor1 = prefilter(normalize(cubemapCoord1));
fragColor2 = prefilter(cubemapCoord2); fragColor2 = prefilter(normalize(cubemapCoord2));
fragColor3 = prefilter(cubemapCoord3); fragColor3 = prefilter(normalize(cubemapCoord3));
fragColor4 = prefilter(cubemapCoord4); fragColor4 = prefilter(normalize(cubemapCoord4));
fragColor5 = prefilter(cubemapCoord5); fragColor5 = prefilter(normalize(cubemapCoord5));
} }