#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); }