web page ready for production

This commit is contained in:
Pavel Dobryakov
2017-08-27 21:47:38 +03:00
parent e5de34ac9b
commit 289d455a1f
5 changed files with 38 additions and 642 deletions

21
LICENSE
View File

@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2017 Pavel Dobryakov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1 +0,0 @@
# WebGL-Fluid-Simulation

View File

@@ -71,6 +71,43 @@
</head> </head>
<body> <body>
<canvas></canvas> <canvas></canvas>
<script src="./script.js"></script> <script>
var canvas=document.getElementsByTagName("canvas")[0];canvas.width=canvas.clientWidth;canvas.height=canvas.clientHeight;var params={alpha:!1,depth:!1,stencil:!1,antialias:!1},gl=canvas.getContext("webgl2",params),isWebGL2=!!gl;isWebGL2||(gl=canvas.getContext("webgl",params)||canvas.getContext("experimental-webgl",params));gl.clearColor(0,0,0,1);gl.enable(gl.BLEND);var halfFloat=gl.getExtension("OES_texture_half_float"),support_linear_float=gl.getExtension("OES_texture_half_float_linear");
isWebGL2&&(gl.getExtension("EXT_color_buffer_float"),support_linear_float=gl.getExtension("OES_texture_float_linear"));var config={TEXTURE_DOWNSAMPLE:1,DENSITY_DISSIPATION:.98,VELOCITY_DISSIPATION:.99,PRESSURE_CONCENTRATION:.8,PRESSURE_ITERATIONS:25,CURL:30,SPLAT_RADIUS:.005},gui=new dat.GUI({width:270});gui.add(config,"TEXTURE_DOWNSAMPLE",{Full:0,Half:1,Quarter:2}).name("resolution").onFinishChange(initFramebuffers);gui.add(config,"DENSITY_DISSIPATION",.9,1).name("density diffusion");
gui.add(config,"VELOCITY_DISSIPATION",.9,1).name("velocity diffusion");gui.add(config,"PRESSURE_CONCENTRATION",0,1).name("jelly");gui.add(config,"PRESSURE_ITERATIONS",1,60).name("iterations");gui.add(config,"CURL",0,50).name("vorticity").step(1);gui.add(config,"SPLAT_RADIUS",1E-4,.01).name("splat radius");var github=gui.add({fun:function(){window.open("https://github.com/PavelDoGreat/WebGL-Fluid-Simulation")}},"fun").name("Github");github.__li.className="cr function bigFont";
github.__li.style.borderLeft="3px solid #8C8C8C";var githubIcon=document.createElement("span");github.domElement.parentElement.appendChild(githubIcon);githubIcon.className="icon github";var twitter=gui.add({fun:function(){window.open("https://twitter.com/PavelDoGreat")}},"fun").name("Twitter");twitter.__li.className="cr function bigFont";twitter.__li.style.borderLeft="3px solid #8C8C8C";var twitterIcon=document.createElement("span");twitter.domElement.parentElement.appendChild(twitterIcon);
twitterIcon.className="icon twitter";var GLProgram=function(a,b){this.uniforms={};this.program=gl.createProgram();gl.attachShader(this.program,a);gl.attachShader(this.program,b);gl.linkProgram(this.program);if(!gl.getProgramParameter(this.program,gl.LINK_STATUS))throw gl.getProgramInfoLog(this.program);a=gl.getProgramParameter(this.program,gl.ACTIVE_UNIFORMS);for(b=0;b<a;b++){var c=gl.getActiveUniform(this.program,b).name;this.uniforms[c]=gl.getUniformLocation(this.program,c)}};
GLProgram.prototype.bind=function(){gl.useProgram(this.program)};function compileShader(a,b){a=gl.createShader(a);gl.shaderSource(a,b);gl.compileShader(a);if(!gl.getShaderParameter(a,gl.COMPILE_STATUS))throw gl.getShaderInfoLog(a);return a}
var baseVertexShader=compileShader(gl.VERTEX_SHADER,"\n precision highp float;\n precision mediump sampler2D;\n\n attribute vec2 aPosition;\n varying vec2 vUv;\n varying vec2 vL;\n varying vec2 vR;\n varying vec2 vT;\n varying vec2 vB;\n uniform vec2 texelSize;\n\n void main () {\n vUv = aPosition * 0.5 + 0.5;\n vL = vUv - vec2(texelSize.x, 0.0);\n vR = vUv + vec2(texelSize.x, 0.0);\n vT = vUv + vec2(0.0, texelSize.y);\n vB = vUv - vec2(0.0, texelSize.y);\n gl_Position = vec4(aPosition, 0.0, 1.0);\n }\n"),clearShader=
compileShader(gl.FRAGMENT_SHADER,"\n precision highp float;\n precision mediump sampler2D;\n\n varying vec2 vUv;\n uniform sampler2D uTexture;\n uniform float value;\n\n void main () {\n gl_FragColor = value * texture2D(uTexture, vUv);\n }\n"),displayShader=compileShader(gl.FRAGMENT_SHADER,"\n precision highp float;\n precision mediump sampler2D;\n\n varying vec2 vUv;\n varying vec2 vL;\n varying vec2 vR;\n varying vec2 vT;\n varying vec2 vB;\n uniform sampler2D uTexture;\n\n void main () {\n gl_FragColor = texture2D(uTexture, vUv);\n }\n"),
splatShader=compileShader(gl.FRAGMENT_SHADER,"\n precision highp float;\n precision mediump sampler2D;\n\n varying vec2 vUv;\n uniform sampler2D uTarget;\n uniform float aspectRatio;\n uniform vec3 color;\n uniform vec2 point;\n uniform float radius;\n\n void main () {\n vec2 p = vUv - point.xy;\n p.x *= aspectRatio;\n vec3 splat = exp(-dot(p, p) / radius) * color;\n vec3 base = texture2D(uTarget, vUv).xyz;\n gl_FragColor = vec4(base + splat, 1.0);\n }\n"),
advectionManualFilteringShader=compileShader(gl.FRAGMENT_SHADER,"\n precision highp float;\n precision mediump sampler2D;\n\n varying vec2 vUv;\n uniform sampler2D uVelocity;\n uniform sampler2D uSource;\n uniform vec2 texelSize;\n uniform float dt;\n uniform float dissipation;\n\n vec4 bilerp (in sampler2D sam, in vec2 p) {\n vec4 st;\n st.xy = floor(p - 0.5) + 0.5;\n st.zw = st.xy + 1.0;\n vec4 uv = st * texelSize.xyxy;\n vec4 a = texture2D(sam, uv.xy);\n vec4 b = texture2D(sam, uv.zy);\n vec4 c = texture2D(sam, uv.xw);\n vec4 d = texture2D(sam, uv.zw);\n vec2 f = p - st.xy;\n return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);\n }\n\n void main () {\n vec2 coord = gl_FragCoord.xy - dt * texture2D(uVelocity, vUv).xy;\n gl_FragColor = dissipation * bilerp(uSource, coord);\n gl_FragColor.a = 1.0;\n }\n"),
advectionShader=compileShader(gl.FRAGMENT_SHADER,"\n precision highp float;\n precision mediump sampler2D;\n\n varying vec2 vUv;\n uniform sampler2D uVelocity;\n uniform sampler2D uSource;\n uniform vec2 texelSize;\n uniform float dt;\n uniform float dissipation;\n\n void main () {\n vec2 coord = vUv - dt * texture2D(uVelocity, vUv).xy * texelSize;\n gl_FragColor = dissipation * texture2D(uSource, coord);\n }\n"),divergenceShader=compileShader(gl.FRAGMENT_SHADER,
"\n precision highp float;\n precision mediump sampler2D;\n\n varying vec2 vUv;\n varying vec2 vL;\n varying vec2 vR;\n varying vec2 vT;\n varying vec2 vB;\n uniform sampler2D uVelocity;\n\n vec2 sampleVelocity (in vec2 uv) {\n vec2 multiplier = vec2(1.0, 1.0);\n if (uv.x < 0.0) { uv.x = 0.0; multiplier.x = -1.0; }\n if (uv.x > 1.0) { uv.x = 1.0; multiplier.x = -1.0; }\n if (uv.y < 0.0) { uv.y = 0.0; multiplier.y = -1.0; }\n if (uv.y > 1.0) { uv.y = 1.0; multiplier.y = -1.0; }\n return multiplier * texture2D(uVelocity, uv).xy;\n }\n\n void main () {\n float L = sampleVelocity(vL).x;\n float R = sampleVelocity(vR).x;\n float T = sampleVelocity(vT).y;\n float B = sampleVelocity(vB).y;\n float div = 0.5 * (R - L + T - B);\n gl_FragColor = vec4(div, 0.0, 0.0, 1.0);\n }\n"),
curlShader=compileShader(gl.FRAGMENT_SHADER,"\n precision highp float;\n precision mediump sampler2D;\n\n varying vec2 vUv;\n varying vec2 vL;\n varying vec2 vR;\n varying vec2 vT;\n varying vec2 vB;\n uniform sampler2D uVelocity;\n\n void main () {\n float L = texture2D(uVelocity, vL).y;\n float R = texture2D(uVelocity, vR).y;\n float T = texture2D(uVelocity, vT).x;\n float B = texture2D(uVelocity, vB).x;\n float vorticity = R - L - T + B;\n gl_FragColor = vec4(vorticity, 0.0, 0.0, 1.0);\n }\n"),
vorticityShader=compileShader(gl.FRAGMENT_SHADER,"\n precision highp float;\n precision mediump sampler2D;\n\n varying vec2 vUv;\n varying vec2 vL;\n varying vec2 vR;\n varying vec2 vT;\n varying vec2 vB;\n uniform sampler2D uVelocity;\n uniform sampler2D uCurl;\n uniform float curl;\n uniform float dt;\n\n void main () {\n float L = texture2D(uCurl, vL).y;\n float R = texture2D(uCurl, vR).y;\n float T = texture2D(uCurl, vT).x;\n float B = texture2D(uCurl, vB).x;\n float C = texture2D(uCurl, vUv).x;\n vec2 force = vec2(abs(T) - abs(B), abs(R) - abs(L));\n force *= 1.0 / length(force + 0.00001) * curl * C;\n vec2 vel = texture2D(uVelocity, vUv).xy;\n gl_FragColor = vec4(vel + force * dt, 0.0, 1.0);\n }\n"),
pressureShader=compileShader(gl.FRAGMENT_SHADER,"\n precision highp float;\n precision mediump sampler2D;\n\n varying vec2 vUv;\n varying vec2 vL;\n varying vec2 vR;\n varying vec2 vT;\n varying vec2 vB;\n uniform sampler2D uPressure;\n uniform sampler2D uDivergence;\n\n vec2 boundary (in vec2 uv) {\n uv = min(max(uv, 0.0), 1.0);\n return uv;\n }\n\n void main () {\n float L = texture2D(uPressure, boundary(vL)).x;\n float R = texture2D(uPressure, boundary(vR)).x;\n float T = texture2D(uPressure, boundary(vT)).x;\n float B = texture2D(uPressure, boundary(vB)).x;\n float C = texture2D(uPressure, vUv).x;\n float divergence = texture2D(uDivergence, vUv).x;\n float pressure = (L + R + B + T - divergence) * 0.25;\n gl_FragColor = vec4(pressure, 0.0, 0.0, 1.0);\n }\n"),
gradientSubtractShader=compileShader(gl.FRAGMENT_SHADER,"\n precision highp float;\n precision mediump sampler2D;\n\n varying vec2 vUv;\n varying vec2 vL;\n varying vec2 vR;\n varying vec2 vT;\n varying vec2 vB;\n uniform sampler2D uPressure;\n uniform sampler2D uVelocity;\n\n vec2 boundary (in vec2 uv) {\n uv = min(max(uv, 0.0), 1.0);\n return uv;\n }\n\n void main () {\n float L = texture2D(uPressure, boundary(vL)).x;\n float R = texture2D(uPressure, boundary(vR)).x;\n float T = texture2D(uPressure, boundary(vT)).x;\n float B = texture2D(uPressure, boundary(vB)).x;\n vec2 velocity = texture2D(uVelocity, vUv).xy;\n velocity.xy -= vec2(R - L, T - B);\n gl_FragColor = vec4(velocity, 0.0, 1.0);\n }\n"),
blit=function(){gl.bindBuffer(gl.ARRAY_BUFFER,gl.createBuffer());gl.bufferData(gl.ARRAY_BUFFER,new Float32Array([-1,-1,-1,1,1,1,1,-1]),gl.STATIC_DRAW);gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER,gl.createBuffer());gl.bufferData(gl.ELEMENT_ARRAY_BUFFER,new Uint16Array([0,1,2,0,2,3]),gl.STATIC_DRAW);gl.vertexAttribPointer(0,2,gl.FLOAT,!1,0,0);gl.enableVertexAttribArray(0);return function(a){gl.bindFramebuffer(gl.FRAMEBUFFER,a);gl.drawElements(gl.TRIANGLES,6,gl.UNSIGNED_SHORT,0)}}();
function clear(a){gl.bindFramebuffer(gl.FRAMEBUFFER,a);gl.clear(gl.COLOR_BUFFER_BIT)}
function createFBO(a,b,c,d,e,h,g){gl.activeTexture(gl.TEXTURE0+a);var f=gl.createTexture();gl.bindTexture(gl.TEXTURE_2D,f);gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MIN_FILTER,g);gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MAG_FILTER,g);gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_WRAP_S,gl.CLAMP_TO_EDGE);gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_WRAP_T,gl.CLAMP_TO_EDGE);gl.texImage2D(gl.TEXTURE_2D,0,d,b,c,0,e,h,null);d=gl.createFramebuffer();gl.bindFramebuffer(gl.FRAMEBUFFER,d);gl.framebufferTexture2D(gl.FRAMEBUFFER,
gl.COLOR_ATTACHMENT0,gl.TEXTURE_2D,f,0);gl.viewport(0,0,b,c);gl.clear(gl.COLOR_BUFFER_BIT);return[f,d,a]}function createDoubleFBO(a,b,c,d,e,h,g){var f=createFBO(a,b,c,d,e,h,g),k=createFBO(a+1,b,c,d,e,h,g);return{get first(){return f},get second(){return k},swap:function(){var a=f;f=k;k=a}}}var textureWidth,textureHeight,density,velocity,divergence,curl,pressure;
function initFramebuffers(){textureWidth=gl.drawingBufferWidth>>config.TEXTURE_DOWNSAMPLE;textureHeight=gl.drawingBufferHeight>>config.TEXTURE_DOWNSAMPLE;var a=isWebGL2?gl.RG16F:gl.RGBA,b=isWebGL2?gl.RG:gl.RGBA,c=isWebGL2?gl.HALF_FLOAT:halfFloat.HALF_FLOAT_OES;density=createDoubleFBO(0,textureWidth,textureHeight,isWebGL2?gl.RGBA16F:gl.RGBA,gl.RGBA,c,support_linear_float?gl.LINEAR:gl.NEAREST);velocity=createDoubleFBO(2,textureWidth,textureHeight,a,b,c,support_linear_float?gl.LINEAR:gl.NEAREST);divergence=
createFBO(4,textureWidth,textureHeight,a,b,c,gl.NEAREST);curl=createFBO(5,textureWidth,textureHeight,a,b,c,gl.NEAREST);pressure=createDoubleFBO(6,textureWidth,textureHeight,a,b,c,gl.NEAREST)}initFramebuffers();
var clearProgram=new GLProgram(baseVertexShader,clearShader),displayProgram=new GLProgram(baseVertexShader,displayShader),splatProgram=new GLProgram(baseVertexShader,splatShader),advectionProgram=new GLProgram(baseVertexShader,support_linear_float?advectionShader:advectionManualFilteringShader),divergenceProgram=new GLProgram(baseVertexShader,divergenceShader),curlProgram=new GLProgram(baseVertexShader,curlShader),vorticityProgram=new GLProgram(baseVertexShader,vorticityShader),pressureProgram=new GLProgram(baseVertexShader,
pressureShader),gradienSubtractProgram=new GLProgram(baseVertexShader,gradientSubtractShader);function pointerPrototype(){this.id=-1;this.dy=this.dx=this.y=this.x=0;this.moved=this.down=!1;this.color=[30,0,300]}var pointers=[];pointers.push(new pointerPrototype);for(var i=0;10>i;i++){var color=[10*Math.random(),10*Math.random(),10*Math.random()],x=canvas.width*Math.random(),y=canvas.height*Math.random(),dx=1E3*(Math.random()-.5),dy=1E3*(Math.random()-.5);splat(x,y,dx,dy,color)}var lastTime=Date.now();
Update();
function Update(){resizeCanvas();var a=Math.min((Date.now()-lastTime)/1E3,.016);lastTime=Date.now();gl.viewport(0,0,textureWidth,textureHeight);advectionProgram.bind();gl.uniform2f(advectionProgram.uniforms.texelSize,1/textureWidth,1/textureHeight);gl.uniform1i(advectionProgram.uniforms.uVelocity,velocity.first[2]);gl.uniform1i(advectionProgram.uniforms.uSource,velocity.first[2]);gl.uniform1f(advectionProgram.uniforms.dt,a);gl.uniform1f(advectionProgram.uniforms.dissipation,config.VELOCITY_DISSIPATION);blit(velocity.second[1]);
velocity.swap();gl.uniform1i(advectionProgram.uniforms.uVelocity,velocity.first[2]);gl.uniform1i(advectionProgram.uniforms.uSource,density.first[2]);gl.uniform1f(advectionProgram.uniforms.dissipation,config.DENSITY_DISSIPATION);blit(density.second[1]);density.swap();for(var b=0;b<pointers.length;b++){var c=pointers[b];c.moved&&(splat(c.x,c.y,c.dx,c.dy,c.color),c.moved=!1)}curlProgram.bind();gl.uniform2f(curlProgram.uniforms.texelSize,1/textureWidth,1/textureHeight);gl.uniform1i(curlProgram.uniforms.uVelocity,
velocity.first[2]);blit(curl[1]);vorticityProgram.bind();gl.uniform2f(vorticityProgram.uniforms.texelSize,1/textureWidth,1/textureHeight);gl.uniform1i(vorticityProgram.uniforms.uVelocity,velocity.first[2]);gl.uniform1i(vorticityProgram.uniforms.uCurl,curl[2]);gl.uniform1f(vorticityProgram.uniforms.curl,config.CURL);gl.uniform1f(vorticityProgram.uniforms.dt,a);blit(velocity.second[1]);velocity.swap();divergenceProgram.bind();gl.uniform2f(divergenceProgram.uniforms.texelSize,1/textureWidth,1/textureHeight);
gl.uniform1i(divergenceProgram.uniforms.uVelocity,velocity.first[2]);blit(divergence[1]);clearProgram.bind();gl.uniform1i(clearProgram.uniforms.uTexture,pressure.first[2]);gl.uniform1f(clearProgram.uniforms.value,config.PRESSURE_CONCENTRATION);blit(pressure.second[1]);pressure.swap();pressureProgram.bind();gl.uniform2f(pressureProgram.uniforms.texelSize,1/textureWidth,1/textureHeight);gl.uniform1i(pressureProgram.uniforms.uDivergence,divergence[2]);for(a=0;a<config.PRESSURE_ITERATIONS;a++)gl.uniform1i(pressureProgram.uniforms.uPressure,
pressure.first[2]),blit(pressure.second[1]),pressure.swap();gradienSubtractProgram.bind();gl.uniform2f(gradienSubtractProgram.uniforms.texelSize,1/textureWidth,1/textureHeight);gl.uniform1i(gradienSubtractProgram.uniforms.uPressure,pressure.first[2]);gl.uniform1i(gradienSubtractProgram.uniforms.uVelocity,velocity.first[2]);blit(velocity.second[1]);velocity.swap();gl.viewport(0,0,gl.drawingBufferWidth,gl.drawingBufferHeight);displayProgram.bind();gl.uniform1i(displayProgram.uniforms.uTexture,density.first[2]);
blit(null);requestAnimationFrame(Update)}
function splat(a,b,c,d,e){splatProgram.bind();gl.uniform1i(splatProgram.uniforms.uTarget,velocity.first[2]);gl.uniform1f(splatProgram.uniforms.aspectRatio,canvas.width/canvas.height);gl.uniform2f(splatProgram.uniforms.point,a/canvas.width,1-b/canvas.height);gl.uniform3f(splatProgram.uniforms.color,c,-d,1);gl.uniform1f(splatProgram.uniforms.radius,config.SPLAT_RADIUS);blit(velocity.second[1]);velocity.swap();gl.uniform1i(splatProgram.uniforms.uTarget,density.first[2]);gl.uniform3f(splatProgram.uniforms.color,
.3*e[0],.3*e[1],.3*e[2]);blit(density.second[1]);density.swap()}function resizeCanvas(){if(canvas.width!=canvas.clientWidth||canvas.height!=canvas.clientHeight)canvas.width=canvas.clientWidth,canvas.height=canvas.clientHeight,initFramebuffers()}canvas.addEventListener("mousemove",function(a){pointers[0].moved=pointers[0].down;pointers[0].dx=10*(a.offsetX-pointers[0].x);pointers[0].dy=10*(a.offsetY-pointers[0].y);pointers[0].x=a.offsetX;pointers[0].y=a.offsetY});
canvas.addEventListener("touchmove",function(a){a.preventDefault();a=a.targetTouches;for(var b=0;b<a.length;b++){var c=pointers[b];c.moved=c.down;c.dx=10*(a[b].pageX-c.x);c.dy=10*(a[b].pageY-c.y);c.x=a[b].pageX;c.y=a[b].pageY}},!1);canvas.addEventListener("mousedown",function(){pointers[0].down=!0;pointers[0].color=[Math.random()+.2,Math.random()+.2,Math.random()+.2]});
canvas.addEventListener("touchstart",function(a){a.preventDefault();a=a.targetTouches;for(var b=0;b<a.length;b++)b>=pointers.length&&pointers.push(new pointerPrototype),pointers[b].id=a[b].identifier,pointers[b].down=!0,pointers[b].x=a[b].pageX,pointers[b].y=a[b].pageY,pointers[b].color=[Math.random()+.2,Math.random()+.2,Math.random()+.2]});window.addEventListener("mouseup",function(){pointers[0].down=!1});
window.addEventListener("touchend",function(a){a=a.changedTouches;for(var b=0;b<a.length;b++)for(var c=0;c<pointers.length;c++)a[b].identifier==pointers[c].id&&(pointers[c].down=!1)});
</script>
</body> </body>
</html> </html>

