diff --git a/frontend/public/assets/ichannel1.png b/frontend/public/assets/ichannel1.png
new file mode 100644
index 0000000..a02f0a0
Binary files /dev/null and b/frontend/public/assets/ichannel1.png differ
diff --git a/frontend/public/vite.svg b/frontend/public/vite.svg
deleted file mode 100644
index e7b8dfb..0000000
--- a/frontend/public/vite.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/frontend/src/ShaderBackground.tsx b/frontend/src/ShaderBackground.tsx
new file mode 100644
index 0000000..f089020
--- /dev/null
+++ b/frontend/src/ShaderBackground.tsx
@@ -0,0 +1,205 @@
+import { useEffect, useRef } from "react";
+
+import SHADER_CODE from "./assets/shader.glsl?raw";
+
+function buildProgram(
+ ctx: WebGL2RenderingContext,
+ fragShader: WebGLShader,
+ vertexShader: WebGLShader
+) {
+ const program = ctx.createProgram();
+ if (!program) return null;
+
+ ctx.attachShader(program, fragShader);
+ ctx.attachShader(program, vertexShader);
+
+ ctx.linkProgram(program);
+
+ const status = ctx.getProgramParameter(program, ctx.LINK_STATUS);
+
+ if (status) {
+ return program;
+ }
+
+ console.error(ctx.getProgramInfoLog(program));
+ ctx.deleteProgram(program);
+ return null;
+}
+
+function buildShader(
+ ctx: WebGL2RenderingContext,
+ type: number,
+ source: string
+) {
+ const shader = ctx.createShader(type);
+ if (!shader) return null;
+
+ ctx.shaderSource(shader, source);
+ ctx.compileShader(shader);
+
+ const status = ctx.getShaderParameter(shader, ctx.COMPILE_STATUS);
+
+ if (status) {
+ return shader;
+ }
+
+ console.error(ctx.getShaderInfoLog(shader));
+ ctx.deleteShader(shader);
+ return null;
+}
+
+function loadTexture(gl: WebGL2RenderingContext, url: string) {
+ const texture = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, texture);
+
+ // Because images have to be downloaded over the internet
+ // they might take a moment until they are ready.
+ // Until then put a single pixel in the texture so we can
+ // use it immediately. When the image has finished downloading
+ // we'll update the texture with the contents of the image.
+ const level = 0;
+ const internalFormat = gl.RGBA;
+ const width = 1;
+ const height = 1;
+ const border = 0;
+ const srcFormat = gl.RGBA;
+ const srcType = gl.UNSIGNED_BYTE;
+ const pixel = new Uint8Array([0, 0, 255, 255]); // opaque blue
+ gl.texImage2D(
+ gl.TEXTURE_2D,
+ level,
+ internalFormat,
+ width,
+ height,
+ border,
+ srcFormat,
+ srcType,
+ pixel
+ );
+
+ const image = new Image();
+ image.onload = () => {
+ gl.bindTexture(gl.TEXTURE_2D, texture);
+ gl.texImage2D(
+ gl.TEXTURE_2D,
+ level,
+ internalFormat,
+ srcFormat,
+ srcType,
+ image
+ );
+
+ // WebGL1 has different requirements for power of 2 images
+ // vs. non power of 2 images so check if the image is a
+ // power of 2 in both dimensions.
+ if (isPowerOf2(image.width) && isPowerOf2(image.height)) {
+ // Yes, it's a power of 2. Generate mips.
+ gl.generateMipmap(gl.TEXTURE_2D);
+ } else {
+ // No, it's not a power of 2. Turn off mips and set
+ // wrapping to clamp to edge
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
+ }
+ };
+ image.src = url;
+
+ return texture;
+}
+
+function isPowerOf2(value: number) {
+ return (value & (value - 1)) === 0;
+}
+
+
+export const ShaderBackground = () => {
+ const canvasRef = useRef(null);
+
+ useEffect(() => {
+ const canvas = canvasRef.current;
+ if (!canvas) return;
+
+ const gl = canvas.getContext("webgl2");
+ if (!gl) return;
+
+ gl.viewport(0, 0, canvas.width, canvas.height);
+ gl.clearColor(0, 0, 0, 1);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+
+ const shader = buildShader(gl, gl.FRAGMENT_SHADER, SHADER_CODE);
+ const vertexShader = buildShader(
+ gl,
+ gl.VERTEX_SHADER,
+ `#version 300 es
+ precision lowp float;
+
+ in vec2 a_position;
+
+ void main() {
+ gl_Position = vec4(a_position, 0.0, 1.0);
+ }`
+ );
+
+ if (!shader || !vertexShader) return;
+
+ const finalProgram = buildProgram(gl, shader, vertexShader);
+
+ if (!finalProgram) return;
+
+ const posBuffer = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, posBuffer);
+ gl.bufferData(
+ gl.ARRAY_BUFFER,
+ new Float32Array([-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1]),
+ gl.STATIC_DRAW
+ );
+
+ gl.useProgram(finalProgram);
+ const loc = gl.getAttribLocation(finalProgram, "a_position");
+ gl.vertexAttribPointer(loc, 2, gl.FLOAT, false, 0, 0);
+ gl.enableVertexAttribArray(loc);
+
+ function setResolution(program: WebGLProgram) {
+ const loc = gl!.getUniformLocation(program, "iResolution");
+ if (loc) gl!.uniform3fv(loc, [canvas!.width, canvas!.height, 1]);
+ }
+
+ function setTime(program: WebGLProgram, now: number) {
+ const loc = gl!.getUniformLocation(program, "iTime");
+ if (loc) gl!.uniform1f(loc, now);
+ }
+
+ const final_iChannel1 = gl.getUniformLocation(finalProgram, "iChannel1");
+
+ const ichannel1_texture = loadTexture(gl, "assets/ichannel1.png");
+
+ gl!.viewport(0, 0, canvas!.width, canvas!.height);
+
+ gl!.useProgram(finalProgram);
+ setResolution(finalProgram!);
+
+ // Bind Texture to Unit 1
+ gl!.activeTexture(gl!.TEXTURE1);
+ gl!.bindTexture(gl!.TEXTURE_2D, ichannel1_texture);
+ gl!.uniform1i(final_iChannel1, 1);
+
+ function update(now: number) {
+ // time in seconds
+ const time = (now) / 1000;
+ gl!.clear(gl!.COLOR_BUFFER_BIT);
+
+ setTime(finalProgram!, time);
+
+ gl!.drawArrays(gl!.TRIANGLES, 0, 6);
+
+ requestAnimationFrame(update);
+ }
+
+ requestAnimationFrame(update);
+ });
+
+ return (
+
+ );
+};
diff --git a/frontend/src/ShaderBackground_regret.tsx b/frontend/src/ShaderBackground_regret.tsx
deleted file mode 100644
index 5bcfca6..0000000
--- a/frontend/src/ShaderBackground_regret.tsx
+++ /dev/null
@@ -1,1165 +0,0 @@
-import { useEffect, useRef } from "react";
-import { generateRandomRGBABytes } from "./randomTextureForShader";
-
-const buffer_1 = `#version 300 es
-precision mediump float;
-uniform vec3 iResolution; // viewport resolution (in pixels)
-uniform float iTime; // shader playback time (in seconds)
-uniform float iTimeDelta; // render time (in seconds)
-uniform float iFrameRate; // shader frame rate
-uniform int iFrame; // shader playback frame
-uniform float iChannelTime[4]; // channel playback time (in seconds)
-uniform vec3 iChannelResolution[4]; // channel resolution (in pixels)
-uniform vec4 iMouse; // mouse pixel coords. xy: current (if MLB down), zw: click
-uniform sampler2D iChannel0;
-uniform sampler2D iChannel1;
-uniform sampler2D iChannel2;
-uniform sampler2D iChannel3;
-
-out vec4 FragColor;
-
-/*
-Main render.
-
-Temporal AA for a smooth image. Temporal accumulation is disabled while moving the view to prevent ghosting.
-*/
-
-#define ITERATIONS 200 //Increase for less grainy result
-#define TEMPORAL_AA
-
-
-const vec3 MainColor = vec3(1.0);
-
-//noise code by iq
-float noise( in vec3 x )
-{
- vec3 p = floor(x);
- vec3 f = fract(x);
- f = f*f*(3.0-2.0*f);
- vec2 uv = (p.xy+vec2(37.0,17.0)*p.z) + f.xy;
- vec2 rg = textureLod( iChannel0, (uv+ 0.5)/256.0, 0.0 ).yx;
- return -1.0+2.0*mix( rg.x, rg.y, f.z );
-}
-
-float saturate(float x)
-{
- return clamp(x, 0.0, 1.0);
-}
-
-vec3 saturate(vec3 x)
-{
- return clamp(x, vec3(0.0), vec3(1.0));
-}
-
-float rand(vec2 coord)
-{
- return saturate(fract(sin(dot(coord, vec2(12.9898, 78.223))) * 43758.5453));
-}
-
-float pcurve( float x, float a, float b )
-{
- float k = pow(a+b,a+b) / (pow(a,a)*pow(b,b));
- return k * pow( x, a ) * pow( 1.0-x, b );
-}
-
-const float pi = 3.14159265;
-
-float atan2(float y, float x)
-{
- if (x > 0.0)
- {
- return atan(y / x);
- }
- else if (x == 0.0)
- {
- if (y > 0.0)
- {
- return pi / 2.0;
- }
- else if (y < 0.0)
- {
- return -(pi / 2.0);
- }
- else
- {
- return 0.0;
- }
- }
- else //(x < 0.0)
- {
- if (y >= 0.0)
- {
- return atan(y / x) + pi;
- }
- else
- {
- return atan(y / x) - pi;
- }
- }
-}
-
-float sdTorus(vec3 p, vec2 t)
-{
- vec2 q = vec2(length(p.xz) - t.x, p.y);
- return length(q)-t.y;
-}
-
-float sdSphere(vec3 p, float r)
-{
- return length(p)-r;
-}
-
-void Haze(inout vec3 color, vec3 pos, float alpha)
-{
- vec2 t = vec2(1.0, 0.01);
-
- float torusDist = length(sdTorus(pos + vec3(0.0, -0.05, 0.0), t));
-
- float bloomDisc = 1.0 / (pow(torusDist, 2.0) + 0.001);
- vec3 col = MainColor;
- bloomDisc *= length(pos) < 0.5 ? 0.0 : 1.0;
-
- color += col * bloomDisc * (2.9 / float(ITERATIONS)) * (1.0 - alpha * 1.0);
-}
-
-void GasDisc(inout vec3 color, inout float alpha, vec3 pos)
-{
- float discRadius = 3.2;
- float discWidth = 5.3;
- float discInner = discRadius - discWidth * 0.5;
- float discOuter = discRadius + discWidth * 0.5;
-
- vec3 origin = vec3(0.0, 0.0, 0.0);
- float mouseZ = iMouse.y / iResolution.y;
- vec3 discNormal = normalize(vec3(0.0, 1.0, 0.0));
- float discThickness = 0.1;
-
- float distFromCenter = distance(pos, origin);
- float distFromDisc = dot(discNormal, pos - origin);
-
- float radialGradient = 1.0 - saturate((distFromCenter - discInner) / discWidth * 0.5);
-
- float coverage = pcurve(radialGradient, 4.0, 0.9);
-
- discThickness *= radialGradient;
- coverage *= saturate(1.0 - abs(distFromDisc) / discThickness);
-
- vec3 dustColorLit = MainColor;
- vec3 dustColorDark = vec3(0.0, 0.0, 0.0);
-
- float dustGlow = 1.0 / (pow(1.0 - radialGradient, 2.0) * 290.0 + 0.002);
- vec3 dustColor = dustColorLit * dustGlow * 8.2;
-
- coverage = saturate(coverage * 0.7);
-
-
- float fade = pow((abs(distFromCenter - discInner) + 0.4), 4.0) * 0.04;
- float bloomFactor = 1.0 / (pow(distFromDisc, 2.0) * 40.0 + fade + 0.00002);
- vec3 b = dustColorLit * pow(bloomFactor, 1.5);
-
- b *= mix(vec3(1.7, 1.1, 1.0), vec3(0.5, 0.6, 1.0), vec3(pow(radialGradient, 2.0)));
- b *= mix(vec3(1.7, 0.5, 0.1), vec3(1.0), vec3(pow(radialGradient, 0.5)));
-
- dustColor = mix(dustColor, b * 150.0, saturate(1.0 - coverage * 1.0));
- coverage = saturate(coverage + bloomFactor * bloomFactor * 0.1);
-
- if (coverage < 0.01)
- {
- return;
- }
-
-
- vec3 radialCoords;
- radialCoords.x = distFromCenter * 1.5 + 0.55;
- radialCoords.y = atan2(-pos.x, -pos.z) * 1.5;
- radialCoords.z = distFromDisc * 1.5;
-
- radialCoords *= 0.95;
-
- float speed = 0.06;
-
- float noise1 = 1.0;
- vec3 rc = radialCoords + 0.0; rc.y += iTime * speed;
- noise1 *= noise(rc * 3.0) * 0.5 + 0.5; rc.y -= iTime * speed;
- noise1 *= noise(rc * 6.0) * 0.5 + 0.5; rc.y += iTime * speed;
- noise1 *= noise(rc * 12.0) * 0.5 + 0.5; rc.y -= iTime * speed;
- noise1 *= noise(rc * 24.0) * 0.5 + 0.5; rc.y += iTime * speed;
-
- float noise2 = 2.0;
- rc = radialCoords + 30.0;
- noise2 *= noise(rc * 3.0) * 0.5 + 0.5; rc.y += iTime * speed;
- noise2 *= noise(rc * 6.0) * 0.5 + 0.5; rc.y -= iTime * speed;
- noise2 *= noise(rc * 12.0) * 0.5 + 0.5; rc.y += iTime * speed;
- noise2 *= noise(rc * 24.0) * 0.5 + 0.5; rc.y -= iTime * speed;
- noise2 *= noise(rc * 48.0) * 0.5 + 0.5; rc.y += iTime * speed;
- noise2 *= noise(rc * 92.0) * 0.5 + 0.5; rc.y -= iTime * speed;
-
- dustColor *= noise1 * 0.998 + 0.002;
- coverage *= noise2;
-
- radialCoords.y += iTime * speed * 0.5;
-
- dustColor *= pow(texture(iChannel1, radialCoords.yx * vec2(0.15, 0.27)).rgb, vec3(2.0)) * 4.0;
-
- coverage = saturate(coverage * 1200.0 / float(ITERATIONS));
- dustColor = max(vec3(0.0), dustColor);
-
- coverage *= pcurve(radialGradient, 4.0, 0.9);
-
- color = (1.0 - alpha) * dustColor * coverage + color;
-
- alpha = (1.0 - alpha) * coverage + alpha;
-}
-
-
-
-vec3 rotate(vec3 p, float x, float y, float z)
-{
- mat3 matx = mat3(1.0, 0.0, 0.0,
- 0.0, cos(x), sin(x),
- 0.0, -sin(x), cos(x));
-
- mat3 maty = mat3(cos(y), 0.0, -sin(y),
- 0.0, 1.0, 0.0,
- sin(y), 0.0, cos(y));
-
- mat3 matz = mat3(cos(z), sin(z), 0.0,
- -sin(z), cos(z), 0.0,
- 0.0, 0.0, 1.0);
-
- p = matx * p;
- p = matz * p;
- p = maty * p;
-
- return p;
-}
-
-void RotateCamera(inout vec3 eyevec, inout vec3 eyepos)
-{
- float mousePosY = iMouse.y / iResolution.y;
- float mousePosX = iMouse.x / iResolution.x;
-
- vec3 angle = vec3(mousePosY * 0.05 + 0.05, 1.0 + mousePosX * 1.0, -0.45);
-
- eyevec = rotate(eyevec, angle.x, angle.y, angle.z);
- eyepos = rotate(eyepos, angle.x, angle.y, angle.z);
-}
-
-void WarpSpace(inout vec3 eyevec, inout vec3 raypos)
-{
- vec3 origin = vec3(0.0, 0.0, 0.0);
-
- float singularityDist = distance(raypos, origin);
- float warpFactor = 1.0 / (pow(singularityDist, 2.0) + 0.000001);
-
- vec3 singularityVector = normalize(origin - raypos);
-
- float warpAmount = 5.0;
-
- eyevec = normalize(eyevec + singularityVector * warpFactor * warpAmount / float(ITERATIONS));
-}
-
-void mainImage( out vec4 fragColor, in vec2 fragCoord )
-{
-
- vec2 uv = fragCoord.xy / iResolution.xy;
-
- float aspect = iResolution.x / iResolution.y;
-
- vec2 uveye = uv;
-
- #ifdef TEMPORAL_AA
- uveye.x += (rand(uv + sin(iTime * 1.0)) / iResolution.x) * (iMouse.z > 1.0 ? 0.0 : 1.0);
- uveye.y += (rand(uv + 1.0 + sin(iTime * 1.0)) / iResolution.y) * (iMouse.z > 1.0 ? 0.0 : 1.0);
- #endif
-
- vec3 eyevec = normalize(vec3((uveye * 2.0 - 1.0) * vec2(aspect, 1.0), 6.0));
- vec3 eyepos = vec3(0.0, -0.0, -10.0);
-
- vec2 mousepos = iMouse.xy / iResolution.xy;
- if (mousepos.x == 0.0)
- {
- mousepos.x = 0.35;
- }
- eyepos.x += mousepos.x * 3.0 - 1.5;
-
- const float far = 15.0;
-
- RotateCamera(eyevec, eyepos);
-
- vec3 color = vec3(0.0, 0.0, 0.0);
-
- float dither = rand(uv
- #ifdef TEMPORAL_AA
- + sin(iTime * 1.0) * (iMouse.z > 1.0 ? 0.0 : 1.0)
- #endif
- ) * 2.0;
-
-
- float alpha = 0.0;
- vec3 raypos = eyepos + eyevec * dither * far / float(ITERATIONS);
- for (int i = 0; i < ITERATIONS; i++)
- {
- WarpSpace(eyevec, raypos);
- raypos += eyevec * far / float(ITERATIONS);
- GasDisc(color, alpha, raypos);
- Haze(color, raypos, alpha);
- }
-
- color *= 0.0001;
-
-
- #ifdef TEMPORAL_AA
- const float p = 1.0;
- vec3 previous = pow(texture(iChannel2, uv).rgb, vec3(1.0 / p));
-
- color = pow(color, vec3(1.0 / p));
-
- float blendWeight = 0.9 * (iMouse.z > 1.0 ? 0.0 : 1.0);
-
- color = mix(color, previous, blendWeight);
-
- color = pow(color, vec3(p));
- #endif
-
- fragColor = vec4(saturate(color), 1.0);
-}
-
-void main()
-{
- // gl_FragCoord.xy is pixel coordinates (1..width, 1..height)
- mainImage(FragColor, gl_FragCoord.xy);
-}
-`;
-
-const buffer_2 = `#version 300 es
-precision mediump float;
-uniform vec3 iResolution; // viewport resolution (in pixels)
-uniform float iTime; // shader playback time (in seconds)
-uniform float iTimeDelta; // render time (in seconds)
-uniform float iFrameRate; // shader frame rate
-uniform int iFrame; // shader playback frame
-uniform float iChannelTime[4]; // channel playback time (in seconds)
-uniform vec3 iChannelResolution[4]; // channel resolution (in pixels)
-uniform vec4 iMouse; // mouse pixel coords. xy: current (if MLB down), zw: click
-uniform sampler2D iChannel0;
-uniform sampler2D iChannel1;
-uniform sampler2D iChannel2;
-uniform sampler2D iChannel3;
-
-out vec4 FragColor;
-//First bloom pass, mipmap tree thing
-
-vec3 ColorFetch(vec2 coord)
-{
- return texture(iChannel0, coord).rgb;
-}
-
-vec3 Grab1(vec2 coord, const float octave, const vec2 offset)
-{
- float scale = exp2(octave);
-
- coord += offset;
- coord *= scale;
-
- if (coord.x < 0.0 || coord.x > 1.0 || coord.y < 0.0 || coord.y > 1.0)
- {
- return vec3(0.0);
- }
-
- vec3 color = ColorFetch(coord);
-
- return color;
-}
-
-vec3 Grab4(vec2 coord, const float octave, const vec2 offset)
-{
- float scale = exp2(octave);
-
- coord += offset;
- coord *= scale;
-
- if (coord.x < 0.0 || coord.x > 1.0 || coord.y < 0.0 || coord.y > 1.0)
- {
- return vec3(0.0);
- }
-
- vec3 color = vec3(0.0);
- float weights = 0.0;
-
- const int oversampling = 4;
-
- for (int i = 0; i < oversampling; i++)
- {
- for (int j = 0; j < oversampling; j++)
- {
- vec2 off = (vec2(i, j) / iResolution.xy + vec2(0.0) / iResolution.xy) * scale / float(oversampling);
- color += ColorFetch(coord + off);
-
-
- weights += 1.0;
- }
- }
-
- color /= weights;
-
- return color;
-}
-
-vec3 Grab8(vec2 coord, const float octave, const vec2 offset)
-{
- float scale = exp2(octave);
-
- coord += offset;
- coord *= scale;
-
- if (coord.x < 0.0 || coord.x > 1.0 || coord.y < 0.0 || coord.y > 1.0)
- {
- return vec3(0.0);
- }
-
- vec3 color = vec3(0.0);
- float weights = 0.0;
-
- const int oversampling = 8;
-
- for (int i = 0; i < oversampling; i++)
- {
- for (int j = 0; j < oversampling; j++)
- {
- vec2 off = (vec2(i, j) / iResolution.xy + vec2(0.0) / iResolution.xy) * scale / float(oversampling);
- color += ColorFetch(coord + off);
-
-
- weights += 1.0;
- }
- }
-
- color /= weights;
-
- return color;
-}
-
-vec3 Grab16(vec2 coord, const float octave, const vec2 offset)
-{
- float scale = exp2(octave);
-
- coord += offset;
- coord *= scale;
-
- if (coord.x < 0.0 || coord.x > 1.0 || coord.y < 0.0 || coord.y > 1.0)
- {
- return vec3(0.0);
- }
-
- vec3 color = vec3(0.0);
- float weights = 0.0;
-
- const int oversampling = 16;
-
- for (int i = 0; i < oversampling; i++)
- {
- for (int j = 0; j < oversampling; j++)
- {
- vec2 off = (vec2(i, j) / iResolution.xy + vec2(0.0) / iResolution.xy) * scale / float(oversampling);
- color += ColorFetch(coord + off);
-
-
- weights += 1.0;
- }
- }
-
- color /= weights;
-
- return color;
-}
-
-vec2 CalcOffset(float octave)
-{
- vec2 offset = vec2(0.0);
-
- vec2 padding = vec2(10.0) / iResolution.xy;
-
- offset.x = -min(1.0, floor(octave / 3.0)) * (0.25 + padding.x);
-
- offset.y = -(1.0 - (1.0 / exp2(octave))) - padding.y * octave;
-
- offset.y += min(1.0, floor(octave / 3.0)) * 0.35;
-
- return offset;
-}
-
-void mainImage( out vec4 fragColor, in vec2 fragCoord )
-{
- vec2 uv = fragCoord.xy / iResolution.xy;
-
-
- vec3 color = vec3(0.0);
-
- /*
- Create a mipmap tree thingy with padding to prevent leaking bloom
-
- Since there's no mipmaps for the previous buffer and the reduction process has to be done in one pass,
- oversampling is required for a proper result
- */
- color += Grab1(uv, 1.0, vec2(0.0, 0.0) );
- color += Grab4(uv, 2.0, vec2(CalcOffset(1.0)) );
- color += Grab8(uv, 3.0, vec2(CalcOffset(2.0)) );
- color += Grab16(uv, 4.0, vec2(CalcOffset(3.0)) );
- color += Grab16(uv, 5.0, vec2(CalcOffset(4.0)) );
- color += Grab16(uv, 6.0, vec2(CalcOffset(5.0)) );
- color += Grab16(uv, 7.0, vec2(CalcOffset(6.0)) );
- color += Grab16(uv, 8.0, vec2(CalcOffset(7.0)) );
-
-
- fragColor = vec4(color, 1.0);
-}
-
-void main()
-{
- // gl_FragCoord.xy is pixel coordinates (1..width, 1..height)
- mainImage(FragColor, gl_FragCoord.xy);
-}
-`;
-
-const buffer_3 = `#version 300 es
-precision mediump float;
-uniform vec3 iResolution; // viewport resolution (in pixels)
-uniform float iTime; // shader playback time (in seconds)
-uniform float iTimeDelta; // render time (in seconds)
-uniform float iFrameRate; // shader frame rate
-uniform int iFrame; // shader playback frame
-uniform float iChannelTime[4]; // channel playback time (in seconds)
-uniform vec3 iChannelResolution[4]; // channel resolution (in pixels)
-uniform vec4 iMouse; // mouse pixel coords. xy: current (if MLB down), zw: click
-uniform sampler2D iChannel0;
-uniform sampler2D iChannel1;
-uniform sampler2D iChannel2;
-uniform sampler2D iChannel3;
-
-out vec4 FragColor;
-//Horizontal gaussian blur leveraging hardware filtering for fewer texture lookups.
-
-vec3 ColorFetch(vec2 coord)
-{
- return texture(iChannel0, coord).rgb;
-}
-
-float weights[5];
-float offsets[5];
-
-
-void mainImage( out vec4 fragColor, in vec2 fragCoord )
-{
-
- weights[0] = 0.19638062;
- weights[1] = 0.29675293;
- weights[2] = 0.09442139;
- weights[3] = 0.01037598;
- weights[4] = 0.00025940;
-
- offsets[0] = 0.00000000;
- offsets[1] = 1.41176471;
- offsets[2] = 3.29411765;
- offsets[3] = 5.17647059;
- offsets[4] = 7.05882353;
-
- vec2 uv = fragCoord.xy / iResolution.xy;
-
- vec3 color = vec3(0.0);
- float weightSum = 0.0;
-
- if (uv.x < 0.52)
- {
- color += ColorFetch(uv) * weights[0];
- weightSum += weights[0];
-
- for(int i = 1; i < 5; i++)
- {
- vec2 offset = vec2(offsets[i]) / iResolution.xy;
- color += ColorFetch(uv + offset * vec2(0.5, 0.0)) * weights[i];
- color += ColorFetch(uv - offset * vec2(0.5, 0.0)) * weights[i];
- weightSum += weights[i] * 2.0;
- }
-
- color /= weightSum;
- }
-
- fragColor = vec4(color,1.0);
-}
-
-void main()
-{
- // gl_FragCoord.xy is pixel coordinates (1..width, 1..height)
- mainImage(FragColor, gl_FragCoord.xy);
-}
-`;
-
-const buffer_4 = `#version 300 es
-precision mediump float;
-uniform vec3 iResolution; // viewport resolution (in pixels)
-uniform float iTime; // shader playback time (in seconds)
-uniform float iTimeDelta; // render time (in seconds)
-uniform float iFrameRate; // shader frame rate
-uniform int iFrame; // shader playback frame
-uniform float iChannelTime[4]; // channel playback time (in seconds)
-uniform vec3 iChannelResolution[4]; // channel resolution (in pixels)
-uniform vec4 iMouse; // mouse pixel coords. xy: current (if MLB down), zw: click
-uniform sampler2D iChannel0;
-uniform sampler2D iChannel1;
-uniform sampler2D iChannel2;
-uniform sampler2D iChannel3;
-
-out vec4 FragColor;
-
-//Vertical gaussian blur leveraging hardware filtering for fewer texture lookups.
-
-vec3 ColorFetch(vec2 coord)
-{
- return texture(iChannel0, coord).rgb;
-}
-
-float weights[5];
-float offsets[5];
-
-
-void mainImage( out vec4 fragColor, in vec2 fragCoord )
-{
-
- weights[0] = 0.19638062;
- weights[1] = 0.29675293;
- weights[2] = 0.09442139;
- weights[3] = 0.01037598;
- weights[4] = 0.00025940;
-
- offsets[0] = 0.00000000;
- offsets[1] = 1.41176471;
- offsets[2] = 3.29411765;
- offsets[3] = 5.17647059;
- offsets[4] = 7.05882353;
-
- vec2 uv = fragCoord.xy / iResolution.xy;
-
- vec3 color = vec3(0.0);
- float weightSum = 0.0;
-
- if (uv.x < 0.52)
- {
- color += ColorFetch(uv) * weights[0];
- weightSum += weights[0];
-
- for(int i = 1; i < 5; i++)
- {
- vec2 offset = vec2(offsets[i]) / iResolution.xy;
- color += ColorFetch(uv + offset * vec2(0.0, 0.5)) * weights[i];
- color += ColorFetch(uv - offset * vec2(0.0, 0.5)) * weights[i];
- weightSum += weights[i] * 2.0;
- }
-
- color /= weightSum;
- }
-
- fragColor = vec4(color,1.0);
-}
-
-void main()
-{
- // gl_FragCoord.xy is pixel coordinates (1..width, 1..height)
- mainImage(FragColor, gl_FragCoord.xy);
-}
-`;
-
-const image_1 = `#version 300 es
-precision mediump float;
-uniform vec3 iResolution; // viewport resolution (in pixels)
-uniform float iTime; // shader playback time (in seconds)
-uniform float iTimeDelta; // render time (in seconds)
-uniform float iFrameRate; // shader frame rate
-uniform int iFrame; // shader playback frame
-uniform float iChannelTime[4]; // channel playback time (in seconds)
-uniform vec3 iChannelResolution[4]; // channel resolution (in pixels)
-uniform vec4 iMouse; // mouse pixel coords. xy: current (if MLB down), zw: click
-uniform sampler2D iChannel0;
-uniform sampler2D iChannel1;
-uniform sampler2D iChannel2;
-uniform sampler2D iChannel3;
-
-out vec4 FragColor;
-vec3 saturate(vec3 x)
-{
- return clamp(x, vec3(0.0), vec3(1.0));
-}
-
-vec4 cubic(float x)
-{
- float x2 = x * x;
- float x3 = x2 * x;
- vec4 w;
- w.x = -x3 + 3.0*x2 - 3.0*x + 1.0;
- w.y = 3.0*x3 - 6.0*x2 + 4.0;
- w.z = -3.0*x3 + 3.0*x2 + 3.0*x + 1.0;
- w.w = x3;
- return w / 6.0;
-}
-
-vec4 BicubicTexture(in sampler2D tex, in vec2 coord)
-{
- vec2 resolution = iResolution.xy;
-
- coord *= resolution;
-
- float fx = fract(coord.x);
- float fy = fract(coord.y);
- coord.x -= fx;
- coord.y -= fy;
-
- fx -= 0.5;
- fy -= 0.5;
-
- vec4 xcubic = cubic(fx);
- vec4 ycubic = cubic(fy);
-
- vec4 c = vec4(coord.x - 0.5, coord.x + 1.5, coord.y - 0.5, coord.y + 1.5);
- vec4 s = vec4(xcubic.x + xcubic.y, xcubic.z + xcubic.w, ycubic.x + ycubic.y, ycubic.z + ycubic.w);
- vec4 offset = c + vec4(xcubic.y, xcubic.w, ycubic.y, ycubic.w) / s;
-
- vec4 sample0 = texture(tex, vec2(offset.x, offset.z) / resolution);
- vec4 sample1 = texture(tex, vec2(offset.y, offset.z) / resolution);
- vec4 sample2 = texture(tex, vec2(offset.x, offset.w) / resolution);
- vec4 sample3 = texture(tex, vec2(offset.y, offset.w) / resolution);
-
- float sx = s.x / (s.x + s.y);
- float sy = s.z / (s.z + s.w);
-
- return mix( mix(sample3, sample2, sx), mix(sample1, sample0, sx), sy);
-}
-
-vec3 ColorFetch(vec2 coord)
-{
- return texture(iChannel0, coord).rgb;
-}
-
-vec3 BloomFetch(vec2 coord)
-{
- return BicubicTexture(iChannel3, coord).rgb;
-}
-
-vec3 Grab(vec2 coord, const float octave, const vec2 offset)
-{
- float scale = exp2(octave);
-
- coord /= scale;
- coord -= offset;
-
- return BloomFetch(coord);
-}
-
-vec2 CalcOffset(float octave)
-{
- vec2 offset = vec2(0.0);
-
- vec2 padding = vec2(10.0) / iResolution.xy;
-
- offset.x = -min(1.0, floor(octave / 3.0)) * (0.25 + padding.x);
-
- offset.y = -(1.0 - (1.0 / exp2(octave))) - padding.y * octave;
-
- offset.y += min(1.0, floor(octave / 3.0)) * 0.35;
-
- return offset;
-}
-
-vec3 GetBloom(vec2 coord)
-{
- vec3 bloom = vec3(0.0);
-
- //Reconstruct bloom from multiple blurred images
- bloom += Grab(coord, 1.0, vec2(CalcOffset(0.0))) * 1.0;
- bloom += Grab(coord, 2.0, vec2(CalcOffset(1.0))) * 1.5;
- bloom += Grab(coord, 3.0, vec2(CalcOffset(2.0))) * 1.0;
- bloom += Grab(coord, 4.0, vec2(CalcOffset(3.0))) * 1.5;
- bloom += Grab(coord, 5.0, vec2(CalcOffset(4.0))) * 1.8;
- bloom += Grab(coord, 6.0, vec2(CalcOffset(5.0))) * 1.0;
- bloom += Grab(coord, 7.0, vec2(CalcOffset(6.0))) * 1.0;
- bloom += Grab(coord, 8.0, vec2(CalcOffset(7.0))) * 1.0;
-
- return bloom;
-}
-
-void mainImage( out vec4 fragColor, in vec2 fragCoord )
-{
-
- vec2 uv = fragCoord.xy / iResolution.xy;
-
- vec3 color = ColorFetch(uv);
-
-
- color += GetBloom(uv) * 0.08;
-
- color *= 200.0;
-
-
- //Tonemapping and color grading
- color = pow(color, vec3(1.5));
- color = color / (1.0 + color);
- color = pow(color, vec3(1.0 / 1.5));
-
-
- color = mix(color, color * color * (3.0 - 2.0 * color), vec3(1.0));
- color = pow(color, vec3(1.3, 1.20, 1.0));
-
- color = saturate(color * 1.01);
-
- color = pow(color, vec3(0.7 / 2.2));
-
- fragColor = vec4(color, 1.0);
-
-}
-
-void main()
-{
- // gl_FragCoord.xy is pixel coordinates (1..width, 1..height)
- mainImage(FragColor, gl_FragCoord.xy);
-}
-`;
-
-function buildProgram(
- ctx: WebGL2RenderingContext,
- fragShader: WebGLShader,
- vertexShader: WebGLShader
-) {
- const program = ctx.createProgram();
- if (!program) return null;
-
- ctx.attachShader(program, fragShader);
- ctx.attachShader(program, vertexShader);
-
- ctx.linkProgram(program);
-
- const status = ctx.getProgramParameter(program, ctx.LINK_STATUS);
-
- if (status) {
- return program;
- }
-
- console.error(ctx.getProgramInfoLog(program));
- ctx.deleteProgram(program);
- return null;
-}
-
-function buildShader(
- ctx: WebGL2RenderingContext,
- type: number,
- source: string
-) {
- const shader = ctx.createShader(type);
- if (!shader) return null;
-
- ctx.shaderSource(shader, source);
- ctx.compileShader(shader);
-
- const status = ctx.getShaderParameter(shader, ctx.COMPILE_STATUS);
-
- if (status) {
- return shader;
- }
-
- console.error(ctx.getShaderInfoLog(shader));
- ctx.deleteShader(shader);
- return null;
-}
-
-export function ShaderBackground() {
- const canvasRef = useRef(null);
-
- useEffect(() => {
- const canvas = canvasRef.current;
- if (!canvas) return;
-
- const gl = canvas.getContext("webgl2");
- if (!gl) return;
-
- gl.viewport(0, 0, canvas.width, canvas.height);
- gl.clearColor(0, 0, 0, 1);
- gl.clear(gl.COLOR_BUFFER_BIT);
-
- const buffer_1_shader = buildShader(gl, gl.FRAGMENT_SHADER, buffer_1);
- const buffer_2_shader = buildShader(gl, gl.FRAGMENT_SHADER, buffer_2);
- const buffer_3_shader = buildShader(gl, gl.FRAGMENT_SHADER, buffer_3);
- const buffer_4_shader = buildShader(gl, gl.FRAGMENT_SHADER, buffer_4);
- const fragmentShader = buildShader(gl, gl.FRAGMENT_SHADER, image_1);
-
- const vertexShader = buildShader(
- gl,
- gl.VERTEX_SHADER,
- `#version 300 es
- precision mediump float;
-
- in vec2 a_position;
-
- void main() {
- gl_Position = vec4(a_position, 0.0, 1.0);
- }`
- );
-
- if (
- !buffer_1_shader ||
- !buffer_2_shader ||
- !buffer_3_shader ||
- !buffer_4_shader ||
- !fragmentShader ||
- !vertexShader
- )
- return;
-
- const buffer_1_program = buildProgram(gl, buffer_1_shader, vertexShader);
- const buffer_2_program = buildProgram(gl, buffer_2_shader, vertexShader);
- const buffer_3_program = buildProgram(gl, buffer_3_shader, vertexShader);
- const buffer_4_program = buildProgram(gl, buffer_4_shader, vertexShader);
- const finalProgram = buildProgram(gl, fragmentShader, vertexShader);
-
- if (
- !buffer_1_program ||
- !buffer_2_program ||
- !buffer_3_program ||
- !buffer_4_program ||
- !finalProgram
- )
- return;
-
- const posBuffer = gl.createBuffer();
- gl.bindBuffer(gl.ARRAY_BUFFER, posBuffer);
- gl.bufferData(
- gl.ARRAY_BUFFER,
- new Float32Array([-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1]),
- gl.STATIC_DRAW
- );
-
- // Vertex attributes for each program
- const programs = [
- buffer_1_program,
- buffer_2_program,
- buffer_3_program,
- buffer_4_program,
- finalProgram,
- ];
- programs.forEach((prog) => {
- gl.useProgram(prog);
- const loc = gl.getAttribLocation(prog, "a_position");
- gl.vertexAttribPointer(loc, 2, gl.FLOAT, false, 0, 0);
- gl.enableVertexAttribArray(loc);
- });
-
- // --- HELPER FUNC ---
- function createFramebufferTexture(w: number, h: number) {
- const tex = gl!.createTexture();
- gl!.bindTexture(gl!.TEXTURE_2D, tex);
- gl!.texImage2D(
- gl!.TEXTURE_2D,
- 0,
- gl!.RGBA,
- w,
- h,
- 0,
- gl!.RGBA,
- gl!.UNSIGNED_BYTE,
- null
- );
- gl!.texParameteri(gl!.TEXTURE_2D, gl!.TEXTURE_MIN_FILTER, gl!.LINEAR);
- gl!.texParameteri(gl!.TEXTURE_2D, gl!.TEXTURE_MAG_FILTER, gl!.LINEAR);
- gl!.texParameteri(gl!.TEXTURE_2D, gl!.TEXTURE_WRAP_S, gl!.CLAMP_TO_EDGE);
- gl!.texParameteri(gl!.TEXTURE_2D, gl!.TEXTURE_WRAP_T, gl!.CLAMP_TO_EDGE);
-
- const fb = gl!.createFramebuffer();
- gl!.bindFramebuffer(gl!.DRAW_FRAMEBUFFER, fb);
- gl!.framebufferTexture2D(
- gl!.DRAW_FRAMEBUFFER,
- gl!.COLOR_ATTACHMENT0,
- gl!.TEXTURE_2D,
- tex,
- 0
- );
-
- return { tex, fb };
- }
-
- // 1. Scene Buffer (Stores the main render)
- const sceneTarget = createFramebufferTexture(canvas.width, canvas.height);
-
- // 2. Ping-Pong Buffers (For doing multipass bloom effects)
- const pingTarget = createFramebufferTexture(canvas.width, canvas.height);
- const pongTarget = createFramebufferTexture(canvas.width, canvas.height);
-
- // --- NOISE TEXTURE ---
- // Fix: Create once, not every frame
- const noiseBytes = generateRandomRGBABytes(canvas.width, canvas.height);
- const noiseTexture = gl.createTexture();
- gl.activeTexture(gl.TEXTURE0);
- gl.bindTexture(gl.TEXTURE_2D, noiseTexture);
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
- gl.texImage2D(
- gl.TEXTURE_2D,
- 0,
- gl.RGBA,
- canvas.width,
- canvas.height,
- 0,
- gl.RGBA,
- gl.UNSIGNED_BYTE,
- noiseBytes
- );
-
- function setResolution(program: WebGLProgram) {
- const loc = gl!.getUniformLocation(program, "iResolution");
- if (loc) gl!.uniform3fv(loc, [canvas!.width, canvas!.height, 1]);
- }
-
- function setTime(program: WebGLProgram, now: number) {
- const loc = gl!.getUniformLocation(program, "iTime");
- if (loc) gl!.uniform1f(loc, now);
- }
-
- // Cache uniform locations
- const locs = {
- b1_iChannel0: gl.getUniformLocation(buffer_1_program, "iChannel0"),
- b2_iChannel0: gl.getUniformLocation(buffer_2_program, "iChannel0"),
- b3_iChannel0: gl.getUniformLocation(buffer_3_program, "iChannel0"),
- b4_iChannel0: gl.getUniformLocation(buffer_4_program, "iChannel0"),
- final_iChannel0: gl.getUniformLocation(finalProgram, "iChannel0"),
- final_iChannel3: gl.getUniformLocation(finalProgram, "iChannel3"), // Bloom result
- };
-
- let reqId: number;
- const started = Date.now();
-
- function update(now: number) {
- // time in seconds
- const time = (now - started) / 1000;
-
- // ----------------------------------------------------
- // PASS 1: Render SCENE -> sceneTarget
- // ----------------------------------------------------
- gl!.bindFramebuffer(gl!.DRAW_FRAMEBUFFER, sceneTarget.fb);
- gl!.viewport(0, 0, canvas!.width, canvas!.height);
- gl!.clear(gl!.COLOR_BUFFER_BIT);
-
- gl!.useProgram(buffer_1_program);
- setResolution(buffer_1_program!);
- setTime(buffer_1_program!, time);
-
- // Bind Noise to Unit 0
- gl!.activeTexture(gl!.TEXTURE0);
- gl!.bindTexture(gl!.TEXTURE_2D, noiseTexture);
- gl!.uniform1i(locs.b1_iChannel0, 0);
-
- gl!.drawArrays(gl!.TRIANGLES, 0, 6);
-
- // ----------------------------------------------------
- // PASS 2: Bloom Prep (Buffer 2) -> pingTarget
- // input: sceneTarget (Unit 0)
- // ----------------------------------------------------
- gl!.bindFramebuffer(gl!.DRAW_FRAMEBUFFER, pingTarget.fb);
-
- gl!.useProgram(buffer_2_program);
- setResolution(buffer_2_program!);
-
- gl!.activeTexture(gl!.TEXTURE0);
- gl!.bindTexture(gl!.TEXTURE_2D, sceneTarget.tex);
- gl!.uniform1i(locs.b2_iChannel0, 0);
-
- gl!.drawArrays(gl!.TRIANGLES, 0, 6);
-
- // ----------------------------------------------------
- // PASS 3: Horizontal Blur (Buffer 3) -> pongTarget
- // input: pingTarget (Unit 0)
- // ----------------------------------------------------
- gl!.bindFramebuffer(gl!.DRAW_FRAMEBUFFER, pongTarget.fb);
-
- gl!.useProgram(buffer_3_program);
- setResolution(buffer_3_program!);
-
- gl!.activeTexture(gl!.TEXTURE0);
- gl!.bindTexture(gl!.TEXTURE_2D, pingTarget.tex);
- gl!.uniform1i(locs.b3_iChannel0, 0);
-
- gl!.drawArrays(gl!.TRIANGLES, 0, 6);
-
- // ----------------------------------------------------
- // PASS 4: Vertical Blur (Buffer 4) -> pingTarget (Final Bloom)
- // input: pongTarget (Unit 0)
- // ----------------------------------------------------
- // Reuse pingTarget to store the final bloom result
- gl!.bindFramebuffer(gl!.DRAW_FRAMEBUFFER, pingTarget.fb);
-
- gl!.useProgram(buffer_4_program);
- setResolution(buffer_4_program!);
-
- gl!.activeTexture(gl!.TEXTURE0);
- gl!.bindTexture(gl!.TEXTURE_2D, pongTarget.tex);
- gl!.uniform1i(locs.b4_iChannel0, 0);
-
- gl!.drawArrays(gl!.TRIANGLES, 0, 6);
-
- // ----------------------------------------------------
- // PASS 5: Final Composition -> Screen
- // Inputs:
- // - iChannel0: sceneTarget (Unit 0)
- // - iChannel3: pingTarget aka Bloom Result (Unit 3)
- // ----------------------------------------------------
- gl!.bindFramebuffer(gl!.DRAW_FRAMEBUFFER, null); // Screen
-
- gl!.useProgram(finalProgram);
- setResolution(finalProgram!);
-
- // Bind Scene to Unit 0
- gl!.activeTexture(gl!.TEXTURE0);
- gl!.bindTexture(gl!.TEXTURE_2D, sceneTarget.tex);
- gl!.uniform1i(locs.final_iChannel0, 0);
-
- // Bind Bloom to Unit 3
- gl!.activeTexture(gl!.TEXTURE3);
- gl!.bindTexture(gl!.TEXTURE_2D, pingTarget.tex);
- gl!.uniform1i(locs.final_iChannel3, 3); // Tell shader iChannel3 is on unit 3
-
- gl!.drawArrays(gl!.TRIANGLES, 0, 6);
-
- reqId = requestAnimationFrame(update);
- }
-
- reqId = requestAnimationFrame(update);
-
- // CLEANUP
- return () => {
- cancelAnimationFrame(reqId);
- gl!.deleteProgram(buffer_1_program);
- gl!.deleteProgram(buffer_2_program);
- gl!.deleteProgram(buffer_3_program);
- gl!.deleteProgram(buffer_4_program);
- gl!.deleteProgram(finalProgram);
-
- gl!.deleteShader(buffer_1_shader);
- gl!.deleteShader(buffer_2_shader);
- gl!.deleteShader(buffer_3_shader);
- gl!.deleteShader(buffer_4_shader);
- gl!.deleteShader(fragmentShader);
- gl!.deleteShader(vertexShader);
-
- gl!.deleteBuffer(posBuffer);
-
- gl!.deleteTexture(noiseTexture);
- gl!.deleteTexture(sceneTarget.tex);
- gl!.deleteFramebuffer(sceneTarget.fb);
- gl!.deleteTexture(pingTarget.tex);
- gl!.deleteFramebuffer(pingTarget.fb);
- gl!.deleteTexture(pongTarget.tex);
- gl!.deleteFramebuffer(pongTarget.fb);
- };
- }, []);
-
- return (
-
- );
-}
diff --git a/frontend/src/assets/shader.glsl b/frontend/src/assets/shader.glsl
new file mode 100644
index 0000000..cb66698
--- /dev/null
+++ b/frontend/src/assets/shader.glsl
@@ -0,0 +1,83 @@
+#version 300 es
+precision lowp float;
+uniform vec3 iResolution; // viewport resolution (in pixels)
+uniform float iTime; // shader playback time (in seconds)
+uniform sampler2D iChannel1;
+
+out vec4 FragColor;
+
+const float pi = 3.1415927f;
+
+float sdSphere(vec3 p, float s) {
+ return length(p) - s;
+}
+
+float sdTorus(vec3 p, vec2 t) {
+ vec2 q = vec2(length(p.xz) - t.x, p.y);
+ return length(q) - t.y;
+}
+
+void mainImage(out vec4 fragColor, in vec2 fragCoord) {
+ vec2 pp = fragCoord.xy / iResolution.xy;
+ pp = -1.0f + 2.0f * pp;
+ pp.x *= iResolution.x / iResolution.y;
+
+ vec3 lookAt = vec3(0.0f, -0.1f, 0.0f);
+
+ float eyer = 2.0f;
+ float eyea = 0.0f;
+ float eyea2 = (-0.24f) * pi * 2.0f;
+
+ vec3 ro = vec3(eyer * cos(eyea) * sin(eyea2), eyer * cos(eyea2), eyer * sin(eyea) * sin(eyea2)); //camera position
+
+ vec3 front = normalize(lookAt - ro);
+ vec3 left = normalize(cross(normalize(vec3(0.0f, 1, -0.1f)), front));
+ vec3 up = normalize(cross(front, left));
+ vec3 rd = normalize(front * 1.5f + left * pp.x + up * pp.y); // rect vector
+
+ vec3 bh = vec3(0.0f, 0.0f, 0.0f);
+ float bhr = 0.1f;
+ float bhmass = 5.0f;
+ bhmass *= 0.001f; // premul G
+
+ vec3 p = ro;
+ vec3 pv = rd;
+ float dt = 0.02f;
+
+ vec3 col = vec3(0.0f);
+
+ float noncaptured = 1.0f;
+
+ vec3 c1 = vec3(0.5f, 0.46f, 0.4f);
+ vec3 c2 = vec3(1.0f, 0.8f, 0.6f);
+
+ for(float t = 0.0f; t < 1.0f; t += 0.005f) {
+ p += pv * dt * noncaptured;
+
+ // gravity
+ vec3 bhv = bh - p;
+ float r = dot(bhv, bhv);
+ pv += normalize(bhv) * ((bhmass) / r);
+
+ noncaptured = smoothstep(0.0f, 0.666f, sdSphere(p - bh, bhr));
+
+ // Texture for the accretion disc
+ float dr = length(bhv.xz);
+ float da = atan(bhv.x, bhv.z);
+ vec2 ra = vec2(dr, da * (0.01f + (dr - bhr) * 0.002f) + 2.0f * pi + iTime * 0.005f);
+ ra *= vec2(10.0f, 20.0f);
+
+ vec3 dcol = mix(c2, c1, pow(length(bhv) - bhr, 2.0f)) * max(0.0f, texture(iChannel1, ra * vec2(0.1f, 0.5f)).r + 0.05f) * (4.0f / ((0.001f + (length(bhv) - bhr) * 50.0f)));
+
+ col += max(vec3(0.0f), dcol * smoothstep(0.0f, 1.0f, -sdTorus((p * vec3(1.0f, 25.0f, 1.0f)) - bh, vec2(0.8f, 0.99f))) * noncaptured);
+
+ col += vec3(1.0f, 0.9f, 0.85f) * (1.0f / vec3(dot(bhv, bhv))) * 0.0033f * noncaptured;
+
+ }
+ fragColor = vec4(col, 1.0f);
+}
+
+void main() {
+ // gl_FragCoord.xy is pixel coordinates (1..width, 1..height)
+ mainImage(FragColor, gl_FragCoord.xy);
+}
\ No newline at end of file
diff --git a/frontend/src/randomTextureForShader.ts b/frontend/src/randomTextureForShader.ts
deleted file mode 100644
index 57cc139..0000000
--- a/frontend/src/randomTextureForShader.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-export function generateRandomRGBABytes(
- width: number,
- height: number
-): Uint8Array {
- if (width <= 0 || height <= 0) {
- throw new Error("width and height must be positive");
- }
- const len = width * height * 4;
- const out = new Uint8Array(len);
-
- // Maximum bytes per getRandomValues call (per spec / browsers)
- const MAX_GETRANDOM_BYTES = 65536;
-
- if (typeof crypto !== "undefined" && "getRandomValues" in crypto) {
- // Fill in chunks of up to MAX_GETRANDOM_BYTES
- let offset = 0;
- while (offset < len) {
- const chunkSize = Math.min(MAX_GETRANDOM_BYTES, len - offset);
- // Subarray view for the current chunk
- const chunkView = out.subarray(offset, offset + chunkSize);
- crypto.getRandomValues(chunkView);
- offset += chunkSize;
- }
- // Ensure alpha channel is fully opaque (255)
- for (let i = 3; i < len; i += 4) out[i] = 255;
- } else {
- // Fallback to Math.random for all bytes
- for (let i = 0; i < len; i++) {
- if ((i + 1) % 4 === 0) out[i] = 255;
- else out[i] = Math.floor(Math.random() * 256);
- }
- }
-
- return out;
-}