diff --git a/frontend/src/ShaderBackground.tsx b/frontend/src/ShaderBackground_regret.tsx similarity index 75% rename from frontend/src/ShaderBackground.tsx rename to frontend/src/ShaderBackground_regret.tsx index 73f8a69..5bcfca6 100644 --- a/frontend/src/ShaderBackground.tsx +++ b/frontend/src/ShaderBackground_regret.tsx @@ -935,91 +935,71 @@ export function ShaderBackground() { gl.STATIC_DRAW ); - { - gl.useProgram(buffer_1_program); - const posAttrLocation = gl.getAttribLocation( - buffer_1_program, - "a_position" + // 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.vertexAttribPointer(posAttrLocation, 2, gl.FLOAT, false, 0, 0); - gl.enableVertexAttribArray(posAttrLocation); - } + 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); - { - gl.useProgram(buffer_2_program); - const posAttrLocation = gl.getAttribLocation( - buffer_2_program, - "a_position" + const fb = gl!.createFramebuffer(); + gl!.bindFramebuffer(gl!.DRAW_FRAMEBUFFER, fb); + gl!.framebufferTexture2D( + gl!.DRAW_FRAMEBUFFER, + gl!.COLOR_ATTACHMENT0, + gl!.TEXTURE_2D, + tex, + 0 ); - gl.vertexAttribPointer(posAttrLocation, 2, gl.FLOAT, false, 0, 0); - gl.enableVertexAttribArray(posAttrLocation); + + return { tex, fb }; } - { - gl.useProgram(buffer_3_program); - const posAttrLocation = gl.getAttribLocation( - buffer_3_program, - "a_position" - ); - gl.vertexAttribPointer(posAttrLocation, 2, gl.FLOAT, false, 0, 0); - gl.enableVertexAttribArray(posAttrLocation); - } + // 1. Scene Buffer (Stores the main render) + const sceneTarget = createFramebufferTexture(canvas.width, canvas.height); - { - gl.useProgram(buffer_4_program); - const posAttrLocation = gl.getAttribLocation( - buffer_4_program, - "a_position" - ); - gl.vertexAttribPointer(posAttrLocation, 2, gl.FLOAT, false, 0, 0); - gl.enableVertexAttribArray(posAttrLocation); - } + // 2. Ping-Pong Buffers (For doing multipass bloom effects) + const pingTarget = createFramebufferTexture(canvas.width, canvas.height); + const pongTarget = createFramebufferTexture(canvas.width, canvas.height); - { - gl.useProgram(finalProgram); - const posAttrLocation = gl.getAttribLocation(finalProgram, "a_position"); - gl.vertexAttribPointer(posAttrLocation, 2, gl.FLOAT, false, 0, 0); - gl.enableVertexAttribArray(posAttrLocation); - } - - function setResolution( - program: WebGLProgram, - canvas: HTMLCanvasElement, - gl: WebGL2RenderingContext - ) { - const loc = gl.getUniformLocation(program, "iResolution"); - if (loc === null) { - console.warn( - "iResolution uniform not found (maybe not used by shader)." - ); - } else { - const w = canvas.width; - const h = canvas.height; - - gl.uniform3fv(loc, [w, h, 1]); - } - } - - function setTime( - program: WebGLProgram, - now: number, - gl: WebGL2RenderingContext - ) { - const loc = gl.getUniformLocation(program, "iTime"); - if (loc === null) { - console.warn( - "iTime uniform not found (maybe not used by shader).", - program - ); - } else { - gl.uniform1f(loc, now); - } - } - - const frameBufferTexture = gl.createTexture(); - if (!frameBufferTexture) return; - - gl.bindTexture(gl.TEXTURE_2D, frameBufferTexture); + // --- 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, @@ -1029,219 +1009,154 @@ export function ShaderBackground() { 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 + noiseBytes ); - 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"); + function setResolution(program: WebGLProgram) { + const loc = gl!.getUniformLocation(program, "iResolution"); + if (loc) gl!.uniform3fv(loc, [canvas!.width, canvas!.height, 1]); } - 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 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.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.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.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.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); + 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; - render(gl as WebGL2RenderingContext, canvas as HTMLCanvasElement, time); - requestAnimationFrame(update); + // ---------------------------------------------------- + // 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); } - 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 (