<!-- VertexShader code here -->
<script id="vertexShader" type="x-shader/x-vertex">#version 300 es
in vec4 vPosition;
void main() {
	gl_Position = vPosition;
}
</script>
<!-- FragmentShader code here -->
<script id="fragmentShader" type="x-shader/x-fragment">#version 300 es
precision mediump float;
out vec4 fragColor;

uniform vec2 resolution;
uniform float time;
uniform vec4 mouse;
uniform float zoom;
uniform float show;
  
uniform vec3 colorA;
uniform vec3 colorB;
uniform vec3 colorC;
  
#define PI  3.1415926
#define PI2 6.2831853


vec3 normal_color( vec3 x ) {
  return (x-min(x,1.))/(max(x,255.)-min(x,1.));
}
  
vec2 hash2( vec2 p ) {
	return fract(
        sin(
            vec2(
                dot(p,vec2(44.3,25.7)),
                dot(p,vec2(87.2,54.1)))
           )*4258.4373);
}


vec2 get_mouse(void) {
    float ax = 10.*mouse.x/resolution.x;
    float ay = 10.*mouse.y/resolution.y;  
    return (mouse.xy==vec2(0)) ? vec2(0.3,0.4) : vec2(ax,ay);
}

// http://www.iquilezles.org/www/articles/voronoilines/voronoilines.htm
// updated to a vec4 to return hash of object in grid

vec4 voronoi( in vec2 x )
{
    vec2 mouse = get_mouse();
    x.x += mouse.x*.55;
    x.y += mouse.y*.25;
    
	float wave = 13.4 + (time*.65) + mouse.x; // mouse.x; // time;
    
    vec2 n = floor(x);
    vec2 f = fract(x);
	float ox = 0.;
	vec2 mg, mr;

    float md = 8.;
    for( int j=-1; j<=1; j++ )
    for( int i=-1; i<=1; i++ )
    {
        vec2 g = vec2(float(i),float(j));
		vec2 o = hash2( n + g );
        ox = o.x;
        o = .5 + .5 *sin(o * wave + PI2);
        vec2 r = g + o - f;
        float d = dot(r,r);

        if( d<md )
        {
            md = d;
            mr = r;
            mg = g;
        }
    }

    md = 8.;
    for( int j=-2; j<=2; j++ )
    for( int i=-2; i<=2; i++ )
    {
        vec2 g = mg + vec2(float(i),float(j));
		vec2 o = hash2( n + g );
        ox = o.x;
        o = .5 + .5 *sin(o * wave + PI2);
        vec2 r = g + o - f;

        if( dot(mr-r,mr-r)>0.00001 )
        md = min( md, dot( 0.5*(mr+r), normalize(r-mr) ) );
    }

    return vec4( md, mr, ox );
}

