• 01 - Critter Models 02
  • 02 - VantaJS Shaders
  • 03 - App Bundle 02
  • 04 - Cathedrals
  • 05 - App Bundle 01
  • 06 - Retroscopic
  • 07 - Games
  • 08 - Voronoi
  • 09 - Terrain
  • 10 - Critter Models 01
  • 11 - Chromatophores
  • 12 - Conformal Maps
  • 13 - Echo Logix
  • 14 - Music
  • 15 - Colour Theory
MIKE'S NOTEBOOK
Notes
Number:
Project:
01Critter Models 02
└ Locust└ Butterfly└ Cockroach└ Spider└ Scorpion└ Dragon└ Brazilian Treehopper└ Tardigrade└ Diatoms└ Bumblebee└ Wasp└ Hammerhead Worm└ Giraffe Weevil└ Trilobite Beetle
02VantaJS Shaders
└ Clouds└ Waves└ Cells└ Rings└ Halo└ Globe└ Net└ Dots└ Birds└ Trunk└ Topology└ About VantaJS
03App Bundle 02
└ Gutindex└ Insect Builder└ OpenLayers
04Cathedrals
└ Lattice└ Lost└ Lucent└ Lumen└ Glass└ Disco└ Hyperion└ Latent
05App Bundle 01
└ Robohash└ PrismJS└ Horoscope└ Binary Jazz└ Morse Code└ URL Extractor└ Entity Escaper└ Phonetic Alphabet└ Toki Pona└ Semaphore└ Cut Up Machine└ Text Mixer└ Currency Freaks└ Host IO└ CrUX API└ Logo Dev└ Web Speech API└ GEO DB
06Retroscopic
└ Galaxy└ Ocean└ Slime└ 24-Cell└ Unicorn└ Polyp└ Oneiric 1└ Oneiric 2└ Node└ Torus Knot└ GLSL Grid└ Liquid Crystal└ Silky└ Butterfly└ Ocean 2└ Icosahedron└ Luma└ Swarm└ Broken Solid└ Hypersphere└ Constraints└ Psycho Killer
01 / 03

OCEAN - RETROSCOPIC CRT SHADER SOURCE CODE

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Retroscope 2 (Ocean)</title>
    <style>
        html,
        body {
            margin: 0;
            padding: 0;
            overflow: hidden;
            background: #000000;
        }
    </style>