View File

619
script.js
View File

@@ -1,619 +0,0 @@
'use strict';
const canvas = document.getElementsByTagName('canvas')[0];
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
const params = { alpha: false, depth: false, stencil: false, antialias: false };
let gl = canvas.getContext('webgl2', params);
const isWebGL2 = !!gl;
if (!isWebGL2) {
gl = canvas.getContext('webgl', params) || canvas.getContext('experimental-webgl', params);
}
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.enable(gl.BLEND);
const halfFloat = gl.getExtension('OES_texture_half_float');
let support_linear_float = gl.getExtension('OES_texture_half_float_linear');
if (isWebGL2) {
gl.getExtension('EXT_color_buffer_float');
support_linear_float = gl.getExtension('OES_texture_float_linear');
}
let config = {
TEXTURE_DOWNSAMPLE: 1,
DENSITY_DISSIPATION: 0.98,
VELOCITY_DISSIPATION: 0.99,
PRESSURE_CONCENTRATION: 0.8,
PRESSURE_ITERATIONS: 25,
CURL: 30,
SPLAT_RADIUS: 0.005
}
var gui = new dat.GUI({ width: 270 });
gui.add(config, 'TEXTURE_DOWNSAMPLE', { Full: 0, Half: 1, Quarter: 2 }).name('resolution').onFinishChange(initFramebuffers);
gui.add(config, 'DENSITY_DISSIPATION', 0.9, 1.0).name('density diffusion');
gui.add(config, 'VELOCITY_DISSIPATION', 0.9, 1.0).name('velocity diffusion');
gui.add(config, 'PRESSURE_CONCENTRATION', 0.0, 1.0).name('jelly');
gui.add(config, 'PRESSURE_ITERATIONS', 1, 60).name('iterations');
gui.add(config, 'CURL', 0, 50).name('vorticity').step(1);
gui.add(config, 'SPLAT_RADIUS', 0.0001, 0.01).name('splat radius');
let github = gui.add({ fun : function () { window.open('https://github.com/PavelDoGreat/WebGL-Fluid-Simulation'); } }, 'fun').name('Github');
github.__li.className = 'cr function bigFont';
github.__li.style.borderLeft = '3px solid #8C8C8C';
let githubIcon = document.createElement('span');
github.domElement.parentElement.appendChild(githubIcon);
githubIcon.className = 'icon github';
let twitter = gui.add({ fun : function () { window.open('https://twitter.com/PavelDoGreat'); } }, 'fun').name('Twitter');
twitter.__li.className = 'cr function bigFont';
twitter.__li.style.borderLeft = '3px solid #8C8C8C';
let twitterIcon = document.createElement('span');
twitter.domElement.parentElement.appendChild(twitterIcon);
twitterIcon.className = 'icon twitter';
class GLProgram {
constructor (vertexShader, fragmentShader) {
this.uniforms = {};
this.program = gl.createProgram();
gl.attachShader(this.program, vertexShader);
gl.attachShader(this.program, fragmentShader);
gl.linkProgram(this.program);
if (!gl.getProgramParameter(this.program, gl.LINK_STATUS))
throw gl.getProgramInfoLog(this.program);
const uniformCount = gl.getProgramParameter(this.program, gl.ACTIVE_UNIFORMS);
for (let i = 0; i < uniformCount; i++) {
const uniformName = gl.getActiveUniform(this.program, i).name;
this.uniforms[uniformName] = gl.getUniformLocation(this.program, uniformName);
}
}
bind () {
gl.useProgram(this.program);
}
}
function compileShader (type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS))
throw gl.getShaderInfoLog(shader);
return shader;
};
const baseVertexShader = compileShader(gl.VERTEX_SHADER, `
precision highp float;
precision mediump sampler2D;
attribute vec2 aPosition;
varying vec2 vUv;
varying vec2 vL;
varying vec2 vR;
varying vec2 vT;
varying vec2 vB;
uniform vec2 texelSize;
void main () {
vUv = aPosition * 0.5 + 0.5;
vL = vUv - vec2(texelSize.x, 0.0);
vR = vUv + vec2(texelSize.x, 0.0);
vT = vUv + vec2(0.0, texelSize.y);
vB = vUv - vec2(0.0, texelSize.y);
gl_Position = vec4(aPosition, 0.0, 1.0);
}
`);
const clearShader = compileShader(gl.FRAGMENT_SHADER, `
precision highp float;
precision mediump sampler2D;
varying vec2 vUv;
uniform sampler2D uTexture;
uniform float value;
void main () {
gl_FragColor = value * texture2D(uTexture, vUv);
}
`);
const displayShader = compileShader(gl.FRAGMENT_SHADER, `
precision highp float;
precision mediump sampler2D;
varying vec2 vUv;
varying vec2 vL;
varying vec2 vR;
varying vec2 vT;
varying vec2 vB;
uniform sampler2D uTexture;
void main () {
gl_FragColor = texture2D(uTexture, vUv);
}
`);
const splatShader = compileShader(gl.FRAGMENT_SHADER, `
precision highp float;
precision mediump sampler2D;
varying vec2 vUv;
uniform sampler2D uTarget;
uniform float aspectRatio;
uniform vec3 color;
uniform vec2 point;
uniform float radius;
void main () {
vec2 p = vUv - point.xy;
p.x *= aspectRatio;
vec3 splat = exp(-dot(p, p) / radius) * color;
vec3 base = texture2D(uTarget, vUv).xyz;
gl_FragColor = vec4(base + splat, 1.0);
}
`);
const advectionManualFilteringShader = compileShader(gl.FRAGMENT_SHADER, `
precision highp float;
precision mediump sampler2D;
varying vec2 vUv;
uniform sampler2D uVelocity;
uniform sampler2D uSource;
uniform vec2 texelSize;
uniform float dt;
uniform float dissipation;
vec4 bilerp (in sampler2D sam, in vec2 p) {
vec4 st;
st.xy = floor(p - 0.5) + 0.5;
st.zw = st.xy + 1.0;
vec4 uv = st * texelSize.xyxy;
vec4 a = texture2D(sam, uv.xy);
vec4 b = texture2D(sam, uv.zy);
vec4 c = texture2D(sam, uv.xw);
vec4 d = texture2D(sam, uv.zw);
vec2 f = p - st.xy;
return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
}
void main () {
vec2 coord = gl_FragCoord.xy - dt * texture2D(uVelocity, vUv).xy;
gl_FragColor = dissipation * bilerp(uSource, coord);
gl_FragColor.a = 1.0;
}
`);
const advectionShader = compileShader(gl.FRAGMENT_SHADER, `
precision highp float;
precision mediump sampler2D;
varying vec2 vUv;
uniform sampler2D uVelocity;
uniform sampler2D uSource;
uniform vec2 texelSize;
uniform float dt;
uniform float dissipation;
void main () {
vec2 coord = vUv - dt * texture2D(uVelocity, vUv).xy * texelSize;
gl_FragColor = dissipation * texture2D(uSource, coord);
}
`);
const divergenceShader = compileShader(gl.FRAGMENT_SHADER, `
precision highp float;
precision mediump sampler2D;
varying vec2 vUv;
varying vec2 vL;
varying vec2 vR;
varying vec2 vT;
varying vec2 vB;
uniform sampler2D uVelocity;
vec2 sampleVelocity (in vec2 uv) {
vec2 multiplier = vec2(1.0, 1.0);
if (uv.x < 0.0) { uv.x = 0.0; multiplier.x = -1.0; }
if (uv.x > 1.0) { uv.x = 1.0; multiplier.x = -1.0; }
if (uv.y < 0.0) { uv.y = 0.0; multiplier.y = -1.0; }
if (uv.y > 1.0) { uv.y = 1.0; multiplier.y = -1.0; }
return multiplier * texture2D(uVelocity, uv).xy;
}
void main () {
float L = sampleVelocity(vL).x;
float R = sampleVelocity(vR).x;
float T = sampleVelocity(vT).y;
float B = sampleVelocity(vB).y;
float div = 0.5 * (R - L + T - B);
gl_FragColor = vec4(div, 0.0, 0.0, 1.0);
}
`);
const curlShader = compileShader(gl.FRAGMENT_SHADER, `
precision highp float;
precision mediump sampler2D;
varying vec2 vUv;
varying vec2 vL;
varying vec2 vR;
varying vec2 vT;
varying vec2 vB;
uniform sampler2D uVelocity;
void main () {
float L = texture2D(uVelocity, vL).y;
float R = texture2D(uVelocity, vR).y;
float T = texture2D(uVelocity, vT).x;
float B = texture2D(uVelocity, vB).x;
float vorticity = R - L - T + B;
gl_FragColor = vec4(vorticity, 0.0, 0.0, 1.0);
}
`);
const vorticityShader = compileShader(gl.FRAGMENT_SHADER, `
precision highp float;
precision mediump sampler2D;
varying vec2 vUv;
varying vec2 vL;
varying vec2 vR;
varying vec2 vT;
varying vec2 vB;
uniform sampler2D uVelocity;
uniform sampler2D uCurl;
uniform float curl;
uniform float dt;
void main () {
float L = texture2D(uCurl, vL).y;
float R = texture2D(uCurl, vR).y;
float T = texture2D(uCurl, vT).x;
float B = texture2D(uCurl, vB).x;
float C = texture2D(uCurl, vUv).x;
vec2 force = vec2(abs(T) - abs(B), abs(R) - abs(L));
force *= 1.0 / length(force + 0.00001) * curl * C;
vec2 vel = texture2D(uVelocity, vUv).xy;
gl_FragColor = vec4(vel + force * dt, 0.0, 1.0);
}
`);
const pressureShader = compileShader(gl.FRAGMENT_SHADER, `
precision highp float;
precision mediump sampler2D;
varying vec2 vUv;
varying vec2 vL;
varying vec2 vR;
varying vec2 vT;
varying vec2 vB;
uniform sampler2D uPressure;
uniform sampler2D uDivergence;
vec2 boundary (in vec2 uv) {
uv = min(max(uv, 0.0), 1.0);
return uv;
}
void main () {
float L = texture2D(uPressure, boundary(vL)).x;
float R = texture2D(uPressure, boundary(vR)).x;
float T = texture2D(uPressure, boundary(vT)).x;
float B = texture2D(uPressure, boundary(vB)).x;
float C = texture2D(uPressure, vUv).x;
float divergence = texture2D(uDivergence, vUv).x;
float pressure = (L + R + B + T - divergence) * 0.25;
gl_FragColor = vec4(pressure, 0.0, 0.0, 1.0);
}
`);
const gradientSubtractShader = compileShader(gl.FRAGMENT_SHADER, `
precision highp float;
precision mediump sampler2D;
varying vec2 vUv;
varying vec2 vL;
varying vec2 vR;
varying vec2 vT;
varying vec2 vB;
uniform sampler2D uPressure;
uniform sampler2D uVelocity;
vec2 boundary (in vec2 uv) {
uv = min(max(uv, 0.0), 1.0);
return uv;
}
void main () {
float L = texture2D(uPressure, boundary(vL)).x;
float R = texture2D(uPressure, boundary(vR)).x;
float T = texture2D(uPressure, boundary(vT)).x;
float B = texture2D(uPressure, boundary(vB)).x;
vec2 velocity = texture2D(uVelocity, vUv).xy;
velocity.xy -= vec2(R - L, T - B);
gl_FragColor = vec4(velocity, 0.0, 1.0);
}
`);
const blit = (() => {
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, -1, 1, 1, 1, 1, -1]), gl.STATIC_DRAW);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, gl.createBuffer());
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array([0, 1, 2, 0, 2, 3]), gl.STATIC_DRAW);
gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(0);
return (destination) => {
gl.bindFramebuffer(gl.FRAMEBUFFER, destination);
gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);
}
})();
function clear (target) {
gl.bindFramebuffer(gl.FRAMEBUFFER, target);
gl.clear(gl.COLOR_BUFFER_BIT);
}
function createFBO (texId, w, h, internalFormat, format, type, param) {
gl.activeTexture(gl.TEXTURE0 + texId);
let texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, param);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, param);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, w, h, 0, format, type, null);
let fbo = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
gl.viewport(0, 0, w, h);
gl.clear(gl.COLOR_BUFFER_BIT);
return [texture, fbo, texId];
}
function createDoubleFBO (texId, w, h, internalFormat, format, type, param) {
let fbo1 = createFBO(texId , w, h, internalFormat, format, type, param);
let fbo2 = createFBO(texId + 1, w, h, internalFormat, format, type, param);
return {
get first () {
return fbo1;
},
get second () {
return fbo2;
},
swap: () => {
let temp = fbo1;
fbo1 = fbo2;
fbo2 = temp;
}
}
}
let textureWidth;
let textureHeight;
let density;
let velocity;
let divergence;
let curl;
let pressure;
function initFramebuffers () {
textureWidth = gl.drawingBufferWidth >> config.TEXTURE_DOWNSAMPLE;
textureHeight = gl.drawingBufferHeight >> config.TEXTURE_DOWNSAMPLE;
const internalFormat = isWebGL2 ? gl.RGBA16F : gl.RGBA;
const internalFormatRG = isWebGL2 ? gl.RG16F : gl.RGBA;
const formatRG = isWebGL2 ? gl.RG : gl.RGBA;
const texType = isWebGL2 ? gl.HALF_FLOAT : halfFloat.HALF_FLOAT_OES;
density = createDoubleFBO(0, textureWidth, textureHeight, internalFormat , gl.RGBA , texType, support_linear_float ? gl.LINEAR : gl.NEAREST);
velocity = createDoubleFBO(2, textureWidth, textureHeight, internalFormatRG, formatRG, texType, support_linear_float ? gl.LINEAR : gl.NEAREST);
divergence = createFBO (4, textureWidth, textureHeight, internalFormatRG, formatRG, texType, gl.NEAREST);
curl = createFBO (5, textureWidth, textureHeight, internalFormatRG, formatRG, texType, gl.NEAREST);
pressure = createDoubleFBO(6, textureWidth, textureHeight, internalFormatRG, formatRG, texType, gl.NEAREST);
}
initFramebuffers();
const clearProgram = new GLProgram(baseVertexShader, clearShader);
const displayProgram = new GLProgram(baseVertexShader, displayShader);
const splatProgram = new GLProgram(baseVertexShader, splatShader);
const advectionProgram = new GLProgram(baseVertexShader, support_linear_float ? advectionShader : advectionManualFilteringShader);
const divergenceProgram = new GLProgram(baseVertexShader, divergenceShader);
const curlProgram = new GLProgram(baseVertexShader, curlShader);
const vorticityProgram = new GLProgram(baseVertexShader, vorticityShader);
const pressureProgram = new GLProgram(baseVertexShader, pressureShader);
const gradienSubtractProgram = new GLProgram(baseVertexShader, gradientSubtractShader);
function pointerPrototype () {
this.id = -1;
this.x = 0;
this.y = 0;
this.dx = 0;
this.dy = 0;
this.down = false;
this.moved = false;
this.color = [30, 0, 300];
}
let pointers = [];
pointers.push(new pointerPrototype());
for (let i = 0; i < 10; i++) {
const color = [Math.random() * 10, Math.random() * 10, Math.random() * 10];
const x = canvas.width * Math.random();
const y = canvas.height * Math.random();
const dx = 1000 * (Math.random() - 0.5);
const dy = 1000 * (Math.random() - 0.5);
splat(x, y, dx, dy, color);
}
let lastTime = Date.now();
Update();
function Update () {
resizeCanvas();
const dt = Math.min((Date.now() - lastTime) / 1000, 0.016);
lastTime = Date.now();
gl.viewport(0, 0, textureWidth, textureHeight);
advectionProgram.bind();
gl.uniform2f(advectionProgram.uniforms.texelSize, 1.0 / textureWidth, 1.0 / textureHeight);
gl.uniform1i(advectionProgram.uniforms.uVelocity, velocity.first[2]);
gl.uniform1i(advectionProgram.uniforms.uSource, velocity.first[2]);
gl.uniform1f(advectionProgram.uniforms.dt, dt);
gl.uniform1f(advectionProgram.uniforms.dissipation, config.VELOCITY_DISSIPATION);
blit(velocity.second[1]);
velocity.swap();
gl.uniform1i(advectionProgram.uniforms.uVelocity, velocity.first[2]);
gl.uniform1i(advectionProgram.uniforms.uSource, density.first[2]);
gl.uniform1f(advectionProgram.uniforms.dissipation, config.DENSITY_DISSIPATION);
blit(density.second[1]);
density.swap();
for (let i = 0; i < pointers.length; i++) {
const pointer = pointers[i];
if (pointer.moved) {
splat(pointer.x, pointer.y, pointer.dx, pointer.dy, pointer.color);
pointer.moved = false;
}
}
curlProgram.bind();
gl.uniform2f(curlProgram.uniforms.texelSize, 1.0 / textureWidth, 1.0 / textureHeight);
gl.uniform1i(curlProgram.uniforms.uVelocity, velocity.first[2]);
blit(curl[1]);
vorticityProgram.bind();
gl.uniform2f(vorticityProgram.uniforms.texelSize, 1.0 / textureWidth, 1.0 / textureHeight);
gl.uniform1i(vorticityProgram.uniforms.uVelocity, velocity.first[2]);
gl.uniform1i(vorticityProgram.uniforms.uCurl, curl[2]);
gl.uniform1f(vorticityProgram.uniforms.curl, config.CURL);
gl.uniform1f(vorticityProgram.uniforms.dt, dt);
blit(velocity.second[1]);
velocity.swap();
divergenceProgram.bind();
gl.uniform2f(divergenceProgram.uniforms.texelSize, 1.0 / textureWidth, 1.0 / textureHeight);
gl.uniform1i(divergenceProgram.uniforms.uVelocity, velocity.first[2]);
blit(divergence[1]);
clearProgram.bind();
gl.uniform1i(clearProgram.uniforms.uTexture, pressure.first[2]);
gl.uniform1f(clearProgram.uniforms.value, config.PRESSURE_CONCENTRATION);
blit(pressure.second[1]);
pressure.swap();
pressureProgram.bind();
gl.uniform2f(pressureProgram.uniforms.texelSize, 1.0 / textureWidth, 1.0 / textureHeight);
gl.uniform1i(pressureProgram.uniforms.uDivergence, divergence[2]);
for (let i = 0; i < config.PRESSURE_ITERATIONS; i++) {
gl.uniform1i(pressureProgram.uniforms.uPressure, pressure.first[2]);
blit(pressure.second[1]);
pressure.swap();
}
gradienSubtractProgram.bind();
gl.uniform2f(gradienSubtractProgram.uniforms.texelSize, 1.0 / textureWidth, 1.0 / textureHeight);
gl.uniform1i(gradienSubtractProgram.uniforms.uPressure, pressure.first[2]);
gl.uniform1i(gradienSubtractProgram.uniforms.uVelocity, velocity.first[2]);
blit(velocity.second[1]);
velocity.swap();
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
displayProgram.bind();
gl.uniform1i(displayProgram.uniforms.uTexture, density.first[2]);
blit(null);
requestAnimationFrame(Update);
}
function splat (x, y, dx, dy, color) {
splatProgram.bind();
gl.uniform1i(splatProgram.uniforms.uTarget, velocity.first[2]);
gl.uniform1f(splatProgram.uniforms.aspectRatio, canvas.width / canvas.height);
gl.uniform2f(splatProgram.uniforms.point, x / canvas.width, 1.0 - y / canvas.height);
gl.uniform3f(splatProgram.uniforms.color, dx, -dy, 1.0);
gl.uniform1f(splatProgram.uniforms.radius, config.SPLAT_RADIUS);
blit(velocity.second[1]);
velocity.swap();
gl.uniform1i(splatProgram.uniforms.uTarget, density.first[2]);
gl.uniform3f(splatProgram.uniforms.color, color[0] * 0.3, color[1] * 0.3, color[2] * 0.3);
blit(density.second[1]);
density.swap();
}
function resizeCanvas () {
if (canvas.width != canvas.clientWidth || canvas.height != canvas.clientHeight) {
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
initFramebuffers();
}
}
canvas.addEventListener('mousemove', (e) => {
pointers[0].moved = pointers[0].down;
pointers[0].dx = (e.offsetX - pointers[0].x) * 10.0;
pointers[0].dy = (e.offsetY - pointers[0].y) * 10.0;
pointers[0].x = e.offsetX;
pointers[0].y = e.offsetY;
});
canvas.addEventListener('touchmove', (e) => {
e.preventDefault();
const touches = e.targetTouches;
for (let i = 0; i < touches.length; i++) {
let pointer = pointers[i];
pointer.moved = pointer.down;
pointer.dx = (touches[i].pageX - pointer.x) * 10.0;
pointer.dy = (touches[i].pageY - pointer.y) * 10.0;
pointer.x = touches[i].pageX;
pointer.y = touches[i].pageY;
}
}, false);
canvas.addEventListener('mousedown', () => {
pointers[0].down = true;
pointers[0].color = [Math.random() + 0.2, Math.random() + 0.2, Math.random() + 0.2];
});
canvas.addEventListener('touchstart', (e) => {
e.preventDefault();
const touches = e.targetTouches;
for (let i = 0; i < touches.length; i++) {
if (i >= pointers.length)
pointers.push(new pointerPrototype());
pointers[i].id = touches[i].identifier;
pointers[i].down = true;
pointers[i].x = touches[i].pageX;
pointers[i].y = touches[i].pageY;
pointers[i].color = [Math.random() + 0.2, Math.random() + 0.2, Math.random() + 0.2];
}
});
window.addEventListener('mouseup', () => {
pointers[0].down = false;
});
window.addEventListener('touchend', (e) => {
const touches = e.changedTouches;
for (let i = 0; i < touches.length; i++)
for (let j = 0; j < pointers.length; j++)
if (touches[i].identifier == pointers[j].id)
pointers[j].down = false;
});