void main(void)
{
    vec2 uv = gl_FragCoord.xy/max(resolution.x,resolution.y);
    // for fun effect 
    // uv = abs(uv-vec2(.5,.27));
    vec2 buv = uv;
    uv.y -= time * .05;
    buv.y -= time * .053;
    float back_zoom = zoom * 4.2;
  
    uv *= zoom; 
    buv *= back_zoom; 
  
    vec2 uid = vec2(
        floor(uv.x),
    		floor(uv.x)
    );
    	
    vec4 c = voronoi( uv );
	  vec4 inset = voronoi( uv + vec2(.09,.06));
    vec4 backv = voronoi( buv);
    
	  vec3 sle = vec3(c.z - c.x);
    vec3 sne = vec3(c.y - c.x);
    
    vec3 col = vec3(1.);
 	  vec3 mate = vec3(1.);
    vec3 dmate = vec3(1.);
    
    // color top layer
    if(c.w<.25){
       mate = normal_color(colorC);
    } else if (c.w<.5) {
       mate = normal_color(colorB);
    } else if (c.w<.75) {
       mate = normal_color(colorA); 
    } else {
        if(show<1.){
            vec2 f=fract(buv.xy * .75 +time)-0.5;
   	        float checkrd = f.x*f.y>0.0?0.5:0.25;
            mate *= checkrd;
        } else {
            mate = vec3( .8 );
        }
    }
    
    // color bottom layer
    if(backv.w<.25){
       dmate = normal_color(colorC);
    } else if (backv.w<.5) {
       dmate = normal_color(colorA);
    } else if (backv.w<.75) {
       dmate = normal_color(colorB);
    } else {
       dmate = vec3( .8 );
    }
 
    float diag = clamp(sin((uv.x - uv.y)*PI2*30.)*1. + .95, 0., 1.)*.08 + .08;
    
 	// background voronoi pattern
	vec3 bkgnd = smoothstep( 0.01, 0.05, backv.x) * dmate;

    float border = 1.-smoothstep( 0.04, 0.05, c.x);
	bkgnd = min(bkgnd,1.-border); // cut the background away from border
	
    // shade which has the inner drop shadow
    vec3 shade = smoothstep( -.01, 0.13, inset.x) * mate - diag;
 	shade = min(shade,1.-border); // cut the shade away from border
    
    // highlights and offsets
    vec3 hglt = clamp(sne + sle, 0., 1.);

    // Check for background pattern
    bkgnd = show < 1. ? vec3(.8) * diag : bkgnd;

    // Layering is hard and still quite frustating.. but this is pretty!
    col = (border*.15) + bkgnd * sne + shade + border * sin(sle*2.4) - hglt*.5;
    
    // Solo variations
    //col = vec3(4.5) * shade * diag + border * sin(sle*2.4);
    
	fragColor = vec4(col,1.);
}
</script>
html {
  height: 100%;
}
body {
  background: #999;
  font-family: Arial,"Helvetica Neue",Helvetica,sans-serif;
  font-size: 16px;
  overflow: hidden;
  padding: 0;
  margin: 0;
  width: 100%;
  height: 100%;
}
// Render Class //
// Boostrap for WebGL and Attaching Shaders //
// Fragment & Vertex Shaders in HTML window //
// Click and Drag //

class Render {
  constructor() {
    this.zoom = 6.75;
    this.showBackground = true;
    this.color1 = [150, 102, 73];
    this.color2 = [89, 96, 107];
    this.color3 = [89, 95, 88];
    
    this.start = Date.now();
    this.mouse = new Mouse();
    this.umouse = [0.0, 0.0, 0.0, 0.0];
    this.tmouse = [0.0, 0.0, 0.0, 0.0];
    const mouse = this.mouse.pointer();
    // Setup WebGL canvas and surface object //
   // Make Canvas and get WebGl2 Context //
    const width = (this.width = ~~(document.documentElement.clientWidth,
    window.innerWidth || 0));
    const height = (this.height = ~~(document.documentElement.clientHeight,
    window.innerHeight || 0));
    const canvas = (this.canvas = document.createElement("canvas"));
    canvas.id = "GLShaders";
    canvas.width = width;
    canvas.height = height;
    document.body.appendChild(canvas);

    const gl = (this.gl = canvas.getContext("webgl2"));

    if (!gl) {
      console.warn("WebGL 2 is not available.");
      return;
    }
    // WebGl and WebGl2 Extension //
    this.gl.getExtension("OES_standard_derivatives");
    this.gl.getExtension("EXT_shader_texture_lod");
    this.gl.getExtension("OES_texture_float");
    this.gl.getExtension("WEBGL_color_buffer_float");
    this.gl.getExtension("OES_texture_float_linear");

    this.gl.viewport(0, 0, canvas.width, canvas.height);

    window.addEventListener(
      "resize",
      () => {
        const width = ~~(document.documentElement.clientWidth,
        window.innerWidth || 0);
        const height = ~~(document.documentElement.clientHeight,
        window.innerHeight || 0);
        this.canvas.width = width;
        this.canvas.height = height;
        this.gl.viewport(0, 0, this.canvas.width, this.canvas.height);
        this.resolution = new Float32Array([width, height]);
        this.gl.uniform2fv(
          this.gl.getUniformLocation(this.program, "resolution"),
          this.resolution
        );
        this.clearCanvas();
      },
      true
    );

     this.init();
  }
  
