LOST - WEBGL CATHEDRAL PROCEDURAL SHADER SOURCE CODE
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Lost Cathedral</title>
<style>
html,body{margin:0;padding:0;overflow:hidden;background:#000;height:100%;width:100%}
canvas{display:block;width:100vw;height:100vh}
</style>
</head>
<body>
<canvas id="c"></canvas>
<script>
const canvas = document.getElementById('c');
const gl = canvas.getContext('webgl', {antialias:true, alpha:false}) ||
canvas.getContext('experimental-webgl', {antialias:true, alpha:false});
if(!gl){
document.body.innerHTML = '<p style="color:#fff;font-family:sans-serif;padding:2em">WebGL not available.</p>';
}
const vsSource = `
attribute vec2 aPos;
void main(){
gl_Position = vec4(aPos, 0.0, 1.0);
}
`;
const fsSource = `
precision highp float;
uniform vec2 uRes;
uniform float uTime;
#define PI 3.14159265359
#define TAU 6.28318530718
vec2 hash2(vec2 p){
p = vec2(dot(p, vec2(127.1, 311.7)), dot(p, vec2(269.5, 183.3)));
return -1.0 + 2.0 * fract(sin(p) * 43758.5453123);
}
float hash1(vec2 p){
return fract(sin(dot(p, vec2(41.3, 289.1))) * 43758.5453123);
}
float vnoise(vec2 p){
vec2 i = floor(p);
vec2 f = fract(p);
vec2 u = f*f*(3.0-2.0*f);
float a = hash1(i);
float b = hash1(i+vec2(1.0,0.0));
float c = hash1(i+vec2(0.0,1.0));
float d = hash1(i+vec2(1.0,1.0));
return mix(mix(a,b,u.x), mix(c,d,u.x), u.y);
}
vec3 voronoi(vec2 p, float t){
vec2 ip = floor(p);
vec2 fp = fract(p);
float minD1 = 8.0, minD2 = 8.0;
float cellId = 0.0;
for(int j=-1;j<=1;j++){
for(int i=-1;i<=1;i++){
vec2 g = vec2(float(i), float(j));
vec2 o = hash2(ip + g);
vec2 cpos = g + 0.5 + 0.5*sin(t*0.13 + o*6.2831 + o.yx*3.0);
vec2 r = cpos - fp;
float d = dot(r, r);
if(d < minD1){
minD2 = minD1;
minD1 = d;
cellId = hash1(ip+g+3.1);
} else if(d < minD2){
minD2 = d;
}
}
}
return vec3(sqrt(minD1), sqrt(minD2), cellId);
}
mat2 rot(float a){
float s = sin(a), c = cos(a);
return mat2(c, -s, s, c);
}
vec2 kaleidoFold(vec2 p, float segments, float time){
float a = atan(p.y, p.x);
float r = length(p);
float seg = TAU / segments;
a = mod(a, seg);
a = abs(a - seg*0.5);
r = r / (1.0 + 0.15*sin(r*3.0 - time*0.4));
return vec2(cos(a), sin(a)) * r;
}
vec2 fractalFold(vec2 p, float time){
for(int i=0; i<4; i++){
p = abs(p) - 0.6 + 0.05*sin(time*0.07 + float(i)*1.7);
p *= rot(0.4 + 0.15*sin(time*0.05 + float(i)));
p *= 1.35;
}
return p;
}
// Fixed: integer loop counter with constant bound (required by GLSL ES / ANGLE)
float quasicrystal(vec2 p, float time){
float sum = 0.0;
const float n = 7.0;
for(int k=0; k<7; k++){
float i = float(k);
float ang = PI * i / n + time*0.015;
vec2 dir = vec2(cos(ang), sin(ang));
sum += cos(dot(p, dir) * 6.0 + time*0.25 + i);
}
return sum / n;
}
vec3 palette(float t, vec3 a, vec3 b, vec3 c, vec3 d){
return a + b*cos(TAU*(c*t+d));
}
float smin(float a, float b, float k){
float h = clamp(0.5 + 0.5*(b-a)/k, 0.0, 1.0);
return mix(b, a, h) - k*h*(1.0-h);
}
void main(){
vec2 uv = (gl_FragCoord.xy - 0.5*uRes) / min(uRes.x, uRes.y);
float time = uTime;
float globalA = time * 0.025 + 0.3*sin(time*0.011);
vec2 p = uv * rot(globalA);
float zoom = 1.6 + 0.45*sin(time*0.027) + 0.15*sin(time*0.071+1.3);
p *= zoom;
float segCount = 6.0 + 4.0*sin(time*0.017);
float symBreak = 0.18*sin(time*0.0083 + 1.0);
vec2 kp = kaleidoFold(p, max(3.0, segCount), time);
kp += symBreak * vec2(vnoise(p*1.3 + time*0.05), vnoise(p*1.7 - time*0.04));
vec2 fp = fractalFold(kp*1.4, time);
vec3 v1 = voronoi(fp*1.4 + 0.0, time);
vec3 v2 = voronoi(fp*3.1 + 7.0, time*1.3);
vec3 v3 = voronoi(fp*6.7 + 19.0, time*0.7);
float edge1 = smoothstep(0.0, 0.06, v1.y - v1.x);
float edge2 = smoothstep(0.0, 0.04, v2.y - v2.x);
float edge3 = smoothstep(0.0, 0.025, v3.y - v3.x);
float qc = quasicrystal(kp*2.2, time);
float qcGlow = smoothstep(-0.2, 1.0, qc);
float structure = 1.0 - edge1;
structure = smin(structure, 1.0 - edge2*0.85, 0.25);
structure = smin(structure, 1.0 - edge3*0.6, 0.15);
float rd = vnoise(fp*2.5 + time*0.08) - vnoise(fp*4.1 - time*0.06);
structure += rd * 0.08;
float idMix = fract(v1.z*1.0 + v2.z*0.6 + v3.z*0.3 + time*0.01);
float hueShift = time*0.02 + 0.4*sin(time*0.007);
vec3 colA = vec3(0.30);
vec3 colB = vec3(0.28);
vec3 colC = vec3(1.0, 0.85, 0.6);
vec3 colD = vec3(0.15, 0.4, 0.6) + hueShift;
vec3 baseColor = palette(idMix + 0.15*qc, colA, colB, colC, colD);
float depth = clamp(1.0 - v1.x*0.9, 0.0, 1.0);
vec3 color = baseColor * (0.15 + 0.6*depth);
color *= mix(0.5, 1.1, qcGlow);
float edgeGlowMask = (1.0-edge1)*0.6 + (1.0-edge2)*0.35 + (1.0-edge3)*0.2;
vec3 edgeColor = palette(idMix*1.7 + 0.5, vec3(0.35), vec3(0.30), vec3(1.0), vec3(0.2,0.5,0.8)+hueShift);
color += edgeColor * edgeGlowMask * 0.4;
float bloomSrc = clamp(structure*0.6 + qcGlow*0.5 + edgeGlowMask*0.7 - 0.4, 0.0, 1.0);
vec3 bloom = baseColor * pow(bloomSrc, 2.0) * 0.5;
color += bloom;
float vig = smoothstep(1.4, 0.0, length(uv));
color *= mix(0.35, 0.95, vig);
float shimmer = 0.04*sin(time*1.7 + length(p)*8.0) * sin(time*0.31);
color += shimmer;
color = color / (1.0 + color*1.0);
color = pow(color, vec3(1.1));
float dither = (hash1(gl_FragCoord.xy + time) - 0.5) / 255.0;
color += dither;
gl_FragColor = vec4(clamp(color, 0.0, 1.0), 1.0);
}
`;
function compile(type, src){
const sh = gl.createShader(type);
gl.shaderSource(sh, src);
gl.compileShader(sh);
if(!gl.getShaderParameter(sh, gl.COMPILE_STATUS)){
console.error(gl.getShaderInfoLog(sh));
}
return sh;
}
const vs = compile(gl.VERTEX_SHADER, vsSource);
const fs = compile(gl.FRAGMENT_SHADER, fsSource);
const program = gl.createProgram();
gl.attachShader(program, vs);
gl.attachShader(program, fs);
gl.linkProgram(program);
if(!gl.getProgramParameter(program, gl.LINK_STATUS)){
console.error(gl.getProgramInfoLog(program));
}
gl.useProgram(program);
const quad = new Float32Array([
-1,-1, 1,-1, -1,1,
-1,1, 1,-1, 1,1
]);
const buf = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
gl.bufferData(gl.ARRAY_BUFFER, quad, gl.STATIC_DRAW);
const aPos = gl.getAttribLocation(program, 'aPos');
gl.enableVertexAttribArray(aPos);
gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, 0, 0);
const uRes = gl.getUniformLocation(program, 'uRes');
const uTime = gl.getUniformLocation(program, 'uTime');
function resize(){
const dpr = Math.min(window.devicePixelRatio || 1, 2.0);
const w = Math.floor(window.innerWidth * dpr);
const h = Math.floor(window.innerHeight * dpr);
if(canvas.width !== w || canvas.height !== h){
canvas.width = w;
canvas.height = h;
}
gl.viewport(0, 0, canvas.width, canvas.height);
}
window.addEventListener('resize', resize);
resize();
let startTime = performance.now();
function render(){
const t = (performance.now() - startTime) * 0.001;
gl.uniform2f(uRes, canvas.width, canvas.height);
gl.uniform1f(uTime, t);
gl.drawArrays(gl.TRIANGLES, 0, 6);
requestAnimationFrame(render);
}
render();
</script>
</body>
</html>