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>