  // Canvas Helper Function //
  createCanvas = name => {
    this.canvas =
      document.getElementById(name) || document.createElement("canvas");
    this.canvas.id = name;
    if (!document.getElementById(name)) {
      document.body.appendChild(this.canvas);
    }
    const context = this.canvas.getContext("webgl2");
    if (!context) {
      console.error("no webgl avaiable");
    }
    this.setViewport();
  };
  // Viewport Helper Function //
  setViewport = () => {
    this.width = ~~(document.documentElement.clientWidth,
    window.innerWidth || 0);
    this.height = ~~(document.documentElement.clientHeight,
    window.innerHeight || 0);
    this.gl = this.canvas.getContext("webgl");
    this.canvas.width = this.width;
    this.canvas.height = this.height;
    this.gl.viewport(0, 0, this.width, this.height);
    this.clearCanvas();
  };

  // Shader Bootstrap code //
  createShader = (type, source) => {
    const shader = this.gl.createShader(type);
    this.gl.shaderSource(shader, source);
    this.gl.compileShader(shader);
    const success = this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS);
    if (!success) {
      console.log(this.gl.getShaderInfoLog(shader));
      this.gl.deleteShader(shader);
      return false;
    }
    return shader;
  };

  createWebGL = (vertexSource, fragmentSource) => {
    // Setup Vertext/Fragment Shader functions
    this.vertexShader = this.createShader(this.gl.VERTEX_SHADER, vertexSource);
    this.fragmentShader = this.createShader(
      this.gl.FRAGMENT_SHADER,
      fragmentSource
    );

    // Setup Program and Attach Shader functions
    this.program = this.gl.createProgram();
    this.gl.attachShader(this.program, this.vertexShader);
    this.gl.attachShader(this.program, this.fragmentShader);
    this.gl.linkProgram(this.program);
    this.gl.useProgram(this.program);

    if (!this.gl.getProgramParameter(this.program, this.gl.LINK_STATUS)) {
      console.warn(
        "Unable to initialize the shader program: " +
          this.gl.getProgramInfoLog(this.program)
      );
      return null;
    }

    // Create and Bind buffer //
    const buffer = this.gl.createBuffer();
    this.gl.bindBuffer(this.gl.ARRAY_BUFFER, buffer);

    this.gl.bufferData(
      this.gl.ARRAY_BUFFER,
      new Float32Array([-1, 1, -1, -1, 1, -1, 1, 1]),
      this.gl.STATIC_DRAW
    );

    const vPosition = this.gl.getAttribLocation(this.program, "vPosition");

    this.gl.enableVertexAttribArray(vPosition);
    this.gl.vertexAttribPointer(
      vPosition,
      2, // size: 2 components per iteration
      this.gl.FLOAT, // type: the data is 32bit floats
      false, // normalize: don't normalize the data
      0, // stride: 0 = move forward size * sizeof(type) each iteration to get the next position
      0 // start at the beginning of the buffer
    );

    this.clearCanvas();
    this.importUniforms();
  };

  clearCanvas = () => {
    this.gl.clearColor(0, 0, 0, 0);
    this.gl.clear(this.gl.COLOR_BUFFER_BIT);
  };

  importUniforms = () => {
    const width = ~~(document.documentElement.clientWidth,
    window.innerWidth || 0);
    const height = ~~(document.documentElement.clientHeight,
    window.innerHeight || 0);
    this.resolution = new Float32Array([width, height]);
    this.gl.uniform2fv(
      this.gl.getUniformLocation(this.program, "resolution"),
      this.resolution
    );
     // get the uniform ins from the shader fragments
    this.ut = this.gl.getUniformLocation(this.program, "time");
    this.ms = this.gl.getUniformLocation(this.program, "mouse");
    this.zm = this.gl.getUniformLocation(this.program, "zoom");
    this.c1 = this.gl.getUniformLocation(this.program, "colorA");
    this.c2 = this.gl.getUniformLocation(this.program, "colorB");
    this.c3 = this.gl.getUniformLocation(this.program, "colorC");
    this.sb = this.gl.getUniformLocation(this.program, "show");
    
    this.resolution = new Float32Array([this.canvas.width, this.canvas.height]);
    this.gl.uniform2fv(
      this.gl.getUniformLocation(this.program, "resolution"),
      this.resolution
    );
    
    this.gl.uniform4fv(this.ms, this.tmouse);
    this.gl.uniform1f(this.zm, this.zoom);
    this.gl.uniform1f(this.sb, this.showBackground);
    this.gl.uniform3fv(this.c1, this.color1);
    this.gl.uniform3fv(this.c2, this.color2);
    this.gl.uniform3fv(this.c3, this.color3);
  };

  updateUniforms = () => {
    this.gl.uniform1f(this.ut, (Date.now() - this.start) / 1000);
    const mouse = this.mouse.pointer();
    this.umouse = [mouse.x, this.canvas.height - mouse.y, mouse.x - mouse.y];
    const factor = 0.15;
    this.tmouse[0] =
      this.tmouse[0] - (this.tmouse[0] - this.umouse[0]) * factor;
    this.tmouse[1] =
      this.tmouse[1] - (this.tmouse[1] - this.umouse[1]) * factor;
    this.tmouse[2] =
      this.tmouse[2] - (this.tmouse[2] - this.umouse[2]) * factor;
    this.gl.uniform4fv(this.ms, this.tmouse);
    
 
    this.gl.drawArrays(
      this.gl.TRIANGLE_FAN, // primitiveType
      0, // Offset
      4 // Count
    );
  };

  init = () => {
    this.createWebGL(
      document.getElementById("vertexShader").textContent,
      document.getElementById("fragmentShader").textContent
    );
    this.createGUI();
    this.renderLoop();
  };

  // DatGUI Bootstrap code //
  createGUI = () => {
    this.options = {
      zoom: this.zoom,
      color1: this.color1,
      color2: this.color2,
      color3: this.color3,
      showBackground: this.showBackground
    };
    this.gui = new dat.GUI();
    const folderRender = this.gui.addFolder("Render Options");
    folderRender
      .add(this.options, "zoom", 1., 50.)
      .step(0.01)
      .onFinishChange(value => {
        this.zoom = value;
        this.gl.uniform1f(this.zm, this.zoom);
      });
    folderRender
      .add(this.options, "showBackground")
      .onFinishChange(value => {
        this.showBackground = value;
        this.gl.uniform1f(this.sb, this.showBackground);
      });
    folderRender.addColor(this.options, "color1").onChange(value => {
      this.color1 = value;
      this.gl.uniform3fv(this.c1, this.color1);
    });
    folderRender.addColor(this.options, "color2").onChange(value => {
      this.color2 = value;
      this.gl.uniform3fv(this.c2, this.color2);
    });
    folderRender.addColor(this.options, "color3").onChange(value => {
      this.color3 = value;
      this.gl.uniform3fv(this.c3, this.color3);
    });
     folderRender.open();
  };

  renderLoop = () => {
    this.updateUniforms();
    this.animation = window.requestAnimationFrame(this.renderLoop);
  };
}

const demo = new Render(document.body);
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.6.0/dat.gui.min.js
  2. https://codepen.io/pjkarlik/pen/791ef12e0bbed18e0305be528fbf623d