← Mike Higgins Home
Custom Code Experiment •

WebGL2 Hex Tiles 1

Source Code View HTML / CSS / JavaScript
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>WebGL2 Hex Warp Tiles</title>
    <style>
        html { height: 100%; }
        body {
            background: #000;
            overflow: hidden;
            padding: 0;
            margin: 0;
            width: 100%;
            height: 100%;
            display: flex;
            align-items: center;
        }
        canvas { height: 100%; width: 100%; }
    </style>
</head>
<body>
    <canvas id="canvas"></canvas>

    <script id="vertexShader" type="x-shader/x-vertex">#version 300 es
        precision highp float;
        in vec4 position;
        void main() {
            gl_Position = vec4(position);
        }
    </script>

    <script id="fragmentShader" type="x-shader/x-fragment">#version 300 es
        precision highp float;
        out vec4 fragColor;
        uniform vec2 u_resolution;
        uniform float u_time;

        #define R   u_resolution
        #define T   u_time
        #define PI  3.141592653
        #define PI2 6.283185307
        #define N   7.0

        mat2 rot(float a) { return mat2(cos(a), sin(a), -sin(a), cos(a)); }

        float hash21(vec2 a) {
            a.y = mod(a.y, N);
            return fract(sin(dot(a, vec2(27.609, 57.583))) * 43758.5453);
        }

        float hash11(float a) {
            return fract(sin(a * 127.1) * 43758.5453);
        }

        float hexSDF(vec2 p, float r) {
            const vec2 k = vec2(-0.866025404, 0.5);
            p = abs(p);
            p -= 2.0 * min(dot(k, p), 0.0) * k;
            p -= vec2(clamp(p.x, -k.y * r, k.y * r), r);
            return length(p) * sign(p.y);
        }

        vec4 hexGrid(vec2 uv) {
            vec2 s = vec2(1.0, sqrt(3.0));
            vec2 p = uv / s;
            vec2 a = mod(p, 1.0) - 0.5;
            vec2 b = mod(p - 0.5, 1.0) - 0.5;
            vec2 gv = (dot(a,a) < dot(b,b)) ? a : b;
            vec2 id = uv - gv * s;
            return vec4(gv * s, id);
        }

        vec3 hsv(vec3 c) {
            vec3 rgb = clamp(abs(mod(c.x * 6.0 + vec3(0,4,2), 6.0) - 3.0) - 1.0, 0.0, 1.0);
            return c.z * mix(vec3(1), rgb, c.y);
        }

        // Crisp step — one pixel wide AA using screen-space derivative
        float edge(float d) {
            float w = fwidth(d);
            return smoothstep(w, 0.0, d);
        }

        void main() {
            vec2 F = gl_FragCoord.xy;
            vec2 uv = (2.0 * F - R) / max(R.x, R.y);

            uv *= rot(-T * 0.07);
            uv = -vec2(log(length(uv)), atan(uv.y, uv.x));
            uv /= PI;
            uv *= N;

            uv.x += T * 0.35;

            vec3 C = vec3(0.0);

            vec4 hx = hexGrid(uv);
            vec2 localP = hx.xy;
            vec2 cellID = hx.zw;

            float h1 = hash21(cellID);
            float h2 = hash11(h1 * 3791.0 + floor(T * 0.3));
            float h3 = fract(h1 * 4785.312);

            // --- Outer hex ---
            float hexR  = 0.90;
            float d     = hexSDF(localP, hexR);

            float hue    = fract(h1 * 0.7 + T * 0.018);
            vec3 baseCol = hsv(vec3(hue, 1.0, 1.0));
            vec3 darkCol = baseCol * 0.2;
            vec3 cellCol = mix(baseCol, darkCol, smoothstep(-0.2, 0.6, localP.x));

            C = mix(C, cellCol, edge(d));

            // --- Inner hex (hard edge) ---
            float innerR = hexR * 0.58 + 0.08 * sin(h1 * PI2 + T * h1 * 1.5);
            float di     = hexSDF(localP, innerR);
            vec3 glowCol = hsv(vec3(fract(h3 * 0.6 + T * 0.025), 1.0, 1.0));
            C = mix(C, glowCol, edge(di));

            // --- Spinning accent hex (hard edge) ---
            float angle  = T * (0.4 + h2 * 0.6) * (h1 > 0.5 ? 1.0 : -1.0);
            vec2 rp      = rot(angle) * localP;
            float da     = hexSDF(rp, innerR * 0.42);
            vec3 accentCol = hsv(vec3(fract(hue + 0.33), 1.0, 1.0));
            C = mix(C, accentCol, edge(da));

            // --- Hard border gap between cells ---
            float borderW = 0.055;
            float borderMask = edge(borderW - d);           // 1 inside border zone
            C = mix(C, vec3(0.0), borderMask);

            // --- Thin bright border line ---
            float lineMask = edge(abs(d + borderW * 0.3) - borderW * 0.15);
            C = mix(C, baseCol * 1.2, lineMask);

            // Gamma
            C = pow(clamp(C, 0.0, 1.0), vec3(0.4545));
            fragColor = vec4(C, 1.0);
        }
    </script>

    <script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>

    <script>
        const glcanvas = document.getElementById("canvas");
        const gl = glcanvas.getContext("webgl2");
        if (!gl) alert("WebGL2 not supported!");

        const programInfo = twgl.createProgramInfo(gl, ["vertexShader", "fragmentShader"]);
        const arrays = {
            position: [-1,-1,0, 1,-1,0, -1,1,0, -1,1,0, 1,-1,0, 1,1,0]
        };
        const bufferInfo = twgl.createBufferInfoFromArrays(gl, arrays);

        const render = (time) => {
            twgl.resizeCanvasToDisplaySize(gl.canvas, 1.0);
            gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);

            const uniforms = {
                u_time: time * 0.001,
                u_resolution: [gl.canvas.width, gl.canvas.height]
            };

            gl.useProgram(programInfo.program);
            twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
            twgl.setUniforms(programInfo, uniforms);
            twgl.drawBufferInfo(gl, bufferInfo);
            requestAnimationFrame(render);
        };

        window.addEventListener("DOMContentLoaded", () => {
            requestAnimationFrame(render);
        });
    </script>
</body>
</html>