[READ-ONLY] Mirror of https://github.com/flo-bit/text_effect_fluid. cool text fluid effect for your website flo-bit.dev/text_effect_fluid/
fluid-dynamics javascript template text-effects
0

Configure Feed

Select the types of activity you want to include in your feed.

text_effect_fluid / script.js
44 kB 1641 lines
1/* 2MIT License 3 4Copyright (c) 2017 Pavel Dobryakov 5 6Permission is hereby granted, free of charge, to any person obtaining a copy 7of this software and associated documentation files (the "Software"), to deal 8in the Software without restriction, including without limitation the rights 9to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10copies of the Software, and to permit persons to whom the Software is 11furnished to do so, subject to the following conditions: 12 13The above copyright notice and this permission notice shall be included in all 14copies or substantial portions of the Software. 15 16THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22SOFTWARE. 23*/ 24 25'use strict'; 26 27const overlayConfig = { 28 text: 'flo-bit', 29 fontWidth: '900', 30 font: 'Arial', 31 fontSize: 1/3, // percentage to canvas width 32} 33 34// overlay canvas 35 36function resizeOverlayCanvas() { 37 let overlayCanvas = document.getElementById('maskCanvas'); 38 let container = document.getElementById('canvasContainer'); 39 40 overlayCanvas.width = container.offsetWidth; 41 overlayCanvas.height = container.offsetHeight; 42 43 let ctx = overlayCanvas.getContext('2d'); 44 45 let dpr = window.devicePixelRatio || 1; 46 let rect = overlayCanvas.getBoundingClientRect(); 47 48 overlayCanvas.width = rect.width * dpr; 49 overlayCanvas.height = rect.height * dpr; 50 ctx.scale(dpr, dpr); 51 52 ctx.fillStyle = 'black'; 53 ctx.fillRect(0, 0, overlayCanvas.width, overlayCanvas.height); 54 55 let fontSize = Math.round(rect.width * overlayConfig.fontSize); 56 ctx.font = overlayConfig.fontWidth + ' ' + fontSize + 'px ' + overlayConfig.font; 57 58 ctx.strokeStyle = 'rgba(255, 255, 255, 0.3)'; 59 ctx.lineWidth = 2; 60 61 ctx.textBaseline = 'middle'; 62 ctx.textAlign = 'center'; 63 64 ctx.strokeText(overlayConfig.text, rect.width / 2, rect.height / 2); 65 66 ctx.globalCompositeOperation = 'destination-out'; 67 ctx.fillText(overlayConfig.text, rect.width / 2, rect.height / 2); 68} 69 70// const input = document.getElementById('name'); 71 72// // on change 73// input.addEventListener('change', function() { 74// overlayConfig.text = input.value; 75// resizeOverlayCanvas(); 76// }); 77 78 79// Initial resize 80resizeOverlayCanvas(); 81 82// Resize canvas on window resize 83window.addEventListener('resize', resizeOverlayCanvas); 84 85 86// Simulation section 87 88const canvas = document.getElementsByTagName('canvas')[0]; 89resizeCanvas(); 90 91let config = { 92 SIM_RESOLUTION: 128, 93 DYE_RESOLUTION: 1024, 94 CAPTURE_RESOLUTION: 512, 95 DENSITY_DISSIPATION: 1.0, 96 VELOCITY_DISSIPATION: 0.1, 97 PRESSURE: 0.8, 98 PRESSURE_ITERATIONS: 20, 99 CURL: 30, 100 SPLAT_RADIUS: 0.25, 101 SPLAT_FORCE: 1000, 102 SHADING: true, 103 COLORFUL: true, 104 COLOR_UPDATE_SPEED: 10, 105 PAUSED: false, 106 BACK_COLOR: { r: 0, g: 0, b: 0 }, 107 TRANSPARENT: false, 108 BLOOM: false, 109 BLOOM_ITERATIONS: 8, 110 BLOOM_RESOLUTION: 256, 111 BLOOM_INTENSITY: 0.8, 112 BLOOM_THRESHOLD: 0.8, 113 BLOOM_SOFT_KNEE: 0.7, 114 SUNRAYS: true, 115 SUNRAYS_RESOLUTION: 196, 116 SUNRAYS_WEIGHT: 1.0, 117 START_HUE: 0.5, 118 END_HUE: 1.0, 119 RENDER_SPEED: 0.4 120}; 121 122function PointerPrototype() { 123 this.id = -1; 124 this.texcoordX = 0; 125 this.texcoordY = 0; 126 this.prevTexcoordX = 0; 127 this.prevTexcoordY = 0; 128 this.deltaX = 0; 129 this.deltaY = 0; 130 this.down = false; 131 this.moved = false; 132 this.color = [30, 0, 300]; 133} 134 135let pointers = []; 136let splatStack = []; 137pointers.push(new PointerPrototype()); 138 139const { gl, ext } = getWebGLContext(canvas); 140 141if (isMobile()) { 142 config.DYE_RESOLUTION = 512; 143} 144if (!ext.supportLinearFiltering) { 145 config.DYE_RESOLUTION = 512; 146 config.SHADING = false; 147 config.BLOOM = false; 148 config.SUNRAYS = false; 149} 150 151function getWebGLContext(canvas) { 152 const params = { 153 alpha: true, 154 depth: false, 155 stencil: true, 156 antialias: false, 157 preserveDrawingBuffer: false 158 }; 159 160 161 let gl = canvas.getContext('webgl2', params); 162 const isWebGL2 = !!gl; 163 if (!isWebGL2) 164 gl = canvas.getContext('webgl', params) || canvas.getContext('experimental-webgl', params); 165 166 let halfFloat; 167 let supportLinearFiltering; 168 if (isWebGL2) { 169 gl.getExtension('EXT_color_buffer_float'); 170 supportLinearFiltering = gl.getExtension('OES_texture_float_linear'); 171 } else { 172 halfFloat = gl.getExtension('OES_texture_half_float'); 173 supportLinearFiltering = gl.getExtension('OES_texture_half_float_linear'); 174 } 175 176 gl.clearColor(0.0, 0.0, 0.0, 1.0); 177 178 const halfFloatTexType = isWebGL2 ? gl.HALF_FLOAT : halfFloat.HALF_FLOAT_OES; 179 let formatRGBA; 180 let formatRG; 181 let formatR; 182 183 if (isWebGL2) { 184 formatRGBA = getSupportedFormat(gl, gl.RGBA16F, gl.RGBA, halfFloatTexType); 185 formatRG = getSupportedFormat(gl, gl.RG16F, gl.RG, halfFloatTexType); 186 formatR = getSupportedFormat(gl, gl.R16F, gl.RED, halfFloatTexType); 187 } else { 188 formatRGBA = getSupportedFormat(gl, gl.RGBA, gl.RGBA, halfFloatTexType); 189 formatRG = getSupportedFormat(gl, gl.RGBA, gl.RGBA, halfFloatTexType); 190 formatR = getSupportedFormat(gl, gl.RGBA, gl.RGBA, halfFloatTexType); 191 } 192 193 return { 194 gl, 195 ext: { 196 formatRGBA, 197 formatRG, 198 formatR, 199 halfFloatTexType, 200 supportLinearFiltering 201 } 202 }; 203} 204 205function getSupportedFormat(gl, internalFormat, format, type) { 206 if (!supportRenderTextureFormat(gl, internalFormat, format, type)) { 207 switch (internalFormat) { 208 case gl.R16F: 209 return getSupportedFormat(gl, gl.RG16F, gl.RG, type); 210 case gl.RG16F: 211 return getSupportedFormat(gl, gl.RGBA16F, gl.RGBA, type); 212 default: 213 return null; 214 } 215 } 216 217 return { 218 internalFormat, 219 format 220 }; 221} 222 223function supportRenderTextureFormat(gl, internalFormat, format, type) { 224 let texture = gl.createTexture(); 225 gl.bindTexture(gl.TEXTURE_2D, texture); 226 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); 227 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); 228 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 229 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); 230 gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, 4, 4, 0, format, type, null); 231 232 let fbo = gl.createFramebuffer(); 233 gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); 234 gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0); 235 236 let status = gl.checkFramebufferStatus(gl.FRAMEBUFFER); 237 return status == gl.FRAMEBUFFER_COMPLETE; 238} 239 240function isMobile() { 241 return /Mobi|Android/i.test(navigator.userAgent); 242} 243 244class Material { 245 constructor(vertexShader, fragmentShaderSource) { 246 this.vertexShader = vertexShader; 247 this.fragmentShaderSource = fragmentShaderSource; 248 this.programs = []; 249 this.activeProgram = null; 250 this.uniforms = []; 251 } 252 253 setKeywords(keywords) { 254 let hash = 0; 255 for (let i = 0; i < keywords.length; i++) hash += hashCode(keywords[i]); 256 257 let program = this.programs[hash]; 258 if (program == null) { 259 let fragmentShader = compileShader(gl.FRAGMENT_SHADER, this.fragmentShaderSource, keywords); 260 program = createProgram(this.vertexShader, fragmentShader); 261 this.programs[hash] = program; 262 } 263 264 if (program == this.activeProgram) return; 265 266 this.uniforms = getUniforms(program); 267 this.activeProgram = program; 268 } 269 270 bind() { 271 gl.useProgram(this.activeProgram); 272 } 273} 274 275class Program { 276 constructor(vertexShader, fragmentShader) { 277 this.uniforms = {}; 278 this.program = createProgram(vertexShader, fragmentShader); 279 this.uniforms = getUniforms(this.program); 280 } 281 282 bind() { 283 gl.useProgram(this.program); 284 } 285} 286 287function createProgram(vertexShader, fragmentShader) { 288 let program = gl.createProgram(); 289 gl.attachShader(program, vertexShader); 290 gl.attachShader(program, fragmentShader); 291 gl.linkProgram(program); 292 293 if (!gl.getProgramParameter(program, gl.LINK_STATUS)) 294 console.trace(gl.getProgramInfoLog(program)); 295 296 return program; 297} 298 299function getUniforms(program) { 300 let uniforms = []; 301 let uniformCount = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS); 302 for (let i = 0; i < uniformCount; i++) { 303 let uniformName = gl.getActiveUniform(program, i).name; 304 uniforms[uniformName] = gl.getUniformLocation(program, uniformName); 305 } 306 return uniforms; 307} 308 309function compileShader(type, source, keywords) { 310 source = addKeywords(source, keywords); 311 312 const shader = gl.createShader(type); 313 gl.shaderSource(shader, source); 314 gl.compileShader(shader); 315 316 if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) console.trace(gl.getShaderInfoLog(shader)); 317 318 return shader; 319} 320 321function addKeywords(source, keywords) { 322 if (keywords == null) return source; 323 let keywordsString = ''; 324 keywords.forEach((keyword) => { 325 keywordsString += '#define ' + keyword + '\n'; 326 }); 327 return keywordsString + source; 328} 329 330const baseVertexShader = compileShader( 331 gl.VERTEX_SHADER, 332 ` 333 precision highp float; 334 335 attribute vec2 aPosition; 336 varying vec2 vUv; 337 varying vec2 vL; 338 varying vec2 vR; 339 varying vec2 vT; 340 varying vec2 vB; 341 uniform vec2 texelSize; 342 343 void main () { 344 vUv = aPosition * 0.5 + 0.5; 345 vL = vUv - vec2(texelSize.x, 0.0); 346 vR = vUv + vec2(texelSize.x, 0.0); 347 vT = vUv + vec2(0.0, texelSize.y); 348 vB = vUv - vec2(0.0, texelSize.y); 349 gl_Position = vec4(aPosition, 0.0, 1.0); 350 } 351` 352); 353 354const blurVertexShader = compileShader( 355 gl.VERTEX_SHADER, 356 ` 357 precision highp float; 358 359 attribute vec2 aPosition; 360 varying vec2 vUv; 361 varying vec2 vL; 362 varying vec2 vR; 363 uniform vec2 texelSize; 364 365 void main () { 366 vUv = aPosition * 0.5 + 0.5; 367 float offset = 1.33333333; 368 vL = vUv - texelSize * offset; 369 vR = vUv + texelSize * offset; 370 gl_Position = vec4(aPosition, 0.0, 1.0); 371 } 372` 373); 374 375const blurShader = compileShader( 376 gl.FRAGMENT_SHADER, 377 ` 378 precision mediump float; 379 precision mediump sampler2D; 380 381 varying vec2 vUv; 382 varying vec2 vL; 383 varying vec2 vR; 384 uniform sampler2D uTexture; 385 386 void main () { 387 vec4 sum = texture2D(uTexture, vUv) * 0.29411764; 388 sum += texture2D(uTexture, vL) * 0.35294117; 389 sum += texture2D(uTexture, vR) * 0.35294117; 390 gl_FragColor = sum; 391 } 392` 393); 394 395const copyShader = compileShader( 396 gl.FRAGMENT_SHADER, 397 ` 398 precision mediump float; 399 precision mediump sampler2D; 400 401 varying highp vec2 vUv; 402 uniform sampler2D uTexture; 403 404 void main () { 405 gl_FragColor = texture2D(uTexture, vUv); 406 } 407` 408); 409 410const clearShader = compileShader( 411 gl.FRAGMENT_SHADER, 412 ` 413 precision mediump float; 414 precision mediump sampler2D; 415 416 varying highp vec2 vUv; 417 uniform sampler2D uTexture; 418 uniform float value; 419 420 void main () { 421 gl_FragColor = value * texture2D(uTexture, vUv); 422 } 423` 424); 425 426const colorShader = compileShader( 427 gl.FRAGMENT_SHADER, 428 ` 429 precision mediump float; 430 431 uniform vec4 color; 432 433 void main () { 434 gl_FragColor = color; 435 } 436` 437); 438 439const checkerboardShader = compileShader( 440 gl.FRAGMENT_SHADER, 441 ` 442 precision highp float; 443 precision highp sampler2D; 444 445 varying vec2 vUv; 446 uniform sampler2D uTexture; 447 uniform float aspectRatio; 448 449 #define SCALE 25.0 450 451 void main () { 452 vec2 uv = floor(vUv * SCALE * vec2(aspectRatio, 1.0)); 453 float v = mod(uv.x + uv.y, 2.0); 454 v = v * 0.1 + 0.8; 455 gl_FragColor = vec4(vec3(v), 1.0); 456 } 457` 458); 459 460const displayShaderSource = ` 461 precision highp float; 462 precision highp sampler2D; 463 464 varying vec2 vUv; 465 varying vec2 vL; 466 varying vec2 vR; 467 varying vec2 vT; 468 varying vec2 vB; 469 uniform sampler2D uTexture; 470 uniform sampler2D uBloom; 471 uniform sampler2D uSunrays; 472 uniform sampler2D uDithering; 473 uniform vec2 ditherScale; 474 uniform vec2 texelSize; 475 476 vec3 linearToGamma (vec3 color) { 477 color = max(color, vec3(0)); 478 return max(1.055 * pow(color, vec3(0.416666667)) - 0.055, vec3(0)); 479 } 480 481 void main () { 482 vec3 c = texture2D(uTexture, vUv).rgb; 483 484 #ifdef SHADING 485 vec3 lc = texture2D(uTexture, vL).rgb; 486 vec3 rc = texture2D(uTexture, vR).rgb; 487 vec3 tc = texture2D(uTexture, vT).rgb; 488 vec3 bc = texture2D(uTexture, vB).rgb; 489 490 float dx = length(rc) - length(lc); 491 float dy = length(tc) - length(bc); 492 493 vec3 n = normalize(vec3(dx, dy, length(texelSize))); 494 vec3 l = vec3(0.0, 0.0, 1.0); 495 496 float diffuse = clamp(dot(n, l) + 0.7, 0.7, 1.0); 497 c *= diffuse; 498 #endif 499 500 #ifdef BLOOM 501 vec3 bloom = texture2D(uBloom, vUv).rgb; 502 #endif 503 504 #ifdef SUNRAYS 505 float sunrays = texture2D(uSunrays, vUv).r; 506 c *= sunrays; 507 #ifdef BLOOM 508 bloom *= sunrays; 509 #endif 510 #endif 511 512 #ifdef BLOOM 513 float noise = texture2D(uDithering, vUv * ditherScale).r; 514 noise = noise * 2.0 - 1.0; 515 bloom += noise / 255.0; 516 bloom = linearToGamma(bloom); 517 c += bloom; 518 #endif 519 520 float a = max(c.r, max(c.g, c.b)); 521 gl_FragColor = vec4(c, a); 522 } 523`; 524 525const bloomPrefilterShader = compileShader( 526 gl.FRAGMENT_SHADER, 527 ` 528 precision mediump float; 529 precision mediump sampler2D; 530 531 varying vec2 vUv; 532 uniform sampler2D uTexture; 533 uniform vec3 curve; 534 uniform float threshold; 535 536 void main () { 537 vec3 c = texture2D(uTexture, vUv).rgb; 538 float br = max(c.r, max(c.g, c.b)); 539 float rq = clamp(br - curve.x, 0.0, curve.y); 540 rq = curve.z * rq * rq; 541 c *= max(rq, br - threshold) / max(br, 0.0001); 542 gl_FragColor = vec4(c, 0.0); 543 } 544` 545); 546 547const bloomBlurShader = compileShader( 548 gl.FRAGMENT_SHADER, 549 ` 550 precision mediump float; 551 precision mediump sampler2D; 552 553 varying vec2 vL; 554 varying vec2 vR; 555 varying vec2 vT; 556 varying vec2 vB; 557 uniform sampler2D uTexture; 558 559 void main () { 560 vec4 sum = vec4(0.0); 561 sum += texture2D(uTexture, vL); 562 sum += texture2D(uTexture, vR); 563 sum += texture2D(uTexture, vT); 564 sum += texture2D(uTexture, vB); 565 sum *= 0.25; 566 gl_FragColor = sum; 567 } 568` 569); 570 571const bloomFinalShader = compileShader( 572 gl.FRAGMENT_SHADER, 573 ` 574 precision mediump float; 575 precision mediump sampler2D; 576 577 varying vec2 vL; 578 varying vec2 vR; 579 varying vec2 vT; 580 varying vec2 vB; 581 uniform sampler2D uTexture; 582 uniform float intensity; 583 584 void main () { 585 vec4 sum = vec4(0.0); 586 sum += texture2D(uTexture, vL); 587 sum += texture2D(uTexture, vR); 588 sum += texture2D(uTexture, vT); 589 sum += texture2D(uTexture, vB); 590 sum *= 0.25; 591 gl_FragColor = sum * intensity; 592 } 593` 594); 595 596const sunraysMaskShader = compileShader( 597 gl.FRAGMENT_SHADER, 598 ` 599 precision highp float; 600 precision highp sampler2D; 601 602 varying vec2 vUv; 603 uniform sampler2D uTexture; 604 605 void main () { 606 vec4 c = texture2D(uTexture, vUv); 607 float br = max(c.r, max(c.g, c.b)); 608 c.a = 1.0 - min(max(br * 20.0, 0.0), 0.8); 609 gl_FragColor = c; 610 } 611` 612); 613 614const sunraysShader = compileShader( 615 gl.FRAGMENT_SHADER, 616 ` 617 precision highp float; 618 precision highp sampler2D; 619 620 varying vec2 vUv; 621 uniform sampler2D uTexture; 622 uniform float weight; 623 624 #define ITERATIONS 16 625 626 void main () { 627 float Density = 0.3; 628 float Decay = 0.95; 629 float Exposure = 0.7; 630 631 vec2 coord = vUv; 632 vec2 dir = vUv - 0.5; 633 634 dir *= 1.0 / float(ITERATIONS) * Density; 635 float illuminationDecay = 1.0; 636 637 float color = texture2D(uTexture, vUv).a; 638 639 for (int i = 0; i < ITERATIONS; i++) 640 { 641 coord -= dir; 642 float col = texture2D(uTexture, coord).a; 643 color += col * illuminationDecay * weight; 644 illuminationDecay *= Decay; 645 } 646 647 gl_FragColor = vec4(color * Exposure, 0.0, 0.0, 1.0); 648 } 649` 650); 651 652const splatShader = compileShader( 653 gl.FRAGMENT_SHADER, 654 ` 655 precision highp float; 656 precision highp sampler2D; 657 658 varying vec2 vUv; 659 uniform sampler2D uTarget; 660 uniform float aspectRatio; 661 uniform vec3 color; 662 uniform vec2 point; 663 uniform float radius; 664 665 void main () { 666 vec2 p = vUv - point.xy; 667 p.x *= aspectRatio; 668 vec3 splat = exp(-dot(p, p) / radius) * color; 669 vec3 base = texture2D(uTarget, vUv).xyz; 670 gl_FragColor = vec4(base + splat, 1.0); 671 } 672` 673); 674 675const advectionShader = compileShader( 676 gl.FRAGMENT_SHADER, 677 ` 678 precision highp float; 679 precision highp sampler2D; 680 681 varying vec2 vUv; 682 uniform sampler2D uVelocity; 683 uniform sampler2D uSource; 684 uniform vec2 texelSize; 685 uniform vec2 dyeTexelSize; 686 uniform float dt; 687 uniform float dissipation; 688 689 vec4 bilerp (sampler2D sam, vec2 uv, vec2 tsize) { 690 vec2 st = uv / tsize - 0.5; 691 692 vec2 iuv = floor(st); 693 vec2 fuv = fract(st); 694 695 vec4 a = texture2D(sam, (iuv + vec2(0.5, 0.5)) * tsize); 696 vec4 b = texture2D(sam, (iuv + vec2(1.5, 0.5)) * tsize); 697 vec4 c = texture2D(sam, (iuv + vec2(0.5, 1.5)) * tsize); 698 vec4 d = texture2D(sam, (iuv + vec2(1.5, 1.5)) * tsize); 699 700 return mix(mix(a, b, fuv.x), mix(c, d, fuv.x), fuv.y); 701 } 702 703 void main () { 704 #ifdef MANUAL_FILTERING 705 vec2 coord = vUv - dt * bilerp(uVelocity, vUv, texelSize).xy * texelSize; 706 vec4 result = bilerp(uSource, coord, dyeTexelSize); 707 #else 708 vec2 coord = vUv - dt * texture2D(uVelocity, vUv).xy * texelSize; 709 vec4 result = texture2D(uSource, coord); 710 #endif 711 float decay = 1.0 + dissipation * dt; 712 gl_FragColor = result / decay; 713 }`, 714 ext.supportLinearFiltering ? null : ['MANUAL_FILTERING'] 715); 716 717const divergenceShader = compileShader( 718 gl.FRAGMENT_SHADER, 719 ` 720 precision mediump float; 721 precision mediump sampler2D; 722 723 varying highp vec2 vUv; 724 varying highp vec2 vL; 725 varying highp vec2 vR; 726 varying highp vec2 vT; 727 varying highp vec2 vB; 728 uniform sampler2D uVelocity; 729 730 void main () { 731 float L = texture2D(uVelocity, vL).x; 732 float R = texture2D(uVelocity, vR).x; 733 float T = texture2D(uVelocity, vT).y; 734 float B = texture2D(uVelocity, vB).y; 735 736 vec2 C = texture2D(uVelocity, vUv).xy; 737 if (vL.x < 0.0) { L = -C.x; } 738 if (vR.x > 1.0) { R = -C.x; } 739 if (vT.y > 1.0) { T = -C.y; } 740 if (vB.y < 0.0) { B = -C.y; } 741 742 float div = 0.5 * (R - L + T - B); 743 gl_FragColor = vec4(div, 0.0, 0.0, 1.0); 744 } 745` 746); 747 748const curlShader = compileShader( 749 gl.FRAGMENT_SHADER, 750 ` 751 precision mediump float; 752 precision mediump sampler2D; 753 754 varying highp vec2 vUv; 755 varying highp vec2 vL; 756 varying highp vec2 vR; 757 varying highp vec2 vT; 758 varying highp vec2 vB; 759 uniform sampler2D uVelocity; 760 761 void main () { 762 float L = texture2D(uVelocity, vL).y; 763 float R = texture2D(uVelocity, vR).y; 764 float T = texture2D(uVelocity, vT).x; 765 float B = texture2D(uVelocity, vB).x; 766 float vorticity = R - L - T + B; 767 gl_FragColor = vec4(0.5 * vorticity, 0.0, 0.0, 1.0); 768 } 769` 770); 771 772const vorticityShader = compileShader( 773 gl.FRAGMENT_SHADER, 774 ` 775 precision highp float; 776 precision highp sampler2D; 777 778 varying vec2 vUv; 779 varying vec2 vL; 780 varying vec2 vR; 781 varying vec2 vT; 782 varying vec2 vB; 783 uniform sampler2D uVelocity; 784 uniform sampler2D uCurl; 785 uniform float curl; 786 uniform float dt; 787 788 void main () { 789 float L = texture2D(uCurl, vL).x; 790 float R = texture2D(uCurl, vR).x; 791 float T = texture2D(uCurl, vT).x; 792 float B = texture2D(uCurl, vB).x; 793 float C = texture2D(uCurl, vUv).x; 794 795 vec2 force = 0.5 * vec2(abs(T) - abs(B), abs(R) - abs(L)); 796 force /= length(force) + 0.0001; 797 force *= curl * C; 798 force.y *= -1.0; 799 800 vec2 velocity = texture2D(uVelocity, vUv).xy; 801 velocity += force * dt; 802 velocity = min(max(velocity, -1000.0), 1000.0); 803 gl_FragColor = vec4(velocity, 0.0, 1.0); 804 } 805` 806); 807 808const pressureShader = compileShader( 809 gl.FRAGMENT_SHADER, 810 ` 811 precision mediump float; 812 precision mediump sampler2D; 813 814 varying highp vec2 vUv; 815 varying highp vec2 vL; 816 varying highp vec2 vR; 817 varying highp vec2 vT; 818 varying highp vec2 vB; 819 uniform sampler2D uPressure; 820 uniform sampler2D uDivergence; 821 822 void main () { 823 float L = texture2D(uPressure, vL).x; 824 float R = texture2D(uPressure, vR).x; 825 float T = texture2D(uPressure, vT).x; 826 float B = texture2D(uPressure, vB).x; 827 float C = texture2D(uPressure, vUv).x; 828 float divergence = texture2D(uDivergence, vUv).x; 829 float pressure = (L + R + B + T - divergence) * 0.25; 830 gl_FragColor = vec4(pressure, 0.0, 0.0, 1.0); 831 } 832` 833); 834 835const gradientSubtractShader = compileShader( 836 gl.FRAGMENT_SHADER, 837 ` 838 precision mediump float; 839 precision mediump sampler2D; 840 841 varying highp vec2 vUv; 842 varying highp vec2 vL; 843 varying highp vec2 vR; 844 varying highp vec2 vT; 845 varying highp vec2 vB; 846 uniform sampler2D uPressure; 847 uniform sampler2D uVelocity; 848 849 void main () { 850 float L = texture2D(uPressure, vL).x; 851 float R = texture2D(uPressure, vR).x; 852 float T = texture2D(uPressure, vT).x; 853 float B = texture2D(uPressure, vB).x; 854 vec2 velocity = texture2D(uVelocity, vUv).xy; 855 velocity.xy -= vec2(R - L, T - B); 856 gl_FragColor = vec4(velocity, 0.0, 1.0); 857 } 858` 859); 860 861const blit = (() => { 862 gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer()); 863 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, -1, 1, 1, 1, 1, -1]), gl.STATIC_DRAW); 864 gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, gl.createBuffer()); 865 gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array([0, 1, 2, 0, 2, 3]), gl.STATIC_DRAW); 866 gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0); 867 gl.enableVertexAttribArray(0); 868 869 return (target, clear = false) => { 870 if (target == null) { 871 gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight); 872 gl.bindFramebuffer(gl.FRAMEBUFFER, null); 873 } else { 874 gl.viewport(0, 0, target.width, target.height); 875 gl.bindFramebuffer(gl.FRAMEBUFFER, target.fbo); 876 } 877 if (clear) { 878 gl.clearColor(0.0, 0.0, 0.0, 1.0); 879 gl.clear(gl.COLOR_BUFFER_BIT); 880 } 881 // CHECK_FRAMEBUFFER_STATUS(); 882 gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0); 883 }; 884})(); 885 886function CHECK_FRAMEBUFFER_STATUS() { 887 let status = gl.checkFramebufferStatus(gl.FRAMEBUFFER); 888 if (status != gl.FRAMEBUFFER_COMPLETE) console.trace('Framebuffer error: ' + status); 889} 890 891let dye; 892let velocity; 893let divergence; 894let curl; 895let pressure; 896let bloom; 897let bloomFramebuffers = []; 898let sunrays; 899let sunraysTemp; 900 901let ditheringTexture = createTextureAsync('LDR_LLL1_0.png'); 902 903const blurProgram = new Program(blurVertexShader, blurShader); 904const copyProgram = new Program(baseVertexShader, copyShader); 905const clearProgram = new Program(baseVertexShader, clearShader); 906const colorProgram = new Program(baseVertexShader, colorShader); 907const checkerboardProgram = new Program(baseVertexShader, checkerboardShader); 908const bloomPrefilterProgram = new Program(baseVertexShader, bloomPrefilterShader); 909const bloomBlurProgram = new Program(baseVertexShader, bloomBlurShader); 910const bloomFinalProgram = new Program(baseVertexShader, bloomFinalShader); 911const sunraysMaskProgram = new Program(baseVertexShader, sunraysMaskShader); 912const sunraysProgram = new Program(baseVertexShader, sunraysShader); 913const splatProgram = new Program(baseVertexShader, splatShader); 914const advectionProgram = new Program(baseVertexShader, advectionShader); 915const divergenceProgram = new Program(baseVertexShader, divergenceShader); 916const curlProgram = new Program(baseVertexShader, curlShader); 917const vorticityProgram = new Program(baseVertexShader, vorticityShader); 918const pressureProgram = new Program(baseVertexShader, pressureShader); 919const gradienSubtractProgram = new Program(baseVertexShader, gradientSubtractShader); 920 921const displayMaterial = new Material(baseVertexShader, displayShaderSource); 922 923function initFramebuffers() { 924 let simRes = getResolution(config.SIM_RESOLUTION); 925 let dyeRes = getResolution(config.DYE_RESOLUTION); 926 927 const texType = ext.halfFloatTexType; 928 const rgba = ext.formatRGBA; 929 const rg = ext.formatRG; 930 const r = ext.formatR; 931 const filtering = ext.supportLinearFiltering ? gl.LINEAR : gl.NEAREST; 932 933 gl.disable(gl.BLEND); 934 935 if (dye == null) 936 dye = createDoubleFBO( 937 dyeRes.width, 938 dyeRes.height, 939 rgba.internalFormat, 940 rgba.format, 941 texType, 942 filtering 943 ); 944 else 945 dye = resizeDoubleFBO( 946 dye, 947 dyeRes.width, 948 dyeRes.height, 949 rgba.internalFormat, 950 rgba.format, 951 texType, 952 filtering 953 ); 954 955 if (velocity == null) 956 velocity = createDoubleFBO( 957 simRes.width, 958 simRes.height, 959 rg.internalFormat, 960 rg.format, 961 texType, 962 filtering 963 ); 964 else 965 velocity = resizeDoubleFBO( 966 velocity, 967 simRes.width, 968 simRes.height, 969 rg.internalFormat, 970 rg.format, 971 texType, 972 filtering 973 ); 974 975 divergence = createFBO( 976 simRes.width, 977 simRes.height, 978 r.internalFormat, 979 r.format, 980 texType, 981 gl.NEAREST 982 ); 983 curl = createFBO(simRes.width, simRes.height, r.internalFormat, r.format, texType, gl.NEAREST); 984 pressure = createDoubleFBO( 985 simRes.width, 986 simRes.height, 987 r.internalFormat, 988 r.format, 989 texType, 990 gl.NEAREST 991 ); 992 993 initBloomFramebuffers(); 994 initSunraysFramebuffers(); 995} 996 997function initBloomFramebuffers() { 998 let res = getResolution(config.BLOOM_RESOLUTION); 999 1000 const texType = ext.halfFloatTexType; 1001 const rgba = ext.formatRGBA; 1002 const filtering = ext.supportLinearFiltering ? gl.LINEAR : gl.NEAREST; 1003 1004 bloom = createFBO(res.width, res.height, rgba.internalFormat, rgba.format, texType, filtering); 1005 1006 bloomFramebuffers.length = 0; 1007 for (let i = 0; i < config.BLOOM_ITERATIONS; i++) { 1008 let width = res.width >> (i + 1); 1009 let height = res.height >> (i + 1); 1010 1011 if (width < 2 || height < 2) break; 1012 1013 let fbo = createFBO(width, height, rgba.internalFormat, rgba.format, texType, filtering); 1014 bloomFramebuffers.push(fbo); 1015 } 1016} 1017 1018function initSunraysFramebuffers() { 1019 let res = getResolution(config.SUNRAYS_RESOLUTION); 1020 1021 const texType = ext.halfFloatTexType; 1022 const r = ext.formatR; 1023 const filtering = ext.supportLinearFiltering ? gl.LINEAR : gl.NEAREST; 1024 1025 sunrays = createFBO(res.width, res.height, r.internalFormat, r.format, texType, filtering); 1026 sunraysTemp = createFBO(res.width, res.height, r.internalFormat, r.format, texType, filtering); 1027} 1028 1029function createFBO(w, h, internalFormat, format, type, param) { 1030 gl.activeTexture(gl.TEXTURE0); 1031 let texture = gl.createTexture(); 1032 gl.bindTexture(gl.TEXTURE_2D, texture); 1033 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, param); 1034 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, param); 1035 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 1036 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); 1037 gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, w, h, 0, format, type, null); 1038 1039 let fbo = gl.createFramebuffer(); 1040 gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); 1041 gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0); 1042 gl.viewport(0, 0, w, h); 1043 gl.clear(gl.COLOR_BUFFER_BIT); 1044 1045 let texelSizeX = 1.0 / w; 1046 let texelSizeY = 1.0 / h; 1047 1048 return { 1049 texture, 1050 fbo, 1051 width: w, 1052 height: h, 1053 texelSizeX, 1054 texelSizeY, 1055 attach(id) { 1056 gl.activeTexture(gl.TEXTURE0 + id); 1057 gl.bindTexture(gl.TEXTURE_2D, texture); 1058 return id; 1059 } 1060 }; 1061} 1062 1063function createDoubleFBO(w, h, internalFormat, format, type, param) { 1064 let fbo1 = createFBO(w, h, internalFormat, format, type, param); 1065 let fbo2 = createFBO(w, h, internalFormat, format, type, param); 1066 1067 return { 1068 width: w, 1069 height: h, 1070 texelSizeX: fbo1.texelSizeX, 1071 texelSizeY: fbo1.texelSizeY, 1072 get read() { 1073 return fbo1; 1074 }, 1075 set read(value) { 1076 fbo1 = value; 1077 }, 1078 get write() { 1079 return fbo2; 1080 }, 1081 set write(value) { 1082 fbo2 = value; 1083 }, 1084 swap() { 1085 let temp = fbo1; 1086 fbo1 = fbo2; 1087 fbo2 = temp; 1088 } 1089 }; 1090} 1091 1092function resizeFBO(target, w, h, internalFormat, format, type, param) { 1093 let newFBO = createFBO(w, h, internalFormat, format, type, param); 1094 copyProgram.bind(); 1095 gl.uniform1i(copyProgram.uniforms.uTexture, target.attach(0)); 1096 blit(newFBO); 1097 return newFBO; 1098} 1099 1100function resizeDoubleFBO(target, w, h, internalFormat, format, type, param) { 1101 if (target.width == w && target.height == h) return target; 1102 target.read = resizeFBO(target.read, w, h, internalFormat, format, type, param); 1103 target.write = createFBO(w, h, internalFormat, format, type, param); 1104 target.width = w; 1105 target.height = h; 1106 target.texelSizeX = 1.0 / w; 1107 target.texelSizeY = 1.0 / h; 1108 return target; 1109} 1110 1111function createTextureAsync(url) { 1112 let texture = gl.createTexture(); 1113 gl.bindTexture(gl.TEXTURE_2D, texture); 1114 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); 1115 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); 1116 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); 1117 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT); 1118 gl.texImage2D( 1119 gl.TEXTURE_2D, 1120 0, 1121 gl.RGB, 1122 1, 1123 1, 1124 0, 1125 gl.RGB, 1126 gl.UNSIGNED_BYTE, 1127 new Uint8Array([255, 255, 255]) 1128 ); 1129 1130 let obj = { 1131 texture, 1132 width: 1, 1133 height: 1, 1134 attach(id) { 1135 gl.activeTexture(gl.TEXTURE0 + id); 1136 gl.bindTexture(gl.TEXTURE_2D, texture); 1137 return id; 1138 } 1139 }; 1140 1141 let image = new Image(); 1142 image.onload = () => { 1143 obj.width = image.width; 1144 obj.height = image.height; 1145 gl.bindTexture(gl.TEXTURE_2D, texture); 1146 gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image); 1147 }; 1148 image.src = url; 1149 1150 return obj; 1151} 1152 1153function updateKeywords() { 1154 let displayKeywords = []; 1155 if (config.SHADING) displayKeywords.push('SHADING'); 1156 if (config.BLOOM) displayKeywords.push('BLOOM'); 1157 if (config.SUNRAYS) displayKeywords.push('SUNRAYS'); 1158 displayMaterial.setKeywords(displayKeywords); 1159} 1160 1161updateKeywords(); 1162initFramebuffers(); 1163multipleSplats(25); 1164 1165let lastUpdateTime = Date.now(); 1166let colorUpdateTimer = 0.0; 1167update(); 1168 1169function update() { 1170 const dt = calcDeltaTime() * (config.RENDER_SPEED ?? 1.0); 1171 if (resizeCanvas()) initFramebuffers(); 1172 updateColors(dt); 1173 applyInputs(); 1174 if (!config.PAUSED) step(dt); 1175 render(null); 1176 requestAnimationFrame(update); 1177} 1178 1179function calcDeltaTime() { 1180 let now = Date.now(); 1181 let dt = (now - lastUpdateTime) / 1000; 1182 dt = Math.min(dt, 0.016666); 1183 lastUpdateTime = now; 1184 return dt; 1185} 1186 1187function resizeCanvas() { 1188 let width = scaleByPixelRatio(canvas.clientWidth); 1189 let height = scaleByPixelRatio(canvas.clientHeight); 1190 if (canvas.width != width || canvas.height != height) { 1191 canvas.width = width; 1192 canvas.height = height; 1193 return true; 1194 } 1195 return false; 1196} 1197 1198function updateColors(dt) { 1199 if (!config.COLORFUL) return; 1200 1201 colorUpdateTimer += dt * config.COLOR_UPDATE_SPEED; 1202 if (colorUpdateTimer >= 1) { 1203 colorUpdateTimer = wrap(colorUpdateTimer, 0, 1); 1204 pointers.forEach((p) => { 1205 p.color = generateColor(); 1206 }); 1207 } 1208} 1209 1210function applyInputs() { 1211 if (splatStack.length > 0) multipleSplats(splatStack.pop()); 1212 1213 pointers.forEach((p) => { 1214 if (p.moved) { 1215 p.moved = false; 1216 splatPointer(p); 1217 } 1218 }); 1219} 1220 1221function step(dt) { 1222 gl.disable(gl.BLEND); 1223 1224 curlProgram.bind(); 1225 gl.uniform2f(curlProgram.uniforms.texelSize, velocity.texelSizeX, velocity.texelSizeY); 1226 gl.uniform1i(curlProgram.uniforms.uVelocity, velocity.read.attach(0)); 1227 blit(curl); 1228 1229 vorticityProgram.bind(); 1230 gl.uniform2f(vorticityProgram.uniforms.texelSize, velocity.texelSizeX, velocity.texelSizeY); 1231 gl.uniform1i(vorticityProgram.uniforms.uVelocity, velocity.read.attach(0)); 1232 gl.uniform1i(vorticityProgram.uniforms.uCurl, curl.attach(1)); 1233 gl.uniform1f(vorticityProgram.uniforms.curl, config.CURL); 1234 gl.uniform1f(vorticityProgram.uniforms.dt, dt); 1235 blit(velocity.write); 1236 velocity.swap(); 1237 1238 divergenceProgram.bind(); 1239 gl.uniform2f(divergenceProgram.uniforms.texelSize, velocity.texelSizeX, velocity.texelSizeY); 1240 gl.uniform1i(divergenceProgram.uniforms.uVelocity, velocity.read.attach(0)); 1241 blit(divergence); 1242 1243 clearProgram.bind(); 1244 gl.uniform1i(clearProgram.uniforms.uTexture, pressure.read.attach(0)); 1245 gl.uniform1f(clearProgram.uniforms.value, config.PRESSURE); 1246 blit(pressure.write); 1247 pressure.swap(); 1248 1249 pressureProgram.bind(); 1250 gl.uniform2f(pressureProgram.uniforms.texelSize, velocity.texelSizeX, velocity.texelSizeY); 1251 gl.uniform1i(pressureProgram.uniforms.uDivergence, divergence.attach(0)); 1252 for (let i = 0; i < config.PRESSURE_ITERATIONS; i++) { 1253 gl.uniform1i(pressureProgram.uniforms.uPressure, pressure.read.attach(1)); 1254 blit(pressure.write); 1255 pressure.swap(); 1256 } 1257 1258 gradienSubtractProgram.bind(); 1259 gl.uniform2f(gradienSubtractProgram.uniforms.texelSize, velocity.texelSizeX, velocity.texelSizeY); 1260 gl.uniform1i(gradienSubtractProgram.uniforms.uPressure, pressure.read.attach(0)); 1261 gl.uniform1i(gradienSubtractProgram.uniforms.uVelocity, velocity.read.attach(1)); 1262 blit(velocity.write); 1263 velocity.swap(); 1264 1265 advectionProgram.bind(); 1266 gl.uniform2f(advectionProgram.uniforms.texelSize, velocity.texelSizeX, velocity.texelSizeY); 1267 if (!ext.supportLinearFiltering) 1268 gl.uniform2f(advectionProgram.uniforms.dyeTexelSize, velocity.texelSizeX, velocity.texelSizeY); 1269 let velocityId = velocity.read.attach(0); 1270 gl.uniform1i(advectionProgram.uniforms.uVelocity, velocityId); 1271 gl.uniform1i(advectionProgram.uniforms.uSource, velocityId); 1272 gl.uniform1f(advectionProgram.uniforms.dt, dt); 1273 gl.uniform1f(advectionProgram.uniforms.dissipation, config.VELOCITY_DISSIPATION); 1274 blit(velocity.write); 1275 velocity.swap(); 1276 1277 if (!ext.supportLinearFiltering) 1278 gl.uniform2f(advectionProgram.uniforms.dyeTexelSize, dye.texelSizeX, dye.texelSizeY); 1279 gl.uniform1i(advectionProgram.uniforms.uVelocity, velocity.read.attach(0)); 1280 gl.uniform1i(advectionProgram.uniforms.uSource, dye.read.attach(1)); 1281 gl.uniform1f(advectionProgram.uniforms.dissipation, config.DENSITY_DISSIPATION); 1282 blit(dye.write); 1283 dye.swap(); 1284} 1285 1286function render(target) { 1287 if (config.BLOOM) applyBloom(dye.read, bloom); 1288 if (config.SUNRAYS) { 1289 applySunrays(dye.read, dye.write, sunrays); 1290 blur(sunrays, sunraysTemp, 1); 1291 } 1292 1293 if (target == null || !config.TRANSPARENT) { 1294 gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); 1295 gl.enable(gl.BLEND); 1296 } else { 1297 gl.disable(gl.BLEND); 1298 } 1299 1300 if (!config.TRANSPARENT) drawColor(target, normalizeColor(config.BACK_COLOR)); 1301 if (target == null && config.TRANSPARENT) drawCheckerboard(target); 1302 drawDisplay(target); 1303} 1304 1305function drawColor(target, color) { 1306 colorProgram.bind(); 1307 gl.uniform4f(colorProgram.uniforms.color, color.r, color.g, color.b, 1); 1308 blit(target); 1309} 1310 1311function drawCheckerboard(target) { 1312 checkerboardProgram.bind(); 1313 gl.uniform1f(checkerboardProgram.uniforms.aspectRatio, canvas.width / canvas.height); 1314 blit(target); 1315} 1316 1317function drawDisplay(target) { 1318 let width = target == null ? gl.drawingBufferWidth : target.width; 1319 let height = target == null ? gl.drawingBufferHeight : target.height; 1320 1321 displayMaterial.bind(); 1322 if (config.SHADING) gl.uniform2f(displayMaterial.uniforms.texelSize, 1.0 / width, 1.0 / height); 1323 gl.uniform1i(displayMaterial.uniforms.uTexture, dye.read.attach(0)); 1324 if (config.BLOOM) { 1325 gl.uniform1i(displayMaterial.uniforms.uBloom, bloom.attach(1)); 1326 gl.uniform1i(displayMaterial.uniforms.uDithering, ditheringTexture.attach(2)); 1327 let scale = getTextureScale(ditheringTexture, width, height); 1328 gl.uniform2f(displayMaterial.uniforms.ditherScale, scale.x, scale.y); 1329 } 1330 if (config.SUNRAYS) gl.uniform1i(displayMaterial.uniforms.uSunrays, sunrays.attach(3)); 1331 blit(target); 1332} 1333 1334function applyBloom(source, destination) { 1335 if (bloomFramebuffers.length < 2) return; 1336 1337 let last = destination; 1338 1339 gl.disable(gl.BLEND); 1340 bloomPrefilterProgram.bind(); 1341 let knee = config.BLOOM_THRESHOLD * config.BLOOM_SOFT_KNEE + 0.0001; 1342 let curve0 = config.BLOOM_THRESHOLD - knee; 1343 let curve1 = knee * 2; 1344 let curve2 = 0.25 / knee; 1345 gl.uniform3f(bloomPrefilterProgram.uniforms.curve, curve0, curve1, curve2); 1346 gl.uniform1f(bloomPrefilterProgram.uniforms.threshold, config.BLOOM_THRESHOLD); 1347 gl.uniform1i(bloomPrefilterProgram.uniforms.uTexture, source.attach(0)); 1348 blit(last); 1349 1350 bloomBlurProgram.bind(); 1351 for (let i = 0; i < bloomFramebuffers.length; i++) { 1352 let dest = bloomFramebuffers[i]; 1353 gl.uniform2f(bloomBlurProgram.uniforms.texelSize, last.texelSizeX, last.texelSizeY); 1354 gl.uniform1i(bloomBlurProgram.uniforms.uTexture, last.attach(0)); 1355 blit(dest); 1356 last = dest; 1357 } 1358 1359 gl.blendFunc(gl.ONE, gl.ONE); 1360 gl.enable(gl.BLEND); 1361 1362 for (let i = bloomFramebuffers.length - 2; i >= 0; i--) { 1363 let baseTex = bloomFramebuffers[i]; 1364 gl.uniform2f(bloomBlurProgram.uniforms.texelSize, last.texelSizeX, last.texelSizeY); 1365 gl.uniform1i(bloomBlurProgram.uniforms.uTexture, last.attach(0)); 1366 gl.viewport(0, 0, baseTex.width, baseTex.height); 1367 blit(baseTex); 1368 last = baseTex; 1369 } 1370 1371 gl.disable(gl.BLEND); 1372 bloomFinalProgram.bind(); 1373 gl.uniform2f(bloomFinalProgram.uniforms.texelSize, last.texelSizeX, last.texelSizeY); 1374 gl.uniform1i(bloomFinalProgram.uniforms.uTexture, last.attach(0)); 1375 gl.uniform1f(bloomFinalProgram.uniforms.intensity, config.BLOOM_INTENSITY); 1376 blit(destination); 1377} 1378 1379function applySunrays(source, mask, destination) { 1380 gl.disable(gl.BLEND); 1381 sunraysMaskProgram.bind(); 1382 gl.uniform1i(sunraysMaskProgram.uniforms.uTexture, source.attach(0)); 1383 blit(mask); 1384 1385 sunraysProgram.bind(); 1386 gl.uniform1f(sunraysProgram.uniforms.weight, config.SUNRAYS_WEIGHT); 1387 gl.uniform1i(sunraysProgram.uniforms.uTexture, mask.attach(0)); 1388 blit(destination); 1389} 1390 1391function blur(target, temp, iterations) { 1392 blurProgram.bind(); 1393 for (let i = 0; i < iterations; i++) { 1394 gl.uniform2f(blurProgram.uniforms.texelSize, target.texelSizeX, 0.0); 1395 gl.uniform1i(blurProgram.uniforms.uTexture, target.attach(0)); 1396 blit(temp); 1397 1398 gl.uniform2f(blurProgram.uniforms.texelSize, 0.0, target.texelSizeY); 1399 gl.uniform1i(blurProgram.uniforms.uTexture, temp.attach(0)); 1400 blit(target); 1401 } 1402} 1403 1404function splatPointer(pointer) { 1405 let dx = pointer.deltaX * config.SPLAT_FORCE * 5; 1406 let dy = pointer.deltaY * config.SPLAT_FORCE * 5; 1407 splat(pointer.texcoordX, pointer.texcoordY, dx, dy, pointer.color); 1408} 1409 1410function multipleSplats(amount) { 1411 for (let i = 0; i < amount; i++) { 1412 const color = generateColor(); 1413 color.r *= 10.0; 1414 color.g *= 10.0; 1415 color.b *= 10.0; 1416 const x = Math.random(); 1417 const y = Math.random() < 0.5 ? 0.8 : 0.2; 1418 const dx = 100 * (Math.random() - 0.5); 1419 const dy = 1000 * (Math.random() - 0.5); 1420 splat(x, y, dx, dy, color); 1421 } 1422} 1423 1424function splat(x, y, dx, dy, color) { 1425 splatProgram.bind(); 1426 gl.uniform1i(splatProgram.uniforms.uTarget, velocity.read.attach(0)); 1427 gl.uniform1f(splatProgram.uniforms.aspectRatio, canvas.width / canvas.height); 1428 gl.uniform2f(splatProgram.uniforms.point, x, y); 1429 gl.uniform3f(splatProgram.uniforms.color, dx, dy, 0.0); 1430 gl.uniform1f(splatProgram.uniforms.radius, correctRadius(config.SPLAT_RADIUS / 100.0)); 1431 blit(velocity.write); 1432 velocity.swap(); 1433 1434 gl.uniform1i(splatProgram.uniforms.uTarget, dye.read.attach(0)); 1435 gl.uniform3f(splatProgram.uniforms.color, color.r, color.g, color.b); 1436 blit(dye.write); 1437 dye.swap(); 1438} 1439 1440function correctRadius(radius) { 1441 let aspectRatio = canvas.width / canvas.height; 1442 if (aspectRatio > 1) radius *= aspectRatio; 1443 return radius; 1444} 1445 1446canvas.addEventListener('mousedown', (e) => { 1447 let posX = scaleByPixelRatio(e.offsetX); 1448 let posY = scaleByPixelRatio(e.offsetY); 1449 let pointer = pointers.find((p) => p.id == -1); 1450 if (pointer == null) pointer = new PointerPrototype(); 1451 updatePointerDownData(pointer, -1, posX, posY); 1452}); 1453 1454document.body.addEventListener('mousemove', (e) => { 1455 let pointer = pointers[0]; 1456 // if (!pointer.down) return; 1457 let posX = scaleByPixelRatio(e.offsetX); 1458 let posY = scaleByPixelRatio(e.offsetY); 1459 updatePointerMoveData(pointer, posX, posY); 1460}); 1461 1462window.addEventListener('mouseup', () => { 1463 updatePointerUpData(pointers[0]); 1464}); 1465 1466document.body.addEventListener('touchstart', (e) => { 1467 e.preventDefault(); 1468 const touches = e.targetTouches; 1469 while (touches.length >= pointers.length) pointers.push(new PointerPrototype()); 1470 for (let i = 0; i < touches.length; i++) { 1471 let posX = scaleByPixelRatio(touches[i].pageX); 1472 let posY = scaleByPixelRatio(touches[i].pageY); 1473 updatePointerDownData(pointers[i + 1], touches[i].identifier, posX, posY); 1474 } 1475}); 1476 1477document.body.addEventListener( 1478 'touchmove', 1479 (e) => { 1480 e.preventDefault(); 1481 const touches = e.targetTouches; 1482 for (let i = 0; i < touches.length; i++) { 1483 let pointer = pointers[i + 1]; 1484 if (!pointer.down) continue; 1485 let posX = scaleByPixelRatio(touches[i].pageX); 1486 let posY = scaleByPixelRatio(touches[i].pageY); 1487 updatePointerMoveData(pointer, posX, posY); 1488 } 1489 }, 1490 false 1491); 1492 1493document.body.addEventListener('touchend', (e) => { 1494 const touches = e.changedTouches; 1495 for (const element of touches) { 1496 let pointer = pointers.find((p) => p.id == element.identifier); 1497 if (pointer == null) continue; 1498 updatePointerUpData(pointer); 1499 } 1500}); 1501 1502// window.addEventListener('keydown', (e) => { 1503// if (e.code === 'KeyP') config.PAUSED = !config.PAUSED; 1504// if (e.key === ' ') splatStack.push(parseInt(Math.random() * 20) + 5); 1505// }); 1506 1507function updatePointerDownData(pointer, id, posX, posY) { 1508 pointer.id = id; 1509 pointer.down = true; 1510 pointer.moved = false; 1511 pointer.texcoordX = posX / canvas.width; 1512 pointer.texcoordY = 1.0 - posY / canvas.height; 1513 pointer.prevTexcoordX = pointer.texcoordX; 1514 pointer.prevTexcoordY = pointer.texcoordY; 1515 pointer.deltaX = 0; 1516 pointer.deltaY = 0; 1517 pointer.color = generateColor(); 1518} 1519 1520function updatePointerMoveData(pointer, posX, posY) { 1521 pointer.prevTexcoordX = pointer.texcoordX; 1522 pointer.prevTexcoordY = pointer.texcoordY; 1523 pointer.texcoordX = posX / canvas.width; 1524 pointer.texcoordY = 1.0 - posY / canvas.height; 1525 pointer.deltaX = correctDeltaX(pointer.texcoordX - pointer.prevTexcoordX); 1526 pointer.deltaY = correctDeltaY(pointer.texcoordY - pointer.prevTexcoordY); 1527 pointer.moved = Math.abs(pointer.deltaX) > 0 || Math.abs(pointer.deltaY) > 0; 1528} 1529 1530function updatePointerUpData(pointer) { 1531 pointer.down = false; 1532} 1533 1534function correctDeltaX(delta) { 1535 let aspectRatio = canvas.width / canvas.height; 1536 if (aspectRatio < 1) delta *= aspectRatio; 1537 return delta; 1538} 1539 1540function correctDeltaY(delta) { 1541 let aspectRatio = canvas.width / canvas.height; 1542 if (aspectRatio > 1) delta /= aspectRatio; 1543 return delta; 1544} 1545 1546function generateColor() { 1547 let c = HSVtoRGB(Math.random() * (config.END_HUE - config.START_HUE) + config.START_HUE, 1.0, 1.0); 1548 c.r *= 0.15; 1549 c.g *= 0.15; 1550 c.b *= 0.15; 1551 return c; 1552} 1553 1554function HSVtoRGB(h, s, v) { 1555 let r, g, b, i, f, p, q, t; 1556 i = Math.floor(h * 6); 1557 f = h * 6 - i; 1558 p = v * (1 - s); 1559 q = v * (1 - f * s); 1560 t = v * (1 - (1 - f) * s); 1561 1562 switch (i % 6) { 1563 case 0: 1564 (r = v), (g = t), (b = p); 1565 break; 1566 case 1: 1567 (r = q), (g = v), (b = p); 1568 break; 1569 case 2: 1570 (r = p), (g = v), (b = t); 1571 break; 1572 case 3: 1573 (r = p), (g = q), (b = v); 1574 break; 1575 case 4: 1576 (r = t), (g = p), (b = v); 1577 break; 1578 case 5: 1579 (r = v), (g = p), (b = q); 1580 break; 1581 } 1582 1583 return { 1584 r, 1585 g, 1586 b 1587 }; 1588} 1589 1590function normalizeColor(input) { 1591 let output = { 1592 r: input.r / 255, 1593 g: input.g / 255, 1594 b: input.b / 255 1595 }; 1596 return output; 1597} 1598 1599function wrap(value, min, max) { 1600 let range = max - min; 1601 if (range == 0) return min; 1602 return ((value - min) % range) + min; 1603} 1604 1605function getResolution(resolution) { 1606 let aspectRatio = gl.drawingBufferWidth / gl.drawingBufferHeight; 1607 if (aspectRatio < 1) aspectRatio = 1.0 / aspectRatio; 1608 1609 let min = Math.round(resolution); 1610 let max = Math.round(resolution * aspectRatio); 1611 1612 if (gl.drawingBufferWidth > gl.drawingBufferHeight) return { width: max, height: min }; 1613 else return { width: min, height: max }; 1614} 1615 1616function getTextureScale(texture, width, height) { 1617 return { 1618 x: width / texture.width, 1619 y: height / texture.height 1620 }; 1621} 1622 1623function scaleByPixelRatio(input) { 1624 let pixelRatio = window.devicePixelRatio || 1; 1625 return Math.floor(input * pixelRatio); 1626} 1627 1628function hashCode(s) { 1629 if (s.length == 0) return 0; 1630 let hash = 0; 1631 for (let i = 0; i < s.length; i++) { 1632 hash = (hash << 5) - hash + s.charCodeAt(i); 1633 hash |= 0; // Convert to 32bit integer 1634 } 1635 return hash; 1636} 1637 1638 1639setInterval(() => { 1640 multipleSplats(5); 1641}, 500)