diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index fa7d1f4..feb681e 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -84,6 +84,8 @@ function App() { + + {isShaderTheme && } diff --git a/frontend/src/ShaderBackground.tsx b/frontend/src/ShaderBackground.tsx index 522806a..ece2667 100644 --- a/frontend/src/ShaderBackground.tsx +++ b/frontend/src/ShaderBackground.tsx @@ -3,6 +3,8 @@ import { useEffect, useRef } from "react"; import BLACKHOLE_SHADER_CODE from "./assets/blackhole.glsl?raw"; import STAR_SHADER_CODE from "./assets/star.glsl?raw"; import BALL_SHADER_CODE from "./assets/ball.glsl?raw"; +import REFLECT_BALL_SHADER_CODE from "./assets/reflect.glsl?raw"; +import CLOUDS_SHADER_CODE from "./assets/clouds.glsl?raw"; function buildProgram( ctx: WebGL2RenderingContext, @@ -145,6 +147,12 @@ export const ShaderBackground: React.FC = ({ case "ball": shader_code = BALL_SHADER_CODE; break; + case "reflect": + shader_code = REFLECT_BALL_SHADER_CODE; + break; + case "clouds": + shader_code = CLOUDS_SHADER_CODE; + break; default: console.error("Unknown shader theme:", theme); return; diff --git a/frontend/src/assets/clouds.glsl b/frontend/src/assets/clouds.glsl new file mode 100644 index 0000000..02cf064 --- /dev/null +++ b/frontend/src/assets/clouds.glsl @@ -0,0 +1,62 @@ +#version 300 es +precision highp float; +uniform vec3 iResolution; +uniform float iTime; + +out vec4 FragColor; + +#define T (iTime) + +float orb(vec3 p) { + // orb time + float t = T * 4.; + return length(p - vec3(sin(sin(t * .2) + t * .4) * 6., 1. + sin(sin(t * .5) + t * .2) * 4., 12. + T + cos(t * .3) * 8.)); +} + +void mainImage(out vec4 o, vec2 u) { + float d, a, e, i, s, t = T; + vec3 p = iResolution; + + // scale coords + u = (u + u - p.xy) / p.y; + + // camera movement + u += vec2(cos(t * .1) * .3, cos(t * .3) * .1); + + for(o *= i; i++ < 128.; + + // accumulate distance + d += s = min(.03 + .2 * abs(s), e = max(.5 * e, .01)), + + // grayscale color and orb light + o += 1. / (s + e * 3.)) + + // noise loop start, march + for(p = vec3(u * d, d + t), // p = ro + rd *d, p.z + t; + + // entity (orb) + e = orb(p) - .1, + + // spin by t, twist by p.z + p.xy *= mat2(cos(.1 * t + p.z / 8. + vec4(0, 33, 11, 0))), + + // mirrored planes 4 units apart + s = 4. - abs(p.y), + + // noise starts at .8 up to 32., grow by a+=a + a = .8; a < 32.; a += a) + + // apply turbulence + p += cos(.7 * t + p.yzx) * .2, + + // apply noise + s -= abs(dot(sin(.1 * t + p * a), .6 + p - p)) / a; + + // tanh tonemap, brightness, light off-screen + o = tanh(o / 1e1); +} + + +void main() { + mainImage(FragColor, gl_FragCoord.xy); +} \ No newline at end of file diff --git a/frontend/src/assets/reflect.glsl b/frontend/src/assets/reflect.glsl new file mode 100644 index 0000000..d808120 --- /dev/null +++ b/frontend/src/assets/reflect.glsl @@ -0,0 +1,375 @@ +#version 300 es +precision highp float; +uniform vec3 iResolution; +uniform float iTime; + +out vec4 FragColor; + +// CC0: Let's self reflect +// Always enjoyed the videos of Platonic solids with inner mirrors +// I made some previous attempts but thought I make another attempt it + +// Reducing the alias effects on the inner reflections turned out to be a bit tricky. +// Simplest solution is just to run run fullscreen on a 4K screen ;) + +// Function to generate the solid found here: https://www.shadertoy.com/view/MsKGzw + +// Tinker with these parameters to create different solids +// ------------------------------------------------------- +const float rotation_speed = 0.25f; + +const float poly_U = 1.f; // [0, inf] +const float poly_V = 0.5f; // [0, inf] +const float poly_W = 1.0f; // [0, inf] +const int poly_type = 5; // [2, 5] +const float poly_zoom = 2.5f; + +const float inner_sphere = 1.f; + +const float refr_index = 0.9f; + +#define MAX_BOUNCES2 6 +// ------------------------------------------------------- + +#define TIME iTime +#define RESOLUTION iResolution +#define PI 3.141592654 +#define TAU (2.0*PI) + +// License: WTFPL, author: sam hocevar, found: https://stackoverflow.com/a/17897228/418488 +const vec4 hsv2rgb_K = vec4(1.0f, 2.0f / 3.0f, 1.0f / 3.0f, 3.0f); +vec3 hsv2rgb(vec3 c) { + vec3 p = abs(fract(c.xxx + hsv2rgb_K.xyz) * 6.0f - hsv2rgb_K.www); + return c.z * mix(hsv2rgb_K.xxx, clamp(p - hsv2rgb_K.xxx, 0.0f, 1.0f), c.y); +} +// License: WTFPL, author: sam hocevar, found: https://stackoverflow.com/a/17897228/418488 +// Macro version of above to enable compile-time constants +#define HSV2RGB(c) (c.z * mix(hsv2rgb_K.xxx, clamp(abs(fract(c.xxx + hsv2rgb_K.xyz) * 6.0 - hsv2rgb_K.www) - hsv2rgb_K.xxx, 0.0, 1.0), c.y)) + +#define TOLERANCE2 0.0005 +//#define MAX_RAY_LENGTH2 10.0 +#define MAX_RAY_MARCHES2 50 +#define NORM_OFF2 0.005 +#define BACKSTEP2 + +#define TOLERANCE3 0.0005 +#define MAX_RAY_LENGTH3 10.0 +#define MAX_RAY_MARCHES3 90 +#define NORM_OFF3 0.005 + +const vec3 rayOrigin = vec3(0.0f, 1.f, -5.f); +const vec3 sunDir = normalize(-rayOrigin); + +const vec3 sunCol = HSV2RGB(vec3(0.06f, 0.90f, 1E-2f)) * 1.f; +const vec3 bottomBoxCol = HSV2RGB(vec3(0.66f, 0.80f, 0.5f)) * 1.f; +const vec3 topBoxCol = HSV2RGB(vec3(0.60f, 0.90f, 1.f)) * 1.f; +const vec3 glowCol0 = HSV2RGB(vec3(0.05f, 0.7f, 1E-3f)) * 1.f; +const vec3 glowCol1 = HSV2RGB(vec3(0.95f, 0.7f, 1E-3f)) * 1.f; +const vec3 beerCol = -HSV2RGB(vec3(0.15f + 0.5f, 0.7f, 2.f)); +const float rrefr_index = 1.f / refr_index; + +// License: Unknown, author: knighty, found: https://www.shadertoy.com/view/MsKGzw +const float poly_cospin = cos(PI / float(poly_type)); +const float poly_scospin = sqrt(0.75f - poly_cospin * poly_cospin); +const vec3 poly_nc = vec3(-0.5f, -poly_cospin, poly_scospin); +const vec3 poly_pab = vec3(0.f, 0.f, 1.f); +const vec3 poly_pbc_ = vec3(poly_scospin, 0.f, 0.5f); +const vec3 poly_pca_ = vec3(0.f, poly_scospin, poly_cospin); +const vec3 poly_p = normalize((poly_U * poly_pab + poly_V * poly_pbc_ + poly_W * poly_pca_)); +const vec3 poly_pbc = normalize(poly_pbc_); +const vec3 poly_pca = normalize(poly_pca_); + +mat3 g_rot; +vec2 g_gd; + +// License: MIT, author: Inigo Quilez, found: https://iquilezles.org/articles/noacos/ +mat3 rot(vec3 d, vec3 z) { + vec3 v = cross(z, d); + float c = dot(z, d); + float k = 1.0f / (1.0f + c); + + return mat3(v.x * v.x * k + c, v.y * v.x * k - v.z, v.z * v.x * k + v.y, v.x * v.y * k + v.z, v.y * v.y * k + c, v.z * v.y * k - v.x, v.x * v.z * k - v.y, v.y * v.z * k + v.x, v.z * v.z * k + c); +} + +// License: Unknown, author: Matt Taylor (https://github.com/64), found: https://64.github.io/tonemapping/ +vec3 aces_approx(vec3 v) { + v = max(v, 0.0f); + v *= 0.6f; + float a = 2.51f; + float b = 0.03f; + float c = 2.43f; + float d = 0.59f; + float e = 0.14f; + return clamp((v * (a * v + b)) / (v * (c * v + d) + e), 0.0f, 1.0f); +} + +float sphere(vec3 p, float r) { + return length(p) - r; +} + +// License: MIT, author: Inigo Quilez, found: https://iquilezles.org/articles/distfunctions/ +float box(vec2 p, vec2 b) { + vec2 d = abs(p) - b; + return length(max(d, 0.0f)) + min(max(d.x, d.y), 0.0f); +} + +// License: Unknown, author: knighty, found: https://www.shadertoy.com/view/MsKGzw +void poly_fold(inout vec3 pos) { + vec3 p = pos; + + for(int i = 0; i < poly_type; ++i) { + p.xy = abs(p.xy); + p -= 2.f * min(0.f, dot(p, poly_nc)) * poly_nc; + } + + pos = p; +} + +float poly_plane(vec3 pos) { + float d0 = dot(pos, poly_pab); + float d1 = dot(pos, poly_pbc); + float d2 = dot(pos, poly_pca); + float d = d0; + d = max(d, d1); + d = max(d, d2); + return d; +} + +float poly_corner(vec3 pos) { + float d = length(pos) - .0125f; + return d; +} + +float dot2(vec3 p) { + return dot(p, p); +} + +float poly_edge(vec3 pos) { + float dla = dot2(pos - min(0.f, pos.x) * vec3(1.f, 0.f, 0.f)); + float dlb = dot2(pos - min(0.f, pos.y) * vec3(0.f, 1.f, 0.f)); + float dlc = dot2(pos - min(0.f, dot(pos, poly_nc)) * poly_nc); + return sqrt(min(min(dla, dlb), dlc)) - 2E-3f; +} + +vec3 shape(vec3 pos) { + pos *= g_rot; + pos /= poly_zoom; + poly_fold(pos); + pos -= poly_p; + + return vec3(poly_plane(pos), poly_edge(pos), poly_corner(pos)) * poly_zoom; +} + +vec3 render0(vec3 ro, vec3 rd) { + vec3 col = vec3(0.0f); + + float srd = sign(rd.y); + float tp = -(ro.y - 6.f) / abs(rd.y); + + if(srd < 0.f) { + col += bottomBoxCol * exp(-0.5f * (length((ro + tp * rd).xz))); + } + + if(srd > 0.0f) { + vec3 pos = ro + tp * rd; + vec2 pp = pos.xz; + float db = box(pp, vec2(5.0f, 9.0f)) - 3.0f; + + col += topBoxCol * rd.y * rd.y * smoothstep(0.25f, 0.0f, db); + col += 0.2f * topBoxCol * exp(-0.5f * max(db, 0.0f)); + col += 0.05f * sqrt(topBoxCol) * max(-db, 0.0f); + } + + col += sunCol / (1.001f - dot(sunDir, rd)); + return col; +} + +float df2(vec3 p) { + vec3 ds = shape(p); + float d2 = ds.y - 5E-3f; + float d0 = min(-ds.x, d2); + float d1 = sphere(p, inner_sphere); + g_gd = min(g_gd, vec2(d2, d1)); + float d = (min(d0, d1)); + return d; +} + +float rayMarch2(vec3 ro, vec3 rd, float tinit) { + float t = tinit; +#if defined(BACKSTEP2) + vec2 dti = vec2(1e10f, 0.0f); +#endif + int i; + for(i = 0; i < MAX_RAY_MARCHES2; ++i) { + float d = df2(ro + rd * t); +#if defined(BACKSTEP2) + if(d < dti.x) { + dti = vec2(d, t); + } +#endif + // Bouncing in a closed shell, will never miss + if(d < TOLERANCE2/* || t > MAX_RAY_LENGTH3 */) { + break; + } + t += d; + } +#if defined(BACKSTEP2) + if(i == MAX_RAY_MARCHES2) { + t = dti.y; + }; +#endif + return t; +} + +vec3 normal2(vec3 pos) { + vec2 eps = vec2(NORM_OFF2, 0.0f); + vec3 nor; + nor.x = df2(pos + eps.xyy) - df2(pos - eps.xyy); + nor.y = df2(pos + eps.yxy) - df2(pos - eps.yxy); + nor.z = df2(pos + eps.yyx) - df2(pos - eps.yyx); + return normalize(nor); +} + +vec3 render2(vec3 ro, vec3 rd, float db) { + vec3 agg = vec3(0.0f); + float ragg = 1.f; + float tagg = 0.f; + + for(int bounce = 0; bounce < MAX_BOUNCES2; ++bounce) { + if(ragg < 0.1f) + break; + g_gd = vec2(1E3f); + float t2 = rayMarch2(ro, rd, min(db + 0.05f, 0.3f)); + vec2 gd2 = g_gd; + tagg += t2; + + vec3 p2 = ro + rd * t2; + vec3 n2 = normal2(p2); + vec3 r2 = reflect(rd, n2); + vec3 rr2 = refract(rd, n2, rrefr_index); + float fre2 = 1.f + dot(n2, rd); + + vec3 beer = ragg * exp(0.2f * beerCol * tagg); + agg += glowCol1 * beer * ((1.f + tagg * tagg * 4E-2f) * 6.f / max(gd2.x, 5E-4f + tagg * tagg * 2E-4f / ragg)); + vec3 ocol = 0.2f * beer * render0(p2, rr2); + if(gd2.y <= TOLERANCE2) { + ragg *= 1.f - 0.9f * fre2; + } else { + agg += ocol; + ragg *= 0.8f; + } + + ro = p2; + rd = r2; + db = gd2.x; + } + + return agg; +} + +float df3(vec3 p) { + vec3 ds = shape(p); + g_gd = min(g_gd, ds.yz); + const float sw = 0.02f; + float d1 = min(ds.y, ds.z) - sw; + float d0 = ds.x; + d0 = min(d0, ds.y); + d0 = min(d0, ds.z); + return d0; +} + +float rayMarch3(vec3 ro, vec3 rd, float tinit, out int iter) { + float t = tinit; + int i; + for(i = 0; i < MAX_RAY_MARCHES3; ++i) { + float d = df3(ro + rd * t); + if(d < TOLERANCE3 || t > MAX_RAY_LENGTH3) { + break; + } + t += d; + } + iter = i; + return t; +} + +vec3 normal3(vec3 pos) { + vec2 eps = vec2(NORM_OFF3, 0.0f); + vec3 nor; + nor.x = df3(pos + eps.xyy) - df3(pos - eps.xyy); + nor.y = df3(pos + eps.yxy) - df3(pos - eps.yxy); + nor.z = df3(pos + eps.yyx) - df3(pos - eps.yyx); + return normalize(nor); +} + +vec3 render3(vec3 ro, vec3 rd) { + int iter; + + vec3 skyCol = render0(ro, rd); + vec3 col = skyCol; + + g_gd = vec2(1E3f); + float t1 = rayMarch3(ro, rd, 0.1f, iter); + vec2 gd1 = g_gd; + vec3 p1 = ro + t1 * rd; + vec3 n1 = normal3(p1); + vec3 r1 = reflect(rd, n1); + vec3 rr1 = refract(rd, n1, refr_index); + float fre1 = 1.f + dot(rd, n1); + fre1 *= fre1; + + float ifo = mix(0.5f, 1.f, smoothstep(1.0f, 0.9f, float(iter) / float(MAX_RAY_MARCHES3))); + + if(t1 < MAX_RAY_LENGTH3) { + col = render0(p1, r1) * (0.5f + 0.5f * fre1) * ifo; + vec3 icol = render2(p1, rr1, gd1.x); + if(gd1.x > TOLERANCE3 && gd1.y > TOLERANCE3 && rr1 != vec3(0.f)) { + col += icol * (1.f - 0.75f * fre1) * ifo; + } + } + + col += (glowCol0 + 1.f * fre1 * (glowCol0)) / max(gd1.x, 3E-4f); + return col; + +} + +vec3 effect(vec2 p, vec2 pp) { + const float fov = 2.0f; + + const vec3 up = vec3(0.f, 1.f, 0.f); + const vec3 la = vec3(0.0f); + + const vec3 ww = normalize(normalize(la - rayOrigin)); + const vec3 uu = normalize(cross(up, ww)); + const vec3 vv = cross(ww, uu); + + vec3 rd = normalize(-p.x * uu + p.y * vv + fov * ww); + + vec3 col = vec3(0.0f); + col = render3(rayOrigin, rd); + + col -= 2E-2f * vec3(2.f, 3.f, 1.f) * (length(p) + 0.25f); + col = aces_approx(col); + col = sqrt(col); + return col; +} + +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 q = fragCoord / RESOLUTION.xy; + vec2 p = -1.f + 2.f * q; + vec2 pp = p; + p.x *= RESOLUTION.x / RESOLUTION.y; + + float a = TIME * rotation_speed; + vec3 r0 = vec3(1.0f, sin(vec2(sqrt(0.5f), 1.0f) * a)); + vec3 r1 = vec3(cos(vec2(sqrt(0.5f), 1.0f) * 0.913f * a), 1.0f); + mat3 rot = rot(normalize(r0), normalize(r1)); + g_rot = rot; + + vec3 col = effect(p, pp); + + fragColor = vec4(col, 1.0f); +} + +void main() { + mainImage(FragColor, gl_FragCoord.xy); +} \ No newline at end of file