Shader: precision highp float; uniform vec2 resolution; uniform float time; uniform vec2 mouse; // ------------------------------------------------------------ // Hash / noise // ------------------------------------------------------------ float hash12(vec2 p) { return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453); } vec2 hash22(vec2 p) { p = vec2(dot(p, vec2(127.1, 311.7)), dot(p, vec2(269.5, 183.3))); return fract(sin(p) * 43758.5453); } float noise(vec2 p) { vec2 i = floor(p); vec2 f = fract(p); float a = hash12(i); float b = hash12(i + vec2(1.0, 0.0)); float c = hash12(i + vec2(0.0, 1.0)); float d = hash12(i + vec2(1.0, 1.0)); vec2 u = f * f * (3.0 - 2.0 * f); return mix(a, b, u.x) + (c - a) * u.y * (1.0 - u.x) + (d - b) * u.x * u.y; } float fbm(vec2 p) { float v = 0.0, a = 0.5; mat2 m = mat2(1.6, 1.2, -1.2, 1.6); for (int i = 0; i < 4; i++) { v += a * noise(p); p = m * p; a *= 0.5; } return v; } // ------------------------------------------------------------ // SDFs // ------------------------------------------------------------ float sdBox(vec3 p, vec3 b) { vec3 q = abs(p) - b; return length(max(q, 0.0)) + min(max(q.x, max(q.y, q.z)), 0.0); } float sdOcta(vec3 p, float s) { p = abs(p); return (p.x + p.y + p.z - s) * 0.57735027; } // ------------------------------------------------------------ // Light tower (kept simple) // ------------------------------------------------------------ float tower(vec3 p, vec2 id, float hMul) { float d = 1e5; float H = 18.0 + 30.0 * hMul; float W = 2.8 + 0.9 * hash12(id + 1.7); // foundations d = min(d, sdBox(p - vec3(0.0, -10.0, 0.0), vec3(W + 2.2, 12.0, W + 2.2))); // main shaft (two steps) d = min(d, sdBox(p - vec3(0.0, H * 0.32, 0.0), vec3(W, H * 0.32, W))); d = min(d, sdBox(p - vec3(0.0, H * 0.68, 0.0), vec3(W * 0.72, H * 0.28, W * 0.72))); // cornices d = min(d, sdBox(p - vec3(0.0, H * 0.28, 0.0), vec3(W + 0.55, 0.25, W + 0.55))); d = min(d, sdBox(p - vec3(0.0, H * 0.55, 0.0), vec3(W * 0.8 + 0.4, 0.22, W * 0.8 + 0.4))); // central spire d = min(d, sdBox(p - vec3(0.0, H + 3.0, 0.0), vec3(0.9, 4.5, 0.9))); d = min(d, sdOcta(p - vec3(0.0, H + 9.0, 0.0), 3.4)); d = min(d, sdBox(p - vec3(0.0, H + 12.5, 0.0), vec3(0.2, 2.5, 0.2))); // 4 corner turrets (simple) for (int i = 0; i < 4; i++) { float a = float(i) * 1.5708 + 0.7854; vec3 o = vec3(cos(a), 0.0, sin(a)) * (W * 0.95); vec3 tp = p - o; float tH = H * 0.55 + 5.0; d = min(d, sdBox(tp - vec3(0.0, tH * 0.45, 0.0), vec3(0.95, tH * 0.45, 0.95))); d = min(d, sdOcta(tp - vec3(0.0, tH + 1.8, 0.0), 1.8)); } // tiny buttresses for (int i = 0; i < 4; i++) { float a = float(i) * 1.5708; vec3 dir = vec3(cos(a), 0.0, sin(a)); d = min(d, sdBox(p - dir * (W + 1.6) - vec3(0.0, H * 0.28, 0.0), vec3(0.55, H * 0.28, 0.55))); } return d * 0.9; } float city(vec3 p) { float cell = 28.0; vec2 id = floor(p.xz / cell); vec3 q = p; q.xz = mod(p.xz + 0.5 * cell, cell) - 0.5 * cell; // slight jitter q.xz -= (hash22(id) - 0.5) * 5.0; float hm = pow(hash12(id), 0.6); float d = tower(q, id, hm); // occasional second lower tower if (hash12(id + 9.0) > 0.72) { vec2 o2 = (hash22(id + 3.1) - 0.5) * 9.0; d = min(d, tower(q - vec3(o2.x, 0.0, o2.y), id + 20.0, hm * 0.7)); } // rare big cathedral if (hash12(id * 0.7) > 0.92) { d = min(d, tower(q * vec3(1.1, 1.25, 1.1), id + 50.0, 1.05)); } return d; } // ------------------------------------------------------------ // Ocean // ------------------------------------------------------------ float ocean(vec2 xz) { float t = time; float h = 0.0; h += 2.4 * sin(xz.x * 0.055 + t * 0.55) * cos(xz.y * 0.042 + t * 0.40); h += 1.8 * sin(xz.y * 0.07 - t * 0.70 + xz.x * 0.02); h += 1.3 * sin(dot(xz, normalize(vec2(0.75, 0.65))) * 0.10 + t * 0.95); h += 0.9 * sin(dot(xz, normalize(vec2(-0.55, 0.8))) * 0.14 - t * 1.1); h += 1.1 * (fbm(xz * 0.06 + t * 0.09) - 0.4); h += 0.35 * fbm(xz * 0.32 + vec2(t * 0.2, -t * 0.15)); return h; } // ------------------------------------------------------------ // Scene // ------------------------------------------------------------ float map(vec3 p) { return min(city(p), p.y - ocean(p.xz)); } float mapB(vec3 p) { return city(p); } vec3 nBuild(vec3 p) { vec2 e = vec2(0.02, 0.0); return normalize(vec3( mapB(p + e.xyy) - mapB(p - e.xyy), mapB(p + e.yxy) - mapB(p - e.yxy), mapB(p + e.yyx) - mapB(p - e.yyx) )); } vec3 nOcean(vec2 xz) { float e = 0.12; float h = ocean(xz); return normalize(vec3( h - ocean(xz + vec2(e, 0.0)), e, h - ocean(xz + vec2(0.0, e)) )); } // ------------------------------------------------------------ // Lightning + sky // ------------------------------------------------------------ float lightning(float t) { float c = t * 0.4; float id = floor(c); float f = fract(c); float r = hash12(vec2(id, 7.3)); if (r > 0.87) { float b = exp(-f * 8.0) * (0.7 + 0.3 * sin(t * 50.0)); b += 0.4 * exp(-abs(f - 0.1) * 28.0) * step(0.4, hash12(vec2(id, 2.2))); return clamp(b, 0.0, 1.4); } return 0.0; } vec3 sky(vec3 rd, float fl) { float y = max(rd.y, 0.0); vec3 col = mix(vec3(0.012, 0.018, 0.03), vec3(0.055, 0.06, 0.075), exp(-y * 3.0)); col = mix(col, vec3(0.07, 0.06, 0.055), exp(-abs(rd.y) * 8.0) * 0.6); vec2 cuv = rd.xz / (rd.y + 0.15); float clouds = smoothstep(0.3, 0.75, fbm(cuv * 1.6 + time * 0.03)); col = mix(col, vec3(0.025, 0.028, 0.04), clouds * 0.85); col += fl * vec3(0.4, 0.45, 0.75) * (0.3 + 0.7 * clouds); return col; } // ------------------------------------------------------------ // Main // ------------------------------------------------------------ void main() { vec2 uv = (gl_FragCoord.xy - 0.5 * resolution.xy) / resolution.y; // mouse look (optional) vec2 mo = mouse / max(resolution, vec2(1.0)); mo = mo * 2.0 - 1.0; if (length(mouse) < 0.5) mo = vec2(0.0); float t = time; float fl = lightning(t); // cinematic path float path = t * 2.9; float h0 = 8.5 + 4.0 * sin(t * 0.22); vec3 ro = vec3(path, h0 + ocean(vec2(path, 0.0)) * 0.35, 7.5 * sin(t * 0.14)); vec3 ta = ro + vec3(12.0, -1.0 + 5.0 * sin(t * 0.16) + mo.y * 4.0, 5.0 * cos(t * 0.12) + mo.x * 6.0); // thunder shake ro.xy += (hash22(vec2(floor(t * 7.0), 1.1)) - 0.5) * fl * 0.3; vec3 ww = normalize(ta - ro); vec3 uu = normalize(cross(ww, vec3(0.0, 1.0, 0.0))); vec3 vv = cross(uu, ww); vec3 rd = normalize(uv.x * uu + uv.y * vv + 1.3 * ww); // ---- raymarch (lighter) ---- float trav = 0.0; float tmax = 160.0; vec3 p; for (int i = 0; i < 90; i++) { p = ro + rd * trav; float d = map(p); if (d < 0.0015 * trav || trav > tmax) break; trav += d * 0.8; } vec3 col = sky(rd, fl); if (trav < tmax) { p = ro + rd * trav; float dB = mapB(p); float h = ocean(p.xz); bool water = (p.y - h) < dB * 0.9 + 0.02; if (water) { // ---- WATER ---- vec3 n = nOcean(p.xz); // foam float eps = 0.15; float steep = length(vec2( ocean(p.xz + vec2(eps, 0.0)) - h, ocean(p.xz + vec2(0.0, eps)) - h)) / eps; float foam = smoothstep(0.55, 1.4, steep); foam = max(foam, smoothstep(1.6, 3.2, h + 0.6 * fbm(p.xz * 0.3 + t * 0.2))); foam = clamp(foam, 0.0, 1.0); float fres = pow(1.0 - max(dot(-rd, n), 0.0), 4.0); // cheap reflection (sky only) vec3 refl = sky(reflect(rd, n), fl); vec3 waterCol = mix(vec3(0.005, 0.03, 0.04), vec3(0.02, 0.055, 0.06), max(dot(-rd, n), 0.0)); waterCol = mix(waterCol, vec3(0.7, 0.76, 0.85), foam * 0.9); vec3 L = normalize(vec3(0.4, 0.8, 0.35)); float diff = max(dot(n, L), 0.0); float spec = pow(max(dot(reflect(-L, n), -rd), 0.0), 70.0); float lspec = fl * max(dot(n, vec3(0.0, 1.0, 0.0)), 0.0) * 1.2; col = waterCol * (0.25 + diff * 0.4 + fl * 0.3); col += vec3(0.9, 0.95, 1.0) * (spec * 0.8 + lspec * 0.5); col = mix(col, refl, fres * (0.65 + 0.3 * foam)); col += foam * foam * 0.25; } else { // ---- STONE ---- vec3 n = nBuild(p); vec2 id = floor(p.xz / 28.0); vec3 albedo = vec3(0.12, 0.11, 0.105); albedo *= 0.7 + 0.45 * hash12(id + floor(p.y * 0.2)); // algae lower down albedo = mix(vec3(0.04, 0.06, 0.045), albedo, smoothstep(-1.0, 20.0, p.y) * 0.9 + 0.1); float wet = clamp(exp(-(p.y - h) * 0.5), 0.0, 1.0); albedo *= mix(1.0, 0.3, wet); // glowing windows float fy = fract(p.y * 0.38); float fxz = min(abs(fract(p.x * 0.5) - 0.5), abs(fract(p.z * 0.5) - 0.5)); float win = step(0.22, fy) * step(fy, 0.7) * step(fxz, 0.09); float lit = step(0.5, hash12(floor(p.xy * vec2(0.5, 0.38)) + id)); lit *= 0.7 + 0.3 * sin(t * 3.0 + hash12(id) * 10.0); vec3 glowHue = mix(vec3(1.0, 0.5, 0.15), vec3(0.3, 0.8, 1.15), hash12(id + 5.0)); vec3 emission = glowHue * win * lit * 2.6; // top lantern glow emission += vec3(1.0, 0.7, 0.3) * smoothstep(24.0, 40.0, p.y) * 0.35 * (0.5 + 0.5 * fl); vec3 L = normalize(vec3(0.35, 0.8, 0.4)); float dif = max(dot(n, L), 0.0); float amb = 0.18 + 0.16 * n.y + fl * 0.55; float spec = pow(max(dot(reflect(-L, n), -rd), 0.0), mix(16.0, 40.0, wet)) * 0.4 * mix(0.2, 0.8, wet); // cheap AO float ao = clamp(mapB(p + n * 1.0) / 1.0, 0.4, 1.0); col = albedo * (amb + dif * 0.65) * ao + vec3(0.95) * spec + emission; } // fog float fog = 1.0 - exp(-trav * trav * 0.00004); fog = clamp(fog + 0.12 * (1.0 - exp(-trav * 0.015)), 0.0, 1.0); vec3 fogC = mix(vec3(0.04, 0.045, 0.06), sky(rd, fl) * 0.5, 0.4); fogC += fl * 0.06; col = mix(col, fogC, fog); } // ---- post ---- // rain vec2 ruv = uv * vec2(48.0, 20.0) + vec2(0.0, -t * 25.0); float rain = smoothstep(0.93, 0.995, noise(floor(ruv))); ruv = uv * vec2(80.0, 35.0) + vec2(12.0, -t * 38.0); rain += 0.5 * smoothstep(0.94, 0.998, noise(floor(ruv))); col += rain * (0.1 + 0.05 * fl) * vec3(0.6, 0.68, 0.8); // screen flash col += fl * 0.07; // bloom-ish col += max(col - 0.7, 0.0) * 0.4; // grade + tone col = col * 1.15 / (1.0 + col * 0.5); col = mix(col, col * vec3(0.82, 0.93, 1.15), 0.35); // vignette vec2 v = gl_FragCoord.xy / resolution.xy; float vig = v.x * v.y * (1.0 - v.x) * (1.0 - v.y); col *= mix(0.55, 1.0, pow(vig * 14.0, 0.25)); col = pow(max(col, 0.0), vec3(0.92)); gl_FragColor = vec4(col, 1.0); }