//////// ================================ //////// SETTINGS //////// ================================ //////// Check the Common tab for settings. //////// ================================ //////// DOCS: Declarations & documentation //////// ================================ // Retrieve a frame from Buffer A and then apply postprocessing. void mainImage(out vec4 fragColor, in vec2 fragCoord); /// Given a color which clips outside the color space (some channel is >1.0), /// reduce the brightness (without affecting hue or saturation) until it no /// longer clips. (The default behavior without doing this is just clipping, /// which affects the saturation of the color dramatically, often turning colors /// into 100% white pixels.) vec3 correct_saturation(vec3 color); /// Round to the nearest color in DITHER_COLORS. vec4 nearest_color(vec4 color); /// The length of the edge of the Bayer matrix used for dithering. const uint DITHER_SIZE = uint(1)< 01011. uint bit_reverse(uint x); /// Bitwise interleave two integers of length BIT_WIDTH into a single /// 2*BIT_WIDTH integer. /// /// example interleave: /// /// x = 0 1 0 0 1 /// y = 1 0 0 1 1 /// ---------- /// r = 0110000111 uint bit_interleave(uint x, uint y); //////// ================================ //////// IMPL: Implementation //////// ================================ void mainImage(out vec4 fragColor, in vec2 fragCoord) { vec2 uv = fragCoord/iResolution.xy; vec4 color = texture(iChannel0, uv); // NOTE: it is possible for this renderer to emit colors brighter than 1.0, // for example if you use very bright or many light sources. These colors will be // displayed incorrectly, appearing desaturated and having their brightness // clamped to whatever color output is supported. // // This is common in particular if you have very bright lights in a scene, // which is sometimes necessary for objects to be clearly visible. The result // will be you seeing flashes of over-bright white pixels where you should // see color. One way to mitigate this is by increasing the number of samples per // pixel; the average brightness per pixel is generally less than 1.0 when averaged // out with the (more common) black pixels when no light source is encountered. // // Another mitigation approach is to do color correction, where instead of // trying to preserve the brightness by clamping the RGB values and losing saturation, // you try to preserve the saturation by scaling down the brightness until the // full saturation of the colors is visible (or at least part of it). #if SATURATION_CORRECTION == 1 color.rgb = correct_saturation(color.rgb); #else // The default behavior if you don't do anything. //color = clamp(vec4(0.), color, vec4(1.)); #endif color.rgb = linear2srgb(color.rgb); #if ENABLE_DITHER == 1 // Dithering after sRGB conversion is slightly worse because the bayer matrix // is linear whereas sRGB is non-linear, but if you do it *before* conversion, // then adjusted colors won't be *quite* close enough to nearest_color that they // should be closest to, which creates horrible artifacts after calling // `nearest_color` (and is just slightly wrong in general). uvec2 coord = uvec2(fragCoord); // for dithering color = dither(coord, color); #endif #if DITHER_NEAREST == 1 color = nearest_color(color); #endif fragColor = color; } vec3 correct_saturation(vec3 color) { // TODO: I'm sure there's a way to do this directly without having to // convert between color spaces twice. This was just more convenient in the moment. // Convert to HSV so we can correct value (brightness) without affecting // hue or saturation. color.xyz = rgb2hsv(color.rgb); color.z = min(color.z, 1.); color.rgb = hsv2rgb(color.xyz); return color; } //////// -------------------------------- //////// DITHER: Ordered dithering //////// -------------------------------- //////// https://en.wikipedia.org/wiki/Ordered_dithering vec4 nearest_color(vec4 color) { return floor(color * float(DITHER_COLORS)) / float(DITHER_COLORS); } vec4 dither(uvec2 coord, vec4 color) { if (DITHER_SIZE < uint(2)) return color; coord %= DITHER_SIZE; vec4 bias = vec4( bayer(coord), bayer(uvec2(uint(DITHER_SIZE) - coord.x - uint(1), coord.y)), bayer(uvec2(coord.x, uint(DITHER_SIZE) - coord.y - uint(1))), bayer(uvec2(uint(DITHER_SIZE) - coord.x - uint(1), uint(DITHER_SIZE) - coord.y - uint(1))) ); return color + (bias / float(DITHER_COLORS)); } float bayer(uvec2 coord) { // Magic bitwise formula from Wikipedia produces values from 0 to (DITHER_SIZE^2)-1. uint magic = bit_reverse(bit_interleave(coord.x ^ coord.y, coord.x)); // Convert to normalized float from -0.5 to 0.5. return float(magic+uint(1)) / (float(DITHER_SIZE)*float(DITHER_SIZE)) - 0.5; } uint bit_reverse(uint x) { uint hi = uint(1 << BIT_WIDTH-uint(1)); uint lo = uint(1); for (uint i = uint(0); i < BIT_WIDTH/uint(2); i++) { uint bit_hi = x & hi; uint bit_lo = x & lo; x &= ~hi & ~lo; if (bit_hi > uint(0)) x |= lo; if (bit_lo > uint(0)) x |= hi; hi >>= 1; lo >>= 1; } return x; } uint bit_interleave(uint x, uint y) { uint mask = uint(1) << BIT_WIDTH-uint(1); uint acc = uint(0); for (uint i = uint(0); i < BIT_WIDTH; i++) { acc |= (x & mask) << uint(2)*i + uint(1); acc |= (y & mask) << uint(2)*i; mask >>= 1; } return acc; }