├── .eslintrc.json ├── .gitignore ├── .prettierignore ├── .prettierrc ├── README.md ├── index.html ├── package-lock.json ├── package.json ├── public ├── model.glb └── vite.svg ├── src ├── Experience.jsx ├── Sphere.jsx ├── assets │ └── react.svg ├── index.css ├── main.jsx └── shaders │ ├── includes │ ├── perlin3d.glsl │ └── simplexNoise3d.glsl │ └── particles │ ├── fragment.glsl │ └── vertex.glsl └── vite.config.js /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "env": { "browser": true, "es2020": true }, 4 | "extends": [ 5 | "eslint:recommended", 6 | "plugin:react/recommended", 7 | "plugin:react/jsx-runtime", 8 | "plugin:react-hooks/recommended" 9 | ], 10 | "ignorePatterns": ["dist", ".eslintrc.cjs"], 11 | "parserOptions": { "ecmaVersion": "latest", "sourceType": "module" }, 12 | "settings": { "react": { "version": "18.2" } }, 13 | "plugins": ["react-refresh", "simple-import-sort"], 14 | "rules": { 15 | "simple-import-sort/imports": "error", 16 | "simple-import-sort/exports": "error", 17 | "semi": ["error", "always"], 18 | "react/jsx-no-target-blank": "off", 19 | "react/no-unknown-property": ["off"], 20 | "react-refresh/only-export-components": [ 21 | "warn", 22 | { "allowConstantExport": true } 23 | ] 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /out 3 | /public.json 4 | /dist -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "none", 4 | "tabWidth": 4 5 | } 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # particle-shader 2 | 3 | ## Setup 4 | 5 | To run this project locally, download [Node.js](https://nodejs.org/en/download) and follow these steps: 6 | 7 | ``` 8 | # Install dependencies 9 | npm install 10 | 11 | # Run the local server at localhost:5173 12 | npm run dev 13 | 14 | # Build for production in the dist/ directory 15 | npm run build 16 | ``` 17 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | particle shader 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "r3f-template", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite --host", 8 | "build": "vite build", 9 | "eslint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview", 11 | "prettier": "prettier --write ." 12 | }, 13 | "dependencies": { 14 | "@gsap/react": "^2.1.1", 15 | "@react-spring/three": "^9.7.3", 16 | "@react-three/drei": "^9.106.0", 17 | "@react-three/fiber": "^8.16.8", 18 | "@react-three/postprocessing": "^2.16.2", 19 | "gsap": "^3.12.5", 20 | "howler": "^2.2.4", 21 | "leva": "^0.9.34", 22 | "postprocessing": "^6.35.5", 23 | "r3f-perf": "^7.2.1", 24 | "react": "^18.2.0", 25 | "react-dom": "^18.2.0", 26 | "three": "^0.167.1", 27 | "vite-plugin-glsl": "^1.3.0", 28 | "zustand": "^4.5.2" 29 | }, 30 | "devDependencies": { 31 | "@types/react": "^18.2.66", 32 | "@types/react-dom": "^18.2.22", 33 | "@vitejs/plugin-react-swc": "^3.5.0", 34 | "eslint": "^8.57.0", 35 | "eslint-plugin-react": "^7.34.2", 36 | "eslint-plugin-react-hooks": "^4.6.0", 37 | "eslint-plugin-react-refresh": "^0.4.6", 38 | "eslint-plugin-simple-import-sort": "^12.1.0", 39 | "prettier": "^3.3.1", 40 | "vite": "^5.2.0" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /public/model.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cresolution128/three.js-particle-animation/2d5b84a1629b88aec0cb26c632556bc6f05aff3a/public/model.glb -------------------------------------------------------------------------------- /public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/Experience.jsx: -------------------------------------------------------------------------------- 1 | import { ScrollControls } from '@react-three/drei'; 2 | import { Canvas } from '@react-three/fiber'; 3 | import { DepthOfField, EffectComposer } from '@react-three/postprocessing'; 4 | import { Leva, useControls } from 'leva'; 5 | import { Perf } from 'r3f-perf'; 6 | 7 | import Sphere from './Sphere'; 8 | 9 | const Experience = () => { 10 | const controls = useControls('experience', { 11 | focalLength: { value: 6, min: 0, max: 100, step: 0.001 }, 12 | focusDistance: { value: 0.2, min: 0, max: 40, step: 0.001 }, 13 | }); 14 | 15 | 16 | return ( 17 | <> 18 | 19 | 26 | 27 | {/* */} 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | ); 40 | }; 41 | 42 | export default Experience; 43 | -------------------------------------------------------------------------------- /src/Sphere.jsx: -------------------------------------------------------------------------------- 1 | import { useAspect, useGLTF, useScroll } from '@react-three/drei'; 2 | import { useFrame } from '@react-three/fiber'; 3 | import { useControls } from 'leva'; 4 | import { useEffect, useMemo, useRef } from 'react'; 5 | import * as THREE from 'three'; 6 | 7 | import particlesFragmentShader from './shaders/particles/fragment.glsl'; 8 | import particlesVertexShader from './shaders/particles/vertex.glsl'; 9 | 10 | const Sphere = () => { 11 | const pointsRef = useRef(); 12 | let particles = {}; 13 | const model = useGLTF('./model.glb'); 14 | const positions = model.scene.children.map((child) => { 15 | return child.geometry.attributes.position; 16 | }); 17 | particles.maxCount = 0; 18 | for (const position of positions) { 19 | if (position.count > particles.maxCount) { 20 | particles.maxCount = position.count; 21 | } 22 | } 23 | particles.positions = []; 24 | for (const position of positions) { 25 | const originalArray = position.array; 26 | 27 | const newArray = new Float32Array(particles.maxCount * 3); 28 | 29 | for (let i = 0; i < particles.maxCount; i++) { 30 | const i3 = i * 3; 31 | if (position.count < 22900) { 32 | if (i3 < originalArray.length) { 33 | newArray[i3] = originalArray[i3] * 0.05; 34 | newArray[i3 + 1] = originalArray[i3 + 1] * 0.05; 35 | newArray[i3 + 2] = originalArray[i3 + 2] * 0.05; 36 | } else { 37 | const ramdomIndex = 38 | Math.floor(position.count * Math.random()) * 3; 39 | newArray[i3] = originalArray[ramdomIndex] * 0.05; 40 | newArray[i3 + 1] = originalArray[ramdomIndex + 1] * 0.05; 41 | newArray[i3 + 2] = originalArray[ramdomIndex + 2] * 0.05; 42 | } 43 | } else { 44 | if (i3 < originalArray.length) { 45 | newArray[i3] = originalArray[i3] * 20.0; 46 | newArray[i3 + 1] = originalArray[i3 + 1] * 2.0 + 3.0; 47 | newArray[i3 + 2] = originalArray[i3 + 2] * 15.0; 48 | } else { 49 | const ramdomIndex = 50 | Math.floor(position.count * Math.random()) * 3; 51 | newArray[i3] = originalArray[ramdomIndex] * 20.0; 52 | newArray[i3 + 1] = 53 | originalArray[ramdomIndex + 1] * 2.0 + 3.0; 54 | newArray[i3 + 2] = originalArray[ramdomIndex + 2] * 15.0; 55 | } 56 | } 57 | } 58 | particles.positions.push(new THREE.Float32BufferAttribute(newArray, 3)); 59 | } 60 | 61 | const sizesArray = new Float32Array(particles.maxCount); 62 | 63 | for (let i = 0; i < particles.maxCount; i++) { 64 | sizesArray[i] = Math.random(); 65 | } 66 | 67 | particles.geometry = new THREE.BufferGeometry(); 68 | particles.geometry.setAttribute('position', particles.positions[1]); 69 | particles.geometry.setIndex(null); 70 | particles.geometry.setAttribute('aPositionTarget', particles.positions[0]); 71 | particles.geometry.setAttribute( 72 | 'aSize', 73 | new THREE.BufferAttribute(sizesArray, 1) 74 | ); 75 | 76 | const particleControls = useControls('Particles', { 77 | uSize: { value: 1.0, min: 0.1, max: 10, step: 0.1 }, 78 | progress: { value: 0, min: 0, max: 1, step: 0.001 }, 79 | RotateX: { value: 1.5, min: -10.0, max: 10.0, step: 0.01 }, 80 | TimeFrequency: { value: 0.25, min: 0.0, max: 1.0, step: 0.01 }, 81 | DistortionFrequencyWave: { 82 | value: 0.08, 83 | min: 0.0, 84 | max: 1.0, 85 | step: 0.01 86 | }, 87 | DistortionStrengthWave: { 88 | value: 3.5, 89 | min: 0.0, 90 | max: 10.0, 91 | step: 0.01 92 | }, 93 | DisplacementFrequencyWave: { 94 | value: 0.15, 95 | min: 0.0, 96 | max: 1.0, 97 | step: 0.01 98 | }, 99 | DisplacementStrengthWave: { value: 1.5, min: 0.0, max: 5.0, step: 0.1 } 100 | }); 101 | 102 | const particleColorControls = useControls('Particles Colors', { 103 | colorA: '#3018eb', 104 | colorB: '#ff6f00' 105 | }); 106 | 107 | // Sizes 108 | const [width, height, pixelRatio] = useAspect( 109 | window.innerWidth, 110 | window.innerHeight, 111 | Math.min(2, window.devicePixelRatio) 112 | ); 113 | 114 | particles.colorA = particleColorControls.colorA; 115 | particles.colorB = particleColorControls.colorB; 116 | 117 | const uniforms = useMemo( 118 | () => ({ 119 | uTime: { value: 0 }, 120 | uSize: { value: particleControls.uSize }, 121 | uResolution: { 122 | value: new THREE.Vector2( 123 | width * pixelRatio, 124 | height * pixelRatio 125 | ) 126 | }, 127 | uProgress: { value: particleControls.progress }, 128 | uRotationX: { value: particleControls.RotateX }, 129 | uTimeFrequency: { value: particleControls.TimeFrequency }, 130 | uColorA: { value: new THREE.Color(particles.colorA) }, 131 | uColorB: { value: new THREE.Color(particles.colorB) }, 132 | uDistortionFrequencyWave: { 133 | value: particleControls.DistortionFrequencyWave 134 | }, 135 | uDistortionStrengthWave: { 136 | value: particleControls.DistortionStrengthWave 137 | }, 138 | uDisplacementFrequencyWave: { 139 | value: particleControls.DisplacementFrequencyWave 140 | }, 141 | uDisplacementStrengthWave: { 142 | value: particleControls.DisplacementStrengthWave 143 | } 144 | }), 145 | [ 146 | width, 147 | height, 148 | pixelRatio, 149 | particleControls, 150 | particles.colorA, 151 | particles.colorB 152 | ] 153 | ); 154 | 155 | const material = useMemo( 156 | () => 157 | new THREE.ShaderMaterial({ 158 | vertexShader: particlesVertexShader, 159 | fragmentShader: particlesFragmentShader, 160 | uniforms: uniforms, 161 | blending: THREE.AdditiveBlending, 162 | depthWrite: false, 163 | transparent: true 164 | }), 165 | [uniforms] 166 | ); 167 | 168 | useFrame((state) => { 169 | uniforms.uTime.value = state.clock.elapsedTime + 50; 170 | }); 171 | 172 | useEffect(() => { 173 | // Update uniforms on resize 174 | uniforms.uResolution.value.set(width * pixelRatio, height * pixelRatio); 175 | }, [width, height, pixelRatio, uniforms]); 176 | 177 | const scroll = useScroll(); 178 | 179 | useFrame(() => { 180 | uniforms.uProgress.value = scroll.offset; 181 | }); 182 | 183 | return ; 184 | }; 185 | 186 | useGLTF.preload('./model.glb'); 187 | export default Sphere; 188 | -------------------------------------------------------------------------------- /src/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | html, 2 | body, 3 | #root { 4 | position: fixed; 5 | top: 0; 6 | left: 0; 7 | width: 100%; 8 | height: 100%; 9 | background-color: #000000; 10 | background-image: linear-gradient(40deg, #0a0a0a 20%, #0e0025 100%); 11 | } 12 | 13 | ::-webkit-scrollbar { 14 | display: none; 15 | } 16 | 17 | .canvas { 18 | touch-action: auto !important; 19 | } 20 | -------------------------------------------------------------------------------- /src/main.jsx: -------------------------------------------------------------------------------- 1 | import './index.css'; 2 | 3 | import React from 'react'; 4 | import ReactDOM from 'react-dom/client'; 5 | 6 | import Experience from './Experience'; 7 | 8 | ReactDOM.createRoot(document.getElementById('root')).render( 9 | 10 | 11 | 12 | ); 13 | -------------------------------------------------------------------------------- /src/shaders/includes/perlin3d.glsl: -------------------------------------------------------------------------------- 1 | // Classic Perlin 3D Noise 2 | // by Stefan Gustavson 3 | // 4 | // vec4 permute(vec4 x){return mod(((x*34.0)+1.0)*x, 289.0);} 5 | // vec4 taylorInvSqrt(vec4 r){return 1.79284291400159 - 0.85373472095314 * r;} 6 | vec3 fade(vec3 t) {return t*t*t*(t*(t*6.0-15.0)+10.0);} 7 | 8 | float perlin3d(vec3 P) 9 | { 10 | vec3 Pi0 = floor(P); // Integer part for indexing 11 | vec3 Pi1 = Pi0 + vec3(1.0); // Integer part + 1 12 | Pi0 = mod(Pi0, 289.0); 13 | Pi1 = mod(Pi1, 289.0); 14 | vec3 Pf0 = fract(P); // Fractional part for interpolation 15 | vec3 Pf1 = Pf0 - vec3(1.0); // Fractional part - 1.0 16 | vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x); 17 | vec4 iy = vec4(Pi0.yy, Pi1.yy); 18 | vec4 iz0 = Pi0.zzzz; 19 | vec4 iz1 = Pi1.zzzz; 20 | 21 | vec4 ixy = permute(permute(ix) + iy); 22 | vec4 ixy0 = permute(ixy + iz0); 23 | vec4 ixy1 = permute(ixy + iz1); 24 | 25 | vec4 gx0 = ixy0 / 7.0; 26 | vec4 gy0 = fract(floor(gx0) / 7.0) - 0.5; 27 | gx0 = fract(gx0); 28 | vec4 gz0 = vec4(0.5) - abs(gx0) - abs(gy0); 29 | vec4 sz0 = step(gz0, vec4(0.0)); 30 | gx0 -= sz0 * (step(0.0, gx0) - 0.5); 31 | gy0 -= sz0 * (step(0.0, gy0) - 0.5); 32 | 33 | vec4 gx1 = ixy1 / 7.0; 34 | vec4 gy1 = fract(floor(gx1) / 7.0) - 0.5; 35 | gx1 = fract(gx1); 36 | vec4 gz1 = vec4(0.5) - abs(gx1) - abs(gy1); 37 | vec4 sz1 = step(gz1, vec4(0.0)); 38 | gx1 -= sz1 * (step(0.0, gx1) - 0.5); 39 | gy1 -= sz1 * (step(0.0, gy1) - 0.5); 40 | 41 | vec3 g000 = vec3(gx0.x,gy0.x,gz0.x); 42 | vec3 g100 = vec3(gx0.y,gy0.y,gz0.y); 43 | vec3 g010 = vec3(gx0.z,gy0.z,gz0.z); 44 | vec3 g110 = vec3(gx0.w,gy0.w,gz0.w); 45 | vec3 g001 = vec3(gx1.x,gy1.x,gz1.x); 46 | vec3 g101 = vec3(gx1.y,gy1.y,gz1.y); 47 | vec3 g011 = vec3(gx1.z,gy1.z,gz1.z); 48 | vec3 g111 = vec3(gx1.w,gy1.w,gz1.w); 49 | 50 | vec4 norm0 = taylorInvSqrt(vec4(dot(g000, g000), dot(g010, g010), dot(g100, g100), dot(g110, g110))); 51 | g000 *= norm0.x; 52 | g010 *= norm0.y; 53 | g100 *= norm0.z; 54 | g110 *= norm0.w; 55 | vec4 norm1 = taylorInvSqrt(vec4(dot(g001, g001), dot(g011, g011), dot(g101, g101), dot(g111, g111))); 56 | g001 *= norm1.x; 57 | g011 *= norm1.y; 58 | g101 *= norm1.z; 59 | g111 *= norm1.w; 60 | 61 | float n000 = dot(g000, Pf0); 62 | float n100 = dot(g100, vec3(Pf1.x, Pf0.yz)); 63 | float n010 = dot(g010, vec3(Pf0.x, Pf1.y, Pf0.z)); 64 | float n110 = dot(g110, vec3(Pf1.xy, Pf0.z)); 65 | float n001 = dot(g001, vec3(Pf0.xy, Pf1.z)); 66 | float n101 = dot(g101, vec3(Pf1.x, Pf0.y, Pf1.z)); 67 | float n011 = dot(g011, vec3(Pf0.x, Pf1.yz)); 68 | float n111 = dot(g111, Pf1); 69 | 70 | vec3 fade_xyz = fade(Pf0); 71 | vec4 n_z = mix(vec4(n000, n100, n010, n110), vec4(n001, n101, n011, n111), fade_xyz.z); 72 | vec2 n_yz = mix(n_z.xy, n_z.zw, fade_xyz.y); 73 | float n_xyz = mix(n_yz.x, n_yz.y, fade_xyz.x); 74 | return 2.2 * n_xyz; 75 | } 76 | -------------------------------------------------------------------------------- /src/shaders/includes/simplexNoise3d.glsl: -------------------------------------------------------------------------------- 1 | // Simplex 3D Noise 2 | // by Ian McEwan, Ashima Arts 3 | // 4 | vec4 permute(vec4 x){ return mod(((x*34.0)+1.0)*x, 289.0); } 5 | vec4 taylorInvSqrt(vec4 r){ return 1.79284291400159 - 0.85373472095314 * r; } 6 | 7 | float simplexNoise3d(vec3 v) 8 | { 9 | const vec2 C = vec2(1.0/6.0, 1.0/3.0) ; 10 | const vec4 D = vec4(0.0, 0.5, 1.0, 2.0); 11 | 12 | // First corner 13 | vec3 i = floor(v + dot(v, C.yyy) ); 14 | vec3 x0 = v - i + dot(i, C.xxx) ; 15 | 16 | // Other corners 17 | vec3 g = step(x0.yzx, x0.xyz); 18 | vec3 l = 1.0 - g; 19 | vec3 i1 = min( g.xyz, l.zxy ); 20 | vec3 i2 = max( g.xyz, l.zxy ); 21 | 22 | // x0 = x0 - 0. + 0.0 * C 23 | vec3 x1 = x0 - i1 + 1.0 * C.xxx; 24 | vec3 x2 = x0 - i2 + 2.0 * C.xxx; 25 | vec3 x3 = x0 - 1. + 3.0 * C.xxx; 26 | 27 | // Permutations 28 | i = mod(i, 289.0 ); 29 | vec4 p = permute( permute( permute( i.z + vec4(0.0, i1.z, i2.z, 1.0 )) + i.y + vec4(0.0, i1.y, i2.y, 1.0 )) + i.x + vec4(0.0, i1.x, i2.x, 1.0 )); 30 | 31 | // Gradients 32 | // ( N*N points uniformly over a square, mapped onto an octahedron.) 33 | float n_ = 1.0/7.0; // N=7 34 | vec3 ns = n_ * D.wyz - D.xzx; 35 | 36 | vec4 j = p - 49.0 * floor(p * ns.z *ns.z); // mod(p,N*N) 37 | 38 | vec4 x_ = floor(j * ns.z); 39 | vec4 y_ = floor(j - 7.0 * x_ ); // mod(j,N) 40 | 41 | vec4 x = x_ *ns.x + ns.yyyy; 42 | vec4 y = y_ *ns.x + ns.yyyy; 43 | vec4 h = 1.0 - abs(x) - abs(y); 44 | 45 | vec4 b0 = vec4( x.xy, y.xy ); 46 | vec4 b1 = vec4( x.zw, y.zw ); 47 | 48 | vec4 s0 = floor(b0)*2.0 + 1.0; 49 | vec4 s1 = floor(b1)*2.0 + 1.0; 50 | vec4 sh = -step(h, vec4(0.0)); 51 | 52 | vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy ; 53 | vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww ; 54 | 55 | vec3 p0 = vec3(a0.xy,h.x); 56 | vec3 p1 = vec3(a0.zw,h.y); 57 | vec3 p2 = vec3(a1.xy,h.z); 58 | vec3 p3 = vec3(a1.zw,h.w); 59 | 60 | // Normalise gradients 61 | vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3))); 62 | p0 *= norm.x; 63 | p1 *= norm.y; 64 | p2 *= norm.z; 65 | p3 *= norm.w; 66 | 67 | // Mix final noise value 68 | vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0); 69 | m = m * m; 70 | return 42.0 * dot( m*m, vec4( dot(p0,x0), dot(p1,x1), dot(p2,x2), dot(p3,x3) ) ); 71 | } -------------------------------------------------------------------------------- /src/shaders/particles/fragment.glsl: -------------------------------------------------------------------------------- 1 | varying vec3 vColor; 2 | 3 | void main() { 4 | vec2 uv = gl_PointCoord; 5 | float distanceToCenter = length(uv - 0.5); 6 | 7 | float alpha = 0.05 / distanceToCenter - 0.1; 8 | 9 | gl_FragColor = vec4(vColor, alpha); 10 | 11 | #include 12 | #include 13 | } -------------------------------------------------------------------------------- /src/shaders/particles/vertex.glsl: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | uniform float uTime; 4 | uniform float uRotationX; 5 | uniform float uTimeFrequency; 6 | 7 | uniform float uDistortionFrequencyWave; 8 | uniform float uDistortionStrengthWave; 9 | uniform float uDisplacementFrequencyWave; 10 | uniform float uDisplacementStrengthWave; 11 | 12 | uniform vec2 uResolution; 13 | uniform float uSize; 14 | uniform float uProgress; 15 | 16 | uniform vec3 uColorA; 17 | uniform vec3 uColorB; 18 | 19 | attribute vec3 aPositionTarget; 20 | attribute float aSize; 21 | 22 | varying vec3 vColor; 23 | 24 | #define PI 3.1415926535897932384626433832795 25 | 26 | #include ../includes/simplexNoise3d.glsl 27 | #include ../includes/perlin3d.glsl 28 | 29 | vec3 applyWaveFunction(vec3 position) { 30 | // Generate Perlin noise based on position and time 31 | float perlinValue = perlin3d(vec3(position.xz * uDisplacementFrequencyWave, uTime * uTimeFrequency * 0.65)); 32 | 33 | // Apply wave height and normalize the wave effect 34 | vec3 waveDisplacement = vec3(0.0, perlinValue * uDisplacementStrengthWave, 0.0); 35 | 36 | // Apply distortion if needed 37 | vec3 distortion = vec3(perlin3d(position * uDistortionFrequencyWave + vec3(1.0, 0.0, 0.0)) - 0.5, -perlin3d(position * uDistortionFrequencyWave + vec3(0.0, 1.0, 0.0)) * 0.3 + 0.5, -perlin3d(position * uDistortionFrequencyWave + vec3(0.0, 0.0, 1.0)) - 1.0) * uDistortionStrengthWave; 38 | 39 | // Final displaced position with wave and distortion 40 | return position + waveDisplacement + distortion; 41 | } 42 | 43 | mat3 rotation3dY(float angle) { 44 | float s = sin(angle); 45 | float c = cos(angle); 46 | 47 | return mat3(c, 0.0, -s, 0.0, 1.0, 0.0, s, 0.0, c); 48 | } 49 | 50 | vec3 rotateY(vec3 v, float angle) { 51 | return rotation3dY(angle) * v; 52 | } 53 | 54 | mat3 rotation3dX(float angle) { 55 | float s = sin(angle); 56 | float c = cos(angle); 57 | 58 | return mat3(1.0, 0.0, 0.0, 0.0, c, s, 0.0, -s, c); 59 | } 60 | 61 | vec3 rotateX(vec3 v, float angle) { 62 | return rotation3dX(angle) * v; 63 | } 64 | 65 | float smoothBlend(float edge0, float edge1, float x) { 66 | x = clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0); 67 | return x * x * x * (x * (x * 6.0 - 15.0) + 10.0); 68 | } 69 | 70 | float easeInOutCubic(float t) { 71 | return t < 0.75 ? 4.0 * t * t * t : 1.0 - pow(-2.0 * t + 2.0, 3.0) / 2.0; 72 | } 73 | 74 | void main() { 75 | float noiseOrigin = simplexNoise3d(position * 0.25); 76 | float noiseTarget = simplexNoise3d(aPositionTarget * 0.25); 77 | float noise = mix(noiseOrigin, noiseTarget, uProgress); 78 | noise = smoothBlend(-1.0, 1.0, noise); 79 | noise = pow(noise, 2.0); 80 | 81 | 82 | float duration = 0.4; 83 | float delay = (1.0 - duration) * noise; 84 | float end = delay + duration; 85 | float progress = smoothstep(delay, end, uProgress); 86 | 87 | vec3 mixedPosition = mix(position, aPositionTarget, progress); 88 | vec3 displacedPosition; 89 | 90 | progress = pow(progress, 3.0); 91 | 92 | if(progress < 0.65){ 93 | displacedPosition.xyz = applyWaveFunction(mixedPosition) * (1.0 - progress) * 0.01; 94 | displacedPosition *= 150.0; 95 | displacedPosition.y -= pow(progress * 1.2, 3.0); 96 | }else{ 97 | displacedPosition.xyz = rotateX(mixedPosition, uRotationX * PI * easeInOutCubic(progress)); 98 | displacedPosition *= pow(progress * 1.1, 2.0); 99 | displacedPosition.y -= pow(progress * 1.2, 3.0); 100 | } 101 | 102 | if(progress >= 0.65) displacedPosition.xyz = rotateY(displacedPosition.xyz, uTime * 0.35 * pow(progress, 2.0)); 103 | 104 | 105 | // Final position 106 | vec4 modelPosition = modelMatrix * vec4(displacedPosition.xyz, 1.0); 107 | vec4 viewPosition = viewMatrix * modelPosition; 108 | vec4 projectedPosition = projectionMatrix * viewPosition; 109 | gl_Position = projectedPosition; 110 | 111 | // Point size 112 | if(progress > 0.5) gl_PointSize = aSize * uSize * uResolution.y * 8.0 * progress; 113 | else gl_PointSize = aSize * uSize * uResolution.y * 25.0 * (1.0 - progress); 114 | 115 | gl_PointSize *= (1.0 / -viewPosition.z); 116 | 117 | //varyings 118 | vColor = mix(uColorB, uColorA, mod(pow(noise, 3.0), 1.0) ); 119 | } 120 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import react from '@vitejs/plugin-react-swc'; 2 | import { defineConfig } from 'vite'; 3 | import glsl from 'vite-plugin-glsl'; 4 | 5 | // https://vitejs.dev/config/ 6 | export default defineConfig({ 7 | plugins: [react(), glsl()] 8 | }); 9 | --------------------------------------------------------------------------------