</head>
<body>

    <script id="vertex-shader" type="no-js">
        void main() {
            gl_Position = vec4(position, 1.0);
        }
    </script>

    <script id="fragment-shader" type="no-js">
        uniform float iGlobalTime;
        uniform vec2 iResolution;

        const int NUM_STEPS = 8;
        const float PI = 3.1415;
        const float EPSILON = 1e-3;

        const int ITER_GEOMETRY = 3;
        const int ITER_FRAGMENT = 5;
        const float SEA_HEIGHT = 0.6;
        const float SEA_CHOPPY = 1.0;
        const float SEA_SPEED = 1.0;
        const float SEA_FREQ = 0.16;
        const mat2 octave_m = mat2(1.6, 1.2, -1.2, 1.6);

        const vec3 PHOSPHOR = vec3(0.15, 1.0, 0.45);
        const vec3 PHOSPHOR_DIM = vec3(0.02, 0.12, 0.06);

        float hash(vec2 p) {
            float h = dot(p, vec2(127.1, 311.7));
            return fract(sin(h) * 43758.5453123);
        }

        float noise(in vec2 p) {
            vec2 i = floor(p);
            vec2 f = fract(p);
            vec2 u = f * f * (3.0 - 2.0 * f);
            return -1.0 + 2.0 * mix(
                mix(hash(i + vec2(0.0, 0.0)), hash(i + vec2(1.0, 0.0)), u.x),
                mix(hash(i + vec2(0.0, 1.0)), hash(i + vec2(1.0, 1.0)), u.x),
                u.y
            );
        }

        float sea_octave(vec2 uv, float choppy) {
            uv += noise(uv);
            vec2 wv = 1.0 - abs(sin(uv));
            vec2 swv = abs(cos(uv));
            wv = mix(wv, swv, wv);
            return pow(1.0 - pow(wv.x * wv.y, 0.65), choppy);
        }

        float map(vec3 p, float time) {
            float freq = SEA_FREQ;
            float amp = SEA_HEIGHT;
            float choppy = SEA_CHOPPY;
            vec2 uv = p.xz;
            uv.x *= 0.75;

            float d, h = 0.0;
            for (int i = 0; i < ITER_GEOMETRY; i++) {
                d = sea_octave((uv + time) * freq, choppy);
                d += sea_octave((uv - time) * freq, choppy);
                h += d * amp;
                uv *= octave_m;
                freq *= 1.9;
                amp *= 0.22;
                choppy = mix(choppy, 1.0, 0.2);
            }
            return p.y - h;
        }

        float map_detailed(vec3 p, float time) {
            float freq = SEA_FREQ;
            float amp = SEA_HEIGHT;
            float choppy = SEA_CHOPPY;
            vec2 uv = p.xz;
            uv.x *= 0.75;

            float d, h = 0.0;
            for (int i = 0; i < ITER_FRAGMENT; i++) {
                d = sea_octave((uv + time) * freq, choppy);
                d += sea_octave((uv - time) * freq, choppy);
                h += d * amp;
                uv *= octave_m;
                freq *= 1.9;
                amp *= 0.22;
                choppy = mix(choppy, 1.0, 0.2);
            }
            return p.y - h;
        }

        vec3 getNormal(vec3 p, float eps, float time) {
            vec3 n;
            n.y = map_detailed(p, time);
            n.x = map_detailed(vec3(p.x + eps, p.y, p.z), time) - n.y;
            n.z = map_detailed(vec3(p.x, p.y, p.z + eps), time) - n.y;
            n.y = eps;
            return normalize(n);
        }

        float heightMapTracing(vec3 ori, vec3 dir, out vec3 p, float time) {
            float tm = 0.0;
            float tx = 1000.0;
            float hx = map(ori + dir * tx, time);

            if (hx > 0.0) {
                p = ori + dir * tx;
                return tx;
            }

            float hm = map(ori + dir * tm, time);
            float tmid = 0.0;
            for (int i = 0; i < NUM_STEPS; i++) {
                tmid = mix(tm, tx, hm / (hm - hx));
                p = ori + dir * tmid;
                float hmid = map(p, time);
                if (hmid < 0.0) {
                    tx = tmid;
                    hx = hmid;
                } else {
                    tm = tmid;
                    hm = hmid;
                }
            }
            return tmid;
        }

        float gridLine(vec2 p, float cell, float lineWidth) {
            vec2 g = abs(fract(p / cell) - 0.5);
            vec2 d = smoothstep(0.5 - lineWidth, 0.5, g);
            return max(d.x, d.y);
        }

        void main() {
            vec2 screenUV = gl_FragCoord.xy / iResolution.xy;

            vec2 uv = screenUV * 2.0 - 1.0;
            uv.x *= iResolution.x / iResolution.y;

            float time = iGlobalTime * 0.3;
            float seaTime = iGlobalTime * SEA_SPEED;
            float epsNrm = 0.1 / iResolution.x;

            vec3 ang = vec3(
                sin(time * 3.0) * 0.1,
                sin(time) * 0.2 + 0.3,
                time
            );
            vec3 ori = vec3(0.0, 3.5, time * 5.0);
            vec3 dir = normalize(vec3(uv.xy, -2.0));
            dir.z += length(uv) * 0.15;
            dir = normalize(dir);

            vec3 p;
            heightMapTracing(ori, dir, p, seaTime);
            vec3 dist = p - ori;
            vec3 n = getNormal(p, dot(dist, dist) * epsNrm, seaTime);
            vec3 light = normalize(vec3(0.0, 1.0, 0.8));

            bool hitSky = dir.y > -0.02;

            float diffTerm = clamp(dot(n, light) * 0.5 + 0.5, 0.0, 1.0);
            float rim = pow(1.0 - max(dot(n, -dir), 0.0), 2.5);
            float wireIntensity = gridLine(p.xz, 4.0, 0.035 + 0.02 * rim);
            float contour = gridLine(vec2(p.y * 6.0, 0.0), 1.0, 0.12);

            float beamPulse = 0.6 + 0.4 * sin(iGlobalTime * 4.0 + p.x * 0.5 + p.z * 0.3);
            float trace = clamp(wireIntensity * 1.1 + contour * 0.6 * diffTerm, 0.0, 1.5) * beamPulse;

            float skyGlow = smoothstep(0.15, -0.05, dir.y) * (0.15 + 0.1 * sin(uv.x * 6.0 + iGlobalTime));

            float signal = hitSky ? skyGlow : trace;

            vec3 color = PHOSPHOR_DIM + PHOSPHOR * signal;
            color += PHOSPHOR * pow(signal, 3.0) * 0.35;

            float jitterLine = step(0.996, hash(vec2(floor(gl_FragCoord.y), floor(iGlobalTime * 8.0))));
            color = mix(color, color.bgr, jitterLine * 0.5);

            float scan = sin(screenUV.y * iResolution.y * PI) * 0.5 + 0.5;
            color *= mix(0.72, 1.0, scan);

            float sweepY = fract(iGlobalTime * 0.12);
            float beam = smoothstep(0.03, 0.0, abs(screenUV.y - sweepY));
            color += PHOSPHOR * beam * 0.25;

            float vig = 1.0 - dot(uv * 0.5, uv * 0.5) * 0.45;
            color *= clamp(vig, 0.0, 1.0);

            float grain = hash(gl_FragCoord.xy + fract(iGlobalTime) * 100.0);
            color += (grain - 0.5) * 0.035;

            float flicker = 0.94 + 0.06 * sin(iGlobalTime * 26.0) * hash(vec2(floor(iGlobalTime * 12.0), 1.0));
            color *= flicker;

            color.r += 0.015 * abs(uv.x);
            color.b += 0.015 * abs(uv.y);

            gl_FragColor = vec4(clamp(color, 0.0, 1.0), 1.0);
        }
    </script>

    <script type="importmap">
        {
            "imports": {
                "three": "https://cdn.jsdelivr.net/npm/three@0.163.0/build/three.module.js"
            }
        }
    </script>

    <script type="module">
        import * as THREE from 'three';

        const uniforms = {
            iGlobalTime: { value: 0.0 },
            iResolution: { value: new THREE.Vector2(window.innerWidth, window.innerHeight) }
        };

        const vertexShader = document.getElementById('vertex-shader').textContent;
        const fragmentShader = document.getElementById('fragment-shader').textContent;

        const scene = new THREE.Scene();

        const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0.1, 10);
        camera.position.z = 1;

        const geometry = new THREE.PlaneGeometry(2, 2);
        const material = new THREE.ShaderMaterial({
            uniforms: uniforms,
            vertexShader: vertexShader,
            fragmentShader: fragmentShader,
            depthWrite: false,
            depthTest: false,
        });

        const mesh = new THREE.Mesh(geometry, material);
        scene.add(mesh);

        const renderer = new THREE.WebGLRenderer({ antialias: true });
        renderer.setClearColor(0x000000, 1);

        const canvas = renderer.domElement;
        canvas.style.position = 'fixed';
        canvas.style.top = '0';
        canvas.style.left = '0';
        canvas.style.display = 'block';
        document.body.appendChild(canvas);

        function getViewportSize() {
            if (window.visualViewport) {
                return [window.visualViewport.width, window.visualViewport.height];
            }
            return [
                window.innerWidth || document.documentElement.clientWidth,
                window.innerHeight || document.documentElement.clientHeight
            ];
        }

        function resize() {
            const [w, h] = getViewportSize();
            if (!w || !h) return;
            const dpr = Math.min(window.devicePixelRatio || 1, 2);
            canvas.style.width = w + 'px';
            canvas.style.height = h + 'px';
            renderer.setPixelRatio(dpr);
            renderer.setSize(w, h, false);
            uniforms.iResolution.value.set(w * dpr, h * dpr);
        }

        resize();
        window.addEventListener('resize', resize);
        window.addEventListener('orientationchange', resize);
        if (window.visualViewport) {
            window.visualViewport.addEventListener('resize', resize);
        }
        [100, 500, 1500].forEach((t) => setTimeout(resize, t));

        const clock = new THREE.Clock();

        function animate() {
            const delta = clock.getDelta();
            uniforms.iGlobalTime.value += Math.min(delta, 0.1);
            renderer.render(scene, camera);
            requestAnimationFrame(animate);
        }

        animate();
    </script>
</body>
</html>
MikeHiggins.me.uk LinkedIn GitHub Bluesky Contra Upwork
© 2026 Mike Higgins. All rights reserved.