diff --git a/path_march 2021-12-16 main.frag b/path_march 2021-12-16 main.frag index 152f41d..bdb039b 100644 --- a/path_march 2021-12-16 main.frag +++ b/path_march 2021-12-16 main.frag @@ -1,14 +1,112 @@ -// All of the interesting code and settings are in Buffer A. +//////// ================================ +//////// SETTINGS: Settings +//////// ================================ +//////// Most of the interesting code and settings are in Buffer A. +//////// This file consists only of postprocessing. + +// The dithering looks *fantastic*. Honestly, at 1440p it makes even +// 3 bits per color channel look *very* convincing. As far as I'm aware +// there are no downsides to leaving it enabled. It would probably help +// reduce color banding and improve color clarity in general. +#define ENABLE_DITHER + +// The number of available colors *per channel*. +const uint DITHER_COLORS = uint(1<<8); + +// The size of the Bayer matrix is 2^DITHER_BASE, so e.g. 4 is a 16x16 matrix. +// This can't be larger than 16 because dithering is implemented using a 16-bit bit hack. +const uint DITHER_BASE = uint(16); + +// Artifically restrict the colors to those specified in DITHER_COLORS. +// If you set DITHER_COLORS to 6 or so and enable DITHER_NEAREST then screenshot +// and convert the image to a GIF* using an appropriate conversion tool, +// it will look exactly the same as it looks on your screen. +// This was the motivating reason for adding dithering, and it looks amazing. +// +// * I pronounce it "yif". Fite me. +//#define DITHER_NEAREST + +//////// ================================ +//////// IMPL: Implementation +//////// ================================ /// Convert a color from linear RGB to the sRGB color space. vec3 linear2srgb(vec3 color); +vec4 dither(uvec2 coord, vec4 color); +vec4 nearest_color(vec4 color); + void mainImage(out vec4 fragColor, in vec2 fragCoord) { - vec2 uv = fragCoord/iResolution.xy; - vec3 color = texture(iChannel0, uv).rgb; - fragColor = vec4(linear2srgb(color), 1.0); + vec2 uv = fragCoord/iResolution.xy; + uvec2 coord = uvec2(fragCoord); // for dithering + vec4 color = texture(iChannel0, uv); + color.rgb = linear2srgb(color.rgb); + #ifdef ENABLE_DITHER + color = dither(coord, color); + #endif + #ifdef DITHER_NEAREST + color = nearest_color(color); + #endif + fragColor = color; } +//////// -------------------------------- +//////// DITHER: Ordered dithering +//////// -------------------------------- +//////// https://en.wikipedia.org/wiki/Ordered_dithering + +const uint DITHER_SIZE = uint(1)< 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; +} + +float bayer(uvec2 coord) { + // magic bitwise formula from Wikipedia + uint magic = bit_reverse(bit_interleave(coord.x ^ coord.y, coord.x)); + return float(magic+uint(1)) / (float(DITHER_SIZE)*float(DITHER_SIZE)) - 0.5; +} + +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)); +} + + //////// ================================ //////// VENDOR: Vendored code //////// ================================ @@ -31,4 +129,4 @@ vec3 linear2srgb(vec3 linear_rgb) { #else return pow(linear_rgb, vec3(1./2.2)); #endif -} \ No newline at end of file +} diff --git a/path_march 2021-12-16.frag b/path_march 2021-12-16.frag index c1437d0..acb55a4 100644 --- a/path_march 2021-12-16.frag +++ b/path_march 2021-12-16.frag @@ -2,6 +2,18 @@ //////// SETTINGS: Settings //////// ================================ +// +// If you don't feel like tweaking settings, try one of these presets: +// +// 0: Maximize FPS at the cost of image quality. (DEFAULT) +// 1: A balance between image quality and FPS. +// 2: Just render a pretty static image. +// 3: Demonstrate off the weird tiling render feature. +// +// The user settings below will override whatever preset you use. +// +#define PRESET 0 + //////// -------------------------------- //////// User settings //////// -------------------------------- @@ -17,10 +29,10 @@ // image quality, reducing graininess and preventing overly-bright pixels ("fireflies"). // However, how much GPU power you need to render a frame scales linearly with // the number of samples. -#define SAMPLES 1 +//#define SAMPLES 1 // The maximum number of times light can reflect or scatter before it is extinguished. -#define PATH_SEGMENTS 14 +//#define PATH_SEGMENTS 14 // If a pixel color is too bright for fit in sRGB, there are two ways to handle it: // @@ -43,16 +55,16 @@ // This shader natively uses a square (circular?) aspect ratio. With ASPECT_RATIO_CROP // enabled, if you use a wide aspect ratio, the frame will have its height // cropped so that the image can take up the full width of the screen. -#define ASPECT_RATIO_CROP 1 +//#define ASPECT_RATIO_CROP 1 // This setting affects how far you zoom in on the scene. // Greater values = more zoom. Fractional values zoom out. Negative values mirror the scene. -#define FOV 1.5 +//#define FOV 1.5 // Camera position and angle. (Feel free to reference `time` here.) -#define CAMERA_POS vec3(0.) +//#define CAMERA_POS vec3(0.) // (Don't worry, we call `normalize` for you. -#define CAMERA_DIR vec3(0., 0., 1.) +//#define CAMERA_DIR vec3(0., 0., 1.) /// /// TILE_PERSPECTIVE and CLAMP_PERSPECTIVE are only relevant if you zoom out @@ -66,17 +78,17 @@ // This tiling is infinite. You might want to combine this with an IMAGE_OFFSET of // (-1, 0) so that you can see two whole hemispheres instead of one whole hemisphere // and two halves on opposite sides. -#define TILE_PERSPECTIVE 0 +//#define TILE_PERSPECTIVE 0 // Points on the screen outside of the unit circle (within a tile) are clamped // to the nearest point on the unit circle. This doesn't look very good, but // might be preferable to just rendering black? -#define CLAMP_PERSPECTIVE 0 +//#define CLAMP_PERSPECTIVE 0 // Slide the image around on the screen. Each time is `2x2` centered on the // origin, so an offset of e.g. (2,0) with TILE_PERSPECTIVE enabled // will show you the portion of the scene *behind* you. -#define IMAGE_OFFSET vec2(0., 0.) +//#define IMAGE_OFFSET vec2(0., 0.) //// //// Simulation settings @@ -84,13 +96,13 @@ // The maximum number of steps a ray can take during marching before giving up // and colliding with nothing. This prevents scenes from taking infinite time to render. -#define MAX_STEPS 200 +//#define MAX_STEPS 200 // The maximum distance a ray can travel before we give up and just say it collides // with nothing. This helps prevent the background from appearing warped by the foreground // due to rays which march close to a foreground object run out of steps before // reaching their destination when slightly farther rays do reach their target. -#define MAX_DIST 20. +//#define MAX_DIST 20. // Average the color across frames by storing them in the buffer. // This is like supersampling, but across frames instead of within a pixel, @@ -116,7 +128,7 @@ // appear to jerk back and forth, so this probably shouldn't be any higher // than (the reciprocal of) your average framerate. Comment this out to // remove any cap on the amount of motion blur. -#define MAX_TAA_DIFF (1./30.) +//#define MAX_TAA_DIFF (1./30.) //////// -------------------------------- @@ -142,11 +154,13 @@ // // I expect that a minimum distance of 2^(-9) would work until about 10km from the origin // with 32-bit floating point before starting to break down, but I have not tested it. -#define MIN_DIST (0.001953125/8.) +//#define MIN_DIST (0.001953125) + // The distance between samples when estimating a surface's normal. Smaller values result // in more precise calculations, but are more sensitive to numerical imprecision. // This should probably be less than MIN_DIST. -#define NORMAL_DELTA (MIN_DIST/4.) +//#define NORMAL_DELTA (MIN_DIST/4.) + // Only march this much of MIN_DIST at a time to account for imprecision in the distance // calculations. Chosen by experimentation. If you have to set this low, that often means // that there's a bug somewhere (e.g. you forgot to call `normalize`). @@ -154,17 +168,82 @@ // Right now, the simulation is numerically stable and I don't have to use it at all! // But I often find that it's necessary to set this to around ~0.92 when debugging // numerical issues. -#define IMPRECISION_FACTOR 1. +//#define IMPRECISION_FACTOR 1. //////// -------------------------------- -//////// Default settings +//////// Default settings & presets //////// -------------------------------- //////// So you can restore a setting to its default value by commenting it out. + +#ifndef PRESET +#define PRESET 0 +#endif + +//// PRESET 1 +#if PRESET == 1 + +#ifndef SAMPLES +#define SAMPLES 6 +#endif +#ifndef PATH_SEGMENTS +#define PATH_SEGMENTS 16 +#endif +#ifndef MAX_TAA_DIFF +#define MAX_TAA_DIFF (1./30.) +#endif + +//// PRESET 2 +#elif PRESET == 2 + +#ifndef PATH_SEGMENTS +#define PATH_SEGMENTS 16 +#endif +#ifndef MAX_STEPS +#define MAX_STEPS 300 +#endif +#ifndef MAX_DIST +#define MAX_DIST 100. +#endif +#ifndef MIN_DIST +#define MIN_DIST (0.001953125/256.) +#endif +#ifndef MAX_TAA_DIFF +#define MAX_TAA_DIFF 0. +#endif +#ifndef AVERAGE_FRAMES +#define AVERAGE_FRAMES 1 +#endif +#ifndef FREEZE_TIME +#define FREEZE_TIME 2.75 +#endif +#ifndef SATURATION_CORRECTION +#define SATURATION_CORRECTION 0 +#endif + +//// PRESET 3 +#elif PRESET == 3 + +#ifndef FOV +#define FOV 0.5 +#endif +#ifndef TILE_PERSPECTIVE +#define TILE_PERSPECTIVE 1 +#endif +#ifndef CLAMP_PERSPECTIVE +#define CLAMP_PERSPECTIVE 1 +#endif +#ifndef IMAGE_OFFSET +#define IMAGE_OFFSET vec2(0., 0.) +#endif + +#endif + +//// PRESET 0 (default values) #ifndef SAMPLES #define SAMPLES 1 #endif #ifndef PATH_SEGMENTS -#define PATH_SEGMENTS 14 +#define PATH_SEGMENTS 10 #endif #ifndef SATURATION_CORRECTION @@ -178,6 +257,12 @@ #ifndef FOV #define FOV 1.5 #endif +#ifndef CAMERA_POS +#define CAMERA_POS vec3(0.) +#endif +#ifndef CAMERA_DIR +#define CAMERA_DIR vec3(0., 0., 1.) +#endif #ifndef ASPECT_RATIO_CROP #define ASPECT_RATIO_CROP 1 #endif @@ -589,7 +674,7 @@ ray camera(ray r) { vec3 d = normalize(CAMERA_DIR); // point projection relative to direction - // this really ought to be simplified, + // TODO: this really ought to be simplified, // but I don't know the math to understand how to do it. vec3 up = vec3(0., 1., 0.); //vec3 x = cross(up, d);