← Mike Higgins Home
Custom Code Experiment •

WebGL2 Hex Tiles 2

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 Star 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 starSDF(vec2 p, float r, float innerR) {
            float an = PI2 / 5.0;
            p *= rot(PI * 0.5);
            float bn = mod(atan(p.x, p.y), an) - an * 0.5;
            p = length(p) * vec2(cos(bn), abs(sin(bn)));
            vec2 w = vec2(-innerR * sin(an * 0.5), innerR * cos(an * 0.5));
            vec2 q = p - r * vec2(cos(an * 0.5), sin(an * 0.5));
            float h = clamp(dot(q, w) / dot(w, w), 0.0, 1.0);
            return length(q - w * h) * sign(q.x * w.y - q.y * w.x);
        }

        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 primaryCol(float idx) {
            int i = int(mod(idx * 6.0, 6.0));
            if (i == 0) return vec3(0.85, 0.06, 0.06);
            if (i == 1) return vec3(0.95, 0.80, 0.00);
            if (i == 2) return vec3(0.00, 0.20, 0.85);
            if (i == 3) return vec3(0.90, 0.40, 0.00);
            if (i == 4) return vec3(0.00, 0.65, 0.15);
                        return vec3(0.45, 0.00, 0.80);
        }

        float edge(float d) {
            return smoothstep(fwidth(d), 0.0, d);
        }

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

            // Clamp radius away from zero before log-polar to kill centre singularity
            float minR = 0.01;
            float len = max(length(uv), minR);
            uv = uv / length(uv) * len;

            uv *= rot(-T * 0.07);

            // Safe log-polar: clamp length to avoid log(0)
            float r = max(length(uv), minR);
            uv = -vec2(log(r), atan(uv.y, uv.x));
            uv /= PI;
            uv *= N;
            uv.x += T * 0.35;

            // Solid background — pure flat colour, nothing else
            vec3 C = vec3(0.05, 0.05, 0.07);

            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.25));
            float h3 = fract(h1 * 4785.312);

            int ci1 = int(mod(h1 * 6.0, 6.0));
            int ci2 = int(mod(h1 * 6.0 + 2.0 + h3 * 2.0, 6.0));
            vec3 col1 = primaryCol(float(ci1) / 6.0);
            vec3 col2 = primaryCol(float(ci2) / 6.0);
            vec3 col3 = primaryCol(fract(h3 * 0.5 + 0.17));

            // Outer star
            float outerR = 0.78;
            float d = starSDF(localP, outerR, outerR * 0.42);
            C = mix(C, col1, edge(d));

            // Inner counter-rotating star
            float angle2 = T * (0.3 + h2 * 0.4) * (h1 > 0.5 ? 1.0 : -1.0);
            float d2 = starSDF(rot(angle2) * localP, outerR * 0.52, outerR * 0.52 * 0.42);
            C = mix(C, col2, edge(d2));

            // Accent star
            float angle3 = T * (0.6 + h3 * 0.5) * (h1 > 0.5 ? -1.0 : 1.0);
            float d3 = starSDF(rot(angle3) * localP, outerR * 0.26, outerR * 0.26 * 0.42);
            C = mix(C, col3, edge(d3));

            // Hard dark border
            float borderW = 0.06;
            C = mix(C, vec3(0.0), edge(borderW - d));

            // Thin border line
            C = mix(C, col1 * 1.3, edge(abs(d + borderW * 0.3) - borderW * 0.15));

            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>