Skip to content

Instantly share code, notes, and snippets.

@jaykdoe
Created June 19, 2025 19:17
Show Gist options
  • Save jaykdoe/a03a0ba334860d4cfe7018a003695851 to your computer and use it in GitHub Desktop.
Save jaykdoe/a03a0ba334860d4cfe7018a003695851 to your computer and use it in GitHub Desktop.
Dithered Blob with Controls
{"config":{"codeTheme":"OneDarkPro","pageThemeSyncCodeTheme":true,"openAlmightyConsole":false,"autoRun":false,"layout":"default","keepPreviousLogs":true,"codeFontSize":16},"title":"Dithered Blob with Controls","code":{"HTML":{"language":"html","content":"<script src=\"https://cdn.tailwindcss.com\"></script>\r\n<link href=\"https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap\" rel=\"stylesheet\">\r\n<meta charset=\"UTF-8\">\r\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n\r\n<div id=\"ui-container\" class=\"fixed top-5 right-5 z-50 bg-gray-900/70 backdrop-blur-md p-6 rounded-xl shadow-2xl border border-gray-700/50 transition-all duration-300 ease-in-out w-72\">\r\n <h3 class=\"text-lg font-semibold text-gray-100 mb-5 tracking-wide uppercase\">Dithering Controls</h3>\r\n <div class=\"mb-5\">\r\n <label for=\"dither-pattern\" class=\"block text-sm font-medium text-gray-300 mb-2\">Dither Pattern</label>\r\n <select id=\"dither-pattern\" class=\"w-full bg-gray-800/80 border border-gray-700 text-gray-200 text-sm rounded-lg focus:ring-indigo-500 focus:border-indigo-500 block p-2.5 placeholder-gray-400 shadow-sm appearance-none\">\r\n <option value=\"0\">Bayer Matrix (8x8)</option>\r\n <option value=\"1\">Halftone Dots</option>\r\n <option value=\"2\">Line Pattern</option>\r\n <option value=\"3\">Noise Dithering</option>\r\n <option value=\"4\">No Dithering</option>\r\n </select>\r\n </div>\r\n <div class=\"mb-3\">\r\n <label for=\"dither-scale\" class=\"block text-sm font-medium text-gray-300 mb-2\">Dither Scale</label>\r\n <select id=\"dither-scale\" class=\"w-full bg-gray-800/80 border border-gray-700 text-gray-200 text-sm rounded-lg focus:ring-indigo-500 focus:border-indigo-500 block p-2.5 placeholder-gray-400 shadow-sm appearance-none\">\r\n <option value=\"1.0\">Fine</option>\r\n <option value=\"1.5\" selected>Medium</option>\r\n <option value=\"2.5\">Coarse</option>\r\n <option value=\"3.5\">Very Coarse</option>\r\n </select>\r\n </div>\r\n</div>"},"CSS":{"language":"css","content":"body {\r\n margin: 0;\r\n overflow: hidden;\r\n font-family: 'Inter', sans-serif;\r\n}\r\ncanvas {\r\n display: block;\r\n width: 100vw;\r\n height: 100vh;\r\n}\r\n::-webkit-scrollbar {\r\n width: 8px;\r\n}\r\n::-webkit-scrollbar-track {\r\n background: rgba(255, 255, 255, 0.1);\r\n border-radius: 10px;\r\n}\r\n::-webkit-scrollbar-thumb {\r\n background: rgba(255, 255, 255, 0.3);\r\n border-radius: 10px;\r\n}\r\n::-webkit-scrollbar-thumb:hover {\r\n background: rgba(255, 255, 255, 0.5);\r\n}\r\nselect {\r\n background-image: url(\"data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22292.4%22%20height%3D%22292.4%22%3E%3Cpath%20fill%3D%22%23DDDDDD%22%20d%3D%22M287%2069.4a17.6%2017.6%200%200%200-13-5.4H18.4c-5%200-9.3%201.8-12.9%205.4A17.6%2017.6%200%200%200%200%2082.2c0%205%201.8%209.3%205.4%2012.9l128%20127.9c3.6%203.6%207.8%205.4%2012.8%205.4s9.2-1.8%2012.8-5.4L287%2095c3.5-3.5%205.4-7.8%205.4-12.8%200-5-1.9-9.2-5.5-12.8z%22%2F%3E%3C%2Fsvg%3E\");\r\n background-repeat: no-repeat;\r\n background-position: right 0.75rem top 50%;\r\n background-size: 0.65em auto;\r\n -webkit-appearance: none;\r\n -moz-appearance: none;\r\n appearance: none;\r\n}","resources":[]},"JS":{"language":"javascript","content":"import * as THREE from \"https://esm.sh/three\";\r\nimport { OrbitControls } from \"https://esm.sh/three/examples/jsm/controls/OrbitControls.js\";\r\nimport { EffectComposer } from \"https://esm.sh/three/examples/jsm/postprocessing/EffectComposer.js\";\r\nimport { RenderPass } from \"https://esm.sh/three/examples/jsm/postprocessing/RenderPass.js\";\r\nimport { UnrealBloomPass } from \"https://esm.sh/three/examples/jsm/postprocessing/UnrealBloomPass.js\";\r\nimport { FilmPass } from \"https://esm.sh/three/examples/jsm/postprocessing/FilmPass.js\";\r\n\r\nconst DITHER_MOTION_SPEED = 2.0;\r\nconst DITHER_MOTION_AMPLITUDE = 1.5;\r\nconst BLOB_BASE_RADIUS = 2.0;\r\nconst BLOB_NOISE_FREQUENCY_VERTEX = 0.75;\r\nconst BLOB_NOISE_AMPLITUDE_VERTEX = 0.65;\r\nconst BLOB_NOISE_SPEED_VERTEX = 0.08;\r\nconst PARTICLE_COUNT = 1200;\r\nconst STAR_COUNT = 3000;\r\n\r\nconst scene = new THREE.Scene();\r\nscene.background = new THREE.Color(0x000000);\r\nscene.fog = new THREE.FogExp2(0x000000, 0.025);\r\n\r\nconst camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);\r\ncamera.position.set(0, 0, 5.0);\r\n\r\nconst renderer = new THREE.WebGLRenderer({ antialias: true, powerPreference: \"high-performance\" });\r\nrenderer.setSize(window.innerWidth, window.innerHeight);\r\nrenderer.setPixelRatio(Math.min(window.devicePixelRatio, 1.5));\r\nrenderer.outputColorSpace = THREE.SRGBColorSpace;\r\ndocument.body.appendChild(renderer.domElement);\r\n\r\nconst composer = new EffectComposer(renderer);\r\nconst renderPass = new RenderPass(scene, camera);\r\ncomposer.addPass(renderPass);\r\n\r\nconst bloomPass = new UnrealBloomPass(new THREE.Vector2(window.innerWidth, window.innerHeight), 0.45, 0.55, 0.75);\r\ncomposer.addPass(bloomPass);\r\n\r\nconst filmPass = new FilmPass(0.20, 0.15, 648, false);\r\ncomposer.addPass(filmPass);\r\n\r\nconst controls = new OrbitControls(camera, renderer.domElement);\r\ncontrols.enableDamping = true;\r\ncontrols.dampingFactor = 0.04;\r\ncontrols.rotateSpeed = 0.20;\r\ncontrols.minDistance = 2.0;\r\ncontrols.maxDistance = 12;\r\ncontrols.enablePan = false;\r\ncontrols.autoRotate = false;\r\n\r\nconst ambientLight = new THREE.AmbientLight(0x606070, 0.6);\r\nscene.add(ambientLight);\r\n\r\nconst pointLight1 = new THREE.PointLight(0xffddaa, 0.9, 60);\r\npointLight1.position.set(5, 5, 5);\r\nscene.add(pointLight1);\r\n\r\nconst pointLight2 = new THREE.PointLight(0xaaccff, 0.6, 60);\r\npointLight2.position.set(-5, -3, -4);\r\nscene.add(pointLight2);\r\n\r\nconst pointLight3 = new THREE.PointLight(0xff8844, 0.75, 60);\r\npointLight3.position.set(0, -5, 3);\r\nscene.add(pointLight3);\r\n\r\nconst starGeometry = new THREE.BufferGeometry();\r\nconst starPositions = [];\r\nconst starColors = [];\r\nconst starSizes = [];\r\n\r\nfor (let i = 0; i < STAR_COUNT; i++) {\r\n const x = THREE.MathUtils.randFloatSpread(200);\r\n const y = THREE.MathUtils.randFloatSpread(200);\r\n const z = THREE.MathUtils.randFloatSpread(200);\r\n starPositions.push(x, y, z);\r\n const color = new THREE.Color();\r\n color.setHSL(THREE.MathUtils.randFloat(0.5, 0.7), 0.2, THREE.MathUtils.randFloat(0.3, 0.6));\r\n starColors.push(color.r, color.g, color.b);\r\n starSizes.push(THREE.MathUtils.randFloat(0.5, 1.5));\r\n}\r\nstarGeometry.setAttribute('position', new THREE.Float32BufferAttribute(starPositions, 3));\r\nstarGeometry.setAttribute('color', new THREE.Float32BufferAttribute(starColors, 3));\r\nstarGeometry.setAttribute('size', new THREE.Float32BufferAttribute(starSizes, 1));\r\n\r\nconst starMaterial = new THREE.ShaderMaterial({\r\n uniforms: {\r\n uTime: { value: 0.0 },\r\n },\r\n vertexShader: `\r\n uniform float uTime;\r\n attribute float size;\r\n varying vec3 vColor;\r\n varying float vAlpha;\r\n void main() {\r\n vColor = color;\r\n vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);\r\n gl_PointSize = size * (100.0 / -mvPosition.z) * (sin(position.x * 0.1 + uTime * 0.3) * 0.2 + 0.8);\r\n vAlpha = clamp(1.0 - (-mvPosition.z / 150.0), 0.1, 0.8);\r\n gl_Position = projectionMatrix * mvPosition;\r\n }\r\n `,\r\n fragmentShader: `\r\n uniform float uTime;\r\n varying vec3 vColor;\r\n varying float vAlpha;\r\n void main() {\r\n float dist = length(gl_PointCoord - vec2(0.5));\r\n if (dist > 0.5) discard;\r\n gl_FragColor = vec4(vColor, vAlpha * (0.6 + 0.4 * sin(uTime * 2.0 + gl_FragCoord.x * 0.5)));\r\n }\r\n `,\r\n transparent: true,\r\n blending: THREE.AdditiveBlending,\r\n depthWrite: false,\r\n vertexColors: true\r\n});\r\nconst stars = new THREE.Points(starGeometry, starMaterial);\r\nscene.add(stars);\r\n\r\nconst ditherPatternsFunction = `\r\n const float bayerMatrix[64] = float[64](\r\n 0.0/64.0, 32.0/64.0, 8.0/64.0, 40.0/64.0, 2.0/64.0, 34.0/64.0, 10.0/64.0, 42.0/64.0,\r\n 48.0/64.0, 16.0/64.0, 56.0/64.0, 24.0/64.0, 50.0/64.0, 18.0/64.0, 58.0/64.0, 26.0/64.0,\r\n 12.0/64.0, 44.0/64.0, 4.0/64.0, 36.0/64.0, 14.0/64.0, 46.0/64.0, 6.0/64.0, 38.0/64.0,\r\n 60.0/64.0, 28.0/64.0, 52.0/64.0, 20.0/64.0, 62.0/64.0, 30.0/64.0, 54.0/64.0, 22.0/64.0,\r\n 3.0/64.0, 35.0/64.0, 11.0/64.0, 43.0/64.0, 1.0/64.0, 33.0/64.0, 9.0/64.0, 41.0/64.0,\r\n 51.0/64.0, 19.0/64.0, 59.0/64.0, 27.0/64.0, 49.0/64.0, 17.0/64.0, 57.0/64.0, 25.0/64.0,\r\n 15.0/64.0, 47.0/64.0, 7.0/64.0, 39.0/64.0, 13.0/64.0, 45.0/64.0, 5.0/64.0, 37.0/64.0,\r\n 63.0/64.0, 31.0/64.0, 55.0/64.0, 23.0/64.0, 61.0/64.0, 29.0/64.0, 53.0/64.0, 21.0/64.0\r\n );\r\n\r\n float getBayerValue(vec2 coord) {\r\n int x = int(mod(coord.x, 8.0));\r\n int y = int(mod(coord.y, 8.0));\r\n return bayerMatrix[y * 8 + x];\r\n }\r\n\r\n float getHalftoneValue(vec2 coord, float time) {\r\n vec2 c = vec2(0.5);\r\n coord = mod(coord * 0.1 + vec2(sin(time*0.1)*0.02, cos(time*0.1)*0.02), 1.0);\r\n float d = distance(coord, c);\r\n return smoothstep(0.28, 0.29, d);\r\n }\r\n\r\n float getLinePatternValue(vec2 coord, float time) {\r\n float lw = 0.35 + sin(time*0.15)*0.1;\r\n float p1 = mod(coord.x*0.15+sin(coord.y*0.04+time*0.08)*0.6,1.0);\r\n float p2 = mod(coord.y*0.15+cos(coord.x*0.04+time*0.12)*0.6,1.0);\r\n return max(smoothstep(0.0,lw,p1)*smoothstep(1.0,1.0-lw,p1), smoothstep(0.0,lw,p2)*smoothstep(1.0,1.0-lw,p2));\r\n }\r\n\r\n float getNoiseDitheringValue(vec2 coord, float time) {\r\n return fract(sin(dot(coord + time * 0.05, vec2(12.9898, 78.233))) * 43758.5453);\r\n }\r\n\r\n vec3 ditherMonochrome(vec3 color, vec2 baseScreenPos, float colorLevels, float time,\r\n float motionSpeed, float motionAmplitude, int patternType) {\r\n float luminance = dot(color, vec3(0.299, 0.587, 0.114));\r\n luminance = pow(luminance, 1.2); \r\n luminance = (luminance - 0.5) * 6.0 + 0.5; \r\n luminance = clamp(luminance, 0.0, 1.0);\r\n\r\n vec2 ditherScreenPos = baseScreenPos;\r\n ditherScreenPos.x += sin(time * motionSpeed * 0.75 + baseScreenPos.y * 0.08) * motionAmplitude;\r\n ditherScreenPos.y += cos(time * motionSpeed * 0.55 + baseScreenPos.x * 0.08) * motionAmplitude;\r\n\r\n float threshold = 0.5; \r\n\r\n if (patternType == 0) { \r\n threshold = getBayerValue(ditherScreenPos);\r\n } else if (patternType == 1) { \r\n threshold = getHalftoneValue(ditherScreenPos, time);\r\n } else if (patternType == 2) { \r\n threshold = getLinePatternValue(ditherScreenPos, time);\r\n } else if (patternType == 3) { \r\n threshold = getNoiseDitheringValue(ditherScreenPos, time);\r\n } else if (patternType == 4) { \r\n threshold = 0.5;\r\n }\r\n\r\n float ditheredValue = (luminance < threshold) ? 0.0 : 1.0;\r\n return vec3(ditheredValue);\r\n }\r\n`;\r\n\r\nconst glslRandFunction = `\r\n float rand(vec3 co){ return fract(sin(dot(co, vec3(12.9898,78.233,53.543))) * 43758.5453); }\r\n float snoise(vec3 p) {\r\n vec3 ip = floor(p); vec3 fp = fract(p); fp = fp*fp*(3.0-2.0*fp);\r\n float v000=rand(ip+vec3(0,0,0)); float v100=rand(ip+vec3(1,0,0)); float v010=rand(ip+vec3(0,1,0)); float v110=rand(ip+vec3(1,1,0));\r\n float v001=rand(ip+vec3(0,0,1)); float v101=rand(ip+vec3(1,0,1)); float v011=rand(ip+vec3(0,1,1)); float v111=rand(ip+vec3(1,1,1));\r\n return mix(mix(mix(v000,v100,fp.x),mix(v010,v110,fp.x),fp.y), mix(mix(v001,v101,fp.x),mix(v011,v111,fp.x),fp.y),fp.z);\r\n }\r\n`;\r\n\r\nconst blobMaterial = new THREE.ShaderMaterial({\r\n uniforms: {\r\n uTime: { value: 0 },\r\n ditherScale: { value: 1.5 },\r\n colorLevels: { value: 2.0 },\r\n uDitherMotionSpeed: { value: DITHER_MOTION_SPEED },\r\n uDitherMotionAmplitude: { value: DITHER_MOTION_AMPLITUDE },\r\n uBaseColor: { value: new THREE.Color(0xffffff) },\r\n uFresnelPower: { value: 2.5 },\r\n uVertexNoiseFrequency: { value: BLOB_NOISE_FREQUENCY_VERTEX },\r\n uVertexNoiseAmplitude: { value: BLOB_NOISE_AMPLITUDE_VERTEX },\r\n uVertexNoiseSpeed: { value: BLOB_NOISE_SPEED_VERTEX },\r\n uSurfaceNoiseFrequency: { value: 2.8 },\r\n uSurfaceNoiseAmplitude: { value: 0.22 },\r\n uDitherPattern: { value: 0 },\r\n uCoreBrightness: { value: 0.2 }\r\n },\r\n vertexShader: `\r\n uniform float uTime;\r\n uniform float uVertexNoiseFrequency;\r\n uniform float uVertexNoiseAmplitude;\r\n uniform float uVertexNoiseSpeed;\r\n varying vec3 vNormal;\r\n varying vec3 vViewPosition;\r\n varying vec3 vWorldPosition;\r\n ${glslRandFunction}\r\n void main() {\r\n vec3 pos = position;\r\n float displacement = snoise(pos * uVertexNoiseFrequency + uTime * uVertexNoiseSpeed) * uVertexNoiseAmplitude;\r\n displacement += snoise(pos * uVertexNoiseFrequency * 2.2 + uTime * uVertexNoiseSpeed * 1.4) * (uVertexNoiseAmplitude * 0.45);\r\n pos += normal * displacement;\r\n vec3 offset = vec3(0.01, 0.01, 0.01);\r\n float ddx_noise_orig = snoise((position + offset.xyy) * uVertexNoiseFrequency + uTime * uVertexNoiseSpeed) * uVertexNoiseAmplitude;\r\n ddx_noise_orig += snoise((position + offset.xyy) * uVertexNoiseFrequency * 2.2 + uTime * uVertexNoiseSpeed * 1.4) * (uVertexNoiseAmplitude * 0.45);\r\n vec3 p_ddx = (position + offset.xyy) + normal * ddx_noise_orig;\r\n float ddy_noise_orig = snoise((position + offset.yxy) * uVertexNoiseFrequency + uTime * uVertexNoiseSpeed) * uVertexNoiseAmplitude;\r\n ddy_noise_orig += snoise((position + offset.yxy) * uVertexNoiseFrequency * 2.2 + uTime * uVertexNoiseSpeed * 1.4) * (uVertexNoiseAmplitude * 0.45);\r\n vec3 p_ddy = (position + offset.yxy) + normal * ddy_noise_orig;\r\n vec3 tangent = normalize(p_ddx - pos);\r\n vec3 bitangent = normalize(p_ddy - pos);\r\n vec3 displacedNormal = normalize(cross(tangent, bitangent));\r\n if (length(displacedNormal) < 0.1) { displacedNormal = normal; }\r\n vNormal = normalize(normalMatrix * displacedNormal);\r\n vec4 mvPosition = modelViewMatrix * vec4(pos, 1.0);\r\n vViewPosition = -mvPosition.xyz;\r\n vWorldPosition = (modelMatrix * vec4(pos, 1.0)).xyz;\r\n gl_Position = projectionMatrix * mvPosition;\r\n }\r\n `,\r\n fragmentShader: `\r\n uniform float uTime;\r\n uniform float ditherScale;\r\n uniform float colorLevels;\r\n uniform float uDitherMotionSpeed;\r\n uniform float uDitherMotionAmplitude;\r\n uniform vec3 uBaseColor;\r\n uniform float uFresnelPower;\r\n uniform float uSurfaceNoiseFrequency;\r\n uniform float uSurfaceNoiseAmplitude;\r\n uniform int uDitherPattern;\r\n uniform float uCoreBrightness;\r\n varying vec3 vNormal;\r\n varying vec3 vViewPosition;\r\n varying vec3 vWorldPosition;\r\n ${ditherPatternsFunction}\r\n ${glslRandFunction}\r\n void main() {\r\n vec3 normal = normalize(vNormal);\r\n vec3 viewDir = normalize(vViewPosition);\r\n float fresnel = pow(1.0 - abs(dot(viewDir, normal)), uFresnelPower);\r\n fresnel = smoothstep(0.0, 1.0, fresnel) * 0.6 + 0.4;\r\n float rim = pow(1.0 - abs(dot(viewDir, normal)), 10.0);\r\n fresnel += rim * 0.25;\r\n float surfaceNoise1 = snoise(vWorldPosition * uSurfaceNoiseFrequency + vec3(uTime * 0.1, uTime * 0.06, uTime * 0.08));\r\n float surfaceNoise2 = snoise(vWorldPosition * uSurfaceNoiseFrequency * 2.7 + vec3(uTime * 0.15, uTime * 0.1, uTime * -0.04)) * 0.45;\r\n float surfaceNoise = (surfaceNoise1 + surfaceNoise2) * 0.5 + 0.5;\r\n surfaceNoise = surfaceNoise * uSurfaceNoiseAmplitude + (1.0 - uSurfaceNoiseAmplitude * 0.6);\r\n float coreGlow = pow(max(0.0, dot(viewDir, normal)), 2.0) * uCoreBrightness;\r\n float intensity = (fresnel + coreGlow) * surfaceNoise;\r\n intensity = clamp(intensity, 0.02, 1.0);\r\n vec3 finalColor = uBaseColor * intensity;\r\n vec2 screenPos = gl_FragCoord.xy / ditherScale;\r\n vec3 ditheredOutput = ditherMonochrome(finalColor, screenPos, colorLevels, uTime, uDitherMotionSpeed, uDitherMotionAmplitude, uDitherPattern);\r\n gl_FragColor = vec4(ditheredOutput, 1.0);\r\n }\r\n `,\r\n});\r\n\r\nconst blobGeometry = new THREE.SphereGeometry(BLOB_BASE_RADIUS, 128, 128);\r\nconst morphingBlob = new THREE.Mesh(blobGeometry, blobMaterial);\r\nscene.add(morphingBlob);\r\n\r\nconst particleGeometry = new THREE.BufferGeometry();\r\nconst particlePositions = new Float32Array(PARTICLE_COUNT * 3);\r\nconst particleSizes = new Float32Array(PARTICLE_COUNT);\r\nconst particleSpeeds = new Float32Array(PARTICLE_COUNT);\r\n\r\nfor (let i = 0; i < PARTICLE_COUNT; i++) {\r\n const radius = BLOB_BASE_RADIUS * 2.5 + Math.random() * BLOB_BASE_RADIUS * 4;\r\n const theta = Math.random() * Math.PI * 2;\r\n const phi = Math.acos(2 * Math.random() - 1);\r\n particlePositions[i * 3] = radius * Math.sin(phi) * Math.cos(theta);\r\n particlePositions[i * 3 + 1] = radius * Math.sin(phi) * Math.sin(theta);\r\n particlePositions[i * 3 + 2] = radius * Math.cos(phi);\r\n particleSizes[i] = Math.random() * 0.06 + 0.015;\r\n particleSpeeds[i] = Math.random() * 0.2 + 0.1;\r\n}\r\nparticleGeometry.setAttribute('position', new THREE.BufferAttribute(particlePositions, 3));\r\nparticleGeometry.setAttribute('size', new THREE.BufferAttribute(particleSizes, 1));\r\nparticleGeometry.setAttribute('speed', new THREE.BufferAttribute(particleSpeeds, 1));\r\n\r\nconst particleMaterial = new THREE.ShaderMaterial({\r\n uniforms: {\r\n uTime: { value: 0 },\r\n uColor: { value: new THREE.Color(0xddddff) },\r\n uBlobBaseRadius: { value: BLOB_BASE_RADIUS }\r\n },\r\n vertexShader: `\r\n uniform float uTime;\r\n uniform float uBlobBaseRadius;\r\n attribute float size;\r\n attribute float speed;\r\n varying float vDistance;\r\n varying float vParticleAlpha;\r\n void main() {\r\n vec3 pos = position;\r\n float waveX = sin(uTime * (speed * 0.8) + position.y * 0.15) * 0.12;\r\n float waveY = cos(uTime * (speed * 1.0) + position.z * 0.20) * 0.12;\r\n float waveZ = sin(uTime * (speed * 0.9) + position.x * 0.18) * 0.12;\r\n pos += vec3(waveX, waveY, waveZ);\r\n vec4 mvPosition = modelViewMatrix * vec4(pos, 1.0);\r\n vDistance = length(mvPosition.xyz);\r\n gl_PointSize = size * (400.0 / -mvPosition.z);\r\n vParticleAlpha = smoothstep(uBlobBaseRadius * 6.0, uBlobBaseRadius * 2.0, vDistance);\r\n gl_Position = projectionMatrix * mvPosition;\r\n }\r\n `,\r\n fragmentShader: `\r\n uniform float uTime;\r\n uniform vec3 uColor;\r\n varying float vDistance;\r\n varying float vParticleAlpha;\r\n\r\n void main() {\r\n float dist = length(gl_PointCoord - vec2(0.5));\r\n if (dist > 0.5) discard;\r\n float pulse = 0.4 + 0.6 * abs(sin(uTime * (1.0 + mod(vDistance, 1.0) * 0.5) + vDistance * 0.2));\r\n float finalAlpha = (1.0 - dist * 2.0) * pulse * vParticleAlpha;\r\n finalAlpha = clamp(finalAlpha, 0.0, 0.5);\r\n gl_FragColor = vec4(uColor, finalAlpha * 0.4);\r\n }\r\n `,\r\n transparent: true,\r\n blending: THREE.AdditiveBlending,\r\n depthWrite: false\r\n});\r\nconst particleSystem = new THREE.Points(particleGeometry, particleMaterial);\r\nscene.add(particleSystem);\r\n\r\nconst ditherPatternSelect = document.getElementById('dither-pattern');\r\nconst ditherScaleSelect = document.getElementById('dither-scale');\r\nconst uiContainer = document.getElementById('ui-container');\r\n\r\nditherPatternSelect.addEventListener('change', (e) => {\r\n blobMaterial.uniforms.uDitherPattern.value = parseInt(e.target.value);\r\n});\r\nditherScaleSelect.addEventListener('change', (e) => {\r\n blobMaterial.uniforms.ditherScale.value = parseFloat(e.target.value);\r\n});\r\n\r\nlet uiTimeout;\r\nconst uiAutoHideDelay = 3000;\r\nconst resetUITimeout = () => {\r\n clearTimeout(uiTimeout);\r\n uiContainer.classList.remove('opacity-0', 'translate-x-12');\r\n uiContainer.classList.add('opacity-100', 'translate-x-0');\r\n uiTimeout = setTimeout(() => {\r\n uiContainer.classList.remove('opacity-100', 'translate-x-0');\r\n uiContainer.classList.add('opacity-0', 'translate-x-12');\r\n }, uiAutoHideDelay);\r\n};\r\ndocument.addEventListener('mousemove', resetUITimeout);\r\ndocument.addEventListener('click', resetUITimeout);\r\ndocument.addEventListener('touchstart', resetUITimeout);\r\nresetUITimeout();\r\n\r\nwindow.addEventListener('resize', () => {\r\n camera.aspect = window.innerWidth / window.innerHeight;\r\n camera.updateProjectionMatrix();\r\n renderer.setSize(window.innerWidth, window.innerHeight);\r\n composer.setSize(window.innerWidth, window.innerHeight);\r\n renderer.setPixelRatio(Math.min(window.devicePixelRatio, 1.5));\r\n bloomPass.resolution.set(window.innerWidth, window.innerHeight);\r\n});\r\n\r\ncamera.lookAt(scene.position);\r\n\r\nconst clock = new THREE.Clock();\r\nfunction animate() {\r\n requestAnimationFrame(animate);\r\n const elapsedTime = clock.getElapsedTime();\r\n\r\n blobMaterial.uniforms.uTime.value = elapsedTime;\r\n particleMaterial.uniforms.uTime.value = elapsedTime;\r\n starMaterial.uniforms.uTime.value = elapsedTime;\r\n\r\n morphingBlob.rotation.x += 0.0004;\r\n morphingBlob.rotation.y += 0.0007;\r\n\r\n pointLight1.position.x = Math.sin(elapsedTime * 0.32) * 6;\r\n pointLight1.position.z = Math.cos(elapsedTime * 0.32) * 6;\r\n pointLight2.position.y = Math.sin(elapsedTime * 0.18) * 4;\r\n pointLight2.position.x = Math.cos(elapsedTime * 0.25) * -5;\r\n pointLight3.position.z = Math.cos(elapsedTime * 0.40) * 5;\r\n pointLight3.position.y = Math.sin(elapsedTime * 0.35) * -4;\r\n\r\n controls.update();\r\n composer.render();\r\n}\r\n\r\nwindow.onload = () => {\r\n animate();\r\n};","resources":[]},"VUE":{"language":"vue2","content":"","resources":[]}}}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment