Files

1 line
9.4 KiB
JSON

{"name":"EvilEye","title":"EvilEye","description":"Procedural evil eye shader with animated iris, slit pupil, and fiery outer glow.","type":"registry:component","add":"when-added","files":[{"type":"registry:component","role":"file","content":"<script setup lang=\"ts\">\nimport { Mesh, Program, Renderer, Texture, Triangle } from 'ogl';\nimport { onBeforeUnmount, onMounted, useTemplateRef, watch } from 'vue';\n\ninterface EvilEyeProps {\n eyeColor?: string;\n intensity?: number;\n pupilSize?: number;\n irisWidth?: number;\n glowIntensity?: number;\n scale?: number;\n noiseScale?: number;\n pupilFollow?: number;\n flameSpeed?: number;\n backgroundColor?: string;\n}\n\nfunction hexToVec3(hex: string): [number, number, number] {\n const h = hex.replace('#', '');\n return [parseInt(h.slice(0, 2), 16) / 255, parseInt(h.slice(2, 4), 16) / 255, parseInt(h.slice(4, 6), 16) / 255];\n}\n\nfunction generateNoiseTexture(size = 256): Uint8Array {\n const data = new Uint8Array(size * size * 4);\n\n function hash(x: number, y: number, s: number): number {\n let n = x * 374761393 + y * 668265263 + s * 1274126177;\n n = Math.imul(n ^ (n >>> 13), 1274126177);\n return ((n ^ (n >>> 16)) >>> 0) / 4294967296;\n }\n\n function noise(px: number, py: number, freq: number, seed: number): number {\n const fx = (px / size) * freq;\n const fy = (py / size) * freq;\n const ix = Math.floor(fx);\n const iy = Math.floor(fy);\n const tx = fx - ix;\n const ty = fy - iy;\n const w = freq | 0;\n const v00 = hash(((ix % w) + w) % w, ((iy % w) + w) % w, seed);\n const v10 = hash((((ix + 1) % w) + w) % w, ((iy % w) + w) % w, seed);\n const v01 = hash(((ix % w) + w) % w, (((iy + 1) % w) + w) % w, seed);\n const v11 = hash((((ix + 1) % w) + w) % w, (((iy + 1) % w) + w) % w, seed);\n return v00 * (1 - tx) * (1 - ty) + v10 * tx * (1 - ty) + v01 * (1 - tx) * ty + v11 * tx * ty;\n }\n\n for (let y = 0; y < size; y++) {\n for (let x = 0; x < size; x++) {\n let v = 0;\n let amp = 0.4;\n let totalAmp = 0;\n for (let o = 0; o < 8; o++) {\n const f = 32 * (1 << o);\n v += amp * noise(x, y, f, o * 31);\n totalAmp += amp;\n amp *= 0.65;\n }\n v /= totalAmp;\n v = (v - 0.5) * 2.2 + 0.5;\n v = Math.max(0, Math.min(1, v));\n const val = Math.round(v * 255);\n const i = (y * size + x) * 4;\n data[i] = val;\n data[i + 1] = val;\n data[i + 2] = val;\n data[i + 3] = 255;\n }\n }\n\n return data;\n}\n\nconst vertexShader = `\nattribute vec2 uv;\nattribute vec2 position;\nvarying vec2 vUv;\nvoid main() {\n vUv = uv;\n gl_Position = vec4(position, 0, 1);\n}\n`;\n\nconst fragmentShader = `\nprecision highp float;\n\nuniform float uTime;\nuniform vec3 uResolution;\nuniform sampler2D uNoiseTexture;\nuniform float uPupilSize;\nuniform float uIrisWidth;\nuniform float uGlowIntensity;\nuniform float uIntensity;\nuniform float uScale;\nuniform float uNoiseScale;\nuniform vec2 uMouse;\nuniform float uPupilFollow;\nuniform float uFlameSpeed;\nuniform vec3 uEyeColor;\nuniform vec3 uBgColor;\n\nvoid main() {\n vec2 uv = (gl_FragCoord.xy * 2.0 - uResolution.xy) / uResolution.y;\n uv /= uScale;\n float ft = uTime * uFlameSpeed;\n\n float polarRadius = length(uv) * 2.0;\n float polarAngle = (2.0 * atan(uv.x, uv.y)) / 6.28 * 0.3;\n vec2 polarUv = vec2(polarRadius, polarAngle);\n\n vec4 noiseA = texture2D(uNoiseTexture, polarUv * vec2(0.2, 7.0) * uNoiseScale + vec2(-ft * 0.1, 0.0));\n vec4 noiseB = texture2D(uNoiseTexture, polarUv * vec2(0.3, 4.0) * uNoiseScale + vec2(-ft * 0.2, 0.0));\n vec4 noiseC = texture2D(uNoiseTexture, polarUv * vec2(0.1, 5.0) * uNoiseScale + vec2(-ft * 0.1, 0.0));\n\n float distanceMask = 1.0 - length(uv);\n\n // Inner ring\n float innerRing = clamp(-1.0 * ((distanceMask - 0.7) / uIrisWidth), 0.0, 1.0);\n innerRing = (innerRing * distanceMask - 0.2) / 0.28;\n innerRing += noiseA.r - 0.5;\n innerRing *= 1.3;\n innerRing = clamp(innerRing, 0.0, 1.0);\n\n float outerRing = clamp(-1.0 * ((distanceMask - 0.5) / 0.2), 0.0, 1.0);\n outerRing = (outerRing * distanceMask - 0.1) / 0.38;\n outerRing += noiseC.r - 0.5;\n outerRing *= 1.3;\n outerRing = clamp(outerRing, 0.0, 1.0);\n\n innerRing += outerRing;\n\n // Inner eye\n float innerEye = distanceMask - 0.1 * 2.0;\n innerEye *= noiseB.r * 2.0;\n\n // Pupil with cursor tracking\n vec2 pupilOffset = uMouse * uPupilFollow * 0.12;\n vec2 pupilUv = uv - pupilOffset;\n float pupil = 1.0 - length(pupilUv * vec2(9.0, 2.3));\n pupil *= uPupilSize;\n pupil = clamp(pupil, 0.0, 1.0);\n pupil /= 0.35;\n\n // Outer eye\n float outerEyeGlow = 1.0 - length(uv * vec2(0.5, 1.5));\n outerEyeGlow = clamp(outerEyeGlow + 0.5, 0.0, 1.0);\n outerEyeGlow += noiseC.r - 0.5;\n float outerBgGlow = outerEyeGlow;\n outerEyeGlow = pow(outerEyeGlow, 2.0);\n outerEyeGlow += distanceMask;\n outerEyeGlow *= uGlowIntensity;\n outerEyeGlow = clamp(outerEyeGlow, 0.0, 1.0);\n outerEyeGlow *= pow(1.0 - distanceMask, 2.0) * 2.5;\n\n // Outer eye bg glow\n outerBgGlow += distanceMask;\n outerBgGlow = pow(outerBgGlow, 0.5);\n outerBgGlow *= 0.15;\n\n vec3 color = uEyeColor * uIntensity * clamp(max(innerRing + innerEye, outerEyeGlow + outerBgGlow) - pupil, 0.0, 3.0);\n color += uBgColor;\n\n gl_FragColor = vec4(color, 1.0);\n}\n`;\n\nconst props = withDefaults(defineProps<EvilEyeProps>(), {\n eyeColor: '#FF6F37',\n intensity: 1.5,\n pupilSize: 0.6,\n irisWidth: 0.25,\n glowIntensity: 0.35,\n scale: 0.8,\n noiseScale: 1.0,\n pupilFollow: 1.0,\n flameSpeed: 1.0,\n backgroundColor: '#000000'\n});\n\nconst containerRef = useTemplateRef<HTMLDivElement>('containerRef');\n\nlet cleanup: (() => void) | null = null;\nconst setup = () => {\n if (!containerRef.value) return;\n const container = containerRef.value;\n const renderer = new Renderer({ alpha: true, premultipliedAlpha: false });\n const gl = renderer.gl;\n gl.clearColor(0, 0, 0, 0);\n\n const noiseData = generateNoiseTexture(256);\n const noiseTexture = new Texture(gl, {\n image: noiseData,\n width: 256,\n height: 256,\n generateMipmaps: false,\n flipY: false\n });\n noiseTexture.minFilter = gl.LINEAR;\n noiseTexture.magFilter = gl.LINEAR;\n noiseTexture.wrapS = gl.REPEAT;\n noiseTexture.wrapT = gl.REPEAT;\n\n const mouse = { x: 0, y: 0, tx: 0, ty: 0 };\n\n function onMouseMove(e: MouseEvent) {\n const rect = container.getBoundingClientRect();\n mouse.tx = ((e.clientX - rect.left) / rect.width) * 2 - 1;\n mouse.ty = -(((e.clientY - rect.top) / rect.height) * 2 - 1);\n }\n\n function onMouseLeave() {\n mouse.tx = 0;\n mouse.ty = 0;\n }\n\n container.addEventListener('mousemove', onMouseMove);\n container.addEventListener('mouseleave', onMouseLeave);\n\n renderer.setSize(container.offsetWidth, container.offsetHeight);\n\n const geometry = new Triangle(gl);\n const program = new Program(gl, {\n vertex: vertexShader,\n fragment: fragmentShader,\n uniforms: {\n uTime: { value: 0 },\n uResolution: { value: [gl.canvas.width, gl.canvas.height, gl.canvas.width / gl.canvas.height] },\n uNoiseTexture: { value: noiseTexture },\n uPupilSize: { value: props.pupilSize },\n uIrisWidth: { value: props.irisWidth },\n uGlowIntensity: { value: props.glowIntensity },\n uIntensity: { value: props.intensity },\n uScale: { value: props.scale },\n uNoiseScale: { value: props.noiseScale },\n uMouse: { value: [0, 0] },\n uPupilFollow: { value: props.pupilFollow },\n uFlameSpeed: { value: props.flameSpeed },\n uEyeColor: { value: hexToVec3(props.eyeColor) },\n uBgColor: { value: hexToVec3(props.backgroundColor) }\n }\n });\n\n function resize() {\n renderer.setSize(container.offsetWidth, container.offsetHeight);\n program.uniforms.uResolution.value = [gl.canvas.width, gl.canvas.height, gl.canvas.width / gl.canvas.height];\n }\n window.addEventListener('resize', resize);\n\n const mesh = new Mesh(gl, { geometry, program });\n container.appendChild(gl.canvas);\n\n let animationFrameId: number;\n\n function update(time: number) {\n animationFrameId = requestAnimationFrame(update);\n mouse.x += (mouse.tx - mouse.x) * 0.05;\n mouse.y += (mouse.ty - mouse.y) * 0.05;\n program.uniforms.uMouse.value = [mouse.x, mouse.y];\n program.uniforms.uTime.value = time * 0.001;\n renderer.render({ scene: mesh });\n }\n animationFrameId = requestAnimationFrame(update);\n\n cleanup = () => {\n cancelAnimationFrame(animationFrameId);\n window.removeEventListener('resize', resize);\n container.removeEventListener('mousemove', onMouseMove);\n container.removeEventListener('mouseleave', onMouseLeave);\n container.removeChild(gl.canvas);\n gl.getExtension('WEBGL_lose_context')?.loseContext();\n };\n};\n\nonMounted(() => {\n setup();\n});\n\nonBeforeUnmount(() => {\n cleanup?.();\n});\n\nwatch(\n () => [\n props.eyeColor,\n props.intensity,\n props.pupilSize,\n props.irisWidth,\n props.glowIntensity,\n props.scale,\n props.noiseScale,\n props.pupilFollow,\n props.flameSpeed,\n props.backgroundColor\n ],\n () => {\n cleanup?.();\n setup();\n }\n);\n</script>\n\n<template>\n <div ref=\"containerRef\" class=\"w-full h-full\" />\n</template>\n","path":"EvilEye/EvilEye.vue","_imports_":[],"registryDependencies":[],"dependencies":[],"devDependencies":[]}],"registryDependencies":[],"dependencies":[{"ecosystem":"js","name":"ogl","version":"^1.0.11"}],"devDependencies":[],"categories":["Backgrounds"]}