diff --git a/path_march 2021-12-16 main.frag b/path_march 2021-12-16 main.frag deleted file mode 100644 index bdb039b..0000000 --- a/path_march 2021-12-16 main.frag +++ /dev/null @@ -1,132 +0,0 @@ -//////// ================================ -//////// 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; - 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 -//////// ================================ - -//// -//// AUTHOR: unknown -//// - -vec3 linear2srgb(vec3 linear_rgb) { - // I believe the first version is technically more accurate, - // but the difference is usually negligable in practice. - #if 1 - // copied from somewhere on the internet - bvec3 cutoff = lessThan(linear_rgb, vec3(0.0031308)); - vec3 higher = vec3(1.055)*pow(linear_rgb, vec3(1.0/2.4)) - vec3(0.055); - vec3 lower = linear_rgb * vec3(12.92); - - return mix(higher, lower, cutoff); - // end copied from somewhere on the internet - #else - return pow(linear_rgb, vec3(1./2.2)); - #endif -} diff --git a/path_march 2021-12-16.frag b/path_march v4/buffer a.frag similarity index 66% rename from path_march 2021-12-16.frag rename to path_march v4/buffer a.frag index acb55a4..8c6b7dc 100644 --- a/path_march 2021-12-16.frag +++ b/path_march v4/buffer a.frag @@ -1,300 +1,7 @@ //////// ================================ -//////// SETTINGS: 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 -//////// -------------------------------- -//////// Tweak these according to your preferences and the power of your graphics card. -//////// Comment out a setting to restore it to its default value. - - -//// -//// Sample settings -//// - -// The number of color samples taken per pixel. Increasing this has a dramatic effect on -// 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 - -// The maximum number of times light can reflect or scatter before it is extinguished. -//#define PATH_SEGMENTS 14 - -// If a pixel color is too bright for fit in sRGB, there are two ways to handle it: -// -// 1. Clamp the pixel within the limits of sRGB, resulting in (near-)maximum -// brightness at the cost of the color's saturation. (If it's too bright, it'll -// become entirely white.) -// 2. Reduce the brightness of the color until it fits within sRGB, preserving -// the color's saturation, but losing even *more* brightness. -// -// Correction for saturation generally looks better, but isn't usually necessary -// for more than five or so samples (because the bright pixels will average out -// with the dark pixels and fall back within sRGB), so this is *on* by default -// with 1-2 samples and *off* by default with 5+ samples. -//#define SATURATION_CORRECTION 1 - -//// -//// Perspective settings -//// - -// 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 - -// 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 - -// Camera position and angle. (Feel free to reference `time` here.) -//#define CAMERA_POS vec3(0.) -// (Don't worry, we call `normalize` for you. -//#define CAMERA_DIR vec3(0., 0., 1.) - -/// -/// TILE_PERSPECTIVE and CLAMP_PERSPECTIVE are only relevant if you zoom out -/// (e.g. an FOV < ~1.15). For more information on how and why these settings -/// behave the way they do, see their extended descriptions in the `project` function. -/// - -// Points on the screen >1 or <-1 show the portion of the scene *behind* you, -// mirrored so that the edges of each adjacent tile lines up (e.g. tiles above -// and below are mirrored vertically, to the left and right horizontally). -// 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 - -// 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 - -// 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.) - -//// -//// Simulation settings -//// - -// 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 - -// 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. - -// Average the color across frames by storing them in the buffer. -// This is like supersampling, but across frames instead of within a pixel, -// which lets you render with thousands of samples without crashing. -// It's strongly advised that you enable FREEZE_TIME when this is enabled! -// This uses iFrame, so if you want to enable this, make sure you hit the -// "reset time" function or things will get screwed up. -//#define AVERAGE_FRAMES 1 - -// Set a time in seconds. The simulation will be frozen at this point in time every frame. -// Comment this out to allow time to pass normally. -//#define FREEZE_TIME 2.75 - -// Loop time over an interval of this duration, beginning at FREEZE_TIME, -// or 0, if FREEZE_TIME is not set. -//#define LOOP_TIME 0. - -// Set the maximum duration of temporal antialiasing (i.e. how much time -// motion blur smears across). Note that this is a *maximum* time, and motion -// blur will never be greater than the duration of a frame. That said, when rendering -// a still image with FREEZE_TIME you probably want this set to 0., and if you're -// stuttering a lot, the large variance in frame times can make objects in the image -// 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.) - - -//////// -------------------------------- -//////// Internal settings -//////// -------------------------------- -//////// If you're just viewing the shader, you shouldn't usually need to tweak these. - -// The minimum distance between two points before they are considered the same point. -// Setting lower values increases the sharpness of the image at the cost of performance -// and rounding errors at objects very far from 0. -// -// Ray marching halves the distance to the surface of an object each iteration, but the -// end goal of ray marching is to pass slightly *inside* the object. Setting a minimum -// distance prevents zeno's paradox. This also serves as a optimization -// because the number of steps increases logarithmically as you decrease the minimum distance. -// -// Chosen to be 2^(-9), or about ~2mm, because that's the largest you can set it before -// the quality of the image is significantly effected. You can set it as low as about -// 2^(-19) before things begin to break. It's good to experiment with both high and low -// values to help find bugs in the numerical precision of the light simulation. -// If you have precision bugs, the simulation ends up getting affected pretty dramatically -// by changes to MIN_DIST, whereas a numerically stable simulation is not affected much at all. -// -// 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) - -// 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.) - -// 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`). -// -// 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. - -//////// -------------------------------- -//////// 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 10 - -#endif -#ifndef SATURATION_CORRECTION -#if SAMPLES > 5 -#define SATURATION_CORRECTION 0 -#else -#define SATURATION_CORRECTION 1 -#endif -#endif - -#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 -#ifndef TILE_PERSPECTIVE -#define TILE_PERSPECTIVE 0 -#endif -#ifndef CLAMP_PERSPECTIVE -#define CLAMP_PERSPECTIVE 0 -#endif -#ifndef IMAGE_OFFSET -#define IMAGE_OFFSET vec2(0., 0.) -#endif -#ifndef MAX_STEPS -#define MAX_STEPS 200 -#endif -#ifndef MAX_DIST -#define MAX_DIST 20. -#endif -#ifndef AVERAGE_FRAMES -#define AVERAGE_FRAMES 0 -#endif -// FREEZE_TIME, LOOP_TIME, and MAX_TAA_DIFF are *undefined* by default. - -#ifndef MIN_DIST -#define MIN_DIST (0.001953125/8.) -#endif -#ifndef NORMAL_DELTA -#define NORMAL_DELTA (MIN_DIST/4.) -#endif -#ifndef IMPRECISION_FACTOR -#define IMPRECISION_FACTOR 1. -#endif +//////// Check the Common tab for settings. //////// ================================ //////// DOCS: Declarations & documentation @@ -425,22 +132,11 @@ int medium(vec3 pos); //////// UTIL: Utility functions (e.g. random number generation) //////// -------------------------------- -// Convenience definitions -#define INF (1./0.) -// NOTE: I used to use `sqrt(-1)`, but apparently that doesn't evaluate to NaN???? -// This makes me wonder if NaN isn't portable due to constant folding or something. -#define NAN (0./0.) #define NULL_RAY ray(vec3(0.), vec3(0.)) /// Used to forcibly set the pixel color from functions. Used for debugging. vec3 _bug = vec3(NAN); -/// Return a random number between 0 and 1 (with uniform distribution); -float rand(); - -/// Use the fragment coordinate and current frame to seed the random number generator. -void seed_randoms(vec2 fragCoord); - /// Randomly select a direction on a hemisphere facing the direction of the normal, /// with a bias toward directions close to the normal. /// Specifically, the bias is by `dot(norm, dir)`, making this a cosine-weighted @@ -448,9 +144,24 @@ void seed_randoms(vec2 fragCoord); /// rendering equation. vec3 cosine_direction(vec3 norm); -// Convert between RGB and HSV. Used for SATURATION_CORRECTION. -vec3 rgb2hsv(vec3 c); -vec3 hsv2rgb(vec3 c); +/// Create a translation + rotation matrix from a position and direction. +mat4 translate(vec3 pos, vec3 forward, vec3 up); +mat4 translate(vec3 pos, vec3 forward); +mat4 translate(vec3 pos); + +/// Rotate a vector angle using a matrix. +vec3 apply_rotate(mat4 r, vec3 d); + +/// Get the direction of a matrix. +/// (Get the translated direction of (0, 0, 1).) +vec3 apply_rotate(mat4 r); + +/// Translate a vector position by a matrix. +vec3 apply_translate(mat4 r, vec3 p); + +/// Get the position offset of a matrix. +/// (Get the translated position of (0, 0, 0).) +vec3 apply_translate(mat4 r); //////// ================================ //////// IMPL: Implementation @@ -462,7 +173,7 @@ vec3 hsv2rgb(vec3 c); //////// -------------------------------- void mainImage(out vec4 fragColor, vec2 fragCoord) { - seed_randoms(fragCoord); + seed_randoms(vec3(fragCoord, float(iFrame))); // Supersampling. Sample the color within each pixel multiple times and take the average. // In raster-based rendering, this is used mostly to prevent jagged edges; @@ -521,33 +232,6 @@ void mainImage(out vec4 fragColor, vec2 fragCoord) { if (!any(isnan(_bug))) { color = vec4(_bug, 1.); } - // 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 - // 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. - color.xyz = rgb2hsv(color.rgb); - color.z = min(color.z, 1.); // clamp value to 1 - color.rgb = hsv2rgb(color.xyz); - #else - //color = clamp(vec4(0.), color, vec4(1.)); - #endif - #if AVERAGE_FRAMES vec2 uv = fragCoord/iResolution.xy; vec4 rest = texture(iChannel0, uv).rgba; @@ -673,23 +357,9 @@ ray camera(ray r) { // camera direction (faces forward, not up) vec3 d = normalize(CAMERA_DIR); - // point projection relative to direction - // 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); - vec3 x = vec3(d.z, 0., -d.x); - //vec3 y = cross(d, x); - vec3 y = vec3(-d.y*d.x, d.z*d.z+d.x*d.x, -d.y*d.z); + mat4 trans = translate(pos, d); - mat3 rot = mat3( - x.x, y.x, d.x, - x.y, y.y, d.y, - x.z, y.z, d.z - ); - - - return ray(r.pos + pos, normalize(rot * r.dir)); + return ray(apply_translate(trans, r.pos), apply_rotate(trans, r.dir)); } //////// -------------------------------- @@ -839,6 +509,9 @@ transmission transmit(ray r) { // don't bother reflecting off the lights. return transmission(NULL_RAY, INF); //return transmission(ray(np, cosine_direction(norm)), 0.); + + case 4: // generic diffuse material + return transmission(ray(np, cosine_direction(norm)), 0.5); } return transmission(ray(vec3(0.), vec3(0.)), 1.); @@ -864,8 +537,13 @@ vec3 emit(ray i, ray o, vec3 color) { switch (m) { case 0: // air material - if (any(isinf(o.pos))) return vec3(0.); - color += sqrt(distance(i.pos, o.pos)) * 0.0005; + if (any(isinf(o.pos))) { + #if SCENE == 2 + return vec3(1.); + #else + return vec3(0.); + #endif + } return color; case 1: // floor material @@ -884,6 +562,9 @@ vec3 emit(ray i, ray o, vec3 color) { color += vec3(5.) * dot(-i.dir, normal(i.pos, m)); return color; + case 4: // generic diffuse material + return color; + } // unknown material return vec3(1., 0., 1.); @@ -965,28 +646,6 @@ vec3 nudge(vec3 pos, int m) { return pos; } -//// -//// Combinators for creating signed distance functions and modelling objects. -//// - -float dist_plane(vec3 p, float y) { - return distance(p.y, y); -} - -// a plane with infinite depth -float dist_floor(vec3 p, float y) { - return p.y - y; -} - -float dist_sphere(vec3 p, vec3 pos, float r) { - return distance(p, pos) - r; -} - -/// Model difference/subtraction (objects in `a` but not in `b`). -float sub(float a, float b) { - return max(a, -b); -} - //// //// Cheat and calculate distance-related stuff with globals //// to reduce the amount of argument-passing we have to do. @@ -1079,11 +738,46 @@ void update_scene(vec3 pos, int focus) { } } +//// +//// Combinators for creating signed distance functions and modelling objects. +//// + +float dist_plane(vec3 pos, mat4 trans) { + vec4 p1w = trans * vec4(0., 0., 0., 1.); + vec4 p2w = trans * vec4(1., 0., 0., 1.); + vec4 p3w = trans * vec4(0., 0., 1., 1.); + vec3 p1 = p1w.xyz / p1w.w; + vec3 p2 = p2w.xyz / p2w.w; + vec3 p3 = p3w.xyz / p3w.w; + vec3 n = normalize(cross(p2 - p1, p3 - p1)); + vec3 rp = pos - p1; + return dot(rp, n); +} + +// a plane with infinite depth +float dist_floor(vec3 p, float y) { + return p.y - y; +} + +float dist_sphere(vec3 p, vec3 pos, float r) { + return distance(p, pos) - r; +} + +/// Model difference/subtraction (objects in `a` but not in `b`). +float sub(float a, float b) { + return max(a, -b); +} + //// //// SCENE: Objects in the scene //// void scene(vec3 p) { + /// ------------- + /// SCENE 1 + /// ------------- + #if SCENE == 1 + // the floor ud(1, dist_floor(p, -1.)); @@ -1096,43 +790,73 @@ void scene(vec3 p) { // the light sources ud(3, dist_sphere(p, vec3(1.5, 1.2, 7.), 0.7)); ud(3, dist_sphere(p, vec3(-1.2, 0.5, 5.0), 0.5)); + + + /// ------------- + /// SCENE 2 + /// ------------- + #elif SCENE == 2 + + #endif +} + +/// Rotate a vector angle using a matrix. +vec3 apply_rotate(mat4 r, vec3 d) { + return normalize((r*vec4(d, 0.)).xyz); +} + +vec3 apply_rotate(mat4 r) { + return apply_rotate(r, vec3(0., 0., 1.)); +} + +vec3 apply_translate(mat4 r, vec3 p) { + vec4 p_ = r*vec4(p, 1.); + return p_.xyz/p_.w; +} + +vec3 apply_translate(mat4 r) { + return apply_translate(r, vec3(0.)); +} + + +mat4 translate(vec3 p, vec3 z, vec3 up) { + z = normalize(z); + up = normalize(up); + vec3 x = cross(up, z); + vec3 y = cross(z, x); + + // GLSL matrices are backwards!!! + // Argh, it took me *two hours* to figure out what was going wrong!! + // What's worse is that I've been bitten by this before and *forgot*. Fuck. + /* + return mat4( + x.x, y.x, z.x, p.x, + x.y, y.y, z.y, p.y, + x.z, y.z, z.z, p.z, + 0.0, 0.0, 0.0, 1. + ); + */ + + return mat4( + x.x, x.y, x.z, 0., + y.x, y.y, y.z, 0., + z.x, z.y, z.z, 0., + p.x, p.y, p.z, 1. + ); +} + +mat4 translate(vec3 p, vec3 z) { + return translate(p, z, vec3(0., 1., 0.)); +} + +mat4 translate(vec3 p) { + return translate(p, vec3(0., 0., 1.)); } //////// ================================ //////// VENDOR: Vendored code //////// ================================ -//// -//// AUTHOR: iq -//// - -// Randoms (https://www.shadertoy.com/view/4sfGzS)) -// oldschool rand() from Visual Studio -int seed = 1; -int irand(void) { seed = seed*0x343fd+0x269ec3; return (seed>>16)&32767; } -float rand(void) { return float(irand())/32767.0; } -// hash to initialize the random sequence (copied from Hugo Elias) -int hash( int n ) -{ - n = (n << 13) ^ n; - return n * (n * n * 15731 + 789221) + 1376312589; -} - -void seed_randoms(vec2 fragCoord) { - ivec2 q = ivec2(fragCoord); - seed = hash(q.x+hash(q.y+hash(iFrame))); -} - -// HSV (https://www.shadertoy.com/view/MsS3Wc), via nmz -vec3 hsv2rgb( in vec3 c ) -{ - vec3 rgb = clamp( abs(mod(c.x*6.0+vec3(0.0,4.0,2.0),6.0)-3.0)-1.0, 0.0, 1.0 ); - - rgb = rgb*rgb*(3.0-2.0*rgb); // cubic smoothing - - return c.z * mix( vec3(1.0), rgb, c.y); -} - //// //// AUTHOR: fizzer, via iq: http://www.amietia.com/lambertnotangent.html //// @@ -1145,18 +869,3 @@ vec3 cosine_direction(vec3 norm) { u = 2.0*u - 1.0; return normalize(norm + vec3(sqrt(1.0 - u*u)*vec2(cos(a), sin(a)), u)); } - -//// -//// AUTHOR: Sam Hocevar, via nmz (http://lolengine.net/blog/2013/07/27/rgb-to-hsv-in-glsl) -//// - -vec3 rgb2hsv(vec3 c) -{ - vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); - vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g)); - vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r)); - - float d = q.x - min(q.w, q.y); - float e = 1.0e-10; - return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); -} diff --git a/path_march v4/common.frag b/path_march v4/common.frag new file mode 100644 index 0000000..b026408 --- /dev/null +++ b/path_march v4/common.frag @@ -0,0 +1,504 @@ +//////// ================================ +//////// 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 2 + +// +// Scenes: +// +// 1: A glossy rotating orange ball with two divets and blue floor. +// 2: +// +#define SCENE 1 + +//////// -------------------------------- +//////// Scene settings +//////// -------------------------------- + +// 0: circle around the origin +// 1: portrait +// 2: profile +// 3: 3/4 +//#define SCENE2_CAMERA 0 + +//////// -------------------------------- +//////// User settings +//////// -------------------------------- +//////// Tweak these according to your preferences and the power of your graphics card. +//////// Comment out a setting to restore it to its default value. + +//// ******************************** +//// Sample settings +//// ******************************** + +// The number of color samples taken per pixel. Increasing this has a dramatic effect on +// 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 + +// The maximum number of times light can reflect or scatter before it is extinguished. +//#define PATH_SEGMENTS 14 + +//// ******************************** +//// Postprocessing settings +//// ******************************** + +// If a pixel color is too bright for fit in sRGB, there are two ways to handle it: +// +// 1. Clamp the pixel within the limits of sRGB, resulting in (near-)maximum +// brightness at the cost of the color's saturation. (If it's too bright, it'll +// become entirely white.) +// 2. Reduce the brightness of the color until it fits within sRGB, preserving +// the color's saturation, but losing even *more* brightness. +// +// Correction for saturation generally looks better, but isn't usually necessary +// for more than five or so samples (because the bright pixels will average out +// with the dark pixels and fall back within sRGB). +//#define SATURATION_CORRECTION 1 + +// The dithering looks pretty good. 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 1 + +// The number of available colors *per channel*. +//define DITHER_COLORS 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. +//#define DITHER_BASE 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. +// +// * Pronounced "yif". Fite me. +//#define DITHER_NEAREST 0 + +//// ******************************** +//// Perspective settings +//// ******************************** + +// 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 + +// 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 + +// Camera position and angle. (Feel free to reference `time` here.) +//#define CAMERA_POS vec3(0.) +// (Don't worry, we call `normalize` for you. +//#define CAMERA_DIR vec3(0., 0., 1.) + +/// +/// TILE_PERSPECTIVE and CLAMP_PERSPECTIVE are only relevant if you zoom out +/// (e.g. an FOV < ~1.15). For more information on how and why these settings +/// behave the way they do, see their extended descriptions in the `project` function. +/// + +// Points on the screen >1 or <-1 show the portion of the scene *behind* you, +// mirrored so that the edges of each adjacent tile lines up (e.g. tiles above +// and below are mirrored vertically, to the left and right horizontally). +// 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 + +// 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 + +// 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.) + +//// ******************************** +//// Simulation settings +//// ******************************** + +// 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 + +// 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. + +// Average the color across frames by storing them in the buffer. +// This is like supersampling, but across frames instead of within a pixel, +// which lets you render with thousands of samples without crashing. +// It's strongly advised that you enable FREEZE_TIME when this is enabled! +// This uses iFrame, so if you want to enable this, make sure you hit the +// "reset time" function or things will get screwed up. +//#define AVERAGE_FRAMES 1 + +// Set a time in seconds. The simulation will be frozen at this point in time every frame. +// Comment this out to allow time to pass normally. +//#define FREEZE_TIME 2.75 + +// Loop time over an interval of this duration, beginning at FREEZE_TIME, +// or 0, if FREEZE_TIME is not set. +//#define LOOP_TIME 0. + +// Set the maximum duration of temporal antialiasing (i.e. how much time +// motion blur smears across). Note that this is a *maximum* time, and motion +// blur will never be greater than the duration of a frame. That said, when rendering +// a still image with FREEZE_TIME you probably want this set to 0., and if you're +// stuttering a lot, the large variance in frame times can make objects in the image +// 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.) + + +//////// -------------------------------- +//////// Internal settings +//////// -------------------------------- +//////// If you're just viewing the shader, you shouldn't usually need to tweak these. + +// The minimum distance between two points before they are considered the same point. +// Setting lower values increases the sharpness of the image at the cost of performance +// and rounding errors at objects very far from 0. +// +// Ray marching halves the distance to the surface of an object each iteration, but the +// end goal of ray marching is to pass slightly *inside* the object. Setting a minimum +// distance prevents zeno's paradox. This also serves as a optimization +// because the number of steps increases logarithmically as you decrease the minimum distance. +// +// Chosen to be 2^(-9), or about ~2mm, because that's the largest you can set it before +// the quality of the image is significantly effected. You can set it as low as about +// 2^(-19) before things begin to break. It's good to experiment with both high and low +// values to help find bugs in the numerical precision of the light simulation. +// If you have precision bugs, the simulation ends up getting affected pretty dramatically +// by changes to MIN_DIST, whereas a numerically stable simulation is not affected much at all. +// +// 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) + +// 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.) + +// 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`). +// +// 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. + +//////// -------------------------------- +//////// Default settings & presets +//////// -------------------------------- +//////// So you can restore a setting to its default value by commenting it out. + +#ifndef PRESET +#define PRESET 0 +#endif + +#ifndef SCENE +#define SCENE 2 +#endif + +//// ******************************** +//// Scene 2 +//// ******************************** +#if SCENE == 2 +#ifndef SCENE2_CAMERA +#define SCENE2_CAMERA 1 +#endif + +#ifndef FOV +#define FOV 1.2 +#endif + +#if SCENE2_CAMERA == 1 + +#ifndef CAMERA_POS +#define CAMERA_POS vec3(0., -0.1, -0.5) +#endif +#ifndef CAMERA_DIR +#define CAMERA_DIR vec3(0., 0., 1.) +#endif + +#elif SCENE2_CAMERA == 2 + +#ifndef CAMERA_POS +#define CAMERA_POS vec3(0.5, -0.1, 0.0) +#endif +#ifndef CAMERA_DIR +#define CAMERA_DIR vec3(-1.0, 0.0, 0.0) +#endif + +#elif SCENE2_CAMERA == 3 + +#ifndef CAMERA_POS +#define CAMERA_POS vec3(-sqrt(0.5/4.), -0.1, -sqrt(0.5/4.)) +#endif +#ifndef CAMERA_DIR +#define CAMERA_DIR vec3(sqrt(0.5), 0., sqrt(0.5)) +#endif + +#else + +#ifndef CAMERA_POS +#define CAMERA_POS vec3(sin(-time)*0.5, -0.1, cos(-time)*0.5) +#endif +#ifndef CAMERA_DIR +#define CAMERA_DIR vec3(-sin(-time), 0., -cos(-time)) +#endif + +#endif + +#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 10 +#endif +#ifndef MAX_STEPS +#define MAX_STEPS 300 +#endif +#ifndef MAX_DIST +#define MAX_DIST 50. +#endif +#ifndef MIN_DIST +#define MIN_DIST (0.001953125/128.) +#endif +#ifndef MAX_TAA_DIFF +#define MAX_TAA_DIFF 0. +#endif +#ifndef AVERAGE_FRAMES +#define AVERAGE_FRAMES 1 +#endif +#ifndef FREEZE_TIME +#if SCENE == 1 +#define FREEZE_TIME 2.75 +#elif SCENE == 2 +#define FREEZE_TIME 2.3 +#else +#define FREEZE_TIME 0. +#endif +#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 (defaults) +//// ******************************** +#ifndef SAMPLES +#define SAMPLES 1 +#endif +#ifndef PATH_SEGMENTS +#define PATH_SEGMENTS 10 +#endif + +#ifndef SATURATION_CORRECTION +#define SATURATION_CORRECTION 1 +#endif +#ifndef ENABLE_DITHER +#define ENABLE_DITHER 1 +#endif +#ifndef DITHER_BASE +#define DITHER_BASE 16 +#endif +#ifndef DITHER_COLORS +#define DITHER_COLORS (1<<8) +#endif +#ifndef DITHER_NEAREST +#define DITHER_NEAREST 0 +#endif + +#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 +#ifndef TILE_PERSPECTIVE +#define TILE_PERSPECTIVE 0 +#endif +#ifndef CLAMP_PERSPECTIVE +#define CLAMP_PERSPECTIVE 0 +#endif +#ifndef IMAGE_OFFSET +#define IMAGE_OFFSET vec2(0., 0.) +#endif +#ifndef MAX_STEPS +#define MAX_STEPS 200 +#endif +#ifndef MAX_DIST +#define MAX_DIST 20. +#endif +#ifndef AVERAGE_FRAMES +#define AVERAGE_FRAMES 0 +#endif +// FREEZE_TIME, LOOP_TIME, and MAX_TAA_DIFF are *undefined* by default. + +#ifndef MIN_DIST +#define MIN_DIST (0.001953125/8.) +#endif +#ifndef NORMAL_DELTA +#define NORMAL_DELTA (MIN_DIST/4.) +#endif +#ifndef IMPRECISION_FACTOR +#define IMPRECISION_FACTOR 1. +#endif + +//////// ================================ +//////// DOCS: Declarations & documentation +//////// ================================ + +/// Convert a color from linear RGB to the sRGB color space. +vec3 linear2srgb(vec3 color); + +/// Convert a color from RGB (Red/Green/Blue) to HSV (Hue/Saturation/Value). +vec3 rgb2hsv(vec3 rgb); + +/// Convert a color from HSV (Hue/Saturation/Value) to RGB (Red/Green/Blue). +vec3 hsv2rgb(vec3 hsv); + +/// Return a random number between 0 and 1 (with uniform distribution); +float rand(); + +/// Use the fragment coordinate and current frame to seed the random number generator. +void seed_randoms(vec3 seed); + +// Convenience definitions +#define INF (1./0.) +// NOTE: I used to use `sqrt(-1)`, but apparently that doesn't evaluate to NaN???? +// This makes me wonder if NaN isn't portable due to constant folding or something. +#define NAN (0./0.) + +//////// ================================ +//////// VENDOR: Vendored code +//////// ================================ + +//// +//// AUTHOR: unknown +//// + +vec3 linear2srgb(vec3 linear_rgb) { + // I believe the first version is technically more accurate, + // but the difference is usually negligable in practice. + #if 1 + // copied from somewhere on the internet + bvec3 cutoff = lessThan(linear_rgb, vec3(0.0031308)); + vec3 higher = vec3(1.055)*pow(linear_rgb, vec3(1.0/2.4)) - vec3(0.055); + vec3 lower = linear_rgb * vec3(12.92); + + return mix(higher, lower, cutoff); + // end copied from somewhere on the internet + #else + return pow(linear_rgb, vec3(1./2.2)); + #endif +} + +//// +//// AUTHOR: Sam Hocevar (http://lolengine.net/blog/2013/07/27/rgb-to-hsv-in-glsl) +//// + +vec3 rgb2hsv(vec3 c) { + vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); + vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g)); + vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r)); + + float d = q.x - min(q.w, q.y); + float e = 1.0e-10; + return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); +} + +vec3 hsv2rgb(vec3 c) { + vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); +} + +//// +//// AUTHOR: iq +//// + +// Randoms (https://www.shadertoy.com/view/4sfGzS)) +// oldschool rand() from Visual Studio +int _seed = 1; +int irand(void) { _seed = _seed*0x343fd+0x269ec3; return (_seed>>16)&32767; } +float rand(void) { return float(irand())/32767.0; } +// hash to initialize the random sequence (copied from Hugo Elias) +int hash( int n ) +{ + n = (n << 13) ^ n; + return n * (n * n * 15731 + 789221) + 1376312589; +} + +void seed_randoms(vec3 s) { + ivec3 q = ivec3(s); + _seed = hash(q.x+hash(q.y+hash(q.z))); +} diff --git a/path_march v4/image.frag b/path_march v4/image.frag new file mode 100644 index 0000000..d9734e1 --- /dev/null +++ b/path_march v4/image.frag @@ -0,0 +1,167 @@ +//////// ================================ +//////// 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; +}