├── .gitignore
├── .prettierrc.json
├── README.md
├── index.html
├── index1.html
├── index2.html
├── index3.html
├── index4.html
├── index5.html
├── index6.html
├── package.json
├── pnpm-lock.yaml
├── public
├── fonts
│ └── inter-var-latin.woff2
├── images
│ ├── +0.webp
│ ├── +0n.webp
│ ├── -0.webp
│ ├── -0n.webp
│ ├── 0.webp
│ ├── 0n.webp
│ ├── 1.webp
│ ├── 1d.webp
│ ├── 2.webp
│ ├── 3.webp
│ ├── 4.webp
│ ├── 5.webp
│ ├── 5b.webp
│ ├── 6.webp
│ ├── noise64x64.png
│ └── noise64x64.webp
└── vite.svg
├── src
├── App.ts
├── gl
│ ├── Plane.shader.ts
│ ├── Plane.ts
│ └── index.ts
├── main.ts
├── math.ts
├── style.css
└── vite-env.d.ts
├── tsconfig.json
└── vite.config.ts
/.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 | .vercel
26 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "tabWidth": 2,
3 | "printWidth": 100
4 | }
5 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Webgl Image Interactions
2 |
3 | Inspired by Aristide Benoist's [tweet](https://twitter.com/AristideBenoist/status/1587086913672036353)
4 |
5 | ## References
6 |
7 | - [Align DOM and WEBGL](https://mofu-dev.com/en/blog/three-dom-alignment/)
8 | - [OGL Flowmap](https://oframe.github.io/ogl/examples/?src=mouse-flowmap.html)
9 | - [Pixel Distortion](https://tympanus.net/codrops/2022/01/12/pixel-distortion-effect-with-three-js/)
10 | - [Goeey Effect](https://tympanus.net/codrops/2019/10/23/making-gooey-image-hover-effects-with-three-js/)
11 | - [Glassy Effect](https://www.shadertoy.com/view/WdSGz1)
12 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
15 |
16 | Webgl Images Interactions Inspired by Aristide Benoist
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
35 |
36 |
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/index1.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
15 |
16 | Webgl Images Interactions Inspired by Aristide Benoist
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
35 |
36 |
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/index2.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
15 |
16 | Webgl Images Interactions Inspired by Aristide Benoist
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
35 |
36 |
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/index3.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
15 |
16 | Webgl Images Interactions Inspired by Aristide Benoist
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
35 |
36 |
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/index4.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
15 |
16 | Webgl Images Interactions Inspired by Aristide Benoist
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
35 |
36 |
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/index5.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
15 |
16 | Webgl Images Interactions Inspired by Aristide Benoist
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
35 |
36 |
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/index6.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
15 |
16 | Webgl Images Interactions Inspired
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
35 |
36 |
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "webgl-image-interactions-aristide",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "tsc && vite build",
9 | "preview": "vite preview"
10 | },
11 | "devDependencies": {
12 | "@types/node": "^20.12.5",
13 | "typescript": "^5.2.2",
14 | "vite": "^5.2.0",
15 | "vite-plugin-glsl": "^1.3.0"
16 | },
17 | "dependencies": {
18 | "ogl": "^1.0.6"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/pnpm-lock.yaml:
--------------------------------------------------------------------------------
1 | lockfileVersion: '6.0'
2 |
3 | settings:
4 | autoInstallPeers: true
5 | excludeLinksFromLockfile: false
6 |
7 | dependencies:
8 | ogl:
9 | specifier: ^1.0.6
10 | version: 1.0.6
11 |
12 | devDependencies:
13 | '@types/node':
14 | specifier: ^20.12.5
15 | version: 20.12.5
16 | typescript:
17 | specifier: ^5.2.2
18 | version: 5.4.3
19 | vite:
20 | specifier: ^5.2.0
21 | version: 5.2.8(@types/node@20.12.5)
22 | vite-plugin-glsl:
23 | specifier: ^1.3.0
24 | version: 1.3.0(vite@5.2.8)
25 |
26 | packages:
27 |
28 | /@esbuild/aix-ppc64@0.20.2:
29 | resolution: {integrity: sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==}
30 | engines: {node: '>=12'}
31 | cpu: [ppc64]
32 | os: [aix]
33 | requiresBuild: true
34 | dev: true
35 | optional: true
36 |
37 | /@esbuild/android-arm64@0.20.2:
38 | resolution: {integrity: sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==}
39 | engines: {node: '>=12'}
40 | cpu: [arm64]
41 | os: [android]
42 | requiresBuild: true
43 | dev: true
44 | optional: true
45 |
46 | /@esbuild/android-arm@0.20.2:
47 | resolution: {integrity: sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==}
48 | engines: {node: '>=12'}
49 | cpu: [arm]
50 | os: [android]
51 | requiresBuild: true
52 | dev: true
53 | optional: true
54 |
55 | /@esbuild/android-x64@0.20.2:
56 | resolution: {integrity: sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==}
57 | engines: {node: '>=12'}
58 | cpu: [x64]
59 | os: [android]
60 | requiresBuild: true
61 | dev: true
62 | optional: true
63 |
64 | /@esbuild/darwin-arm64@0.20.2:
65 | resolution: {integrity: sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==}
66 | engines: {node: '>=12'}
67 | cpu: [arm64]
68 | os: [darwin]
69 | requiresBuild: true
70 | dev: true
71 | optional: true
72 |
73 | /@esbuild/darwin-x64@0.20.2:
74 | resolution: {integrity: sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==}
75 | engines: {node: '>=12'}
76 | cpu: [x64]
77 | os: [darwin]
78 | requiresBuild: true
79 | dev: true
80 | optional: true
81 |
82 | /@esbuild/freebsd-arm64@0.20.2:
83 | resolution: {integrity: sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==}
84 | engines: {node: '>=12'}
85 | cpu: [arm64]
86 | os: [freebsd]
87 | requiresBuild: true
88 | dev: true
89 | optional: true
90 |
91 | /@esbuild/freebsd-x64@0.20.2:
92 | resolution: {integrity: sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==}
93 | engines: {node: '>=12'}
94 | cpu: [x64]
95 | os: [freebsd]
96 | requiresBuild: true
97 | dev: true
98 | optional: true
99 |
100 | /@esbuild/linux-arm64@0.20.2:
101 | resolution: {integrity: sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==}
102 | engines: {node: '>=12'}
103 | cpu: [arm64]
104 | os: [linux]
105 | requiresBuild: true
106 | dev: true
107 | optional: true
108 |
109 | /@esbuild/linux-arm@0.20.2:
110 | resolution: {integrity: sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==}
111 | engines: {node: '>=12'}
112 | cpu: [arm]
113 | os: [linux]
114 | requiresBuild: true
115 | dev: true
116 | optional: true
117 |
118 | /@esbuild/linux-ia32@0.20.2:
119 | resolution: {integrity: sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==}
120 | engines: {node: '>=12'}
121 | cpu: [ia32]
122 | os: [linux]
123 | requiresBuild: true
124 | dev: true
125 | optional: true
126 |
127 | /@esbuild/linux-loong64@0.20.2:
128 | resolution: {integrity: sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==}
129 | engines: {node: '>=12'}
130 | cpu: [loong64]
131 | os: [linux]
132 | requiresBuild: true
133 | dev: true
134 | optional: true
135 |
136 | /@esbuild/linux-mips64el@0.20.2:
137 | resolution: {integrity: sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==}
138 | engines: {node: '>=12'}
139 | cpu: [mips64el]
140 | os: [linux]
141 | requiresBuild: true
142 | dev: true
143 | optional: true
144 |
145 | /@esbuild/linux-ppc64@0.20.2:
146 | resolution: {integrity: sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==}
147 | engines: {node: '>=12'}
148 | cpu: [ppc64]
149 | os: [linux]
150 | requiresBuild: true
151 | dev: true
152 | optional: true
153 |
154 | /@esbuild/linux-riscv64@0.20.2:
155 | resolution: {integrity: sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==}
156 | engines: {node: '>=12'}
157 | cpu: [riscv64]
158 | os: [linux]
159 | requiresBuild: true
160 | dev: true
161 | optional: true
162 |
163 | /@esbuild/linux-s390x@0.20.2:
164 | resolution: {integrity: sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==}
165 | engines: {node: '>=12'}
166 | cpu: [s390x]
167 | os: [linux]
168 | requiresBuild: true
169 | dev: true
170 | optional: true
171 |
172 | /@esbuild/linux-x64@0.20.2:
173 | resolution: {integrity: sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==}
174 | engines: {node: '>=12'}
175 | cpu: [x64]
176 | os: [linux]
177 | requiresBuild: true
178 | dev: true
179 | optional: true
180 |
181 | /@esbuild/netbsd-x64@0.20.2:
182 | resolution: {integrity: sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==}
183 | engines: {node: '>=12'}
184 | cpu: [x64]
185 | os: [netbsd]
186 | requiresBuild: true
187 | dev: true
188 | optional: true
189 |
190 | /@esbuild/openbsd-x64@0.20.2:
191 | resolution: {integrity: sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==}
192 | engines: {node: '>=12'}
193 | cpu: [x64]
194 | os: [openbsd]
195 | requiresBuild: true
196 | dev: true
197 | optional: true
198 |
199 | /@esbuild/sunos-x64@0.20.2:
200 | resolution: {integrity: sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==}
201 | engines: {node: '>=12'}
202 | cpu: [x64]
203 | os: [sunos]
204 | requiresBuild: true
205 | dev: true
206 | optional: true
207 |
208 | /@esbuild/win32-arm64@0.20.2:
209 | resolution: {integrity: sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==}
210 | engines: {node: '>=12'}
211 | cpu: [arm64]
212 | os: [win32]
213 | requiresBuild: true
214 | dev: true
215 | optional: true
216 |
217 | /@esbuild/win32-ia32@0.20.2:
218 | resolution: {integrity: sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==}
219 | engines: {node: '>=12'}
220 | cpu: [ia32]
221 | os: [win32]
222 | requiresBuild: true
223 | dev: true
224 | optional: true
225 |
226 | /@esbuild/win32-x64@0.20.2:
227 | resolution: {integrity: sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==}
228 | engines: {node: '>=12'}
229 | cpu: [x64]
230 | os: [win32]
231 | requiresBuild: true
232 | dev: true
233 | optional: true
234 |
235 | /@rollup/pluginutils@5.1.0:
236 | resolution: {integrity: sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==}
237 | engines: {node: '>=14.0.0'}
238 | peerDependencies:
239 | rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
240 | peerDependenciesMeta:
241 | rollup:
242 | optional: true
243 | dependencies:
244 | '@types/estree': 1.0.5
245 | estree-walker: 2.0.2
246 | picomatch: 2.3.1
247 | dev: true
248 |
249 | /@rollup/rollup-android-arm-eabi@4.14.0:
250 | resolution: {integrity: sha512-jwXtxYbRt1V+CdQSy6Z+uZti7JF5irRKF8hlKfEnF/xJpcNGuuiZMBvuoYM+x9sr9iWGnzrlM0+9hvQ1kgkf1w==}
251 | cpu: [arm]
252 | os: [android]
253 | requiresBuild: true
254 | dev: true
255 | optional: true
256 |
257 | /@rollup/rollup-android-arm64@4.14.0:
258 | resolution: {integrity: sha512-fI9nduZhCccjzlsA/OuAwtFGWocxA4gqXGTLvOyiF8d+8o0fZUeSztixkYjcGq1fGZY3Tkq4yRvHPFxU+jdZ9Q==}
259 | cpu: [arm64]
260 | os: [android]
261 | requiresBuild: true
262 | dev: true
263 | optional: true
264 |
265 | /@rollup/rollup-darwin-arm64@4.14.0:
266 | resolution: {integrity: sha512-BcnSPRM76/cD2gQC+rQNGBN6GStBs2pl/FpweW8JYuz5J/IEa0Fr4AtrPv766DB/6b2MZ/AfSIOSGw3nEIP8SA==}
267 | cpu: [arm64]
268 | os: [darwin]
269 | requiresBuild: true
270 | dev: true
271 | optional: true
272 |
273 | /@rollup/rollup-darwin-x64@4.14.0:
274 | resolution: {integrity: sha512-LDyFB9GRolGN7XI6955aFeI3wCdCUszFWumWU0deHA8VpR3nWRrjG6GtGjBrQxQKFevnUTHKCfPR4IvrW3kCgQ==}
275 | cpu: [x64]
276 | os: [darwin]
277 | requiresBuild: true
278 | dev: true
279 | optional: true
280 |
281 | /@rollup/rollup-linux-arm-gnueabihf@4.14.0:
282 | resolution: {integrity: sha512-ygrGVhQP47mRh0AAD0zl6QqCbNsf0eTo+vgwkY6LunBcg0f2Jv365GXlDUECIyoXp1kKwL5WW6rsO429DBY/bA==}
283 | cpu: [arm]
284 | os: [linux]
285 | requiresBuild: true
286 | dev: true
287 | optional: true
288 |
289 | /@rollup/rollup-linux-arm64-gnu@4.14.0:
290 | resolution: {integrity: sha512-x+uJ6MAYRlHGe9wi4HQjxpaKHPM3d3JjqqCkeC5gpnnI6OWovLdXTpfa8trjxPLnWKyBsSi5kne+146GAxFt4A==}
291 | cpu: [arm64]
292 | os: [linux]
293 | requiresBuild: true
294 | dev: true
295 | optional: true
296 |
297 | /@rollup/rollup-linux-arm64-musl@4.14.0:
298 | resolution: {integrity: sha512-nrRw8ZTQKg6+Lttwqo6a2VxR9tOroa2m91XbdQ2sUUzHoedXlsyvY1fN4xWdqz8PKmf4orDwejxXHjh7YBGUCA==}
299 | cpu: [arm64]
300 | os: [linux]
301 | requiresBuild: true
302 | dev: true
303 | optional: true
304 |
305 | /@rollup/rollup-linux-powerpc64le-gnu@4.14.0:
306 | resolution: {integrity: sha512-xV0d5jDb4aFu84XKr+lcUJ9y3qpIWhttO3Qev97z8DKLXR62LC3cXT/bMZXrjLF9X+P5oSmJTzAhqwUbY96PnA==}
307 | cpu: [ppc64le]
308 | os: [linux]
309 | requiresBuild: true
310 | dev: true
311 | optional: true
312 |
313 | /@rollup/rollup-linux-riscv64-gnu@4.14.0:
314 | resolution: {integrity: sha512-SDDhBQwZX6LPRoPYjAZWyL27LbcBo7WdBFWJi5PI9RPCzU8ijzkQn7tt8NXiXRiFMJCVpkuMkBf4OxSxVMizAw==}
315 | cpu: [riscv64]
316 | os: [linux]
317 | requiresBuild: true
318 | dev: true
319 | optional: true
320 |
321 | /@rollup/rollup-linux-s390x-gnu@4.14.0:
322 | resolution: {integrity: sha512-RxB/qez8zIDshNJDufYlTT0ZTVut5eCpAZ3bdXDU9yTxBzui3KhbGjROK2OYTTor7alM7XBhssgoO3CZ0XD3qA==}
323 | cpu: [s390x]
324 | os: [linux]
325 | requiresBuild: true
326 | dev: true
327 | optional: true
328 |
329 | /@rollup/rollup-linux-x64-gnu@4.14.0:
330 | resolution: {integrity: sha512-C6y6z2eCNCfhZxT9u+jAM2Fup89ZjiG5pIzZIDycs1IwESviLxwkQcFRGLjnDrP+PT+v5i4YFvlcfAs+LnreXg==}
331 | cpu: [x64]
332 | os: [linux]
333 | requiresBuild: true
334 | dev: true
335 | optional: true
336 |
337 | /@rollup/rollup-linux-x64-musl@4.14.0:
338 | resolution: {integrity: sha512-i0QwbHYfnOMYsBEyjxcwGu5SMIi9sImDVjDg087hpzXqhBSosxkE7gyIYFHgfFl4mr7RrXksIBZ4DoLoP4FhJg==}
339 | cpu: [x64]
340 | os: [linux]
341 | requiresBuild: true
342 | dev: true
343 | optional: true
344 |
345 | /@rollup/rollup-win32-arm64-msvc@4.14.0:
346 | resolution: {integrity: sha512-Fq52EYb0riNHLBTAcL0cun+rRwyZ10S9vKzhGKKgeD+XbwunszSY0rVMco5KbOsTlwovP2rTOkiII/fQ4ih/zQ==}
347 | cpu: [arm64]
348 | os: [win32]
349 | requiresBuild: true
350 | dev: true
351 | optional: true
352 |
353 | /@rollup/rollup-win32-ia32-msvc@4.14.0:
354 | resolution: {integrity: sha512-e/PBHxPdJ00O9p5Ui43+vixSgVf4NlLsmV6QneGERJ3lnjIua/kim6PRFe3iDueT1rQcgSkYP8ZBBXa/h4iPvw==}
355 | cpu: [ia32]
356 | os: [win32]
357 | requiresBuild: true
358 | dev: true
359 | optional: true
360 |
361 | /@rollup/rollup-win32-x64-msvc@4.14.0:
362 | resolution: {integrity: sha512-aGg7iToJjdklmxlUlJh/PaPNa4PmqHfyRMLunbL3eaMO0gp656+q1zOKkpJ/CVe9CryJv6tAN1HDoR8cNGzkag==}
363 | cpu: [x64]
364 | os: [win32]
365 | requiresBuild: true
366 | dev: true
367 | optional: true
368 |
369 | /@types/estree@1.0.5:
370 | resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==}
371 | dev: true
372 |
373 | /@types/node@20.12.5:
374 | resolution: {integrity: sha512-BD+BjQ9LS/D8ST9p5uqBxghlN+S42iuNxjsUGjeZobe/ciXzk2qb1B6IXc6AnRLS+yFJRpN2IPEHMzwspfDJNw==}
375 | dependencies:
376 | undici-types: 5.26.5
377 | dev: true
378 |
379 | /esbuild@0.20.2:
380 | resolution: {integrity: sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==}
381 | engines: {node: '>=12'}
382 | hasBin: true
383 | requiresBuild: true
384 | optionalDependencies:
385 | '@esbuild/aix-ppc64': 0.20.2
386 | '@esbuild/android-arm': 0.20.2
387 | '@esbuild/android-arm64': 0.20.2
388 | '@esbuild/android-x64': 0.20.2
389 | '@esbuild/darwin-arm64': 0.20.2
390 | '@esbuild/darwin-x64': 0.20.2
391 | '@esbuild/freebsd-arm64': 0.20.2
392 | '@esbuild/freebsd-x64': 0.20.2
393 | '@esbuild/linux-arm': 0.20.2
394 | '@esbuild/linux-arm64': 0.20.2
395 | '@esbuild/linux-ia32': 0.20.2
396 | '@esbuild/linux-loong64': 0.20.2
397 | '@esbuild/linux-mips64el': 0.20.2
398 | '@esbuild/linux-ppc64': 0.20.2
399 | '@esbuild/linux-riscv64': 0.20.2
400 | '@esbuild/linux-s390x': 0.20.2
401 | '@esbuild/linux-x64': 0.20.2
402 | '@esbuild/netbsd-x64': 0.20.2
403 | '@esbuild/openbsd-x64': 0.20.2
404 | '@esbuild/sunos-x64': 0.20.2
405 | '@esbuild/win32-arm64': 0.20.2
406 | '@esbuild/win32-ia32': 0.20.2
407 | '@esbuild/win32-x64': 0.20.2
408 | dev: true
409 |
410 | /estree-walker@2.0.2:
411 | resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
412 | dev: true
413 |
414 | /fsevents@2.3.3:
415 | resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
416 | engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
417 | os: [darwin]
418 | requiresBuild: true
419 | dev: true
420 | optional: true
421 |
422 | /nanoid@3.3.7:
423 | resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==}
424 | engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
425 | hasBin: true
426 | dev: true
427 |
428 | /ogl@1.0.6:
429 | resolution: {integrity: sha512-ephp/AP2qR2JV/BLoFEoeMSs6JofFZIx3nB6iLKfom88Q3GFjFLXwFjW0ZQObHJaWHhmNGPcMi1n1nxUm+30TA==}
430 | dev: false
431 |
432 | /picocolors@1.0.0:
433 | resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
434 | dev: true
435 |
436 | /picomatch@2.3.1:
437 | resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
438 | engines: {node: '>=8.6'}
439 | dev: true
440 |
441 | /postcss@8.4.38:
442 | resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==}
443 | engines: {node: ^10 || ^12 || >=14}
444 | dependencies:
445 | nanoid: 3.3.7
446 | picocolors: 1.0.0
447 | source-map-js: 1.2.0
448 | dev: true
449 |
450 | /rollup@4.14.0:
451 | resolution: {integrity: sha512-Qe7w62TyawbDzB4yt32R0+AbIo6m1/sqO7UPzFS8Z/ksL5mrfhA0v4CavfdmFav3D+ub4QeAgsGEe84DoWe/nQ==}
452 | engines: {node: '>=18.0.0', npm: '>=8.0.0'}
453 | hasBin: true
454 | dependencies:
455 | '@types/estree': 1.0.5
456 | optionalDependencies:
457 | '@rollup/rollup-android-arm-eabi': 4.14.0
458 | '@rollup/rollup-android-arm64': 4.14.0
459 | '@rollup/rollup-darwin-arm64': 4.14.0
460 | '@rollup/rollup-darwin-x64': 4.14.0
461 | '@rollup/rollup-linux-arm-gnueabihf': 4.14.0
462 | '@rollup/rollup-linux-arm64-gnu': 4.14.0
463 | '@rollup/rollup-linux-arm64-musl': 4.14.0
464 | '@rollup/rollup-linux-powerpc64le-gnu': 4.14.0
465 | '@rollup/rollup-linux-riscv64-gnu': 4.14.0
466 | '@rollup/rollup-linux-s390x-gnu': 4.14.0
467 | '@rollup/rollup-linux-x64-gnu': 4.14.0
468 | '@rollup/rollup-linux-x64-musl': 4.14.0
469 | '@rollup/rollup-win32-arm64-msvc': 4.14.0
470 | '@rollup/rollup-win32-ia32-msvc': 4.14.0
471 | '@rollup/rollup-win32-x64-msvc': 4.14.0
472 | fsevents: 2.3.3
473 | dev: true
474 |
475 | /source-map-js@1.2.0:
476 | resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==}
477 | engines: {node: '>=0.10.0'}
478 | dev: true
479 |
480 | /typescript@5.4.3:
481 | resolution: {integrity: sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==}
482 | engines: {node: '>=14.17'}
483 | hasBin: true
484 | dev: true
485 |
486 | /undici-types@5.26.5:
487 | resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
488 | dev: true
489 |
490 | /vite-plugin-glsl@1.3.0(vite@5.2.8):
491 | resolution: {integrity: sha512-SzEoLet9Bp5VSozjrhUiSc3xX1+u7rCTjXAsq4qWM3u8UjilI76A9ucX/T+CRGQCe25j50GSY+9mKSGUVPET1w==}
492 | engines: {node: '>= 16.15.1', npm: '>= 8.11.0'}
493 | peerDependencies:
494 | vite: ^3.0.0 || ^4.0.0 || ^5.0.0
495 | dependencies:
496 | '@rollup/pluginutils': 5.1.0
497 | vite: 5.2.8(@types/node@20.12.5)
498 | transitivePeerDependencies:
499 | - rollup
500 | dev: true
501 |
502 | /vite@5.2.8(@types/node@20.12.5):
503 | resolution: {integrity: sha512-OyZR+c1CE8yeHw5V5t59aXsUPPVTHMDjEZz8MgguLL/Q7NblxhZUlTu9xSPqlsUO/y+X7dlU05jdhvyycD55DA==}
504 | engines: {node: ^18.0.0 || >=20.0.0}
505 | hasBin: true
506 | peerDependencies:
507 | '@types/node': ^18.0.0 || >=20.0.0
508 | less: '*'
509 | lightningcss: ^1.21.0
510 | sass: '*'
511 | stylus: '*'
512 | sugarss: '*'
513 | terser: ^5.4.0
514 | peerDependenciesMeta:
515 | '@types/node':
516 | optional: true
517 | less:
518 | optional: true
519 | lightningcss:
520 | optional: true
521 | sass:
522 | optional: true
523 | stylus:
524 | optional: true
525 | sugarss:
526 | optional: true
527 | terser:
528 | optional: true
529 | dependencies:
530 | '@types/node': 20.12.5
531 | esbuild: 0.20.2
532 | postcss: 8.4.38
533 | rollup: 4.14.0
534 | optionalDependencies:
535 | fsevents: 2.3.3
536 | dev: true
537 |
--------------------------------------------------------------------------------
/public/fonts/inter-var-latin.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devloop01/webgl-image-interactions-aristide/8b9b445c4278690f050c2cd50ea39655c021a5a9/public/fonts/inter-var-latin.woff2
--------------------------------------------------------------------------------
/public/images/+0.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devloop01/webgl-image-interactions-aristide/8b9b445c4278690f050c2cd50ea39655c021a5a9/public/images/+0.webp
--------------------------------------------------------------------------------
/public/images/+0n.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devloop01/webgl-image-interactions-aristide/8b9b445c4278690f050c2cd50ea39655c021a5a9/public/images/+0n.webp
--------------------------------------------------------------------------------
/public/images/-0.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devloop01/webgl-image-interactions-aristide/8b9b445c4278690f050c2cd50ea39655c021a5a9/public/images/-0.webp
--------------------------------------------------------------------------------
/public/images/-0n.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devloop01/webgl-image-interactions-aristide/8b9b445c4278690f050c2cd50ea39655c021a5a9/public/images/-0n.webp
--------------------------------------------------------------------------------
/public/images/0.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devloop01/webgl-image-interactions-aristide/8b9b445c4278690f050c2cd50ea39655c021a5a9/public/images/0.webp
--------------------------------------------------------------------------------
/public/images/0n.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devloop01/webgl-image-interactions-aristide/8b9b445c4278690f050c2cd50ea39655c021a5a9/public/images/0n.webp
--------------------------------------------------------------------------------
/public/images/1.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devloop01/webgl-image-interactions-aristide/8b9b445c4278690f050c2cd50ea39655c021a5a9/public/images/1.webp
--------------------------------------------------------------------------------
/public/images/1d.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devloop01/webgl-image-interactions-aristide/8b9b445c4278690f050c2cd50ea39655c021a5a9/public/images/1d.webp
--------------------------------------------------------------------------------
/public/images/2.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devloop01/webgl-image-interactions-aristide/8b9b445c4278690f050c2cd50ea39655c021a5a9/public/images/2.webp
--------------------------------------------------------------------------------
/public/images/3.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devloop01/webgl-image-interactions-aristide/8b9b445c4278690f050c2cd50ea39655c021a5a9/public/images/3.webp
--------------------------------------------------------------------------------
/public/images/4.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devloop01/webgl-image-interactions-aristide/8b9b445c4278690f050c2cd50ea39655c021a5a9/public/images/4.webp
--------------------------------------------------------------------------------
/public/images/5.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devloop01/webgl-image-interactions-aristide/8b9b445c4278690f050c2cd50ea39655c021a5a9/public/images/5.webp
--------------------------------------------------------------------------------
/public/images/5b.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devloop01/webgl-image-interactions-aristide/8b9b445c4278690f050c2cd50ea39655c021a5a9/public/images/5b.webp
--------------------------------------------------------------------------------
/public/images/6.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devloop01/webgl-image-interactions-aristide/8b9b445c4278690f050c2cd50ea39655c021a5a9/public/images/6.webp
--------------------------------------------------------------------------------
/public/images/noise64x64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devloop01/webgl-image-interactions-aristide/8b9b445c4278690f050c2cd50ea39655c021a5a9/public/images/noise64x64.png
--------------------------------------------------------------------------------
/public/images/noise64x64.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devloop01/webgl-image-interactions-aristide/8b9b445c4278690f050c2cd50ea39655c021a5a9/public/images/noise64x64.webp
--------------------------------------------------------------------------------
/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/App.ts:
--------------------------------------------------------------------------------
1 | import { Gl } from "@/gl";
2 | import { Plane } from "@/gl/Plane";
3 |
4 | export class App {
5 | demoIndex: number;
6 | dom: Record;
7 | rafId: number;
8 | glObjects: any[];
9 | gl: Gl;
10 |
11 | constructor() {
12 | this.demoIndex = parseInt(document.documentElement.getAttribute("data-demoIndex") || "0");
13 | this.dom = {
14 | canvas: document.querySelector("canvas.gl")!,
15 | img: document.querySelector("figure > img")!,
16 | };
17 | this.rafId = 0;
18 | this.glObjects = [];
19 |
20 | this.init();
21 | }
22 |
23 | init() {
24 | this.gl = new Gl(this.dom.canvas as HTMLCanvasElement);
25 |
26 | this.addListeners();
27 |
28 | this.createGlObjects();
29 | this.handleResize();
30 | this.update();
31 | }
32 |
33 | createGlObjects() {
34 | const plane = new Plane(this.gl, {
35 | domElement: this.dom.img,
36 | demoIndex: this.demoIndex,
37 | });
38 | this.glObjects.push(plane);
39 | }
40 |
41 | update() {
42 | this.glObjects.forEach((glObject) => {
43 | glObject.update();
44 | });
45 |
46 | this.gl.render();
47 | this.rafId = requestAnimationFrame(this.update.bind(this));
48 | }
49 |
50 | handleTouchMove(event: PointerEvent | TouchEvent) {
51 | event.preventDefault();
52 |
53 | let x = 0,
54 | y = 0;
55 |
56 | if (event.type === "pointermove") {
57 | const mouseEvent = event as PointerEvent;
58 | x = mouseEvent.clientX;
59 | y = mouseEvent.clientY;
60 | } else if (event.type === "touchmove") {
61 | const touchEvent = event as TouchEvent;
62 | x = touchEvent.touches[0].clientX;
63 | y = touchEvent.touches[0].clientY;
64 | }
65 |
66 | this.gl.updateMouse(x, y);
67 | }
68 |
69 | handleResize() {
70 | this.gl.resize(window.innerWidth, window.innerHeight);
71 | this.glObjects.forEach((glObject) => {
72 | glObject.resize();
73 | });
74 | }
75 |
76 | addListeners() {
77 | window.addEventListener("resize", this.handleResize.bind(this));
78 | window.addEventListener("pointermove", this.handleTouchMove.bind(this));
79 | window.addEventListener("touchmove", this.handleTouchMove.bind(this), { passive: false });
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/gl/Plane.shader.ts:
--------------------------------------------------------------------------------
1 | const simplex3d = /*glsl*/ `
2 | //
3 | // Description : Array and textureless GLSL 2D/3D/4D simplex
4 | // noise functions.
5 | // Author : Ian McEwan, Ashima Arts.
6 | // Maintainer : ijm
7 | // Lastmod : 20110822 (ijm)
8 | // License : Copyright (C) 2011 Ashima Arts. All rights reserved.
9 | // Distributed under the MIT License. See LICENSE file.
10 | // https://github.com/ashima/webgl-noise
11 | //
12 |
13 | vec3 mod289(vec3 x) {
14 | return x - floor(x * (1.0 / 289.0)) * 289.0;
15 | }
16 |
17 | vec4 mod289(vec4 x) {
18 | return x - floor(x * (1.0 / 289.0)) * 289.0;
19 | }
20 |
21 | vec4 permute(vec4 x) {
22 | return mod289(((x*34.0)+1.0)*x);
23 | }
24 |
25 | vec4 taylorInvSqrt(vec4 r)
26 | {
27 | return 1.79284291400159 - 0.85373472095314 * r;
28 | }
29 |
30 | float snoise(vec3 v)
31 | {
32 | const vec2 C = vec2(1.0/6.0, 1.0/3.0) ;
33 | const vec4 D = vec4(0.0, 0.5, 1.0, 2.0);
34 |
35 | // First corner
36 | vec3 i = floor(v + dot(v, C.yyy) );
37 | vec3 x0 = v - i + dot(i, C.xxx) ;
38 |
39 | // Other corners
40 | vec3 g = step(x0.yzx, x0.xyz);
41 | vec3 l = 1.0 - g;
42 | vec3 i1 = min( g.xyz, l.zxy );
43 | vec3 i2 = max( g.xyz, l.zxy );
44 |
45 | // x0 = x0 - 0.0 + 0.0 * C.xxx;
46 | // x1 = x0 - i1 + 1.0 * C.xxx;
47 | // x2 = x0 - i2 + 2.0 * C.xxx;
48 | // x3 = x0 - 1.0 + 3.0 * C.xxx;
49 | vec3 x1 = x0 - i1 + C.xxx;
50 | vec3 x2 = x0 - i2 + C.yyy; // 2.0*C.x = 1/3 = C.y
51 | vec3 x3 = x0 - D.yyy; // -1.0+3.0*C.x = -0.5 = -D.y
52 |
53 | // Permutations
54 | i = mod289(i);
55 | vec4 p = permute( permute( permute(
56 | i.z + vec4(0.0, i1.z, i2.z, 1.0 ))
57 | + i.y + vec4(0.0, i1.y, i2.y, 1.0 ))
58 | + i.x + vec4(0.0, i1.x, i2.x, 1.0 ));
59 |
60 | // Gradients: 7x7 points over a square, mapped onto an octahedron.
61 | // The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294)
62 | float n_ = 0.142857142857; // 1.0/7.0
63 | vec3 ns = n_ * D.wyz - D.xzx;
64 |
65 | vec4 j = p - 49.0 * floor(p * ns.z * ns.z); // mod(p,7*7)
66 |
67 | vec4 x_ = floor(j * ns.z);
68 | vec4 y_ = floor(j - 7.0 * x_ ); // mod(j,N)
69 |
70 | vec4 x = x_ *ns.x + ns.yyyy;
71 | vec4 y = y_ *ns.x + ns.yyyy;
72 | vec4 h = 1.0 - abs(x) - abs(y);
73 |
74 | vec4 b0 = vec4( x.xy, y.xy );
75 | vec4 b1 = vec4( x.zw, y.zw );
76 |
77 | //vec4 s0 = vec4(lessThan(b0,0.0))*2.0 - 1.0;
78 | //vec4 s1 = vec4(lessThan(b1,0.0))*2.0 - 1.0;
79 | vec4 s0 = floor(b0)*2.0 + 1.0;
80 | vec4 s1 = floor(b1)*2.0 + 1.0;
81 | vec4 sh = -step(h, vec4(0.0));
82 |
83 | vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy ;
84 | vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww ;
85 |
86 | vec3 p0 = vec3(a0.xy,h.x);
87 | vec3 p1 = vec3(a0.zw,h.y);
88 | vec3 p2 = vec3(a1.xy,h.z);
89 | vec3 p3 = vec3(a1.zw,h.w);
90 |
91 | //Normalise gradients
92 | vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3)));
93 | p0 *= norm.x;
94 | p1 *= norm.y;
95 | p2 *= norm.z;
96 | p3 *= norm.w;
97 |
98 | // Mix final noise value
99 | vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0);
100 | m = m * m;
101 | return 42.0 * dot( m*m, vec4( dot(p0,x0), dot(p1,x1),
102 | dot(p2,x2), dot(p3,x3) ) );
103 | }
104 |
105 | `;
106 |
107 | const vertex = /*glsl*/ `
108 | attribute vec3 position;
109 | attribute vec2 uv;
110 |
111 | varying vec2 vUv;
112 |
113 | uniform mat4 modelViewMatrix;
114 | uniform mat4 projectionMatrix;
115 |
116 | void main() {
117 | vUv = uv;
118 | gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
119 | }
120 | `;
121 |
122 | const fragmentHead = /*glsl*/ `
123 | precision highp float;
124 |
125 | varying vec2 vUv;
126 |
127 | uniform sampler2D uTexture;
128 | uniform sampler2D uTexture2;
129 | uniform sampler2D uDataTexture;
130 | uniform sampler2D uFlow;
131 |
132 | uniform vec2 uResolution;
133 | uniform vec2 uSize;
134 | uniform vec2 uMouse;
135 | uniform vec2 uLerpedMouse;
136 |
137 | uniform float uTime;
138 | uniform float uPeekRadius;
139 | uniform float uHoverProgress;
140 |
141 | vec2 backgroundCoverUv(vec2 screenSize, vec2 imageSize, vec2 uv) {
142 | float screenRatio = screenSize.x / screenSize.y;
143 | float imageRatio = imageSize.x / imageSize.y;
144 | vec2 newSize = screenRatio < imageRatio ? vec2(imageSize.x * screenSize.y / imageSize.y, screenSize.y) : vec2(screenSize.x, imageSize.y * screenSize.x / imageSize.x);
145 | vec2 newOffset = (screenRatio < imageRatio ? vec2((newSize.x - screenSize.x) / 2.0, 0.0) : vec2(0.0, (newSize.y - screenSize.y) / 2.0)) / newSize;
146 | return uv * screenSize / newSize + newOffset;
147 | }
148 |
149 | float luminance(vec3 rgb) {
150 | const vec3 W = vec3(0.2125, 0.7154, 0.0721);
151 | return dot(rgb, W);
152 | }
153 |
154 | float circle(vec2 _st, float r, float b){
155 | return 1.-smoothstep(r-(r*b), r+(r*b), dot(_st,_st)*4.0);
156 | }
157 |
158 | ${simplex3d}
159 |
160 | `;
161 |
162 | export const demo0 = {
163 | vertex,
164 | fragment: /*glsl*/ `
165 | ${fragmentHead}
166 |
167 | void main() {
168 | vec2 aspect = uSize / max(uSize.x, uSize.y);
169 | vec2 uv = (vUv - 0.5) * aspect;
170 | vec2 mouse = uLerpedMouse * aspect;
171 |
172 | vec2 coverUV = backgroundCoverUv(uSize, uResolution, vUv);
173 | vec3 tex = texture2D(uTexture, coverUV).rgb;
174 | vec3 normal = texture2D(uTexture2, coverUV).rgb * 2.0 - 1.0;
175 | normal = normalize(normal);
176 |
177 | vec3 lightPos = vec3(uv-mouse, 0.0);
178 | vec3 lightDir = normalize(vec3(lightPos.xy, .6));
179 |
180 | float intensity = max(dot(normal, lightDir), 0.0);
181 | intensity = pow(intensity, 2.0);
182 |
183 | vec3 diffuse = tex.rgb*intensity;
184 | vec3 ambientColor = vec3(.4);
185 | vec3 diffuseAmbient = tex.rgb*ambientColor;
186 | vec3 finalDiffuse = diffuse+diffuseAmbient;
187 |
188 | gl_FragColor = vec4(finalDiffuse, 1.0);
189 | }
190 | `,
191 | };
192 |
193 | // ---------------------------------------------
194 |
195 | export const demo1 = {
196 | vertex,
197 | fragment: /*glsl*/ `
198 | ${fragmentHead}
199 |
200 | void main() {
201 | vec2 aspect = uSize / max(uSize.x, uSize.y);
202 |
203 | vec2 coverUV = backgroundCoverUv(uSize, uResolution, vUv);
204 |
205 | const float zoom = 0.9;
206 | vec2 uv = ((coverUV-0.5)*zoom)+0.5;
207 | vec2 mouse = uLerpedMouse * aspect;
208 | vec2 offset = (mouse*0.08);
209 |
210 | vec3 depth = texture2D(uTexture2, uv).rgb;
211 | vec3 tex = texture2D(uTexture, uv+offset*depth.r).rgb;
212 |
213 | vec3 final = mix(tex, vec3(depth), 0.5);
214 | // final = depth;
215 | final = tex;
216 |
217 | gl_FragColor = vec4(final, 1.0);
218 | }
219 | `,
220 | };
221 |
222 | // ---------------------------------------------
223 |
224 | export const demo2 = {
225 | vertex,
226 | fragment: /*glsl*/ `
227 | ${fragmentHead}
228 |
229 | void main() {
230 | vec2 aspect = uSize / max(uSize.x, uSize.y);
231 | vec2 mouse = uMouse * aspect;
232 |
233 | vec2 coverUV = backgroundCoverUv(uSize, uResolution, vUv);
234 | vec3 offset = texture2D(uDataTexture, coverUV).rgb;
235 | vec3 tex = texture2D(uTexture, coverUV - 0.02 * offset.rg).rgb;
236 |
237 | gl_FragColor = vec4(tex, 1.0);
238 | }
239 | `,
240 | };
241 |
242 | // ---------------------------------------------
243 |
244 | export const demo3 = {
245 | vertex,
246 | fragment: /*glsl*/ `
247 | ${fragmentHead}
248 |
249 | void main() {
250 | vec2 aspect = uSize / max(uSize.x, uSize.y);
251 | vec2 uv = (vUv - 0.5) * aspect;
252 | vec2 mouse = uLerpedMouse * aspect;
253 |
254 | vec2 coverUV = backgroundCoverUv(uSize, uResolution, vUv);
255 |
256 | float c = circle(uv - mouse, uPeekRadius, 0.08);
257 |
258 | vec3 tex = texture2D(uTexture, coverUV).rgb;
259 |
260 | vec3 final = mix(vec3(0.0), tex, c);
261 | gl_FragColor = vec4(final, 1.0);
262 | }
263 | `,
264 | };
265 |
266 | // ---------------------------------------------
267 |
268 | export const demo4 = {
269 | vertex,
270 | fragment: /*glsl*/ `
271 | ${fragmentHead}
272 |
273 | void main() {
274 | vec2 aspect = uSize / max(uSize.x, uSize.y);
275 | vec2 mouse = uMouse * aspect;
276 |
277 | vec3 flow = texture2D(uFlow, vUv).rgb;
278 | vec2 coverUV = backgroundCoverUv(uSize, uResolution, vUv);
279 | coverUV += flow.rg * 0.05;
280 | vec3 tex = texture2D(uTexture, coverUV).rgb;
281 | // tex = flow * 0.5 + 0.5;
282 | gl_FragColor = vec4(tex, 1.0);
283 | }
284 | `,
285 | };
286 |
287 | // ---------------------------------------------
288 |
289 | export const demo5 = {
290 | vertex,
291 | fragment: /*glsl*/ `
292 | ${fragmentHead}
293 |
294 | void main() {
295 | vec2 aspect = uSize / max(uSize.x, uSize.y);
296 | vec2 uv = (vUv - 0.5) * aspect;
297 | vec2 mouse = uLerpedMouse * aspect;
298 |
299 | vec2 coverUV = backgroundCoverUv(uSize, uResolution, vUv);
300 | vec3 tex = texture2D(uTexture, coverUV).rgb;
301 | vec3 blr = texture2D(uTexture2, coverUV).rgb;
302 | vec3 gs = vec3(pow(luminance(blr), 2.25));
303 |
304 | float c = circle(uv - mouse, uPeekRadius, 2.0);
305 |
306 | float time = uTime * 0.05;
307 | float offX = uv.x + sin(uv.y + time * 2.);
308 | float offY = uv.y - time * .5 - cos(time * 2.) * .5;
309 | float n = (snoise(vec3(offX, offY, time * .5) * 15.));
310 |
311 | float mask = smoothstep(.98, 1., pow(c, 2.) * 4. + n);
312 | vec3 final = mix(gs, tex, mask);
313 |
314 | gl_FragColor = vec4(final, 1.0);
315 | }
316 | `,
317 | };
318 |
319 | // ---------------------------------------------
320 |
321 | export const demo6 = {
322 | vertex,
323 | fragment: /*glsl*/ `
324 | ${fragmentHead}
325 |
326 | void main() {
327 | vec2 aspect = uSize / max(uSize.x, uSize.y);
328 | vec2 uv = (vUv - 0.5) * aspect;
329 | vec2 mouse = uLerpedMouse * aspect;
330 | vec2 coverUV = backgroundCoverUv(uSize, uResolution, vUv);
331 |
332 | float c = length(uv-mouse)+(1.-uHoverProgress);
333 | c = clamp(c, 0., 1.);
334 | vec2 noise = texture2D(uTexture2, uv * 10.0).xy * mix(0.02, 0.04, uHoverProgress);
335 | vec2 glassyUV = coverUV + noise * c;
336 | vec3 tex = texture2D(uTexture, glassyUV).rgb;
337 |
338 | gl_FragColor = vec4(tex, 1.0);
339 | }
340 | `,
341 | };
342 |
--------------------------------------------------------------------------------
/src/gl/Plane.ts:
--------------------------------------------------------------------------------
1 | import { Plane as BasePlane, Program, Mesh, Texture, Flowmap, Vec2 } from "ogl";
2 |
3 | import { Gl } from "./index";
4 | import * as demos from "./Plane.shader";
5 | import { clamp, lerp } from "@/math";
6 |
7 | export type PlaneOptions = {
8 | domElement: HTMLElement;
9 | demoIndex: number;
10 | };
11 |
12 | export class Plane {
13 | gl: Gl;
14 | options: PlaneOptions;
15 | domElement: HTMLElement;
16 | mouse: { current: Vec2; previous: Vec2; velocity: Vec2 };
17 | lerpedMouse: { current: Vec2 };
18 |
19 | geometry: BasePlane;
20 | uniforms: Record;
21 | program: Program;
22 | mesh: Mesh;
23 | elementBounds: DOMRect;
24 |
25 | flowmap: Flowmap;
26 |
27 | pixelDistortSettings = {
28 | gridSize: 18,
29 | mouseSize: 0.2,
30 | strength: 0.3,
31 | relaxation: 0.95,
32 | };
33 |
34 | constructor(gl: Gl, options: PlaneOptions) {
35 | this.gl = gl;
36 | this.options = options;
37 | this.domElement = options.domElement;
38 |
39 | this.mouse = {
40 | current: new Vec2(),
41 | previous: new Vec2(),
42 | velocity: new Vec2(),
43 | };
44 | this.lerpedMouse = {
45 | current: new Vec2(),
46 | };
47 |
48 | this.uniforms = {
49 | uTime: { value: 0 },
50 | uMouse: { value: [0, 0] },
51 | uLerpedMouse: { value: [0, 0] },
52 | uTexture: { value: new Texture(this.gl.ctx) },
53 | uTexture2: {
54 | value: new Texture(this.gl.ctx, {
55 | wrapS: this.gl.ctx.REPEAT,
56 | wrapT: this.gl.ctx.REPEAT,
57 | }),
58 | },
59 | uResolution: { value: [0, 0] },
60 | uSize: { value: [1, 1] },
61 | uPeekRadius: { value: 1 },
62 | uHoverProgress: { value: 0 },
63 | };
64 |
65 | this.geometry = new BasePlane(this.gl.ctx);
66 | this.program = new Program(this.gl.ctx, {
67 | uniforms: this.uniforms,
68 | ...Object.values(demos)[options.demoIndex],
69 | });
70 |
71 | this.mesh = new Mesh(this.gl.ctx, {
72 | geometry: this.geometry,
73 | program: this.program,
74 | });
75 | this.mesh.setParent(this.gl.scene);
76 |
77 | this.loadTextures();
78 | this.createDataTexture();
79 | this.createFlowmap();
80 | }
81 |
82 | update() {
83 | this.lerpedMouse.current.lerp(this.gl.intersect.point, 0.05);
84 |
85 | this.uniforms.uTime.value += 0.01;
86 |
87 | if (this.gl.intersect.objectId === this.mesh.id) {
88 | const p = this.gl.intersect.point;
89 |
90 | this.mouse.current.copy(p);
91 | this.mouse.velocity.copy(this.mouse.current.clone().sub(this.mouse.previous));
92 | this.mouse.previous.copy(this.mouse.current);
93 |
94 | this.uniforms.uPeekRadius.value = lerp(this.uniforms.uPeekRadius.value, 0.1, 0.15);
95 | this.uniforms.uHoverProgress.value = lerp(this.uniforms.uHoverProgress.value, 1, 0.05);
96 | } else {
97 | this.mouse.current.set(-1);
98 | this.mouse.velocity.set(0);
99 |
100 | this.uniforms.uPeekRadius.value = lerp(this.uniforms.uPeekRadius.value, 10.0, 0.015);
101 | this.uniforms.uHoverProgress.value = lerp(this.uniforms.uHoverProgress.value, 0, 0.05);
102 | }
103 |
104 | this.uniforms.uMouse.value = this.gl.intersect.point;
105 | this.uniforms.uLerpedMouse.value = this.lerpedMouse.current;
106 |
107 | if (this.options.demoIndex === 2) this.updateDataTexture();
108 |
109 | if (this.options.demoIndex === 4) {
110 | this.flowmap.mouse.set(this.mouse.current.x + 0.5, this.mouse.current.y + 0.5);
111 | this.flowmap.velocity.lerp(
112 | this.mouse.velocity.multiply(50),
113 | this.mouse.velocity.len() ? 0.5 : 0.1,
114 | );
115 | this.flowmap.update();
116 | }
117 | }
118 |
119 | resize() {
120 | this.updateMeshSize();
121 | }
122 |
123 | updateMeshSize() {
124 | this.elementBounds = this.domElement.getBoundingClientRect();
125 |
126 | const dpr = this.gl.renderer.dpr;
127 | const w = this.elementBounds.width * dpr;
128 | const h = this.elementBounds.height * dpr;
129 | const left = this.elementBounds.left * dpr;
130 | const top = this.elementBounds.top * dpr;
131 |
132 | // scale the plane to match the element's size
133 | this.mesh.scale.set(w, h, 1);
134 | this.uniforms.uSize.value = [w, h];
135 |
136 | // position the plane to the element's center
137 | this.mesh.position.set(
138 | left + w * 0.5 - this.gl.canvas.width * 0.5,
139 | -top - h * 0.5 + this.gl.canvas.height * 0.5,
140 | 0,
141 | );
142 |
143 | const aspect = w / h;
144 | this.flowmap.aspect = aspect;
145 | }
146 |
147 | updateDataTexture() {
148 | if (!this.uniforms.uDataTexture) return;
149 |
150 | const data = this.uniforms.uDataTexture.value.image;
151 |
152 | for (let i = 0; i < data.length; i += 3) {
153 | data[i] *= this.pixelDistortSettings.relaxation;
154 | data[i + 1] *= this.pixelDistortSettings.relaxation;
155 | }
156 |
157 | let gridMouseX = this.pixelDistortSettings.gridSize * (this.mouse.current.x + 0.5);
158 | let gridMouseY = this.pixelDistortSettings.gridSize * (1 - (this.mouse.current.y + 0.5));
159 | let maxDist = this.pixelDistortSettings.gridSize * this.pixelDistortSettings.mouseSize;
160 | let aspect = this.gl.canvas.height / this.gl.canvas.width;
161 |
162 | for (let i = 0; i < this.pixelDistortSettings.gridSize; i++) {
163 | for (let j = 0; j < this.pixelDistortSettings.gridSize; j++) {
164 | let distance = (gridMouseX - i) ** 2 / aspect + (gridMouseY - j) ** 2;
165 | let maxDistSq = maxDist ** 2;
166 |
167 | if (distance < maxDistSq) {
168 | let index = 3 * (i + this.pixelDistortSettings.gridSize * j);
169 |
170 | let power = maxDist / Math.sqrt(distance);
171 | power = clamp(power, 0, 10);
172 |
173 | data[index] += this.pixelDistortSettings.strength * 100 * this.mouse.velocity.x * power;
174 | data[index + 1] -=
175 | this.pixelDistortSettings.strength * 100 * this.mouse.velocity.y * power;
176 | }
177 | }
178 | }
179 |
180 | this.uniforms.uDataTexture.value.needsUpdate = true;
181 | }
182 |
183 | loadTextures() {
184 | {
185 | const img = new Image();
186 | img.src = this.domElement.getAttribute("src")!;
187 | img.onload = () => {
188 | this.uniforms.uTexture.value.image = img;
189 | this.uniforms.uResolution.value = [img.width, img.height];
190 | };
191 | }
192 |
193 | {
194 | const img = new Image();
195 | img.src = this.domElement.getAttribute("data-src2")!;
196 | img.onload = () => {
197 | this.uniforms.uTexture2.value.image = img;
198 | };
199 | }
200 | }
201 |
202 | createDataTexture() {
203 | const width = this.pixelDistortSettings.gridSize;
204 | const height = this.pixelDistortSettings.gridSize;
205 |
206 | const size = width * height;
207 | const data = new Float32Array(3 * size);
208 |
209 | const texture = new Texture(this.gl.ctx, {
210 | image: data,
211 | width,
212 | height,
213 | // @ts-ignore
214 | internalFormat: this.gl.ctx.RGB16F,
215 | format: this.gl.ctx.RGB,
216 | type: this.gl.ctx.FLOAT,
217 | magFilter: this.gl.ctx.NEAREST,
218 | minFilter: this.gl.ctx.NEAREST,
219 | generateMipmaps: false,
220 | });
221 |
222 | this.uniforms["uDataTexture"] = { value: texture };
223 | this.uniforms["uDataTexture"].value.needsUpdate = true;
224 | }
225 |
226 | createFlowmap() {
227 | this.flowmap = new Flowmap(this.gl.ctx, {
228 | dissipation: 0.95,
229 | falloff: 0.25,
230 | });
231 | this.uniforms["uFlow"] = this.flowmap.uniform;
232 | }
233 | }
234 |
--------------------------------------------------------------------------------
/src/gl/index.ts:
--------------------------------------------------------------------------------
1 | import { type OGLRenderingContext, Renderer, Camera, Transform, Vec2, Raycast } from "ogl";
2 |
3 | export class Gl {
4 | canvas: HTMLCanvasElement;
5 |
6 | renderer: Renderer;
7 | ctx: OGLRenderingContext;
8 | camera: Camera;
9 | scene: Transform;
10 | mouse: Vec2;
11 | raycaster: Raycast;
12 | intersect: { objectId: number | null; point: Vec2 };
13 |
14 | constructor(canvas: HTMLCanvasElement) {
15 | this.canvas = canvas;
16 |
17 | this.renderer = new Renderer({
18 | canvas,
19 | antialias: true,
20 | alpha: true,
21 | dpr: Math.min(window.devicePixelRatio, 2),
22 | });
23 |
24 | this.ctx = this.renderer.gl;
25 | this.ctx.clearColor(0, 0, 0, 0);
26 |
27 | this.camera = new Camera(this.renderer.gl, {
28 | fov: 75,
29 | aspect: this.ctx.canvas.width / this.ctx.canvas.height,
30 | near: 1,
31 | far: 2000,
32 | });
33 |
34 | this.scene = new Transform();
35 |
36 | this.mouse = new Vec2(-1, -1);
37 | this.raycaster = new Raycast();
38 | this.intersect = {
39 | objectId: null,
40 | point: new Vec2(),
41 | };
42 | }
43 |
44 | updateMouse(x: number, y: number) {
45 | // prettier-ignore
46 | this.mouse.set(
47 | (x / this.renderer.width) * 2.0 - 1.0,
48 | -(y / this.renderer.height) * 2.0 + 1.0,
49 | );
50 | }
51 |
52 | resize(w: number, h: number) {
53 | this.renderer.dpr = Math.min(window.devicePixelRatio, 2);
54 | this.renderer.setSize(w, h);
55 | this.camera.perspective({ aspect: w / h });
56 |
57 | const z = (this.ctx.canvas.height / Math.tan((this.camera.fov * Math.PI) / 360)) * 0.5;
58 | this.camera.position.z = z;
59 | }
60 |
61 | render() {
62 | this.raycaster.castMouse(this.camera, this.mouse);
63 |
64 | const intersects = this.raycaster.intersectBounds(this.scene.children as any[]);
65 | if (intersects.length) {
66 | const object = intersects[0];
67 | const point = object.hit?.localPoint;
68 | if (point) {
69 | const [x, y] = point;
70 | this.intersect.objectId = object.id;
71 | this.intersect.point.set(x, y);
72 | }
73 | } else {
74 | this.intersect.objectId = null;
75 | }
76 |
77 | this.renderer.render({ scene: this.scene, camera: this.camera });
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import { App } from "./App";
2 |
3 | new App();
4 |
--------------------------------------------------------------------------------
/src/math.ts:
--------------------------------------------------------------------------------
1 | export const clamp = (n: number, min: number, max: number) => Math.max(min, Math.min(max, n));
2 | export const lerp = (a: number, b: number, t: number) => a + (b - a) * t;
3 |
--------------------------------------------------------------------------------
/src/style.css:
--------------------------------------------------------------------------------
1 | /* font faces */
2 | @font-face {
3 | font-family: "Inter";
4 | font-style: normal;
5 | font-weight: 100 900;
6 | font-display: swap;
7 | src: url("/fonts/inter-var-latin.woff2") format("woff2");
8 | }
9 |
10 | * {
11 | box-sizing: border-box;
12 | margin: 0;
13 | padding: 0;
14 | }
15 |
16 | html,
17 | body {
18 | height: 100%;
19 | width: 100%;
20 | }
21 |
22 | body {
23 | overflow: hidden;
24 | font-family: "Inter", sans-serif;
25 | display: grid;
26 | place-items: center;
27 | }
28 |
29 | a {
30 | color: inherit;
31 | text-decoration: none;
32 | }
33 |
34 | figure {
35 | height: min(95vh, 95vw);
36 | aspect-ratio: 2 / 3;
37 | opacity: 0;
38 | pointer-events: none;
39 |
40 | & > img {
41 | width: 100%;
42 | height: 100%;
43 | object-fit: cover;
44 | }
45 | }
46 |
47 | nav {
48 | position: absolute;
49 | top: 0;
50 | left: 0;
51 | padding: 0.5rem;
52 | display: flex;
53 | gap: 0.5rem;
54 |
55 | & > a {
56 | font-size: 1.1rem;
57 | font-weight: 500;
58 | opacity: 1;
59 | transition: opacity 0.2s;
60 | text-decoration: underline;
61 |
62 | &:not([data-active]) {
63 | opacity: 0.3;
64 | text-decoration: none;
65 | }
66 |
67 | &:hover {
68 | opacity: 0.75;
69 | }
70 | }
71 | }
72 |
73 | footer {
74 | position: fixed;
75 | bottom: 0;
76 | left: 0;
77 | width: 100%;
78 | padding: 0.5rem;
79 | }
80 |
81 | canvas {
82 | display: block;
83 |
84 | &.gl {
85 | position: fixed;
86 | left: 0;
87 | top: 0;
88 | z-index: -1;
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "useDefineForClassFields": true,
5 | "module": "ESNext",
6 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
7 | "skipLibCheck": true,
8 |
9 | /* Bundler mode */
10 | "moduleResolution": "bundler",
11 | "allowImportingTsExtensions": true,
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "noEmit": true,
15 |
16 | /* Linting */
17 | "strict": true,
18 | "noUnusedLocals": true,
19 | "noUnusedParameters": true,
20 | "noFallthroughCasesInSwitch": true,
21 |
22 | "strictPropertyInitialization": false,
23 |
24 | "baseUrl": ".",
25 | "paths": {
26 | "@/*": ["src/*"]
27 | },
28 |
29 | "types": ["vite-plugin-glsl/ext"]
30 | },
31 | "include": ["src"]
32 | }
33 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { resolve } from "path";
2 | import { defineConfig } from "vite";
3 |
4 | export default defineConfig({
5 | resolve: {
6 | alias: {
7 | "@": "/src",
8 | },
9 | },
10 |
11 | build: {
12 | rollupOptions: {
13 | input: {
14 | main: resolve(__dirname, "index.html"),
15 | index1: resolve(__dirname, "index1.html"),
16 | index2: resolve(__dirname, "index2.html"),
17 | index3: resolve(__dirname, "index3.html"),
18 | index4: resolve(__dirname, "index4.html"),
19 | index5: resolve(__dirname, "index5.html"),
20 | index6: resolve(__dirname, "index6.html"),
21 | },
22 | },
23 | },
24 | });
25 |
--------------------------------------------------------------------------------