base shader
This commit is contained in:
parent
6128804352
commit
8706d510db
BIN
frontend/public/assets/ichannel1.png
Normal file
BIN
frontend/public/assets/ichannel1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||
|
Before Width: | Height: | Size: 1.5 KiB |
205
frontend/src/ShaderBackground.tsx
Normal file
205
frontend/src/ShaderBackground.tsx
Normal file
@ -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<HTMLCanvasElement>(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 (
|
||||
<canvas ref={canvasRef} id="blackhole_canvas" width="800" height="450" />
|
||||
);
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
83
frontend/src/assets/shader.glsl
Normal file
83
frontend/src/assets/shader.glsl
Normal file
@ -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);
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user