mirror of
https://github.com/DavidHDev/vue-bits.git
synced 2026-03-07 06:29:30 -07:00
1 line
8.4 KiB
JSON
1 line
8.4 KiB
JSON
{"name":"PixelSnow","title":"PixelSnow","description":"Falling pixelated snow effect with customizable density and speed.","type":"registry:component","add":"when-added","files":[{"type":"registry:component","role":"file","content":"<script setup lang=\"ts\">\nimport {\n Color,\n Mesh,\n OrthographicCamera,\n PlaneGeometry,\n Scene,\n ShaderMaterial,\n Vector2,\n Vector3,\n WebGLRenderer\n} from 'three';\nimport { onBeforeUnmount, onMounted, ref, useTemplateRef, watch, type CSSProperties } from 'vue';\n\nconst vertexShader = `\nvoid main() {\n gl_Position = vec4(position, 1.0);\n}\n`;\n\nconst fragmentShader = `\nprecision highp float;\n\nuniform float uTime;\nuniform vec2 uResolution;\nuniform float uFlakeSize;\nuniform float uMinFlakeSize;\nuniform float uPixelResolution;\nuniform float uSpeed;\nuniform float uDepthFade;\nuniform float uFarPlane;\nuniform vec3 uColor;\nuniform float uBrightness;\nuniform float uGamma;\nuniform float uDensity;\nuniform float uVariant;\nuniform float uDirection;\n\n#define M1 1597334677U\n#define M2 3812015801U\n#define M3 3299493293U\n#define F0 (1.0/float(0xffffffffU))\n#define hash(n) n*(n^(n>>15))\n#define coord3(p) (uvec3(p).x*M1^uvec3(p).y*M2^uvec3(p).z*M3)\n\nvec3 hash3(uint n) {\n return vec3(hash(n) * uvec3(0x1U, 0x1ffU, 0x3ffffU)) * F0;\n}\n\nfloat snowflakeDist(vec2 p) {\n float r = length(p);\n float a = atan(p.y, p.x);\n float PI = 3.14159265;\n a = abs(mod(a + PI / 6.0, PI / 3.0) - PI / 6.0);\n vec2 q = r * vec2(cos(a), sin(a));\n float dMain = abs(q.y);\n dMain = max(dMain, max(-q.x, q.x - 1.0));\n vec2 b1s = vec2(0.4, 0.0);\n vec2 b1d = vec2(0.574, 0.819);\n float b1t = clamp(dot(q - b1s, b1d), 0.0, 0.4);\n float dB1 = length(q - b1s - b1t * b1d);\n vec2 b2s = vec2(0.7, 0.0);\n float b2t = clamp(dot(q - b2s, b1d), 0.0, 0.25);\n float dB2 = length(q - b2s - b2t * b1d);\n return min(dMain, min(dB1, dB2)) * 10.0;\n}\n\nvoid main() {\n float pixelSize = max(1.0, floor(0.5 + uResolution.x / uPixelResolution));\n vec2 fragCoord = floor(gl_FragCoord.xy / pixelSize);\n vec2 res = uResolution / pixelSize;\n\n vec3 ray = normalize(vec3((fragCoord - res * 0.5) / res.x, 1.0));\n\n vec3 camK = normalize(vec3(1.0, 1.0, 1.0));\n vec3 camI = normalize(vec3(1.0, 0.0, -1.0));\n vec3 camJ = cross(camK, camI);\n ray = ray.x * camI + ray.y * camJ + ray.z * camK;\n\n float windX = cos(uDirection) * 0.4;\n float windY = sin(uDirection) * 0.4;\n vec3 camPos = (windX * camI + windY * camJ + 0.1 * camK) * uTime * uSpeed;\n vec3 pos = camPos;\n\n vec3 strides = 1.0 / max(abs(ray), vec3(0.001));\n vec3 phase = fract(pos) * strides;\n phase = mix(strides - phase, phase, step(ray, vec3(0.0)));\n\n float t = 0.0;\n for (int i = 0; i < 256; i++) {\n if (t >= uFarPlane) break;\n vec3 fpos = floor(pos);\n float cellHash = hash3(coord3(fpos)).x;\n\n if (cellHash < uDensity) {\n vec3 h = hash3(coord3(fpos));\n vec3 flakePos = 0.5 - 0.5 * cos(\n 4.0 * sin(fpos.yzx * 0.073) +\n 4.0 * sin(fpos.zxy * 0.27) +\n 2.0 * h +\n uTime * uSpeed * 0.1 * vec3(7.0, 8.0, 5.0)\n );\n flakePos = flakePos * 0.8 + 0.1 + fpos;\n\n float toIntersection = dot(flakePos - pos, camK) / dot(ray, camK);\n if (toIntersection > 0.0) {\n vec3 testPos = pos + ray * toIntersection - flakePos;\n vec2 testUV = abs(vec2(dot(testPos, camI), dot(testPos, camJ)));\n float depth = dot(flakePos - camPos, camK);\n float flakeSize = max(uFlakeSize, uMinFlakeSize * depth * 0.5 / res.x);\n float dist;\n if (uVariant < 0.5) dist = max(testUV.x, testUV.y);\n else if (uVariant < 1.5) dist = length(testUV);\n else dist = snowflakeDist(vec2(dot(testPos, camI), dot(testPos, camJ)) / flakeSize) * flakeSize;\n\n if (dist < flakeSize) {\n float intensity = exp2(-(t + toIntersection) / uDepthFade) *\n min(1.0, pow(uFlakeSize / flakeSize, 2.0)) * uBrightness;\n gl_FragColor = vec4(uColor * pow(vec3(intensity), vec3(uGamma)), 1.0);\n return;\n }\n }\n }\n\n float nextStep = min(min(phase.x, phase.y), phase.z);\n vec3 sel = step(phase, vec3(nextStep));\n phase = phase - nextStep + strides * sel;\n t += nextStep;\n pos = mix(pos + ray * nextStep, floor(pos + ray * nextStep + 0.5), sel);\n }\n\n gl_FragColor = vec4(0.0);\n}\n`;\n\ninterface PixelSnowProps {\n color?: string;\n flakeSize?: number;\n minFlakeSize?: number;\n pixelResolution?: number;\n speed?: number;\n depthFade?: number;\n farPlane?: number;\n brightness?: number;\n gamma?: number;\n density?: number;\n variant?: 'square' | 'round' | 'snowflake';\n direction?: number;\n className?: string;\n style?: CSSProperties;\n}\n\nconst props = withDefaults(defineProps<PixelSnowProps>(), {\n color: '#ffffff',\n flakeSize: 0.01,\n minFlakeSize: 1.25,\n pixelResolution: 200,\n speed: 1.25,\n depthFade: 8,\n farPlane: 20,\n brightness: 1,\n gamma: 0.4545,\n density: 0.3,\n variant: 'square',\n direction: 125,\n className: '',\n style: () => ({})\n});\n\nconst containerRef = useTemplateRef<HTMLDivElement>('containerRef');\nconst animationRef = ref<number>(0);\n\nlet cleanupFn: (() => void) | null = null;\nconst setupFn = () => {\n const container = containerRef.value;\n if (!container) return;\n\n const scene = new Scene();\n const camera = new OrthographicCamera(-1, 1, 1, -1, 0, 1);\n const renderer = new WebGLRenderer({\n antialias: false,\n alpha: true,\n premultipliedAlpha: false,\n powerPreference: 'high-performance'\n });\n\n renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));\n renderer.setSize(container.offsetWidth, container.offsetHeight);\n renderer.setClearColor(0x000000, 0);\n container.appendChild(renderer.domElement);\n\n const threeColor = new Color(props.color);\n const material = new ShaderMaterial({\n vertexShader,\n fragmentShader,\n uniforms: {\n uTime: { value: 0 },\n uResolution: { value: new Vector2(container.offsetWidth, container.offsetHeight) },\n uFlakeSize: { value: props.flakeSize },\n uMinFlakeSize: { value: props.minFlakeSize },\n uPixelResolution: { value: props.pixelResolution },\n uSpeed: { value: props.speed },\n uDepthFade: { value: props.depthFade },\n uFarPlane: { value: props.farPlane },\n uColor: { value: new Vector3(threeColor.r, threeColor.g, threeColor.b) },\n uBrightness: { value: props.brightness },\n uGamma: { value: props.gamma },\n uDensity: { value: props.density },\n uVariant: { value: props.variant === 'round' ? 1.0 : props.variant === 'snowflake' ? 2.0 : 0.0 },\n uDirection: { value: (props.direction * Math.PI) / 180 }\n },\n transparent: true\n });\n\n const geometry = new PlaneGeometry(2, 2);\n scene.add(new Mesh(geometry, material));\n\n const handleResize = () => {\n const w = container.offsetWidth,\n h = container.offsetHeight;\n renderer.setSize(w, h);\n material.uniforms.uResolution.value.set(w, h);\n };\n window.addEventListener('resize', handleResize);\n\n const startTime = performance.now();\n const animate = () => {\n animationRef.value = requestAnimationFrame(animate);\n material.uniforms.uTime.value = (performance.now() - startTime) * 0.001;\n renderer.render(scene, camera);\n };\n animate();\n\n cleanupFn = () => {\n cancelAnimationFrame(animationRef.value);\n window.removeEventListener('resize', handleResize);\n container.removeChild(renderer.domElement);\n renderer.dispose();\n geometry.dispose();\n material.dispose();\n };\n};\n\nonMounted(() => {\n setupFn();\n});\n\nonBeforeUnmount(() => {\n cleanupFn?.();\n});\n\nwatch(\n () => [\n props.color,\n props.flakeSize,\n props.minFlakeSize,\n props.pixelResolution,\n props.speed,\n props.depthFade,\n props.farPlane,\n props.brightness,\n props.gamma,\n props.density,\n props.variant,\n props.direction\n ],\n () => {\n cleanupFn?.();\n setupFn();\n },\n { deep: true }\n);\n</script>\n\n<template>\n <div ref=\"containerRef\" :class=\"['absolute inset-0 w-full h-full', className]\" :style=\"style\"></div>\n</template>\n","path":"PixelSnow/PixelSnow.vue","_imports_":[],"registryDependencies":[],"dependencies":[],"devDependencies":[]}],"registryDependencies":[],"dependencies":[{"ecosystem":"js","name":"three","version":"^0.178.0"}],"devDependencies":[],"categories":["Backgrounds"]} |