base shader

This commit is contained in:
2025-12-08 12:15:16 +01:00
parent 6128804352
commit 8706d510db
6 changed files with 288 additions and 1201 deletions
+205
View 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" />
);
};