diff options
| author | syuilo <4439005+syuilo@users.noreply.github.com> | 2025-09-27 20:53:21 +0900 |
|---|---|---|
| committer | syuilo <4439005+syuilo@users.noreply.github.com> | 2025-09-27 20:53:21 +0900 |
| commit | e24233c1c7811e4aba2a07bac81a7464d4e1d190 (patch) | |
| tree | 60e20c3534bc3f74796642425cd59b260b51fadb | |
| parent | enhance(frontend): improve zoomLines image effect (diff) | |
| download | misskey-e24233c1c7811e4aba2a07bac81a7464d4e1d190.tar.gz misskey-e24233c1c7811e4aba2a07bac81a7464d4e1d190.tar.bz2 misskey-e24233c1c7811e4aba2a07bac81a7464d4e1d190.zip | |
add ideas
| -rw-r--r-- | idea/MkAnimatedBg.dotted-ripples.vue | 232 | ||||
| -rw-r--r-- | idea/MkAnimatedBg.dotted.vue | 190 |
2 files changed, 422 insertions, 0 deletions
diff --git a/idea/MkAnimatedBg.dotted-ripples.vue b/idea/MkAnimatedBg.dotted-ripples.vue new file mode 100644 index 0000000000..f8f809c8ce --- /dev/null +++ b/idea/MkAnimatedBg.dotted-ripples.vue @@ -0,0 +1,232 @@ +<!-- +SPDX-FileCopyrightText: syuilo and misskey-project +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<canvas ref="canvasEl" style="display: block; width: 100%; height: 100%; pointer-events: none;"></canvas> +</template> + +<script lang="ts" setup> +import { onMounted, onUnmounted, useTemplateRef } from 'vue'; +import isChromatic from 'chromatic/isChromatic'; +import { initShaderProgram } from '@/utility/webgl.js'; + +const VERTEX_SHADER = `#version 300 es +in vec2 position; +out vec2 in_uv; + +void main() { + in_uv = (position + 1.0) / 2.0; + gl_Position = vec4(position, 0.0, 1.0); +} +`; + +const FRAGMENT_SHADER = `#version 300 es +precision mediump float; + +const float PI = 3.141592653589793; +const float TWO_PI = 6.283185307179586; +const float HALF_PI = 1.5707963267948966; + +in vec2 in_uv; +uniform vec2 in_resolution; +uniform float u_scale; +uniform float u_time; +uniform float u_seed; +uniform float u_angle; +uniform float u_radius; +uniform vec3 u_color; +uniform vec2 u_ripplePositions[16]; +uniform float u_rippleRadiuses[16]; +out vec4 out_color; + +float getRipple(vec2 uv) { + float strength = 0.0; + float thickness = 0.05; + for (int i = 0; i < 16; i++) { + if (u_rippleRadiuses[i] <= 0.0) continue; + + float d = distance(uv, u_ripplePositions[i]); + + // フチ + if (d < u_rippleRadiuses[i] + thickness && d > u_rippleRadiuses[i] - thickness) { + float gradate = abs(d - u_rippleRadiuses[i] + thickness) / thickness; + strength += (1.0 - u_rippleRadiuses[i]) * gradate; + } + + // 内側 + if (d < u_rippleRadiuses[i] + thickness) { + strength += 0.25 * (1.0 - u_rippleRadiuses[i]); + } + } + return strength; +} + +void main() { + float x_ratio = min(in_resolution.x / in_resolution.y, 1.0); + float y_ratio = min(in_resolution.y / in_resolution.x, 1.0); + + float angle = -(u_angle * PI); + vec2 centeredUv = (in_uv - vec2(0.5, 0.5)) * vec2(x_ratio, y_ratio); + vec2 rotatedUV = vec2( + centeredUv.x * cos(angle) - centeredUv.y * sin(angle), + centeredUv.x * sin(angle) + centeredUv.y * cos(angle) + ); + vec2 uv = rotatedUV; + + float time = u_time * 0.00025; + + float size = 1.0 / u_scale; + float size_half = size / 2.0; + float modX = mod(uv.x, size); + float modY = mod(uv.y, size); + + vec2 pixelated_uv = vec2( + (size * (floor((uv.x - 0.5 - size) / size) + 0.5)), + (size * (floor((uv.y - 0.5 - size) / size) + 0.5)) + ) + vec2(0.5 + size, 0.5 + size); + + float strength = getRipple(pixelated_uv); + + float opacity = min(max(strength, 0.0), 1.0); + + float threshold = ((u_radius / 2.0) / u_scale); + if (length(vec2(modX - size_half, modY - size_half)) < threshold) { + out_color = vec4(u_color.r, u_color.g, u_color.b, opacity); + //out_color = vec4(1.0); + return; + } + + // debug + //float a = min(max(getRipple(uv), 0.0), 1.0); + //out_color = vec4(u_color.r, u_color.g, u_color.b, (opacity + a) / 2.0); + + out_color = vec4(0.0, 0.0, 0.0, 0.0); +} +`; + +const canvasEl = useTemplateRef('canvasEl'); + +const props = withDefaults(defineProps<{ + scale?: number; +}>(), { + scale: 48, +}); + +let handle: ReturnType<typeof window['requestAnimationFrame']> | null = null; + +onMounted(() => { + const canvas = canvasEl.value!; + let width = canvas.offsetWidth; + let height = canvas.offsetHeight; + canvas.width = width; + canvas.height = height; + + const maybeGl = canvas.getContext('webgl2', { preserveDrawingBuffer: false, alpha: true, premultipliedAlpha: false, antialias: true }); + if (maybeGl == null) return; + + const gl = maybeGl; + + const VERTICES = new Float32Array([-1, -1, -1, 1, 1, 1, -1, -1, 1, 1, 1, -1]); + const vertexBuffer = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer); + gl.bufferData(gl.ARRAY_BUFFER, VERTICES, gl.STATIC_DRAW); + + //gl.clearColor(0.0, 0.0, 0.0, 0.0); + //gl.clear(gl.COLOR_BUFFER_BIT); + + const shaderProgram = initShaderProgram(gl, VERTEX_SHADER, FRAGMENT_SHADER); + + gl.useProgram(shaderProgram); + + const positionLocation = gl.getAttribLocation(shaderProgram, 'position'); + gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0); + gl.enableVertexAttribArray(positionLocation); + + const in_resolution = gl.getUniformLocation(shaderProgram, 'in_resolution'); + gl.uniform2fv(in_resolution, [canvas.width, canvas.height]); + + const u_time = gl.getUniformLocation(shaderProgram, 'u_time'); + const u_seed = gl.getUniformLocation(shaderProgram, 'u_seed'); + const u_scale = gl.getUniformLocation(shaderProgram, 'u_scale'); + const u_angle = gl.getUniformLocation(shaderProgram, 'u_angle'); + const u_radius = gl.getUniformLocation(shaderProgram, 'u_radius'); + const u_color = gl.getUniformLocation(shaderProgram, 'u_color'); + gl.uniform1f(u_seed, Math.random() * 1000); + gl.uniform1f(u_scale, props.scale); + gl.uniform1f(u_angle, 0.0); + gl.uniform1f(u_radius, 0.15); + gl.uniform3fv(u_color, [0.5, 1.0, 0]); + + if (isChromatic()) { + gl.uniform1f(u_time, 0); + gl.drawArrays(gl.TRIANGLES, 0, 6); + } else { + let ripples = [] as { position: [number, number]; startTime: number; }[]; + const LIFE_TIME = 1000 * 4; + + function render(timeStamp: number) { + let sizeChanged = false; + if (Math.abs(height - canvas.offsetHeight) > 2) { + height = canvas.offsetHeight; + canvas.height = height; + sizeChanged = true; + } + if (Math.abs(width - canvas.offsetWidth) > 2) { + width = canvas.offsetWidth; + canvas.width = width; + sizeChanged = true; + } + if (sizeChanged && gl) { + gl.uniform2fv(in_resolution, [width, height]); + gl.viewport(0, 0, width, height); + } + + gl.uniform1f(u_time, timeStamp); + + if (Math.random() < 0.01 && ripples.length < 16) { + ripples.push({ position: [(Math.random() * 2) - 1, (Math.random() * 2) - 1], startTime: timeStamp }); + } + + for (let i = 0; i < 16; i++) { + const o = gl.getUniformLocation(shaderProgram, `u_ripplePositions[${i.toString()}]`); + const r = gl.getUniformLocation(shaderProgram, `u_rippleRadiuses[${i.toString()}]`); + const ripple = ripples[i]; + if (ripple == null) { + gl.uniform2f(o, 0, 0); + gl.uniform1f(r, 0.0); + continue; + } + + const delta = timeStamp - ripple.startTime; + + gl.uniform2f(o, ripple.position[0], ripple.position[1]); + gl.uniform1f(r, delta / LIFE_TIME); + } + + ripples = ripples.filter(r => (timeStamp - r.startTime) < LIFE_TIME); + if (ripples.length === 0) { + ripples.push({ position: [(Math.random() * 2) - 1, (Math.random() * 2) - 1], startTime: timeStamp }); + } + + gl.drawArrays(gl.TRIANGLES, 0, 6); + + handle = window.requestAnimationFrame(render); + } + + handle = window.requestAnimationFrame(render); + } +}); + +onUnmounted(() => { + if (handle) { + window.cancelAnimationFrame(handle); + } + + // TODO: WebGLリソースの解放 +}); +</script> + +<style lang="scss" module> +</style> diff --git a/idea/MkAnimatedBg.dotted.vue b/idea/MkAnimatedBg.dotted.vue new file mode 100644 index 0000000000..7f68b8972a --- /dev/null +++ b/idea/MkAnimatedBg.dotted.vue @@ -0,0 +1,190 @@ +<!-- +SPDX-FileCopyrightText: syuilo and misskey-project +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<canvas ref="canvasEl" style="display: block; width: 100%; height: 100%; pointer-events: none;"></canvas> +</template> + +<script lang="ts" setup> +import { onMounted, onUnmounted, useTemplateRef } from 'vue'; +import isChromatic from 'chromatic/isChromatic'; +import { GLSL_LIB_SNOISE, initShaderProgram } from '@/utility/webgl.js'; + +const VERTEX_SHADER = `#version 300 es +in vec2 position; +out vec2 in_uv; + +void main() { + in_uv = (position + 1.0) / 2.0; + gl_Position = vec4(position, 0.0, 1.0); +} +`; + +const FRAGMENT_SHADER = `#version 300 es +precision mediump float; + +const float PI = 3.141592653589793; +const float TWO_PI = 6.283185307179586; +const float HALF_PI = 1.5707963267948966; + +${GLSL_LIB_SNOISE} + +in vec2 in_uv; +uniform vec2 in_resolution; +uniform float u_scale; +uniform float u_time; +uniform float u_seed; +uniform float u_angle; +uniform float u_radius; +uniform vec3 u_color; +out vec4 out_color; + +void main() { + float x_ratio = min(in_resolution.x / in_resolution.y, 1.0); + float y_ratio = min(in_resolution.y / in_resolution.x, 1.0); + + float size = 1.0 / u_scale; + float size_half = size / 2.0; + + float angle = -(u_angle * PI); + vec2 centeredUv = (in_uv - vec2(0.5, 0.5)) * vec2(x_ratio, y_ratio); + vec2 rotatedUV = vec2( + centeredUv.x * cos(angle) - centeredUv.y * sin(angle), + centeredUv.x * sin(angle) + centeredUv.y * cos(angle) + ); + vec2 uv = rotatedUV; + + float modX = mod(uv.x, size); + float modY = mod(uv.y, size); + + vec2 pixelated_uv = vec2( + (size * (floor((uv.x - 0.5 - size) / size) + 0.5)), + (size * (floor((uv.y - 0.5 - size) / size) + 0.5)) + ) + vec2(0.5 + size, 0.5 + size); + + float time = u_time * 0.00025; + + float noiseAScale = 1.0; + float noiseAX = (pixelated_uv.x + u_seed) * (u_scale / noiseAScale); + float noiseAY = (pixelated_uv.y + u_seed) * (u_scale / noiseAScale); + float noiseA = snoise(vec3(noiseAX, noiseAY, time * 2.0)); + + float noiseBScale = 32.0; + float noiseBX = (pixelated_uv.x + u_seed) * (u_scale / noiseBScale); + float noiseBY = (pixelated_uv.y + u_seed) * (u_scale / noiseBScale); + float noiseB = snoise(vec3(noiseBX, noiseBY, time)); + + float strength = 0.0; + strength += noiseA * 0.2; + strength += noiseB * 0.8; + + float opacity = min(max(strength, 0.0), 1.0); + + float threshold = ((u_radius / 2.0) / u_scale); + if (length(vec2(modX - size_half, modY - size_half)) < threshold) { + out_color = vec4(u_color.r, u_color.g, u_color.b, opacity); + return; + } + + out_color = vec4(0.0, 0.0, 0.0, 0.0); +} +`; + +const canvasEl = useTemplateRef('canvasEl'); + +const props = withDefaults(defineProps<{ + scale?: number; +}>(), { + scale: 48, +}); + +let handle: ReturnType<typeof window['requestAnimationFrame']> | null = null; + +onMounted(() => { + const canvas = canvasEl.value!; + let width = canvas.offsetWidth; + let height = canvas.offsetHeight; + canvas.width = width; + canvas.height = height; + + const maybeGl = canvas.getContext('webgl2', { preserveDrawingBuffer: false, alpha: true, premultipliedAlpha: false, antialias: true }); + if (maybeGl == null) return; + + const gl = maybeGl; + + const VERTICES = new Float32Array([-1, -1, -1, 1, 1, 1, -1, -1, 1, 1, 1, -1]); + const vertexBuffer = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer); + gl.bufferData(gl.ARRAY_BUFFER, VERTICES, gl.STATIC_DRAW); + + //gl.clearColor(0.0, 0.0, 0.0, 0.0); + //gl.clear(gl.COLOR_BUFFER_BIT); + + const shaderProgram = initShaderProgram(gl, VERTEX_SHADER, FRAGMENT_SHADER); + + gl.useProgram(shaderProgram); + + const positionLocation = gl.getAttribLocation(shaderProgram, 'position'); + gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0); + gl.enableVertexAttribArray(positionLocation); + + const in_resolution = gl.getUniformLocation(shaderProgram, 'in_resolution'); + gl.uniform2fv(in_resolution, [canvas.width, canvas.height]); + + const u_time = gl.getUniformLocation(shaderProgram, 'u_time'); + const u_seed = gl.getUniformLocation(shaderProgram, 'u_seed'); + const u_scale = gl.getUniformLocation(shaderProgram, 'u_scale'); + const u_angle = gl.getUniformLocation(shaderProgram, 'u_angle'); + const u_radius = gl.getUniformLocation(shaderProgram, 'u_radius'); + const u_color = gl.getUniformLocation(shaderProgram, 'u_color'); + gl.uniform1f(u_seed, Math.random() * 1000); + gl.uniform1f(u_scale, props.scale); + gl.uniform1f(u_angle, 0.0); + gl.uniform1f(u_radius, 0.15); + gl.uniform3fv(u_color, [0.5, 1.0, 0]); + + if (isChromatic()) { + gl.uniform1f(u_time, 0); + gl.drawArrays(gl.TRIANGLES, 0, 6); + } else { + function render(timeStamp: number) { + let sizeChanged = false; + if (Math.abs(height - canvas.offsetHeight) > 2) { + height = canvas.offsetHeight; + canvas.height = height; + sizeChanged = true; + } + if (Math.abs(width - canvas.offsetWidth) > 2) { + width = canvas.offsetWidth; + canvas.width = width; + sizeChanged = true; + } + if (sizeChanged && gl) { + gl.uniform2fv(in_resolution, [width, height]); + gl.viewport(0, 0, width, height); + } + + gl.uniform1f(u_time, timeStamp); + + gl.drawArrays(gl.TRIANGLES, 0, 6); + + handle = window.requestAnimationFrame(render); + } + + handle = window.requestAnimationFrame(render); + } +}); + +onUnmounted(() => { + if (handle) { + window.cancelAnimationFrame(handle); + } + + // TODO: WebGLリソースの解放 +}); +</script> + +<style lang="scss" module> +</style> |