17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-17 21:29:19 +02:00
Files
maszyna/shaders/envmap_prefilter.comp

101 lines
3.6 KiB
Plaintext

// prefilters the environment map with the GGX lobe for rough reflections (split sum approximation, Karis 2013), one level
// of the prefiltered map per dispatch. filtered importance sampling (Krivanek, Colbert 2008): each sample reads the mipmap
// of the source whose texels are about as large as the solid angle the sample stands for, so a few dozen samples give a
// smooth result. run when an update of the environment map is complete
#include <common>
layout(local_size_x = ENVMAP_GGX_GROUP_SIZE, local_size_y = ENVMAP_GGX_GROUP_SIZE) in;
layout(binding = 0, rgba16f) writeonly uniform imageCube prefilter_target;
// the plain map with its box filtered mipmaps, on the unit gl::program binds envmap to
uniform samplerCube envmap;
// perceptual roughness of the level being written
layout(location = 0) uniform float roughness;
// size of the level being written, and of the first level of the source
layout(location = 1) uniform int target_size;
layout(location = 2) uniform float source_size;
layout(location = 3) uniform int sample_count;
const float PI = 3.14159265359;
// direction through the centre of a texel of a cube map face, in the order and orientation of GL_TEXTURE_CUBE_MAP_POSITIVE_X..
vec3 cube_direction(ivec3 texel, int size)
{
vec2 uv = (vec2(texel.xy) + 0.5) / float(size) * 2.0 - 1.0;
switch (texel.z)
{
case 0: return vec3(1.0, -uv.y, -uv.x);
case 1: return vec3(-1.0, -uv.y, uv.x);
case 2: return vec3(uv.x, 1.0, uv.y);
case 3: return vec3(uv.x, -1.0, -uv.y);
case 4: return vec3(uv.x, -uv.y, 1.0);
default: return vec3(-uv.x, -uv.y, -1.0);
}
}
vec2 hammersley(uint i, uint n)
{
uint bits = bitfieldReverse(i);
return vec2(float(i) / float(n), float(bits) * 2.3283064365386963e-10);
}
// half vector around N for the GGX distribution of linear roughness a
vec3 importance_sample_ggx(vec2 xi, float a, vec3 N)
{
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(max(1.0 - costheta * costheta, 0.0));
vec3 H = vec3(sintheta * cos(phi), sintheta * sin(phi), costheta);
vec3 up = abs(N.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0);
vec3 tangentx = normalize(cross(up, N));
vec3 tangenty = cross(N, tangentx);
return tangentx * H.x + tangenty * H.y + N * H.z;
}
float distribution_ggx(float NdotH, float a)
{
float a2 = a * a;
float d = NdotH * NdotH * (a2 - 1.0) + 1.0;
return a2 / (PI * d * d);
}
void main()
{
ivec3 texel = ivec3(gl_GlobalInvocationID);
if (texel.x >= target_size || texel.y >= target_size)
return;
vec3 N = normalize(cube_direction(texel, target_size));
// the view is taken along the normal, which is what the split sum approximation assumes
vec3 V = N;
float a = max(roughness * roughness, 1e-3);
float texelsolidangle = 4.0 * PI / (6.0 * source_size * source_size);
vec3 sum = vec3(0.0);
float weight = 0.0;
uint count = uint(sample_count);
for (uint i = 0U; i < count; ++i)
{
vec3 H = importance_sample_ggx(hammersley(i, count), a, N);
float VdotH = dot(V, H);
vec3 L = 2.0 * VdotH * H - V;
float NdotL = dot(N, L);
if (NdotL <= 0.0)
continue;
float NdotH = max(dot(N, H), 0.0);
// pdf of L: D * NdotH / (4 * VdotH)
float pdf = distribution_ggx(NdotH, a) * NdotH / max(4.0 * VdotH, 1e-4);
float samplesolidangle = 1.0 / (float(count) * max(pdf, 1e-6));
float level = max(0.5 * log2(samplesolidangle / texelsolidangle) + 1.0, 0.0);
vec3 color = textureLod(envmap, L, level).rgb;
if (any(isnan(color)) || any(isinf(color)))
color = vec3(0.0);
sum += max(color, vec3(0.0)) * NdotL;
weight += NdotL;
}
imageStore(prefilter_target, texel, vec4(sum / max(weight, 1e-4), 1.0));
}