├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── public └── waves.jpg ├── src ├── index.html ├── index.js ├── shaders │ ├── object │ │ ├── fragment.js │ │ └── vertex.js │ ├── utils.js │ └── water │ │ ├── fragment.js │ │ └── vertex.js └── style.css └── vite.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .DS_Store -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Demo: https://waves-threejs-webgl.vercel.app 2 | 3 | Reference: https://twitter.com/cmzw_/status/1706988554856059307?s=12 4 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "waves", 3 | "version": "0.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "waves", 9 | "version": "0.0.0", 10 | "dependencies": { 11 | "dat.gui": "^0.7.9", 12 | "three": "^0.157.0" 13 | }, 14 | "devDependencies": { 15 | "vite": "^4.4.9" 16 | } 17 | }, 18 | "node_modules/@esbuild/android-arm": { 19 | "version": "0.18.20", 20 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", 21 | "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", 22 | "cpu": [ 23 | "arm" 24 | ], 25 | "dev": true, 26 | "optional": true, 27 | "os": [ 28 | "android" 29 | ], 30 | "engines": { 31 | "node": ">=12" 32 | } 33 | }, 34 | "node_modules/@esbuild/android-arm64": { 35 | "version": "0.18.20", 36 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", 37 | "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", 38 | "cpu": [ 39 | "arm64" 40 | ], 41 | "dev": true, 42 | "optional": true, 43 | "os": [ 44 | "android" 45 | ], 46 | "engines": { 47 | "node": ">=12" 48 | } 49 | }, 50 | "node_modules/@esbuild/android-x64": { 51 | "version": "0.18.20", 52 | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", 53 | "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", 54 | "cpu": [ 55 | "x64" 56 | ], 57 | "dev": true, 58 | "optional": true, 59 | "os": [ 60 | "android" 61 | ], 62 | "engines": { 63 | "node": ">=12" 64 | } 65 | }, 66 | "node_modules/@esbuild/darwin-arm64": { 67 | "version": "0.18.20", 68 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", 69 | "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", 70 | "cpu": [ 71 | "arm64" 72 | ], 73 | "dev": true, 74 | "optional": true, 75 | "os": [ 76 | "darwin" 77 | ], 78 | "engines": { 79 | "node": ">=12" 80 | } 81 | }, 82 | "node_modules/@esbuild/darwin-x64": { 83 | "version": "0.18.20", 84 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", 85 | "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", 86 | "cpu": [ 87 | "x64" 88 | ], 89 | "dev": true, 90 | "optional": true, 91 | "os": [ 92 | "darwin" 93 | ], 94 | "engines": { 95 | "node": ">=12" 96 | } 97 | }, 98 | "node_modules/@esbuild/freebsd-arm64": { 99 | "version": "0.18.20", 100 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", 101 | "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", 102 | "cpu": [ 103 | "arm64" 104 | ], 105 | "dev": true, 106 | "optional": true, 107 | "os": [ 108 | "freebsd" 109 | ], 110 | "engines": { 111 | "node": ">=12" 112 | } 113 | }, 114 | "node_modules/@esbuild/freebsd-x64": { 115 | "version": "0.18.20", 116 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", 117 | "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", 118 | "cpu": [ 119 | "x64" 120 | ], 121 | "dev": true, 122 | "optional": true, 123 | "os": [ 124 | "freebsd" 125 | ], 126 | "engines": { 127 | "node": ">=12" 128 | } 129 | }, 130 | "node_modules/@esbuild/linux-arm": { 131 | "version": "0.18.20", 132 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", 133 | "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", 134 | "cpu": [ 135 | "arm" 136 | ], 137 | "dev": true, 138 | "optional": true, 139 | "os": [ 140 | "linux" 141 | ], 142 | "engines": { 143 | "node": ">=12" 144 | } 145 | }, 146 | "node_modules/@esbuild/linux-arm64": { 147 | "version": "0.18.20", 148 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", 149 | "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", 150 | "cpu": [ 151 | "arm64" 152 | ], 153 | "dev": true, 154 | "optional": true, 155 | "os": [ 156 | "linux" 157 | ], 158 | "engines": { 159 | "node": ">=12" 160 | } 161 | }, 162 | "node_modules/@esbuild/linux-ia32": { 163 | "version": "0.18.20", 164 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", 165 | "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", 166 | "cpu": [ 167 | "ia32" 168 | ], 169 | "dev": true, 170 | "optional": true, 171 | "os": [ 172 | "linux" 173 | ], 174 | "engines": { 175 | "node": ">=12" 176 | } 177 | }, 178 | "node_modules/@esbuild/linux-loong64": { 179 | "version": "0.18.20", 180 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", 181 | "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", 182 | "cpu": [ 183 | "loong64" 184 | ], 185 | "dev": true, 186 | "optional": true, 187 | "os": [ 188 | "linux" 189 | ], 190 | "engines": { 191 | "node": ">=12" 192 | } 193 | }, 194 | "node_modules/@esbuild/linux-mips64el": { 195 | "version": "0.18.20", 196 | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", 197 | "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", 198 | "cpu": [ 199 | "mips64el" 200 | ], 201 | "dev": true, 202 | "optional": true, 203 | "os": [ 204 | "linux" 205 | ], 206 | "engines": { 207 | "node": ">=12" 208 | } 209 | }, 210 | "node_modules/@esbuild/linux-ppc64": { 211 | "version": "0.18.20", 212 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", 213 | "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", 214 | "cpu": [ 215 | "ppc64" 216 | ], 217 | "dev": true, 218 | "optional": true, 219 | "os": [ 220 | "linux" 221 | ], 222 | "engines": { 223 | "node": ">=12" 224 | } 225 | }, 226 | "node_modules/@esbuild/linux-riscv64": { 227 | "version": "0.18.20", 228 | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", 229 | "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", 230 | "cpu": [ 231 | "riscv64" 232 | ], 233 | "dev": true, 234 | "optional": true, 235 | "os": [ 236 | "linux" 237 | ], 238 | "engines": { 239 | "node": ">=12" 240 | } 241 | }, 242 | "node_modules/@esbuild/linux-s390x": { 243 | "version": "0.18.20", 244 | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", 245 | "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", 246 | "cpu": [ 247 | "s390x" 248 | ], 249 | "dev": true, 250 | "optional": true, 251 | "os": [ 252 | "linux" 253 | ], 254 | "engines": { 255 | "node": ">=12" 256 | } 257 | }, 258 | "node_modules/@esbuild/linux-x64": { 259 | "version": "0.18.20", 260 | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", 261 | "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", 262 | "cpu": [ 263 | "x64" 264 | ], 265 | "dev": true, 266 | "optional": true, 267 | "os": [ 268 | "linux" 269 | ], 270 | "engines": { 271 | "node": ">=12" 272 | } 273 | }, 274 | "node_modules/@esbuild/netbsd-x64": { 275 | "version": "0.18.20", 276 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", 277 | "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", 278 | "cpu": [ 279 | "x64" 280 | ], 281 | "dev": true, 282 | "optional": true, 283 | "os": [ 284 | "netbsd" 285 | ], 286 | "engines": { 287 | "node": ">=12" 288 | } 289 | }, 290 | "node_modules/@esbuild/openbsd-x64": { 291 | "version": "0.18.20", 292 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", 293 | "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", 294 | "cpu": [ 295 | "x64" 296 | ], 297 | "dev": true, 298 | "optional": true, 299 | "os": [ 300 | "openbsd" 301 | ], 302 | "engines": { 303 | "node": ">=12" 304 | } 305 | }, 306 | "node_modules/@esbuild/sunos-x64": { 307 | "version": "0.18.20", 308 | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", 309 | "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", 310 | "cpu": [ 311 | "x64" 312 | ], 313 | "dev": true, 314 | "optional": true, 315 | "os": [ 316 | "sunos" 317 | ], 318 | "engines": { 319 | "node": ">=12" 320 | } 321 | }, 322 | "node_modules/@esbuild/win32-arm64": { 323 | "version": "0.18.20", 324 | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", 325 | "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", 326 | "cpu": [ 327 | "arm64" 328 | ], 329 | "dev": true, 330 | "optional": true, 331 | "os": [ 332 | "win32" 333 | ], 334 | "engines": { 335 | "node": ">=12" 336 | } 337 | }, 338 | "node_modules/@esbuild/win32-ia32": { 339 | "version": "0.18.20", 340 | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", 341 | "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", 342 | "cpu": [ 343 | "ia32" 344 | ], 345 | "dev": true, 346 | "optional": true, 347 | "os": [ 348 | "win32" 349 | ], 350 | "engines": { 351 | "node": ">=12" 352 | } 353 | }, 354 | "node_modules/@esbuild/win32-x64": { 355 | "version": "0.18.20", 356 | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", 357 | "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", 358 | "cpu": [ 359 | "x64" 360 | ], 361 | "dev": true, 362 | "optional": true, 363 | "os": [ 364 | "win32" 365 | ], 366 | "engines": { 367 | "node": ">=12" 368 | } 369 | }, 370 | "node_modules/dat.gui": { 371 | "version": "0.7.9", 372 | "resolved": "https://registry.npmjs.org/dat.gui/-/dat.gui-0.7.9.tgz", 373 | "integrity": "sha512-sCNc1OHobc+Erc1HqiswYgHdVNpSJUlk/Hz8vzOCsER7rl+oF/4+v8GXFUyCgtXpoCX6+bnmg07DedLvBLwYKQ==" 374 | }, 375 | "node_modules/esbuild": { 376 | "version": "0.18.20", 377 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", 378 | "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", 379 | "dev": true, 380 | "hasInstallScript": true, 381 | "bin": { 382 | "esbuild": "bin/esbuild" 383 | }, 384 | "engines": { 385 | "node": ">=12" 386 | }, 387 | "optionalDependencies": { 388 | "@esbuild/android-arm": "0.18.20", 389 | "@esbuild/android-arm64": "0.18.20", 390 | "@esbuild/android-x64": "0.18.20", 391 | "@esbuild/darwin-arm64": "0.18.20", 392 | "@esbuild/darwin-x64": "0.18.20", 393 | "@esbuild/freebsd-arm64": "0.18.20", 394 | "@esbuild/freebsd-x64": "0.18.20", 395 | "@esbuild/linux-arm": "0.18.20", 396 | "@esbuild/linux-arm64": "0.18.20", 397 | "@esbuild/linux-ia32": "0.18.20", 398 | "@esbuild/linux-loong64": "0.18.20", 399 | "@esbuild/linux-mips64el": "0.18.20", 400 | "@esbuild/linux-ppc64": "0.18.20", 401 | "@esbuild/linux-riscv64": "0.18.20", 402 | "@esbuild/linux-s390x": "0.18.20", 403 | "@esbuild/linux-x64": "0.18.20", 404 | "@esbuild/netbsd-x64": "0.18.20", 405 | "@esbuild/openbsd-x64": "0.18.20", 406 | "@esbuild/sunos-x64": "0.18.20", 407 | "@esbuild/win32-arm64": "0.18.20", 408 | "@esbuild/win32-ia32": "0.18.20", 409 | "@esbuild/win32-x64": "0.18.20" 410 | } 411 | }, 412 | "node_modules/fsevents": { 413 | "version": "2.3.3", 414 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 415 | "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 416 | "dev": true, 417 | "hasInstallScript": true, 418 | "optional": true, 419 | "os": [ 420 | "darwin" 421 | ], 422 | "engines": { 423 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 424 | } 425 | }, 426 | "node_modules/nanoid": { 427 | "version": "3.3.6", 428 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", 429 | "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", 430 | "dev": true, 431 | "funding": [ 432 | { 433 | "type": "github", 434 | "url": "https://github.com/sponsors/ai" 435 | } 436 | ], 437 | "bin": { 438 | "nanoid": "bin/nanoid.cjs" 439 | }, 440 | "engines": { 441 | "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" 442 | } 443 | }, 444 | "node_modules/picocolors": { 445 | "version": "1.0.0", 446 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", 447 | "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", 448 | "dev": true 449 | }, 450 | "node_modules/postcss": { 451 | "version": "8.4.31", 452 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", 453 | "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", 454 | "dev": true, 455 | "funding": [ 456 | { 457 | "type": "opencollective", 458 | "url": "https://opencollective.com/postcss/" 459 | }, 460 | { 461 | "type": "tidelift", 462 | "url": "https://tidelift.com/funding/github/npm/postcss" 463 | }, 464 | { 465 | "type": "github", 466 | "url": "https://github.com/sponsors/ai" 467 | } 468 | ], 469 | "dependencies": { 470 | "nanoid": "^3.3.6", 471 | "picocolors": "^1.0.0", 472 | "source-map-js": "^1.0.2" 473 | }, 474 | "engines": { 475 | "node": "^10 || ^12 || >=14" 476 | } 477 | }, 478 | "node_modules/rollup": { 479 | "version": "3.29.4", 480 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz", 481 | "integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==", 482 | "dev": true, 483 | "bin": { 484 | "rollup": "dist/bin/rollup" 485 | }, 486 | "engines": { 487 | "node": ">=14.18.0", 488 | "npm": ">=8.0.0" 489 | }, 490 | "optionalDependencies": { 491 | "fsevents": "~2.3.2" 492 | } 493 | }, 494 | "node_modules/source-map-js": { 495 | "version": "1.0.2", 496 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", 497 | "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", 498 | "dev": true, 499 | "engines": { 500 | "node": ">=0.10.0" 501 | } 502 | }, 503 | "node_modules/three": { 504 | "version": "0.157.0", 505 | "resolved": "https://registry.npmjs.org/three/-/three-0.157.0.tgz", 506 | "integrity": "sha512-CeAwQrf4x3z0/e+MC4F+nXLW5t0gh3pw+L6CCBqpHvOq3bGYIgRYub7Pv0j/9wR+d++OiEglyZzWyuSYbwWGOA==" 507 | }, 508 | "node_modules/vite": { 509 | "version": "4.4.9", 510 | "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.9.tgz", 511 | "integrity": "sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==", 512 | "dev": true, 513 | "dependencies": { 514 | "esbuild": "^0.18.10", 515 | "postcss": "^8.4.27", 516 | "rollup": "^3.27.1" 517 | }, 518 | "bin": { 519 | "vite": "bin/vite.js" 520 | }, 521 | "engines": { 522 | "node": "^14.18.0 || >=16.0.0" 523 | }, 524 | "funding": { 525 | "url": "https://github.com/vitejs/vite?sponsor=1" 526 | }, 527 | "optionalDependencies": { 528 | "fsevents": "~2.3.2" 529 | }, 530 | "peerDependencies": { 531 | "@types/node": ">= 14", 532 | "less": "*", 533 | "lightningcss": "^1.21.0", 534 | "sass": "*", 535 | "stylus": "*", 536 | "sugarss": "*", 537 | "terser": "^5.4.0" 538 | }, 539 | "peerDependenciesMeta": { 540 | "@types/node": { 541 | "optional": true 542 | }, 543 | "less": { 544 | "optional": true 545 | }, 546 | "lightningcss": { 547 | "optional": true 548 | }, 549 | "sass": { 550 | "optional": true 551 | }, 552 | "stylus": { 553 | "optional": true 554 | }, 555 | "sugarss": { 556 | "optional": true 557 | }, 558 | "terser": { 559 | "optional": true 560 | } 561 | } 562 | } 563 | } 564 | } 565 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "waves", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build" 9 | }, 10 | "devDependencies": { 11 | "vite": "^4.4.9" 12 | }, 13 | "dependencies": { 14 | "dat.gui": "^0.7.9", 15 | "three": "^0.157.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /public/waves.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git1029/waves_threejs/4aec76a628c7c84f72c07927fd8c5fc8a91640da/public/waves.jpg -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Waves 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import "./style.css"; 2 | import * as THREE from "three"; 3 | import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js"; 4 | import * as dat from "dat.gui"; 5 | import waterVertexShader from "./shaders/water/vertex"; 6 | import waterFragmentShader from "./shaders/water/fragment"; 7 | import objectVertexShader from "./shaders/object/vertex"; 8 | import objectFragmentShader from "./shaders/object/fragment"; 9 | 10 | const sizes = { 11 | width: window.innerWidth, 12 | height: window.innerHeight, 13 | }; 14 | 15 | const settings = { 16 | waveAmp: 0.6, 17 | waveNoise: 1, 18 | object: false, 19 | }; 20 | const gui = new dat.GUI({ width: 340 }); 21 | 22 | const canvas = document.querySelector("canvas.webgl"); 23 | const scene = new THREE.Scene(); 24 | const loadingManager = new THREE.LoadingManager(); 25 | const textureLoader = new THREE.TextureLoader(loadingManager); 26 | 27 | const camera = new THREE.PerspectiveCamera( 28 | 75, 29 | sizes.width / sizes.height, 30 | 0.1, 31 | 100 32 | ); 33 | camera.position.set(-5, 3, -2); 34 | scene.add(camera); 35 | 36 | const controls = new OrbitControls(camera, canvas); 37 | controls.enableDamping = true; 38 | 39 | const renderer = new THREE.WebGLRenderer({ 40 | canvas: canvas, 41 | }); 42 | renderer.setSize(sizes.width, sizes.height); 43 | renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); 44 | renderer.setClearColor("#000523"); 45 | 46 | const waterGeometry = new THREE.PlaneGeometry(4, 4, 512, 512); 47 | const waterMaterial = new THREE.ShaderMaterial({ 48 | vertexShader: waterVertexShader, 49 | fragmentShader: waterFragmentShader, 50 | transparent: true, 51 | uniforms: { 52 | uTime: { value: 0 }, 53 | PI: { value: Math.PI }, 54 | uTex: { value: null }, 55 | uWaveAmp: { value: 0.6 }, 56 | uWaveNoise: { value: 1 }, 57 | }, 58 | side: THREE.DoubleSide, 59 | }); 60 | 61 | gui 62 | .add(settings, "waveAmp") 63 | .min(0) 64 | .max(1) 65 | .step(0.001) 66 | .name("Amp") 67 | .onChange((value) => { 68 | waterMaterial.uniforms.uWaveAmp.value = value; 69 | objectMaterial.uniforms.uWaveAmp.value = value; 70 | }); 71 | gui 72 | .add(settings, "waveNoise") 73 | .min(0) 74 | .max(1) 75 | .step(0.001) 76 | .name("Noise") 77 | .onChange((value) => { 78 | waterMaterial.uniforms.uWaveNoise.value = value; 79 | objectMaterial.uniforms.uWaveNoise.value = value; 80 | }); 81 | gui 82 | .add(settings, "object") 83 | .name("Object") 84 | .onChange((value) => { 85 | object.visible = value; 86 | }); 87 | 88 | textureLoader.load("/waves.jpg", (tex) => { 89 | waterMaterial.uniforms.uTex.value = tex; 90 | waterMaterial.needsUpdate = true; 91 | }); 92 | 93 | const water = new THREE.Mesh(waterGeometry, waterMaterial); 94 | scene.add(water); 95 | 96 | const objectGeometry = new THREE.BoxGeometry(0.3, 0.3, 0.9); 97 | // const objectGeometry = new THREE.SphereGeometry(0.5, 32, 32); 98 | const objectMaterial = new THREE.ShaderMaterial({ 99 | vertexShader: objectVertexShader, 100 | fragmentShader: objectFragmentShader, 101 | transparent: true, 102 | uniforms: { 103 | uTime: { value: 0 }, 104 | PI: { value: Math.PI }, 105 | uWaveAmp: { value: 0.6 }, 106 | uWaveNoise: { value: 1 }, 107 | }, 108 | side: THREE.DoubleSide, 109 | }); 110 | const object = new THREE.Mesh(objectGeometry, objectMaterial); 111 | object.visible = false; 112 | scene.add(object); 113 | 114 | const clock = new THREE.Clock(); 115 | 116 | const tick = () => { 117 | const elapsedTime = clock.getElapsedTime(); 118 | waterMaterial.uniforms.uTime.value = elapsedTime; 119 | objectMaterial.uniforms.uTime.value = elapsedTime; 120 | controls.update(); 121 | renderer.render(scene, camera); 122 | window.requestAnimationFrame(tick); 123 | }; 124 | 125 | tick(); 126 | 127 | window.addEventListener("resize", () => { 128 | sizes.width = window.innerWidth; 129 | sizes.height = window.innerHeight; 130 | camera.aspect = sizes.width / sizes.height; 131 | camera.updateProjectionMatrix(); 132 | renderer.setSize(sizes.width, sizes.height); 133 | renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); 134 | }); 135 | -------------------------------------------------------------------------------- /src/shaders/object/fragment.js: -------------------------------------------------------------------------------- 1 | const fragmentShader = /* glsl */ ` 2 | varying vec3 vNormal; 3 | 4 | void main() { 5 | gl_FragColor = vec4(vNormal, 1.0); 6 | } 7 | `; 8 | 9 | export default fragmentShader; 10 | -------------------------------------------------------------------------------- /src/shaders/object/vertex.js: -------------------------------------------------------------------------------- 1 | import { map, perlin3d, rotation3d } from "../utils"; 2 | 3 | const vertexShader = /* glsl */ ` 4 | uniform float uTime; 5 | uniform float PI; 6 | uniform float uWaveAmp; 7 | uniform float uWaveNoise; 8 | 9 | varying vec3 vNormal; 10 | 11 | ${perlin3d} 12 | ${rotation3d} 13 | ${map} 14 | 15 | void main() { 16 | float ld = 2.; 17 | float off = 0.; 18 | float t = mod(uTime + off, ld) / ld; 19 | float u = t * PI * 2.; 20 | 21 | vec3 pos = position; 22 | 23 | vec2 dir = vec2( 24 | sin(u + 2. - cos(u + .5) * .5) * uWaveAmp, 25 | cos(u) * uWaveAmp 26 | ); 27 | 28 | float za = cnoise(vec3(uTime * .5)); 29 | 30 | pos = (vec4(pos, 1.) * rotation3d(vec3(1., 0., 0.), -sin(u) * PI / 4. * uWaveAmp)).xyz; 31 | pos = (vec4(pos, 1.) * rotation3d(vec3(0., 0., 1.), za * PI / 4. * uWaveNoise)).xyz; 32 | 33 | pos.yz += dir; 34 | 35 | vec4 modelPosition = modelMatrix * vec4(pos, 1.0); 36 | 37 | vec4 viewPosition = viewMatrix * modelPosition; 38 | vec4 projectedPosition = projectionMatrix * viewPosition; 39 | gl_Position = projectedPosition; 40 | 41 | vNormal = normal; 42 | }`; 43 | 44 | export default vertexShader; 45 | -------------------------------------------------------------------------------- /src/shaders/utils.js: -------------------------------------------------------------------------------- 1 | export const map = /* glsl */ ` 2 | float map(float value, float min1, float max1, float min2, float max2) { 3 | return min2 + (value - min1) * (max2 - min2) / (max1 - min1); 4 | } 5 | `; 6 | 7 | export const perlin3d = /* glsl */ ` 8 | // Classic Perlin 3D Noise 9 | // by Stefan Gustavson 10 | // 11 | vec4 permute(vec4 x) 12 | { 13 | return mod(((x*34.0)+1.0)*x, 289.0); 14 | } 15 | vec4 taylorInvSqrt(vec4 r) 16 | { 17 | return 1.79284291400159 - 0.85373472095314 * r; 18 | } 19 | vec3 fade(vec3 t) 20 | { 21 | return t*t*t*(t*(t*6.0-15.0)+10.0); 22 | } 23 | 24 | float cnoise(vec3 P) 25 | { 26 | vec3 Pi0 = floor(P); // Integer part for indexing 27 | vec3 Pi1 = Pi0 + vec3(1.0); // Integer part + 1 28 | Pi0 = mod(Pi0, 289.0); 29 | Pi1 = mod(Pi1, 289.0); 30 | vec3 Pf0 = fract(P); // Fractional part for interpolation 31 | vec3 Pf1 = Pf0 - vec3(1.0); // Fractional part - 1.0 32 | vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x); 33 | vec4 iy = vec4(Pi0.yy, Pi1.yy); 34 | vec4 iz0 = Pi0.zzzz; 35 | vec4 iz1 = Pi1.zzzz; 36 | 37 | vec4 ixy = permute(permute(ix) + iy); 38 | vec4 ixy0 = permute(ixy + iz0); 39 | vec4 ixy1 = permute(ixy + iz1); 40 | 41 | vec4 gx0 = ixy0 / 7.0; 42 | vec4 gy0 = fract(floor(gx0) / 7.0) - 0.5; 43 | gx0 = fract(gx0); 44 | vec4 gz0 = vec4(0.5) - abs(gx0) - abs(gy0); 45 | vec4 sz0 = step(gz0, vec4(0.0)); 46 | gx0 -= sz0 * (step(0.0, gx0) - 0.5); 47 | gy0 -= sz0 * (step(0.0, gy0) - 0.5); 48 | 49 | vec4 gx1 = ixy1 / 7.0; 50 | vec4 gy1 = fract(floor(gx1) / 7.0) - 0.5; 51 | gx1 = fract(gx1); 52 | vec4 gz1 = vec4(0.5) - abs(gx1) - abs(gy1); 53 | vec4 sz1 = step(gz1, vec4(0.0)); 54 | gx1 -= sz1 * (step(0.0, gx1) - 0.5); 55 | gy1 -= sz1 * (step(0.0, gy1) - 0.5); 56 | 57 | vec3 g000 = vec3(gx0.x,gy0.x,gz0.x); 58 | vec3 g100 = vec3(gx0.y,gy0.y,gz0.y); 59 | vec3 g010 = vec3(gx0.z,gy0.z,gz0.z); 60 | vec3 g110 = vec3(gx0.w,gy0.w,gz0.w); 61 | vec3 g001 = vec3(gx1.x,gy1.x,gz1.x); 62 | vec3 g101 = vec3(gx1.y,gy1.y,gz1.y); 63 | vec3 g011 = vec3(gx1.z,gy1.z,gz1.z); 64 | vec3 g111 = vec3(gx1.w,gy1.w,gz1.w); 65 | 66 | vec4 norm0 = taylorInvSqrt(vec4(dot(g000, g000), dot(g010, g010), dot(g100, g100), dot(g110, g110))); 67 | g000 *= norm0.x; 68 | g010 *= norm0.y; 69 | g100 *= norm0.z; 70 | g110 *= norm0.w; 71 | vec4 norm1 = taylorInvSqrt(vec4(dot(g001, g001), dot(g011, g011), dot(g101, g101), dot(g111, g111))); 72 | g001 *= norm1.x; 73 | g011 *= norm1.y; 74 | g101 *= norm1.z; 75 | g111 *= norm1.w; 76 | 77 | float n000 = dot(g000, Pf0); 78 | float n100 = dot(g100, vec3(Pf1.x, Pf0.yz)); 79 | float n010 = dot(g010, vec3(Pf0.x, Pf1.y, Pf0.z)); 80 | float n110 = dot(g110, vec3(Pf1.xy, Pf0.z)); 81 | float n001 = dot(g001, vec3(Pf0.xy, Pf1.z)); 82 | float n101 = dot(g101, vec3(Pf1.x, Pf0.y, Pf1.z)); 83 | float n011 = dot(g011, vec3(Pf0.x, Pf1.yz)); 84 | float n111 = dot(g111, Pf1); 85 | 86 | vec3 fade_xyz = fade(Pf0); 87 | vec4 n_z = mix(vec4(n000, n100, n010, n110), vec4(n001, n101, n011, n111), fade_xyz.z); 88 | vec2 n_yz = mix(n_z.xy, n_z.zw, fade_xyz.y); 89 | float n_xyz = mix(n_yz.x, n_yz.y, fade_xyz.x); 90 | return 2.2 * n_xyz; 91 | } 92 | `; 93 | 94 | export const rotation3d = /* glsl */ ` 95 | mat4 rotation3d(vec3 axis, float angle) { 96 | axis = normalize(axis); 97 | float s = sin(angle); 98 | float c = cos(angle); 99 | float oc = 1.0 - c; 100 | 101 | return mat4( 102 | oc * axis.x * axis.x + c, oc * axis.x * axis.y - axis.z * s, oc * axis.z * axis.x + axis.y * s, 0.0, 103 | oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s, 0.0, 104 | oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c, 0.0, 105 | 0.0, 0.0, 0.0, 1.0 106 | ); 107 | } 108 | `; 109 | -------------------------------------------------------------------------------- /src/shaders/water/fragment.js: -------------------------------------------------------------------------------- 1 | const fragmentShader = /* glsl */ ` 2 | uniform sampler2D uTex; 3 | uniform float uWaveAmp; 4 | 5 | varying vec2 vUv; 6 | varying float vElevation; 7 | varying float vT; 8 | 9 | void main() { 10 | vec4 c0 = vec4(vec3(2., 44., 104.)/255., 0.); 11 | vec4 c1 = vec4(vec3(16., 144., 161.)/255., .0); 12 | vec4 c2 = vec4(vec3(159., 184., 189.)/200., .5); 13 | vec4 c3 = vec4(vec3(200., 228., 238.)/255., 1.); 14 | 15 | vec3 colorA = c0.rgb; 16 | float stopA = c0.a; 17 | for (float i = 1.; i < 4.; i++) { 18 | vec4 cB = c1; 19 | if (i == 2.) cB = c2; 20 | if (i == 3.) cB = c3; 21 | vec3 colorB = cB.rgb; 22 | float stopB = cB.a; 23 | float fc = smoothstep(stopA, stopB, vElevation); 24 | colorA = mix(colorA, colorB, fc); 25 | stopA = cB.a; 26 | } 27 | 28 | colorA = mix(texture2D(uTex, vUv).rgb, colorA, vElevation); 29 | colorA = mix(colorA, c1.rgb, pow(1. - vElevation, 2.) * .9); 30 | colorA *= mix(vec3(1.), c0.rgb, pow(vT, 2.) * mix(.9, 1., smoothstep(.6, 1., uWaveAmp)) * smoothstep(0., .6, uWaveAmp)); 31 | 32 | gl_FragColor = vec4(colorA, 1.0); 33 | } 34 | `; 35 | 36 | export default fragmentShader; 37 | -------------------------------------------------------------------------------- /src/shaders/water/vertex.js: -------------------------------------------------------------------------------- 1 | import { map, perlin3d, rotation3d } from "../utils"; 2 | 3 | const vertexShader = /* glsl */ ` 4 | uniform float uTime; 5 | uniform float PI; 6 | uniform float uWaveAmp; 7 | uniform float uWaveNoise; 8 | 9 | varying float vElevation; 10 | varying float vT; 11 | varying vec2 vUv; 12 | 13 | ${perlin3d} 14 | ${rotation3d} 15 | ${map} 16 | 17 | vec4 displace(vec3 p) { 18 | vec3 q = p; 19 | float ld = 2.; 20 | float off = -q.z * 1.25 * (ld / 4.); 21 | float t = mod(uTime + off, ld) / ld; 22 | float u = t * PI * 2.; 23 | 24 | q.y += sin(u + 2. - cos(u + .5) * .5) * uWaveAmp; 25 | q.z += cos(u) * uWaveAmp; 26 | 27 | return vec4(q, u); 28 | } 29 | 30 | void main() { 31 | vec3 rotated = (vec4(position, 1.) * rotation3d(vec3(1., 0., 0.), -PI/2.)).xyz; 32 | vec4 pos = displace(rotated); 33 | float u = pos.w; 34 | 35 | float fy = sin(u) * .5 + .5; 36 | 37 | float yoff = cnoise(vec3(position * 2. + uTime * .5)) * mix(.1, .3, fy) + 38 | cnoise(vec3(position * 4. + uTime * .5)) * mix(.1, .3, fy) * .5 + 39 | cnoise(vec3(position * 8. + uTime * .5)) * mix(.1, .3, fy) * .25; 40 | 41 | pos.y += yoff * uWaveNoise; 42 | 43 | float zoff = cnoise(vec3(position * .5 + vec3(uTime, 0., 0.) * .0)) * .5 + 44 | cnoise(vec3(position * 1.)) * .3 + 45 | cnoise(vec3(position * 4.)) * .15; 46 | 47 | float fz = sin(u + 2.) * .5 + .5; 48 | pos.z += zoff * fz * uWaveNoise; 49 | 50 | vec4 modelPosition = modelMatrix * vec4(pos.xyz, 1.0); 51 | 52 | vec4 viewPosition = viewMatrix * modelPosition; 53 | vec4 projectedPosition = projectionMatrix * viewPosition; 54 | gl_Position = projectedPosition; 55 | 56 | vUv = uv; 57 | vT = sin(u - 1.75 + zoff + yoff) * .5 + .5; 58 | 59 | float yd = uWaveAmp + mix(.1, .3, fy) * 1.75; 60 | vElevation = smoothstep(.1, .75, map(pos.y, -yd, yd, 0., 1.)); 61 | // vElevation = clamp(map(pos.y, -.6, .25, 0., 1.), 0., 1.); 62 | }`; 63 | 64 | export default vertexShader; 65 | -------------------------------------------------------------------------------- /src/style.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | } 5 | 6 | html, 7 | body { 8 | overflow: hidden; 9 | } 10 | 11 | .webgl { 12 | position: fixed; 13 | top: 0; 14 | left: 0; 15 | outline: none; 16 | } 17 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | const isCodeSandbox = 2 | "SANDBOX_URL" in process.env || "CODESANDBOX_HOST" in process.env; 3 | 4 | export default { 5 | root: "src/", 6 | publicDir: "../public/", 7 | base: "./", 8 | server: { 9 | host: true, 10 | open: !isCodeSandbox, // Open if it's not a CodeSandbox 11 | }, 12 | build: { 13 | outDir: "../dist", 14 | emptyOutDir: true, 15 | sourcemap: true, 16 | }, 17 | }; 18 | --------------------------------------------------------------------------------