Header
Your content here
Snippets (Version 1)
Shadertoy
// Shadertoy - Raymarched Neon Tunnel
// Actual SDF tunnel with bend, twist, ribs, glow, and forward motion.
#define MAX_STEPS 120
#define MAX_DIST 80.0
#define SURF_DIST 0.0015
#define PI 3.14159265359
#define TAU 6.28318530718
mat2 rot(float a)
{
float c = cos(a), s = sin(a);
return mat2(c, -s, s, c);
}
float hash11(float p)
{
p = fract(p * 0.1031);
p *= p + 33.33;
p *= p + p;
return fract(p);
}
float hash21(vec2 p)
{
p = fract(p * vec2(123.34, 345.45));
p += dot(p, p + 34.345);
return fract(p.x * p.y);
}
float noise(vec3 x)
{
vec3 p = floor(x);
vec3 f = fract(x);
f = f * f * (3.0 - 2.0 * f);
float n = p.x + p.y * 57.0 + 113.0 * p.z;
float a = hash11(n + 0.0);
float b = hash11(n + 1.0);
float c = hash11(n + 57.0);
float d = hash11(n + 58.0);
float e = hash11(n + 113.0);
float f1= hash11(n + 114.0);
float g = hash11(n + 170.0);
float h = hash11(n + 171.0);
return mix(
mix(mix(a, b, f.x), mix(c, d, f.x), f.y),
mix(mix(e, f1,f.x), mix(g, h, f.x), f.y),
f.z
);
}
float fbm(vec3 p)
{
float v = 0.0;
float a = 0.5;
for (int i = 0; i < 5; i++)
{
v += a * noise(p);
p = p * 2.02 + vec3(1.7, -0.9, 1.3);
a *= 0.5;
}
return v;
}
vec3 palette(float t)
{
vec3 a = vec3(0.5);
vec3 b = vec3(0.5);
vec3 c = vec3(1.0);
vec3 d = vec3(0.00, 0.18, 0.38);
return a + b * cos(TAU * (c * t + d));
}
// Tunnel centerline offset as a function of z.
// The world bends because the tunnel's center moves through space.
vec2 tunnelCenter(float z)
{
return vec2(
0.9 * sin(z * 0.18) + 0.35 * sin(z * 0.51),
0.7 * cos(z * 0.16) + 0.25 * cos(z * 0.43)
);
}
vec2 localTubeFrame(vec3 p)
{
vec2 c = tunnelCenter(p.z);
vec2 q = p.xy - c;
float tw = 0.55 * sin(p.z * 0.23) + 0.15 * sin(p.z * 0.71);
q *= rot(tw);
return q;
}
// SDF for the tunnel wall. Negative inside the solid wall region near the shell.
// We raymarch the shell itself, not just a cylinder.
float map(vec3 p)
{
vec2 q = localTubeFrame(p);
float r = length(q);
float ang = atan(q.y, q.x);
float baseRadius =
1.10
+ 0.08 * sin(p.z * 0.70)
+ 0.05 * sin(p.z * 1.90 + sin(p.z * 0.2));
float shell = abs(r - baseRadius) - 0.10;
float ribs = sin(ang * 14.0 + p.z * 2.4);
ribs = 0.5 + 0.5 * ribs;
ribs = pow(ribs, 8.0);
float lanes = sin(ang * 6.0 - p.z * 1.3);
lanes = 0.5 + 0.5 * lanes;
lanes = pow(lanes, 18.0);
float detail = fbm(vec3(ang * 2.5, r * 3.0, p.z * 0.4));
shell -= 0.025 * ribs;
shell -= 0.035 * lanes;
shell -= 0.015 * detail;
return shell;
}
vec3 getNormal(vec3 p)
{
vec2 e = vec2(0.0015, 0.0);
float d = map(p);
vec3 n = d - vec3(
map(p - e.xyy),
map(p - e.yxy),
map(p - e.yyx)
);
return normalize(n);
}
float softShadow(vec3 ro, vec3 rd, float mint, float maxt, float k)
{
float res = 1.0;
float t = mint;
for (int i = 0; i < 40; i++)
{
float h = map(ro + rd * t);
res = min(res, k * h / t);
t += clamp(h, 0.02, 0.25);
if (h < 0.001 || t > maxt) break;
}
return clamp(res, 0.0, 1.0);
}
float ao(vec3 p, vec3 n)
{
float occ = 0.0;
float sca = 1.0;
for (int i = 1; i <= 5; i++)
{
float h = 0.03 * float(i);
float d = map(p + n * h);
occ += (h - d) * sca;
sca *= 0.7;
}
return clamp(1.0 - occ, 0.0, 1.0);
}
bool raymarch(vec3 ro, vec3 rd, out vec3 hitPos, out float tHit, out float glowAccum)
{
float t = 0.0;
glowAccum = 0.0;
for (int i = 0; i < MAX_STEPS; i++)
{
vec3 p = ro + rd * t;
float d = map(p);
// accumulate near-surface glow
glowAccum += 0.02 / (0.02 + abs(d) * abs(d) * 40.0);
if (d < SURF_DIST)
{
hitPos = p;
tHit = t;
return true;
}
if (t > MAX_DIST) break;
t += clamp(d, 0.006, 0.35);
}
hitPos = ro + rd * t;
tHit = t;
return false;
}
vec3 background(vec3 rd)
{
float t = 0.5 + 0.5 * rd.y;
vec3 col = mix(vec3(0.005, 0.008, 0.015), vec3(0.02, 0.03, 0.06), t);
vec2 suv = rd.xy / max(0.2, abs(rd.z));
vec2 gv = fract(suv * 28.0) - 0.5;
vec2 id = floor(suv * 28.0);
float n = hash21(id);
float d = length(gv);
float star = smoothstep(0.035, 0.0, d) * step(0.992, n);
col += vec3(0.6, 0.8, 1.2) * star * 2.0;
return col;
}
void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y;
float time = iTime;
// Forward speed through tunnel
float speed = 8.0;
float zTravel = time * speed;
// Camera follows the tunnel centerline
vec3 ro = vec3(tunnelCenter(zTravel), zTravel);
// Look ahead down the tunnel
float lookZ = zTravel + 1.5;
vec3 target = vec3(tunnelCenter(lookZ), lookZ);
// Camera wobble
ro.xy += 0.03 * vec2(sin(time * 1.7), cos(time * 1.2));
// Build camera basis
vec3 forward = normalize(target - ro);
vec3 right = normalize(cross(vec3(0.0, 1.0, 0.0), forward));
vec3 up = cross(forward, right);
vec3 rd = normalize(forward + uv.x * right + uv.y * up);
vec3 hitPos;
float tHit;
float glowAccum;
bool hit = raymarch(ro, rd, hitPos, tHit, glowAccum);
vec3 col = background(rd);
if (hit)
{
vec3 n = getNormal(hitPos);
vec2 q = localTubeFrame(hitPos);
float r = length(q);
float ang = atan(q.y, q.x);
float baseRadius =
1.10
+ 0.08 * sin(hitPos.z * 0.70)
+ 0.05 * sin(hitPos.z * 1.90 + sin(hitPos.z * 0.2));
float ribs = sin(ang * 14.0 + hitPos.z * 2.4);
ribs = pow(0.5 + 0.5 * ribs, 8.0);
float lanes = sin(ang * 6.0 - hitPos.z * 1.3);
lanes = pow(0.5 + 0.5 * lanes, 18.0);
float rings = sin(hitPos.z * 5.2);
rings = pow(0.5 + 0.5 * rings, 20.0);
float panelNoise = fbm(vec3(ang * 3.0, r * 4.0, hitPos.z * 0.6));
float hue = hitPos.z * 0.035 + ang * 0.12 + panelNoise * 0.35;
vec3 base = palette(hue);
vec3 lightPos = ro + vec3(0.0, 0.0, 3.0);
vec3 l = normalize(lightPos - hitPos);
vec3 v = normalize(ro - hitPos);
vec3 h = normalize(l + v);
float diff = max(dot(n, l), 0.0);
float spec = pow(max(dot(n, h), 0.0), 48.0);
float sh = softShadow(hitPos + n * 0.01, l, 0.02, 6.0, 12.0);
float occ = ao(hitPos, n);
float fres = pow(1.0 - max(dot(n, v), 0.0), 3.0);
vec3 emissive = vec3(0.0);
emissive += palette(hue + 0.08) * ribs * 1.6;
emissive += vec3(0.15, 0.85, 1.8) * lanes * 2.1;
emissive += vec3(1.3, 0.2, 1.6) * rings * 0.8;
emissive += base * panelNoise * 0.25;
vec3 surf = base * (0.10 + 0.90 * diff * sh) * occ;
surf += vec3(1.0) * spec * 0.9 * sh;
surf += emissive;
surf += base * fres * 0.35;
// Distance fog toward neon
float fog = 1.0 - exp(-tHit * 0.035);
vec3 fogCol = palette(hitPos.z * 0.03 + 0.2) * 0.25;
col = mix(surf, fogCol, fog);
}
// Volumetric-ish accumulated glow from near surfaces
col += vec3(0.08, 0.25, 0.55) * glowAccum * 0.22;
col += vec3(0.45, 0.10, 0.65) * glowAccum * 0.08;
// Bright center boost / motion feel
float center = 0.03 / (dot(uv, uv) + 0.04);
col += vec3(0.08, 0.16, 0.30) * center;
// Vignette
float vig = smoothstep(1.4, 0.2, length(uv));
col *= vig;
// Tonemap + gamma
col = 1.0 - exp(-col * 0.95);
col = pow(col, vec3(0.92));
fragColor = vec4(col, 1.0);
}
Variations
Version 2
With obstacles.
Shadertoy
// Shadertoy - Neon Tunnel + Chrome Drones + Floating Obstacles
#define MAX_STEPS 140
#define MAX_DIST 90.0
#define SURF_DIST 0.0015
#define PI 3.14159265359
#define TAU 6.28318530718
mat2 rot(float a)
{
float c = cos(a), s = sin(a);
return mat2(c, -s, s, c);
}
float hash11(float p)
{
p = fract(p * 0.1031);
p *= p + 33.33;
p *= p + p;
return fract(p);
}
float hash21(vec2 p)
{
p = fract(p * vec2(123.34, 345.45));
p += dot(p, p + 34.345);
return fract(p.x * p.y);
}
vec2 hash22(vec2 p)
{
float n = hash21(p);
return vec2(n, hash21(p + 19.37));
}
float noise(vec3 x)
{
vec3 p = floor(x);
vec3 f = fract(x);
f = f * f * (3.0 - 2.0 * f);
float n = p.x + p.y * 57.0 + 113.0 * p.z;
float a = hash11(n + 0.0);
float b = hash11(n + 1.0);
float c = hash11(n + 57.0);
float d = hash11(n + 58.0);
float e = hash11(n + 113.0);
float f1= hash11(n + 114.0);
float g = hash11(n + 170.0);
float h = hash11(n + 171.0);
return mix(
mix(mix(a, b, f.x), mix(c, d, f.x), f.y),
mix(mix(e, f1, f.x), mix(g, h, f.x), f.y),
f.z
);
}
float fbm(vec3 p)
{
float v = 0.0;
float a = 0.5;
for (int i = 0; i < 5; i++)
{
v += a * noise(p);
p = p * 2.02 + vec3(1.7, -0.9, 1.3);
a *= 0.5;
}
return v;
}
vec3 palette(float t)
{
vec3 a = vec3(0.5);
vec3 b = vec3(0.5);
vec3 c = vec3(1.0);
vec3 d = vec3(0.00, 0.18, 0.38);
return a + b * cos(TAU * (c * t + d));
}
vec2 tunnelCenter(float z)
{
return vec2(
0.9 * sin(z * 0.18) + 0.35 * sin(z * 0.51),
0.7 * cos(z * 0.16) + 0.25 * cos(z * 0.43)
);
}
vec2 localTubeFrame(vec3 p)
{
vec2 c = tunnelCenter(p.z);
vec2 q = p.xy - c;
float tw = 0.55 * sin(p.z * 0.23) + 0.15 * sin(p.z * 0.71);
q *= rot(tw);
return q;
}
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;
}
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);
}
struct Res
{
float d;
float m;
};
Res makeRes(float d, float m)
{
Res r;
r.d = d;
r.m = m;
return r;
}
Res opU(Res a, Res b)
{
if (a.d < b.d)
return a;
else
return b;
}
Res mapScene(vec3 p)
{
vec2 q = localTubeFrame(p);
float r = length(q);
float ang = atan(q.y, q.x);
float baseRadius =
1.10
+ 0.08 * sin(p.z * 0.70)
+ 0.05 * sin(p.z * 1.90 + sin(p.z * 0.2));
float shell = abs(r - baseRadius) - 0.10;
float ribs = sin(ang * 14.0 + p.z * 2.4);
ribs = pow(0.5 + 0.5 * ribs, 8.0);
float lanes = sin(ang * 6.0 - p.z * 1.3);
lanes = pow(0.5 + 0.5 * lanes, 18.0);
float detail = fbm(vec3(ang * 2.5, r * 3.0, p.z * 0.4));
shell -= 0.025 * ribs;
shell -= 0.035 * lanes;
shell -= 0.015 * detail;
Res res = makeRes(shell, 1.0); // 1=tunnel
// Repeating objects down the tunnel in z-cells
float cell = floor((p.z + 3.0) / 4.0);
float localZ = mod(p.z + 3.0, 4.0) - 2.0;
for (int i = -1; i <= 1; i++)
{
float cidx = cell + float(i);
float zc = cidx * 4.0;
vec2 rnd = hash22(vec2(cidx, 7.13));
vec2 rnd2 = hash22(vec2(cidx, 13.77));
float a0 = rnd.x * TAU + p.z * 0.03;
float rad = 0.55 + 0.18 * sin(cidx * 1.7);
vec2 off = vec2(cos(a0), sin(a0)) * rad;
vec3 lp = p;
lp.xy -= tunnelCenter(zc);
lp.xy = rot(-(0.55 * sin(zc * 0.23) + 0.15 * sin(zc * 0.71))) * lp.xy;
lp.xy -= off;
lp.z -= zc;
// Chrome drone: spinning octahedron with a small body blend
vec3 dp = lp;
dp.xy *= rot(iTime * 1.6 + cidx);
dp.xz *= rot(iTime * 1.1 + rnd.y * 4.0);
float droneCore = sdOcta(dp, 0.34);
float droneBody = sdBox(dp, vec3(0.08, 0.08, 0.28));
float drone = smin(droneCore, droneBody, 0.14);
drone -= 0.02 * sin(18.0 * atan(dp.y, dp.x) + iTime * 3.0);
res = opU(res, makeRes(drone, 2.0)); // 2=chrome drone
// Floating obstacle: glowing cube / gate fragment
vec3 op = p;
op.xy -= tunnelCenter(zc + 1.8);
op.xy = rot(-(0.55 * sin((zc + 1.8) * 0.23) + 0.15 * sin((zc + 1.8) * 0.71))) * op.xy;
float oa = rnd2.x * TAU + iTime * (0.4 + rnd2.y);
vec2 ooff = vec2(cos(oa), sin(oa)) * (0.28 + 0.18 * rnd2.x);
op.xy -= ooff;
op.z -= (zc + 1.8);
op.xy *= rot(iTime * 0.8 + cidx * 2.0);
op.yz *= rot(iTime * 0.6 + cidx * 0.7);
float obstacle = sdBox(op, vec3(0.18 + 0.08 * rnd2.y));
res = opU(res, makeRes(obstacle, 3.0)); // 3=obstacle
}
return res;
}
vec3 getNormal(vec3 p)
{
vec2 e = vec2(0.0015, 0.0);
float d = mapScene(p).d;
vec3 n = d - vec3(
mapScene(p - e.xyy).d,
mapScene(p - e.yxy).d,
mapScene(p - e.yyx).d
);
return normalize(n);
}
float softShadow(vec3 ro, vec3 rd, float mint, float maxt, float k)
{
float res = 1.0;
float t = mint;
for (int i = 0; i < 40; i++)
{
float h = mapScene(ro + rd * t).d;
res = min(res, k * h / t);
t += clamp(h, 0.02, 0.25);
if (h < 0.001 || t > maxt) break;
}
return clamp(res, 0.0, 1.0);
}
float ao(vec3 p, vec3 n)
{
float occ = 0.0;
float sca = 1.0;
for (int i = 1; i <= 5; i++)
{
float h = 0.03 * float(i);
float d = mapScene(p + n * h).d;
occ += (h - d) * sca;
sca *= 0.7;
}
return clamp(1.0 - occ, 0.0, 1.0);
}
bool raymarch(vec3 ro, vec3 rd, out vec3 hitPos, out float tHit, out float glowAccum, out float matId)
{
float t = 0.0;
glowAccum = 0.0;
matId = 0.0;
for (int i = 0; i < MAX_STEPS; i++)
{
vec3 p = ro + rd * t;
Res h = mapScene(p);
glowAccum += 0.02 / (0.02 + abs(h.d) * abs(h.d) * 40.0);
if (h.d < SURF_DIST)
{
hitPos = p;
tHit = t;
matId = h.m;
return true;
}
if (t > MAX_DIST) break;
t += clamp(h.d, 0.006, 0.35);
}
hitPos = ro + rd * t;
tHit = t;
return false;
}
vec3 background(vec3 rd)
{
float t = 0.5 + 0.5 * rd.y;
vec3 col = mix(vec3(0.005, 0.008, 0.015), vec3(0.02, 0.03, 0.06), t);
vec2 suv = rd.xy / max(0.2, abs(rd.z));
vec2 gv = fract(suv * 28.0) - 0.5;
vec2 id = floor(suv * 28.0);
float n = hash21(id);
float d = length(gv);
float star = smoothstep(0.035, 0.0, d) * step(0.992, n);
col += vec3(0.6, 0.8, 1.2) * star * 2.0;
return col;
}
vec3 tunnelEnv(vec3 p, vec3 rd)
{
vec2 q = localTubeFrame(p + rd * 1.8);
float ang = atan(q.y, q.x);
float z = p.z + rd.z * 2.0;
float phase = z * 0.04 + ang * 0.12;
vec3 env = palette(phase) * 0.25;
float stripes = pow(0.5 + 0.5 * sin(ang * 12.0 + z * 2.8), 12.0);
float rings = pow(0.5 + 0.5 * sin(z * 6.0), 20.0);
env += vec3(0.10, 0.45, 1.10) * stripes;
env += vec3(1.10, 0.15, 1.30) * rings * 0.4;
return env;
}
vec3 shadeTunnel(vec3 ro, vec3 rd, vec3 p, vec3 n, float tHit)
{
vec2 q = localTubeFrame(p);
float r = length(q);
float ang = atan(q.y, q.x);
float panelNoise = fbm(vec3(ang * 3.0, r * 4.0, p.z * 0.6));
float hue = p.z * 0.035 + ang * 0.12 + panelNoise * 0.35;
vec3 base = palette(hue);
float ribs = pow(0.5 + 0.5 * sin(ang * 14.0 + p.z * 2.4), 8.0);
float lanes = pow(0.5 + 0.5 * sin(ang * 6.0 - p.z * 1.3), 18.0);
float rings = pow(0.5 + 0.5 * sin(p.z * 5.2), 20.0);
vec3 lightPos = ro + vec3(0.0, 0.0, 3.0);
vec3 l = normalize(lightPos - p);
vec3 v = normalize(ro - p);
vec3 h = normalize(l + v);
float diff = max(dot(n, l), 0.0);
float spec = pow(max(dot(n, h), 0.0), 48.0);
float sh = softShadow(p + n * 0.01, l, 0.02, 6.0, 12.0);
float occ = ao(p, n);
float fres = pow(1.0 - max(dot(n, v), 0.0), 3.0);
vec3 emissive = vec3(0.0);
emissive += palette(hue + 0.08) * ribs * 1.6;
emissive += vec3(0.15, 0.85, 1.8) * lanes * 2.1;
emissive += vec3(1.3, 0.2, 1.6) * rings * 0.8;
emissive += base * panelNoise * 0.25;
vec3 surf = base * (0.10 + 0.90 * diff * sh) * occ;
surf += vec3(1.0) * spec * 0.9 * sh;
surf += emissive;
surf += base * fres * 0.35;
float fog = 1.0 - exp(-tHit * 0.035);
vec3 fogCol = palette(p.z * 0.03 + 0.2) * 0.25;
return mix(surf, fogCol, fog);
}
vec3 shadeDrone(vec3 ro, vec3 rd, vec3 p, vec3 n, float tHit)
{
vec3 v = normalize(ro - p);
vec3 refl = reflect(-v, n);
vec3 env = tunnelEnv(p, refl);
vec3 lightPos = ro + vec3(0.0, 0.0, 3.0);
vec3 l = normalize(lightPos - p);
vec3 h = normalize(l + v);
float diff = max(dot(n, l), 0.0);
float spec = pow(max(dot(n, h), 0.0), 96.0);
float fres = pow(1.0 - max(dot(n, v), 0.0), 5.0);
float occ = ao(p, n);
float seam = pow(0.5 + 0.5 * sin(20.0 * atan(n.y, n.x) + p.z * 8.0), 24.0);
vec3 col = vec3(0.03) + env * 1.3;
col += vec3(1.0) * spec * 0.8;
col += vec3(0.10, 0.90, 1.60) * seam * 0.9;
col += diff * vec3(0.08);
col = mix(col, vec3(1.0), fres * 0.25);
col *= occ;
float fog = 1.0 - exp(-tHit * 0.04);
return mix(col, palette(p.z * 0.03) * 0.18, fog);
}
vec3 shadeObstacle(vec3 ro, vec3 rd, vec3 p, vec3 n, float tHit)
{
vec3 v = normalize(ro - p);
vec3 lightPos = ro + vec3(0.0, 0.0, 3.0);
vec3 l = normalize(lightPos - p);
vec3 h = normalize(l + v);
float diff = max(dot(n, l), 0.0);
float spec = pow(max(dot(n, h), 0.0), 32.0);
float fres = pow(1.0 - max(dot(n, v), 0.0), 4.0);
float occ = ao(p, n);
float edge = pow(1.0 - abs(dot(n, normalize(vec3(1.0, 1.0, 1.0)))), 8.0);
float pulse = pow(0.5 + 0.5 * sin(p.z * 8.0 - iTime * 8.0), 10.0);
vec3 base = vec3(0.04, 0.03, 0.05);
vec3 emi = vec3(1.2, 0.15, 1.5) * edge + vec3(0.1, 0.7, 1.5) * pulse;
vec3 col = base * (0.15 + 0.85 * diff) * occ;
col += vec3(1.0) * spec * 0.6;
col += emi;
col += fres * vec3(0.4, 0.1, 0.7);
float fog = 1.0 - exp(-tHit * 0.04);
return mix(col, palette(p.z * 0.025) * 0.20, fog);
}
void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y;
float time = iTime;
float speed = 8.0;
float zTravel = time * speed;
vec3 ro = vec3(tunnelCenter(zTravel), zTravel);
float lookZ = zTravel + 1.5;
vec3 target = vec3(tunnelCenter(lookZ), lookZ);
ro.xy += 0.03 * vec2(sin(time * 1.7), cos(time * 1.2));
vec3 forward = normalize(target - ro);
vec3 right = normalize(cross(vec3(0.0, 1.0, 0.0), forward));
vec3 up = cross(forward, right);
vec3 rd = normalize(forward + uv.x * right + uv.y * up);
vec3 hitPos;
float tHit;
float glowAccum;
float matId;
bool hit = raymarch(ro, rd, hitPos, tHit, glowAccum, matId);
vec3 col = background(rd);
if (hit)
{
vec3 n = getNormal(hitPos);
if (matId < 1.5)
col = shadeTunnel(ro, rd, hitPos, n, tHit);
else if (matId < 2.5)
col = shadeDrone(ro, rd, hitPos, n, tHit);
else
col = shadeObstacle(ro, rd, hitPos, n, tHit);
}
col += vec3(0.08, 0.25, 0.55) * glowAccum * 0.22;
col += vec3(0.45, 0.10, 0.65) * glowAccum * 0.08;
float center = 0.03 / (dot(uv, uv) + 0.04);
col += vec3(0.08, 0.16, 0.30) * center;
float vig = smoothstep(1.4, 0.2, length(uv));
col *= vig;
col = 1.0 - exp(-col * 0.95);
col = pow(col, vec3(0.92));
fragColor = vec4(col, 1.0);
}