shadertoy-shaders/path_march v4/image.frag

168 lines
6.2 KiB
GLSL

//////// ================================
//////// 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)<<uint(DITHER_BASE);
/// Apply ordered dithering, which reduces color banding and produces the appearance
/// of more colors when in a limited color space (e.g. when making a GIF*).
vec4 dither(uvec2 coord, vec4 color);
/// Directly index into a DITHER_SIZE^2 Bayer Matrix, returning a number from -0.5 to 0.5.
float bayer(uvec2 coord);
/// The width in bits of a single coordinate into the Bayer matrix
/// for bitwise operations (log2(DITHER_SIZE) i.e. just DITHER_BASE).
const uint BIT_WIDTH = uint(DITHER_BASE);
/// Bitwise reverse the lower BIT_WIDTH bits of the integer.
/// e.g. with bit width 5, 11010 --> 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;
}