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