├── .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 |
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 |
--------------------------------------------------------------------------------