import noise from "~/assets/shaders/noise.frag?raw"; import resolveLygia from "~/assets/workers/resolve-lygia.esm"; let interval: number; let timeout: number; const shaderOptions = { quality: 1, framerate: 1000 / 60 }; const screen = { width: 0, height: 0, devicePixelRatio: 0 }; const _colorStart = [0, 0, 0]; const _colorEnd = [0, 0, 0]; let fragmentShader = resolveLygia(noise); let canvas: OffscreenCanvas; let render: (time: number) => void; let must_resize = false; async function main(canvas: OffscreenCanvas) { const gl = canvas.getContext("webgl"); if (!gl) { return; } // setup GLSL program const program = gl.createProgram(); if (!program) { return; } const fragment = gl.createShader(gl.FRAGMENT_SHADER); if (!fragment) { return; } gl.shaderSource(fragment, fragmentShader); gl.compileShader(fragment); if (!gl.getShaderParameter(fragment, gl.COMPILE_STATUS)) { const lastError = gl.getShaderInfoLog(fragment); console.error(lastError); gl.deleteShader(fragment); return; } const vertex = gl.createShader(gl.VERTEX_SHADER); if (!vertex) { return; } gl.shaderSource( vertex, `attribute vec4 a_position;void main() {gl_Position = a_position;}` ); gl.compileShader(vertex); if (!gl.getShaderParameter(vertex, gl.COMPILE_STATUS)) { const lastError = gl.getShaderInfoLog(vertex); console.error(lastError); gl.deleteShader(vertex); return; } gl.attachShader(program, fragment); gl.attachShader(program, vertex); gl.linkProgram(program); const linked = gl.getProgramParameter(program, gl.LINK_STATUS); if (!linked) { return; } // const program = webglUtils.createProgramFromSources(gl, [vs, fragmentShader2]); // look up where the vertex data needs to go. const positionAttributeLocation = gl.getAttribLocation(program, "a_position"); // look up uniform locations const offset = gl.getUniformLocation(program, "u_offset"); const resolutionLocation = gl.getUniformLocation(program, "u_resolution"); const colorStart = gl.getUniformLocation(program, "u_color_start"); const quality = gl.getUniformLocation(program, "u_quality"); const colorEnd = gl.getUniformLocation(program, "u_color_end"); const timeLocation = gl.getUniformLocation(program, "u_time"); // Create a buffer to put three 2d clip space points in const positionBuffer = gl.createBuffer(); // Bind it to ARRAY_BUFFER (think of it as ARRAY_BUFFER = positionBuffer) gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); // fill it with a 2 triangles that cover clipspace gl.bufferData( gl.ARRAY_BUFFER, new Float32Array([ -1, -1, // first triangle 1, -1, -1, 1, -1, 1, // second triangle 1, -1, 1, 1, ]), gl.STATIC_DRAW ); const gl2 = gl; function render(time: number) { if (must_resize) { canvas.width = (screen.width * shaderOptions.quality) / 10; canvas.height = (screen.height * shaderOptions.quality) / 10; must_resize = false; } time *= 0.001; // convert to seconds // Tell WebGL how to convert from clip space to pixels gl2.viewport(0, 0, gl2.canvas.width, gl2.canvas.height); // Tell it to use our program (pair of shaders) gl2.useProgram(program); // Turn on the attribute gl2.enableVertexAttribArray(positionAttributeLocation); // Bind the position buffer. gl2.bindBuffer(gl2.ARRAY_BUFFER, positionBuffer); // Tell the attribute how to get data out of positionBuffer (ARRAY_BUFFER) gl2.vertexAttribPointer( positionAttributeLocation, 2, // 2 components per iteration gl2.FLOAT, // the data is 32bit floats false, // don't normalize the data 0, // 0 = move forward size * sizeof(type) each iteration to get the next position 0 // start at the beginning of the buffer ); const secondRatio = screen.width / screen.height; if (secondRatio < 1) { const half = canvas.height / 2; gl2.uniform2f( resolutionLocation, canvas.width, canvas.height * secondRatio ); gl2.uniform2f(offset, 0, -half + half / (1 / secondRatio)); } else { const half = canvas.width / 2; gl2.uniform2f( resolutionLocation, canvas.width / secondRatio, canvas.height ); gl2.uniform2f(offset, -half + half / secondRatio, 0); } gl2.uniform1i(quality, shaderOptions.quality); gl2.uniform3f(colorStart, _colorStart[0], _colorStart[1], _colorStart[2]); gl2.uniform3f(colorEnd, _colorEnd[0], _colorEnd[1], _colorEnd[2]); gl2.uniform1f(timeLocation, time); gl2.drawArrays( gl2.TRIANGLES, 0, // offset 6 // num vertices to process ); if (timeout) { self.clearTimeout(timeout); timeout = self.setTimeout(() => { requestAnimationFrame(render); }, 1000 / shaderOptions.framerate); } } requestAnimationFrame(render); // self.clearInterval(interval) // interval = self.setInterval(() => requestAnimationFrame(render), 1000 / shaderOptions.framerate) self.clearTimeout(timeout); if (shaderOptions.framerate == 0) { return render; } timeout = self.setTimeout(() => { requestAnimationFrame(render); }, 1000 / shaderOptions.framerate); return render; } self.addEventListener("message", (e) => { const data = e.data; if (data.command == "initShader") { canvas = data.canvas; shaderOptions.quality = data.shaderOptions.quality; shaderOptions.framerate = data.shaderOptions.framerate; if (shaderOptions.framerate == 65) { shaderOptions.framerate == Infinity; } screen.devicePixelRatio = data.screen.devicePixelRatio; screen.width = data.screen.width; screen.height = data.screen.height; loadShader(); } else if (data.command == "killShader") { self.clearInterval(interval); self.clearTimeout(timeout); } else if (data.command == "resumeShader") { self.clearInterval(interval); self.clearTimeout(timeout); shaderOptions.framerate = data.shaderOptions.framerate; if (shaderOptions.framerate == 65) { shaderOptions.framerate == Infinity; } timeout = self.setTimeout(() => { requestAnimationFrame(render); }, 1000 / shaderOptions.framerate); // interval = self.setInterval(() => requestAnimationFrame(render), 1000 / shaderOptions.framerate) } else if (data.command == "recolorShader") { _colorStart[0] = data.colorStart[0]; _colorStart[1] = data.colorStart[1]; _colorStart[2] = data.colorStart[2]; _colorEnd[0] = data.colorEnd[0]; _colorEnd[1] = data.colorEnd[1]; _colorEnd[2] = data.colorEnd[2]; } else if (data.command == "resizeShader") { shaderOptions.quality = data.shaderOptions.quality; screen.devicePixelRatio = data.screen.devicePixelRatio; screen.width = data.screen.width; screen.height = data.screen.height; must_resize = true; } }); async function loadShader() { if (!canvas) { return; } // let fragmentShader2 = fragmentShader.replace("#define FBM_OCTAVES 6", "#define FBM_OCTAVES " + shaderOptions.quality); const temp = await main(canvas); if (temp) { render = temp; } postMessage("ready"); }