#version 450 /* Quasi Infinite Zoom Voronoi --------------------------- The infinite zoom effect has been keeping me amused for years. This one is based on something I wrote some time ago, but was inspired by Fabrice Neyret's "Infinite Fall" shader. I've aired on the side of caution and called it "quasi infinite," just in case it doesn't adhere to his strict infinite zoom standards. :) Seriously though, I put together a couple of overly optimized versions a couple of days ago, just for fun, and Fabrice's comments were pretty helpful. I also liked the way he did the layer rotation in his "Infinite Fall" version, so I'm using that. The rest is stock standard infinite zoom stuff that has been around for years. Most people like to use noise for this effect, so I figured I'd do something different and use Voronoi. I've also bump mapped it, added specular highlights, etc. It was tempting to add a heap of other things, but I wanted to keep the example relatively simple. By the way, most of the code is basic bump mapping and lighting. The infinite zoom code takes up just a small portion. Fabrice Neyret's versions: infinite fall - short https://www.shadertoy.com/view/ltjXWW infinite fall - FabriceNeyret2 https://www.shadertoy.com/view/4sl3RX Other examples: Fractal Noise - mu6k https://www.shadertoy.com/view/Msf3Wr Infinite Sierpinski - gleurop https://www.shadertoy.com/view/MdfGR8 Infinite Zoom - fizzer https://www.shadertoy.com/view/MlXGW7 Private link to a textured version of this. Bumped Infinite Zoom Texture - Shane https://www.shadertoy.com/view/Xl2XWw */ layout(std140, set = 0, binding = 0) uniform UBO { mat4 MVP; vec4 OutputSize; vec4 OriginalSize; vec4 SourceSize; uint FrameCount; } global; #pragma stage vertex layout(location = 0) in vec4 Position; layout(location = 1) in vec2 TexCoord; layout(location = 0) out vec2 vTexCoord; const vec2 madd = vec2(0.5, 0.5); void main() { gl_Position = global.MVP * Position; vTexCoord = gl_Position.xy; } #pragma stage fragment layout(location = 0) in vec2 vTexCoord; layout(location = 0) out vec4 FragColor; float iGlobalTime = float(global.FrameCount)*0.025; vec2 iResolution = global.OutputSize.xy; vec2 hash22(vec2 p) { // Faster, but doesn't disperse things quite as nicely. However, when framerate // is an issue, and it often is, this is a good one to use. Basically, it's a tweaked // amalgamation I put together, based on a couple of other random algorithms I've // seen around... so use it with caution, because I make a tonne of mistakes. :) float n = sin(dot(p, vec2(41, 289))); return fract(vec2(262144, 32768)*n); // Animated. //p = fract(vec2(262144, 32768)*n); //return sin( p*6.2831853 + time )*0.5 + 0.5; } // One of many 2D Voronoi algorithms getting around, but all are based on IQ's // original. I got bored and roughly explained it. It was a slow day. :) The // explanations will be obvious to many, but not all. float Voronoi(vec2 p) { // Partitioning the 2D space into repeat cells. vec2 ip = floor(p); // Analogous to the cell's unique ID. p = fract(p); // Fractional reference point within the cell. // Set the minimum distance (squared distance, in this case, because it's // faster) to a maximum of 1. Outliers could reach as high as 2 (sqrt(2)^2) // but it's being capped to 1, because it covers a good portion of the range // (basically an inscribed unit circle) and dispenses with the need to // normalize the final result. // // If you're finding that your Voronoi patterns are a little too contrasty, // you could raise "d" to something like "1.5." Just remember to divide // the final result by the same amount. float d = 1.; // Put a "unique" random point in the cell (using the cell ID above), and it's 8 // neighbors (using their cell IDs), then check for the minimum squared distance // between the current fractional cell point and these random points. for (float i = -1.; i < 1.1; i++){ for (float j = -1.; j < 1.1; j++){ vec2 cellRef = vec2(i, j); // Base cell reference point. vec2 offset = hash22(ip + cellRef); // 2D offset. // Vector from the point in the cell to the offset point. vec2 r = cellRef + offset - p; float d2 = dot(r, r); // Squared length of the vector above. d = min(d, d2); // If it's less than the previous minimum, store it. } } // In this case, the distance is being returned, but the squared distance // can be used too, if prefered. return sqrt(d); } /* // 2D 2nd-order Voronoi: Obviously, this is just a rehash of IQ's original. I've tidied // up those if-statements. Since there's less writing, it should go faster. That's how // it works, right? :) // float Voronoi2(vec2 p){ vec2 g = floor(p), o; p -= g;// p = fract(p); vec2 d = vec2(1); // 1.4, etc. for(int y = -1; y <= 1; y++){ for(int x = -1; x <= 1; x++){ o = vec2(x, y); o += hash22(g + o) - p; float h = dot(o, o); d.y = max(d.x, min(d.y, h)); d.x = min(d.x, h); } } //return sqrt(d.y) - sqrt(d.x); return (d.y - d.x); // etc. } */ void mainImage( out vec4 fragColor, in vec2 fragCoord ){ // Screen coordinates. vec2 uv = (fragCoord - iResolution.xy*.5)/iResolution.y; // Variable setup, plus rotation. float t = iGlobalTime, s, a, b, e; // Rotation the canvas back and forth. float th = sin(iGlobalTime*0.1)*sin(iGlobalTime*0.13)*4.; float cs = cos(th), si = sin(th); uv *= mat2(cs, -si, si, cs); // Setup: I find 2D bump mapping more intuitive to pretend I'm raytracing, then lighting a bump mapped plane // situated at the origin. Others may disagree. :) vec3 sp = vec3(uv, 0); // Surface posion. Hit point, if you prefer. Essentially, a screen at the origin. vec3 ro = vec3(0, 0, -1); // Camera position, ray origin, etc. vec3 rd = normalize(sp-ro); // Unit direction vector. From the origin to the screen plane. vec3 lp = vec3(cos(iGlobalTime)*0.375, sin(iGlobalTime)*0.1, -1.); // Light position - Back from the screen. // The number of layers. More gives you a more continous blend, but is obviously slower. // If you change the layer number, you'll proably have to tweak the "gFreq" value. const float L = 8.; // Global layer frequency, or global zoom, if you prefer. const float gFreq = 0.5; float sum = 0.; // Amplitude sum, of sorts. // Setting up the layer rotation matrix, used to rotate each layer. // Not completely necessary, but it helps mix things up. It's standard practice, but // this one is based on Fabrice's example. th = 3.14159265*0.7071/L; cs = cos(th), si = sin(th); mat2 M = mat2(cs, -si, si, cs); // The overall scene color. Initiated to zero. vec3 col = vec3(0); // Setting up the bump mapping variables and initiating them to zero. // f - Function value // fx - Change in "f" in in the X-direction. // fy - Change in "f" in in the Y-direction. float f=0., fx=0., fy=0.; vec2 eps = vec2(4./iResolution.y, 0.); // I've had to off-center this just a little to avoid an annoying white speck right // in the middle of the canvas. If anyone knows how to get rid of it, I'm all ears. :) vec2 offs = vec2(0.1); // Infinite Zoom. // // The first three lines are a little difficult to explain without describing what infinite // zooming is in the first place. A lot of it is analogous to fBm. Sum a bunch of increasing // frequencies with decreasing amplitudes. // // Anyway, the effect is nothing more than a series of layers being expanded from an initial // size to a final size, then being snapped back to its original size to repeat the process // again. However, each layer is doing it at diffent incremental stages in time, which tricks // the brain into believing the process is continuous. If you wish to spoil the illusion, // simply reduce the layer count. If you wish to completely ruin the effect, set it to one. // Infinite zoom loop. for (float i = 0.; i