304 lines
8.7 KiB
GLSL
304 lines
8.7 KiB
GLSL
#version 430 core
|
|
|
|
// general luminance threshold at which antialiasing will take place
|
|
const float contrastThreshold = 0.03;
|
|
// additional relative luminance threshold at which antialiasing will take place
|
|
const float relativeThreshold = 0.06;
|
|
|
|
// gamma value
|
|
const float epsilon = 1.5; // used for tone mapping and gamma correcting DoF bokeh blur
|
|
|
|
// distance from the camera's origin to focal point
|
|
const float focusDistance = 0.4;
|
|
// // range of focus.
|
|
const float focusRange = 0.5; // Lower values mean more sharper image
|
|
|
|
// contrast of fragment
|
|
const float contrast = 0.3;
|
|
// additional brightness of fragment
|
|
const float brightness = 0.05;
|
|
|
|
// output fragment color
|
|
out vec4 fragColor;
|
|
|
|
// screen resolution in pixels
|
|
uniform vec2 resolution;
|
|
// fbo texture sampler
|
|
uniform sampler2D fboTexture; // RGB-components store pixel color, alpha holds linear world scene depth from the camera's origin
|
|
|
|
uniform int boolFilterDoF; // enabled Depth of Field
|
|
uniform int boolFilterFXAA; // enabled Fast Approximate Anti Aliasing
|
|
uniform int boolFilterGrain;// enable some graininess to add to every pixels
|
|
|
|
// normalized fragment coordinates
|
|
const vec2 fragCoords = gl_FragCoord.xy / resolution;
|
|
// texel size
|
|
const vec2 fboTexelSize = 1.0 / resolution;
|
|
|
|
#define true 1 // true is not natively defined
|
|
|
|
// FXAA implementation from: https://catlikecoding.com/unity/tutorials/advanced-rendering/fxaa/
|
|
// with some minor optimizations regarding branches and texture lookups
|
|
struct LuminanceData {
|
|
// stores information about luminance
|
|
float max, min, diff; // maximum, minimum and luminance difference between max/min
|
|
// fragment values and its neighboors
|
|
float texels[3][3];
|
|
// field names
|
|
#define lumSouthWest lumData.texels[0][0]
|
|
#define lumSouth lumData.texels[1][0]
|
|
#define lumSouthEast lumData.texels[2][0]
|
|
#define lumWest lumData.texels[0][1]
|
|
#define lumMiddle lumData.texels[1][1]
|
|
#define lumEast lumData.texels[2][1]
|
|
#define lumNorthWest lumData.texels[0][2]
|
|
#define lumNorth lumData.texels[1][2]
|
|
#define lumNorthEast lumData.texels[2][2]
|
|
};
|
|
|
|
float luminanceFromLinearRgb(in vec3 linearRgb)
|
|
{
|
|
// relative luminance coefficent factors
|
|
const vec3 luminanceFactors = vec3(0.2126729, 0.7151522, 0.0721750);
|
|
// compute luminance
|
|
return dot(linearRgb, luminanceFactors);
|
|
}
|
|
|
|
float texelLuminosity(in vec2 pixelOffset)
|
|
{
|
|
vec3 linearRgb = texture2D(fboTexture, fragCoords + pixelOffset * fboTexelSize).rgb;
|
|
|
|
return luminanceFromLinearRgb(linearRgb);
|
|
}
|
|
|
|
// clamp between [0, 1]
|
|
#define saturate(x) clamp(x, 0.0, 1.0)
|
|
|
|
// collect luminance data and fill struct
|
|
void collectLuminance(inout LuminanceData lumData)
|
|
{
|
|
// collect data
|
|
for (int x = -1; x < 2; x++) {
|
|
for (int y = -1; y < 2; y++) {
|
|
lumData.texels[x + 1][y + 1] = texelLuminosity(vec2(float(x), float(y) ) );
|
|
}
|
|
}
|
|
// calculate highest and lowest + difference i.e the contrast
|
|
lumData.max = max(max(max(max(lumEast, lumWest), lumSouth), lumNorth), lumMiddle);
|
|
lumData.min = min(min(min(min(lumEast, lumWest), lumSouth), lumNorth), lumMiddle);
|
|
// compute difference
|
|
lumData.diff = lumData.max - lumData.min;
|
|
}
|
|
|
|
// apply a basic tent blur filter, to average the fragments luminance
|
|
float fragBlendFactor(in LuminanceData lumData)
|
|
{
|
|
// these should matter more, so double their influence
|
|
float fac = 2.0 * (lumNorth + lumSouth + lumWest + lumEast);
|
|
fac += lumNorthWest + lumNorthEast + lumSouthWest + lumSouthEast;
|
|
fac /= 12.0; // scale down
|
|
|
|
fac = abs(fac - lumMiddle);
|
|
fac = saturate(fac / lumData.diff);
|
|
|
|
float blendFac = smoothstep(0.0, 1.0, fac);
|
|
|
|
return blendFac * blendFac;
|
|
}
|
|
|
|
// determine the edge type
|
|
bool determineEdge(in LuminanceData lumData)
|
|
{
|
|
// change in horizontal pixels
|
|
float hori =
|
|
abs(lumNorth + lumSouth - 2.0 * lumMiddle) * 2.0 +
|
|
abs(lumNorthEast + lumSouthEast - 2.0 * lumEast) +
|
|
abs(lumNorthWest + lumSouthWest - 2.0 * lumWest);
|
|
// change in vertical pixels
|
|
float veri =
|
|
abs(lumEast + lumWest - 2.0 * lumMiddle) * 2.0 +
|
|
abs(lumNorthEast + lumNorthWest - 2.0 * lumNorth) +
|
|
abs(lumSouthEast + lumSouthWest - 2.0 * lumSouth);
|
|
|
|
return hori >= veri;
|
|
}
|
|
|
|
vec3 filterFXAA()
|
|
{
|
|
// collect luminance data off current fragment
|
|
LuminanceData lumData;
|
|
collectLuminance(lumData);
|
|
|
|
vec2 st = fragCoords;
|
|
if (lumData.diff > max(contrastThreshold, relativeThreshold * lumData.max) )
|
|
{
|
|
float fac = fragBlendFactor(lumData);
|
|
bool edge = determineEdge(lumData);
|
|
|
|
float pixelStep = edge ? fboTexelSize.y : fboTexelSize.x;
|
|
|
|
float pLum = edge ? lumNorth : lumEast;
|
|
float nLum = edge ? lumSouth : lumWest;
|
|
|
|
float pGrad = abs(pLum - lumMiddle);
|
|
float nGrad = abs(nLum - lumMiddle);
|
|
|
|
pixelStep = pixelStep * sign(pGrad - nGrad);
|
|
|
|
st += edge ? vec2(0, pixelStep * fac) : vec2(pixelStep * fac, 0);
|
|
}
|
|
return texture2D(fboTexture, st).rgb;
|
|
}
|
|
|
|
// apply gamma correctio to linear RGB color
|
|
vec3 gamma(in vec3 linearRgb)
|
|
{
|
|
return pow(linearRgb, vec3(epsilon) );
|
|
}
|
|
|
|
// de-gamma linear RGB color
|
|
// degamma() is the inverse function of gamma()
|
|
vec3 degamma(in vec3 linearRgb)
|
|
{
|
|
return pow(linearRgb, vec3(1.0 / epsilon) );
|
|
}
|
|
|
|
// Gold Noise ©2015 dcerisano@standard3d.com
|
|
// - based on the Golden Ratio
|
|
// - uniform normalized distribution
|
|
// - (fastest?) static noise generator function (also runs at low precision)
|
|
|
|
|
|
// generate some 1D noise
|
|
// no seed required
|
|
float goldNoise(in vec2 xy)
|
|
{
|
|
const float phi = 1.61803398874989484820459; // Φ = Golden Ratio
|
|
|
|
return fract(tan(distance(xy * phi, xy) ) * xy.x) * 2.0 - 1.0;
|
|
}
|
|
|
|
// gamma corrected bokeh down sampling filter as DoF blur
|
|
vec3 bokeh(in float bokehSize)
|
|
{
|
|
|
|
// if sampling rate is low enough, clamp it to use FXAA instead of a single unfiltered texture lookup
|
|
if (bokehSize < 2.0)
|
|
{
|
|
if (boolFilterFXAA == true)
|
|
{
|
|
// if filter FXAA
|
|
return filterFXAA();
|
|
}
|
|
else
|
|
{
|
|
// if no DoF and no FXAA, do nearly nothing
|
|
return texture2D(fboTexture, fragCoords).rgb;
|
|
}
|
|
}
|
|
|
|
vec3 color = vec3(0);
|
|
|
|
const int steps = 8; // samples to take per axis {x,y}
|
|
|
|
const int stepsHalf = steps / 2;
|
|
const float stepSize = bokehSize / steps;
|
|
|
|
float samples = 0.0; // samples actually taken
|
|
|
|
for (int i = -stepsHalf; i < stepsHalf; i++) {
|
|
float u = stepSize * float(i) * fboTexelSize.x;
|
|
|
|
for (int j = -stepsHalf; j < stepsHalf; j++) {
|
|
float v = stepSize * float(j) * fboTexelSize.y;
|
|
|
|
// calculate sample effectiveness, as circle for perfect bokeh
|
|
float shape = 1.0 - step(stepsHalf, length(vec2(i, j) ) );
|
|
|
|
// if we have enough effectiveness, take a sample
|
|
if (shape > 0.5) {
|
|
// calculate offset coordinates for sample, and add some noise to break of grid patterns when using low amount of samples
|
|
float noiseX = goldNoise(vec2(i, j) + gl_FragCoord.xy + 1.0);
|
|
float noiseY = goldNoise(vec2(i, j) + gl_FragCoord.xy + 4.0);
|
|
vec2 coords = vec2(noiseX, noiseY) * fboTexelSize + fragCoords + vec2(u, v);
|
|
|
|
// gamma correct texture lookup and contribute
|
|
color += gamma(texture2D(fboTexture, coords).rgb * shape);
|
|
samples += shape; // we have taken a sample, so remember for normalization later
|
|
}
|
|
}
|
|
}
|
|
// de-gamma correct and normalize
|
|
return degamma(color / samples);
|
|
}
|
|
|
|
vec3 filterDoF()
|
|
{
|
|
// get scene depth
|
|
float depth = texture2D(fboTexture, fragCoords).a;
|
|
|
|
// Circle of Confusion
|
|
float CoC = clamp( (depth - focusDistance) / focusRange, -1, 1) * 16.0;
|
|
// compute bokeh blur
|
|
return bokeh(abs(CoC) );
|
|
}
|
|
|
|
void filmGrain(inout vec3 color)
|
|
{
|
|
float noiseRed = goldNoise(gl_FragCoord.xy + 1.0);
|
|
float noiseGreen = goldNoise(gl_FragCoord.xy + 16.0);
|
|
float noiseBlue = goldNoise(gl_FragCoord.xy + 32.0);
|
|
|
|
vec3 noise = vec3(noiseRed, noiseGreen, noiseBlue);
|
|
|
|
float intensity = 1.0 - sqrt(texelLuminosity(vec2(0) ) );
|
|
|
|
color = color + color * noise * intensity * 0.2;
|
|
}
|
|
|
|
void sampleColorTexture(inout vec3 color)
|
|
{
|
|
if (boolFilterDoF == true)
|
|
{
|
|
// apply DoF
|
|
color = filterDoF();
|
|
}
|
|
else if (boolFilterFXAA == true)
|
|
{
|
|
// if no DoF, filter FXAA
|
|
color = filterFXAA();
|
|
}
|
|
else
|
|
{
|
|
// if no DoF and no FXAA, do nearly nothing
|
|
color = texture2D(fboTexture, fragCoords).rgb;
|
|
}
|
|
}
|
|
|
|
void main()
|
|
{
|
|
vec3 color;
|
|
// get primary scene color
|
|
sampleColorTexture(color);
|
|
|
|
// adjust contrast and brightness
|
|
color = (color - 0.5) * (1.0 + contrast) + 0.5 + brightness;
|
|
// vignette
|
|
// falloff
|
|
float falloff = distance(fragCoords, vec2(0.5, 0.5) );
|
|
// adjust falloff and darken
|
|
color = color * (1.0 - falloff * falloff * falloff * 0.2);
|
|
|
|
// tone map
|
|
color = clamp(color, 0.0, 1.0);
|
|
color = 1.0 - pow(1.0 - color, vec3(epsilon) );
|
|
|
|
if (boolFilterGrain == true)
|
|
{
|
|
// add some film grain
|
|
filmGrain(color);
|
|
}
|
|
|
|
fragColor = vec4(color, 1);
|
|
}
|