#version 330 core
// This shader performs downsampling on a texture,
// as taken from Call Of Duty method, presented at ACM Siggraph 2014.
// This particular method was customly designed to eliminate
// "pulsating artifacts and temporal stability issues".
// Remember to add bilinear minification filter for this texture!
// Remember to use a floating-point texture format (for HDR)!
// Remember to use edge clamping for this texture!
uniform sampler2D srcTexture;
uniform vec2 srcResolution;
// which mip we are writing to, used for Karis average
uniform int mipLevel = 1;
in vec2 texCoord;
layout (location = 0) out vec3 downsample;
vec3 PowVec3(vec3 v, float p)
{
return vec3(pow(v.x, p), pow(v.y, p), pow(v.z, p));
}
const float invGamma = 1.0 / 2.2;
vec3 ToSRGB(vec3 v) { return PowVec3(v, invGamma); }
float sRGBToLuma(vec3 col)
{
//return dot(col, vec3(0.2126f, 0.7152f, 0.0722f));
return dot(col, vec3(0.299f, 0.587f, 0.114f));
}
float KarisAverage(vec3 col)
{
// Formula is 1 / (1 + luma)
float luma = sRGBToLuma(ToSRGB(col)) * 0.25f;
return 1.0f / (1.0f + luma);
}
// NOTE: This is the readable version of this shader. It will be optimized!
void main()
{
vec2 srcTexelSize = 1.0 / srcResolution;
float x = srcTexelSize.x;
float y = srcTexelSize.y;
// Take 13 samples around current texel:
// a - b - c
// - j - k -
// d - e - f
// - l - m -
// g - h - i
// === ('e' is the current texel) ===
vec3 a = texture(srcTexture, vec2(texCoord.x - 2*x, texCoord.y + 2*y)).rgb;
vec3 b = texture(srcTexture, vec2(texCoord.x, texCoord.y + 2*y)).rgb;
vec3 c = texture(srcTexture, vec2(texCoord.x + 2*x, texCoord.y + 2*y)).rgb;
vec3 d = texture(srcTexture, vec2(texCoord.x - 2*x, texCoord.y)).rgb;
vec3 e = texture(srcTexture, vec2(texCoord.x, texCoord.y)).rgb;
vec3 f = texture(srcTexture, vec2(texCoord.x + 2*x, texCoord.y)).rgb;
vec3 g = texture(srcTexture, vec2(texCoord.x - 2*x, texCoord.y - 2*y)).rgb;
vec3 h = texture(srcTexture, vec2(texCoord.x, texCoord.y - 2*y)).rgb;
vec3 i = texture(srcTexture, vec2(texCoord.x + 2*x, texCoord.y - 2*y)).rgb;
vec3 j = texture(srcTexture, vec2(texCoord.x - x, texCoord.y + y)).rgb;
vec3 k = texture(srcTexture, vec2(texCoord.x + x, texCoord.y + y)).rgb;
vec3 l = texture(srcTexture, vec2(texCoord.x - x, texCoord.y - y)).rgb;
vec3 m = texture(srcTexture, vec2(texCoord.x + x, texCoord.y - y)).rgb;
// Apply weighted distribution:
// 0.5 + 0.125 + 0.125 + 0.125 + 0.125 = 1
// a,b,d,e * 0.125
// b,c,e,f * 0.125
// d,e,g,h * 0.125
// e,f,h,i * 0.125
// j,k,l,m * 0.5
// This shows 5 square areas that are being sampled. But some of them overlap,
// so to have an energy preserving downsample we need to make some adjustments.
// The weights are the distributed, so that the sum of j,k,l,m (e.g.)
// contribute 0.5 to the final color output. The code below is written
// to effectively yield this sum. We get:
// 0.125*5 + 0.03125*4 + 0.0625*4 = 1
// Check if we need to perform Karis average on each block of 4 samples
vec3 groups[5];
switch (mipLevel)
{
case 0:
// We are writing to mip 0, so we need to apply Karis average to each block
// of 4 samples to prevent fireflies (very bright subpixels, leads to pulsating
// artifacts).
groups[0] = (a+b+d+e) * (0.125f/4.0f);
groups[1] = (b+c+e+f) * (0.125f/4.0f);
groups[2] = (d+e+g+h) * (0.125f/4.0f);
groups[3] = (e+f+h+i) * (0.125f/4.0f);
groups[4] = (j+k+l+m) * (0.5f/4.0f);
groups[0] *= KarisAverage(groups[0]);
groups[1] *= KarisAverage(groups[1]);
groups[2] *= KarisAverage(groups[2]);
groups[3] *= KarisAverage(groups[3]);
groups[4] *= KarisAverage(groups[4]);
downsample = groups[0]+groups[1]+groups[2]+groups[3]+groups[4];
downsample = max(downsample, 0.0001f);
break;
default:
downsample = e*0.125; // ok
downsample += (a+c+g+i)*0.03125; // ok
downsample += (b+d+f+h)*0.0625; // ok
downsample += (j+k+l+m)*0.125; // ok
break;
}
}
HI