From d2a8edb0045b5f7c5e81db1be5e67cde015d1eb8 Mon Sep 17 00:00:00 2001 From: code002lover Date: Sat, 6 Dec 2025 19:45:28 +0100 Subject: [PATCH] feat: Implement multi-pass rendering using WebGL framebuffers and integrate random texture generation directly into the shader background component. --- frontend/src/ShaderBackground.tsx | 202 +++++++++++++++++++++++-- frontend/src/randomTextureForShader.ts | 58 ------- 2 files changed, 190 insertions(+), 70 deletions(-) diff --git a/frontend/src/ShaderBackground.tsx b/frontend/src/ShaderBackground.tsx index 782c68f..d476a74 100644 --- a/frontend/src/ShaderBackground.tsx +++ b/frontend/src/ShaderBackground.tsx @@ -1,5 +1,5 @@ import { useEffect, useRef } from "react"; -import { createAndBindRandomTextureToIChannel0 } from "./randomTextureForShader"; +import { generateRandomRGBABytes } from "./randomTextureForShader"; const buffer_1 = `#version 300 es precision mediump float; @@ -964,51 +964,227 @@ export function ShaderBackground() { } } + const frameBufferTexture = gl.createTexture(); + if (!frameBufferTexture) return; + + gl.bindTexture(gl.TEXTURE_2D, frameBufferTexture); + gl.texImage2D( + gl.TEXTURE_2D, + 0, + gl.RGBA, + canvas.width, + canvas.height, + 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); + + const frameBufferTexture2 = gl.createTexture(); + if (!frameBufferTexture2) return; + + gl.bindTexture(gl.TEXTURE_2D, frameBufferTexture2); + gl.texImage2D( + gl.TEXTURE_2D, + 0, + gl.RGBA, + canvas.width, + canvas.height, + 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); + + const frameBuffer = gl.createFramebuffer(); + if (!frameBuffer) return; + + gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, frameBuffer); + + gl.framebufferTexture2D( + gl.DRAW_FRAMEBUFFER, + gl.COLOR_ATTACHMENT0, + gl.TEXTURE_2D, + frameBufferTexture, + 0 + ); + + const status = gl.checkFramebufferStatus(gl.DRAW_FRAMEBUFFER); + switch (status) { + case gl.FRAMEBUFFER_COMPLETE: + console.log("Framebuffer is complete"); + break; + case gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT: + console.log("Framebuffer is incomplete attachment"); + break; + case gl.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: + console.log("Framebuffer is incomplete missing attachment"); + break; + case gl.FRAMEBUFFER_INCOMPLETE_DIMENSIONS: + console.log("Framebuffer is incomplete dimensions"); + break; + case gl.FRAMEBUFFER_UNSUPPORTED: + console.log("Framebuffer is unsupported"); + break; + default: + console.log("Framebuffer is unknown"); + } + + const posBuffer = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, posBuffer); + gl.bufferData( + gl.ARRAY_BUFFER, + new Float32Array([0, 0, 1, 0, 0, 1, 1, 1]), + gl.STATIC_DRAW + ); + + const ichannel0_location_buffer_1 = gl.getUniformLocation( + buffer_1_program as WebGLProgram, + "iChannel0" + ); + + const ichannel0_location_buffer_2 = gl.getUniformLocation( + buffer_2_program as WebGLProgram, + "iChannel0" + ); + + const ichannel0_location_buffer_3 = gl.getUniformLocation( + buffer_3_program as WebGLProgram, + "iChannel0" + ); + + const ichannel0_location_buffer_4 = gl.getUniformLocation( + buffer_4_program as WebGLProgram, + "iChannel0" + ); + + const ichannel0_location_final = gl.getUniformLocation( + finalProgram as WebGLProgram, + "iChannel0" + ); + function render( gl: WebGL2RenderingContext, canvas: HTMLCanvasElement, now: number ) { + gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, frameBuffer); + //Clear with Clear color gl.clear(gl.COLOR_BUFFER_BIT); gl.useProgram(buffer_1_program); setResolution(buffer_1_program as WebGLProgram, canvas, gl); setTime(buffer_1_program as WebGLProgram, now, gl); - const tex = createAndBindRandomTextureToIChannel0( - gl, - buffer_1_program as WebGLProgram, - canvas.width, - canvas.height - ); + const bytes = generateRandomRGBABytes(canvas.width, canvas.height); + + const tex = gl.createTexture(); + if (!tex) throw new Error("Failed to create texture"); + + // Activate texture unit 0 and bind the texture gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, tex); + // Texture parameters suitable for data textures + 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); + + // Upload pixel data as RGBA UNSIGNED_BYTE + gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1); + gl.texImage2D( + gl.TEXTURE_2D, + 0, + gl.RGBA, + canvas.width, + canvas.height, + 0, + gl.RGBA, + gl.UNSIGNED_BYTE, + bytes + ); + + // Set sampler uniform to texture unit 0 + gl.uniform1i(ichannel0_location_buffer_1, 0); + // RENDER 1 - gl.drawBuffers([gl.BACK]); + gl.framebufferTexture2D( + gl.DRAW_FRAMEBUFFER, + gl.COLOR_ATTACHMENT0, + gl.TEXTURE_2D, + frameBufferTexture, + 0 + ); + gl.drawArrays(gl.TRIANGLES, 0, 6); gl.useProgram(buffer_2_program); setResolution(buffer_2_program as WebGLProgram, canvas, gl); // RENDER 2 - gl.drawBuffers([gl.BACK]); + gl.activeTexture(gl.TEXTURE1); + gl.bindTexture(gl.TEXTURE_2D, frameBufferTexture); + gl.uniform1i(ichannel0_location_buffer_2, 1); + gl.framebufferTexture2D( + gl.DRAW_FRAMEBUFFER, + gl.COLOR_ATTACHMENT0, + gl.TEXTURE_2D, + frameBufferTexture2, + 0 + ); + gl.drawArrays(gl.TRIANGLES, 0, 6); gl.useProgram(buffer_3_program); setResolution(buffer_3_program as WebGLProgram, canvas, gl); // RENDER 3 - gl.drawBuffers([gl.BACK]); + gl.activeTexture(gl.TEXTURE1); + gl.bindTexture(gl.TEXTURE_2D, frameBufferTexture2); + gl.uniform1i(ichannel0_location_buffer_3, 1); + gl.framebufferTexture2D( + gl.DRAW_FRAMEBUFFER, + gl.COLOR_ATTACHMENT0, + gl.TEXTURE_2D, + frameBufferTexture, + 0 + ); + gl.drawArrays(gl.TRIANGLES, 0, 6); gl.useProgram(buffer_4_program); setResolution(buffer_4_program as WebGLProgram, canvas, gl); // RENDER 4 - gl.drawBuffers([gl.BACK]); + gl.activeTexture(gl.TEXTURE1); + gl.bindTexture(gl.TEXTURE_2D, frameBufferTexture); + gl.uniform1i(ichannel0_location_buffer_4, 1); + gl.framebufferTexture2D( + gl.DRAW_FRAMEBUFFER, + gl.COLOR_ATTACHMENT0, + gl.TEXTURE_2D, + frameBufferTexture2, + 0 + ); + gl.drawArrays(gl.TRIANGLES, 0, 6); gl.useProgram(finalProgram); setResolution(finalProgram as WebGLProgram, canvas, gl); // FINAL RENDER + gl.activeTexture(gl.TEXTURE1); + gl.bindTexture(gl.TEXTURE_2D, frameBufferTexture2); + gl.uniform1i(ichannel0_location_final, 1); + gl.framebufferTexture2D( + gl.DRAW_FRAMEBUFFER, + gl.COLOR_ATTACHMENT0, + gl.TEXTURE_2D, + frameBufferTexture, + 0 + ); + gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null); gl.drawArrays(gl.TRIANGLES, 0, 6); } @@ -1024,5 +1200,7 @@ export function ShaderBackground() { requestAnimationFrame(update); }, []); - return ; + return ( + + ); } diff --git a/frontend/src/randomTextureForShader.ts b/frontend/src/randomTextureForShader.ts index 60d133a..57cc139 100644 --- a/frontend/src/randomTextureForShader.ts +++ b/frontend/src/randomTextureForShader.ts @@ -33,61 +33,3 @@ export function generateRandomRGBABytes( return out; } - -/** - * Create a WebGL texture from random data and bind it to texture unit 0, - * then set the sampler2D uniform named `iChannel0` to use unit 0. - * - * gl: WebGLRenderingContext or WebGL2RenderingContext - * program: compiled & linked shader program (must be in use or will be used) - */ -export function createAndBindRandomTextureToIChannel0( - gl: WebGLRenderingContext | WebGL2RenderingContext, - program: WebGLProgram, - width: number, - height: number -): WebGLTexture { - const bytes = generateRandomRGBABytes(width, height); - - const tex = gl.createTexture(); - if (!tex) throw new Error("Failed to create texture"); - - // Activate texture unit 0 and bind the texture - gl.activeTexture(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D, tex); - - // Texture parameters suitable for data textures - 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); - - // Upload pixel data as RGBA UNSIGNED_BYTE - gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1); - gl.texImage2D( - gl.TEXTURE_2D, - 0, - gl.RGBA, - width, - height, - 0, - gl.RGBA, - gl.UNSIGNED_BYTE, - bytes - ); - - const location = gl.getUniformLocation(program, "iChannel0"); - if (location === null) { - // Clean up binding and return but signal with thrown error if desired - gl.bindTexture(gl.TEXTURE_2D, null); - throw new Error("Uniform 'iChannel0' not found in program"); - } - - // Set sampler uniform to texture unit 0 - gl.uniform1i(location, 0); - - // Unbind the texture if you prefer (not strictly necessary) - gl.bindTexture(gl.TEXTURE_2D, null); - - return tex; -}