From ca87817b33219eddb4c8fd30da39e5af9b2802a8 Mon Sep 17 00:00:00 2001 From: legonzaur Date: Wed, 22 May 2024 10:32:42 +0200 Subject: [PATCH 1/5] WIP : shader Worker --- assets/workers/ShaderWorker.ts | 156 +++++++++++++++++++++++++++ components/AnimatedBackground.vue | 171 ++++++++++-------------------- nuxt.config.ts | 9 -- 3 files changed, 211 insertions(+), 125 deletions(-) create mode 100644 assets/workers/ShaderWorker.ts diff --git a/assets/workers/ShaderWorker.ts b/assets/workers/ShaderWorker.ts new file mode 100644 index 0000000..e2326ff --- /dev/null +++ b/assets/workers/ShaderWorker.ts @@ -0,0 +1,156 @@ +import noise from "~/assets/shaders/noise.frag?raw" +// @ts-ignore Import module +import resolveLygia from "https://lygia.xyz/resolve.esm.js" + + +declare const resolveLygia: (source: string) => string + +let interval: number + +const shaderOptions = { quality: 6, framerate: 1000 / 60 } +const bounds = { left: 0, bottom: 0 } +const screen = { width: 0, height: 0, devicePixelRatio: 0 } + +let fragmentShader = resolveLygia(noise); + +let canvas: OffscreenCanvas + +async function main(canvas: OffscreenCanvas, fragmentShader2: string, _colorStart: [number, number, number], _colorEnd: [number, number, number]) { + 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, fragmentShader2) + 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) { + 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 aspectRatio = Math.max(screen.width, screen.height) * screen.devicePixelRatio + + gl2.uniform2f(offset, bounds.left, -bounds.bottom); + // console.log(canvas.getBoundingClientRect().left * window.devicePixelRatio) + gl2.uniform1i(quality, shaderOptions.quality) + gl2.uniform2f(resolutionLocation, aspectRatio, aspectRatio); + 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 + ); + } + self.clearInterval(interval) + interval = self.setInterval(() => requestAnimationFrame(render), shaderOptions.framerate) +} + +self.addEventListener("message", (e) => { + const data = e.data + if (data.command == "initShader") { + canvas = data.canvas + } else if (data.command == "killShader") { + self.clearInterval(interval) + } else { + bounds.bottom = data.bounds.bottom + bounds.left = data.bounds.left + screen.devicePixelRatio = data.screen.devicePixelRatio + screen.width = data.screen.width + screen.height = data.screen.height + if (data.command == "reloadShader") { + shaderOptions.quality = data.shaderOptions.quality + shaderOptions.framerate = data.shaderOptions.framerate + + reloadShader(data.canvas, data.colorStart, data.colorEnd) + } + } +}) + +async function reloadShader(canvas: OffscreenCanvas, _colorStart: [number, number, number], _colorEnd: [number, number, number]) { + if (!canvas) { return } + let fragmentShader2 = fragmentShader.replace("#define FBM_OCTAVES 6", "#define FBM_OCTAVES " + shaderOptions.quality); + self.clearInterval(interval) + main(canvas, fragmentShader2, _colorStart, _colorEnd); +} \ No newline at end of file diff --git a/components/AnimatedBackground.vue b/components/AnimatedBackground.vue index fd2be20..a014fd8 100644 --- a/components/AnimatedBackground.vue +++ b/components/AnimatedBackground.vue @@ -1,16 +1,11 @@ diff --git a/nuxt.config.ts b/nuxt.config.ts index 875119d..43369d5 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -12,13 +12,4 @@ export default defineNuxtConfig({ '@pinia/nuxt', '@pinia-plugin-persistedstate/nuxt', ], - app: { - head: { - script: [{ - src: "/webgl-utils.js" - }, { - src: "https://lygia.xyz/resolve.js" - }] - } - }, }) From bd6978d39208fa2fcde761ffe665c99532779368 Mon Sep 17 00:00:00 2001 From: legonzaur Date: Wed, 22 May 2024 12:45:34 +0200 Subject: [PATCH 2/5] Fix : prevent render retrigger when color changes --- assets/workers/ShaderWorker.ts | 29 +++++++++++++++++++---------- components/AnimatedBackground.vue | 17 +++++++++++++++-- components/PlayerSlot.vue | 1 - 3 files changed, 34 insertions(+), 13 deletions(-) diff --git a/assets/workers/ShaderWorker.ts b/assets/workers/ShaderWorker.ts index e2326ff..c0a4967 100644 --- a/assets/workers/ShaderWorker.ts +++ b/assets/workers/ShaderWorker.ts @@ -11,11 +11,14 @@ const shaderOptions = { quality: 6, framerate: 1000 / 60 } const bounds = { left: 0, bottom: 0 } 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 -async function main(canvas: OffscreenCanvas, fragmentShader2: string, _colorStart: [number, number, number], _colorEnd: [number, number, number]) { +async function main(canvas: OffscreenCanvas, fragmentShader2: string) { const gl = canvas.getContext("webgl"); if (!gl) { return; @@ -109,9 +112,7 @@ async function main(canvas: OffscreenCanvas, fragmentShader2: string, _colorStar const aspectRatio = Math.max(screen.width, screen.height) * screen.devicePixelRatio - gl2.uniform2f(offset, bounds.left, -bounds.bottom); - // console.log(canvas.getBoundingClientRect().left * window.devicePixelRatio) gl2.uniform1i(quality, shaderOptions.quality) gl2.uniform2f(resolutionLocation, aspectRatio, aspectRatio); gl2.uniform3f(colorStart, _colorStart[0], _colorStart[1], _colorStart[2]); @@ -122,9 +123,11 @@ async function main(canvas: OffscreenCanvas, fragmentShader2: string, _colorStar 0, // offset 6, // num vertices to process ); + requestAnimationFrame(render) } - self.clearInterval(interval) - interval = self.setInterval(() => requestAnimationFrame(render), shaderOptions.framerate) + // self.clearInterval(interval) + // interval = self.setInterval(() => requestAnimationFrame(render), shaderOptions.framerate) + requestAnimationFrame(render) } self.addEventListener("message", (e) => { @@ -132,25 +135,31 @@ self.addEventListener("message", (e) => { if (data.command == "initShader") { canvas = data.canvas } else if (data.command == "killShader") { + console.log("killShader") self.clearInterval(interval) } else { + console.log(screen.devicePixelRatio) bounds.bottom = data.bounds.bottom bounds.left = data.bounds.left screen.devicePixelRatio = data.screen.devicePixelRatio screen.width = data.screen.width screen.height = data.screen.height + _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] if (data.command == "reloadShader") { shaderOptions.quality = data.shaderOptions.quality shaderOptions.framerate = data.shaderOptions.framerate - - reloadShader(data.canvas, data.colorStart, data.colorEnd) + reloadShader() } } }) -async function reloadShader(canvas: OffscreenCanvas, _colorStart: [number, number, number], _colorEnd: [number, number, number]) { +async function reloadShader() { if (!canvas) { return } let fragmentShader2 = fragmentShader.replace("#define FBM_OCTAVES 6", "#define FBM_OCTAVES " + shaderOptions.quality); - self.clearInterval(interval) - main(canvas, fragmentShader2, _colorStart, _colorEnd); + main(canvas, fragmentShader2); } \ No newline at end of file diff --git a/components/AnimatedBackground.vue b/components/AnimatedBackground.vue index a014fd8..3b57ee5 100644 --- a/components/AnimatedBackground.vue +++ b/components/AnimatedBackground.vue @@ -1,6 +1,6 @@ \ No newline at end of file diff --git a/components/OptionsDialog.vue b/components/OptionsDialog.vue index 2aa4181..eaab71f 100644 --- a/components/OptionsDialog.vue +++ b/components/OptionsDialog.vue @@ -6,6 +6,9 @@ export interface Props { isVisible: boolean; } +const _devicePixelRatio = computed(() => devicePixelRatio) +const _window = computed(() => window) + const props = withDefaults(defineProps(), { }) @@ -15,10 +18,6 @@ const close = () => { emit('close') } -watch(() => settingsStore.shaderQuality, async () => { - console.log(settingsStore.shaderQuality) -}) -