Repository: PavelDoGreat/WebGL-Fluid-Simulation
Branch: master
Commit: a2d292931f19
Files: 5
Total size: 59.5 KB
Directory structure:
gitextract_spjirf4e/
├── .github/
│ └── FUNDING.yml
├── LICENSE
├── README.md
├── index.html
└── script.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: ['https://play.google.com/store/apps/details?id=games.paveldogreat.fluidsimfree', 'https://apps.apple.com/app/fluid-simulation/id1443124993']# Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
================================================
FILE: LICENSE
================================================
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.
================================================
FILE: README.md
================================================
# WebGL Fluid Simulation
[Play here](https://paveldogreat.github.io/WebGL-Fluid-Simulation/)
<img src="/screenshot.jpg?raw=true" width="880">
## References
https://developer.nvidia.com/gpugems/gpugems/part-vi-beyond-triangles/chapter-38-fast-fluid-dynamics-simulation-gpu
https://github.com/mharrys/fluids-2d
https://github.com/haxiomic/GPU-Fluid-Experiments
## License
The code is available under the [MIT license](LICENSE)
================================================
FILE: index.html
================================================
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="Cache-Control" content="no-cache">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes">
<link rel="apple-touch-icon" href="logo.png">
<link rel="icon" href="logo.png">
<title>WebGL Fluid Simulation</title>
<meta name="description" content="A WebGL fluid simulation that works in mobile browsers.">
<meta property="og:type" content="website">
<meta property="og:title" content="Webgl Fluid Simulation">
<meta property="og:description" content="A WebGL fluid simulation that works in mobile browsers.">
<meta property="og:url" content="https://paveldogreat.github.io/WebGL-Fluid-Simulation/">
<meta property="og:image" content="https://paveldogreat.github.io/WebGL-Fluid-Simulation/logo.png">
<script type="text/javascript" src="dat.gui.min.js"></script>
<style>
@font-face {
font-family: 'iconfont';
src: url('iconfont.ttf') format('truetype');
}
* {
user-select: none;
}
html, body {
overflow: hidden;
background-color: #000;
}
body {
margin: 0;
position: fixed;
width: 100%;
height: 100%;
}
canvas {
width: 100%;
height: 100%;
}
.dg {
opacity: 0.9;
}
.dg .property-name {
overflow: visible;
}
.bigFont {
font-size: 150%;
color: #8C8C8C;
}
.cr.function.appBigFont {
font-size: 150%;
line-height: 27px;
color: #A5F8D3;
background-color: #023C40;
}
.cr.function.appBigFont .property-name {
float: none;
}
.cr.function.appBigFont .icon {
position: sticky;
bottom: 27px;
}
.icon {
font-family: 'iconfont';
font-size: 130%;
float: right;
}
.twitter:before {
content: 'a';
}
.github:before {
content: 'b';
}
.app:before {
content: 'c';
}
.discord:before {
content: 'd';
}
.promo {
display: none;
/* display: table; */
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1;
overflow: auto;
color: lightblue;
background-color: rgba(0,0,0,0.4);
animation: promo-appear-animation 0.35s ease-out;
}
.promo-middle {
display: table-cell;
vertical-align: middle;
}
.promo-content {
width: 80vw;
height: 80vh;
max-width: 80vh;
max-height: 80vw;
margin: auto;
padding: 0;
font-size: 2.8vmax;
font-family: Futura, "Trebuchet MS", Arial, sans-serif;
text-align: center;
background-image: url("promo_back.png");
background-position: center;
background-repeat: no-repeat;
background-size: cover;
border-radius: 15px;
box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2), 0 6px 20px 0 rgba(0,0,0,0.19);
}
.promo-header {
height: 10%;
padding: 2px 16px;
}
.promo-close {
width: 10%;
height: 100%;
text-align: left;
float: left;
font-size: 1.3em;
/* transition: 0.2s; */
}
.promo-close:hover {
/* transform: scale(1.25); */
cursor: pointer;
}
.promo-body {
padding: 8px 16px 16px 16px;
margin: auto;
}
.promo-body p {
margin-top: 0;
mix-blend-mode: color-dodge;
}
.link {
width: 100%;
display: inline-block;
}
.link img {
width: 100%;
}
@keyframes promo-appear-animation {
0% {
transform: scale(2.0);
opacity: 0;
}
100% {
transform: scale(1.0);
opacity: 1;
}
}
</style>
<script>
window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date;
ga('create', 'UA-105392568-1', 'auto');
ga('send', 'pageview');
</script>
<script async src="https://www.google-analytics.com/analytics.js"></script>
</head>
<body>
<canvas></canvas>
<!-- Mother of God, pls forgive me -->
<div class="promo">
<div class="promo-middle">
<div class="promo-content">
<div class="promo-header">
<span class="promo-close">×</span>
</div>
<div class="promo-body">
<p>Try Fluid Simulation app!</p>
<div class="links-container">
<a class="link" id="apple_link" target="_blank">
<img class="link-img" alt="Download on the App Store" src="app_badge.png"/>
</a>
<a class="link" id="google_link" target="_blank">
<img class="link-img" alt="Get it on Google Play" src="gp_badge.png"/>
</a>
</div>
</div>
</div>
</div>
</div>
<script src="./script.js"></script>
</body>
</html>
================================================
FILE: script.js
================================================
/*
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.
*/
'use strict';
// Mobile promo section
const promoPopup = document.getElementsByClassName('promo')[0];
const promoPopupClose = document.getElementsByClassName('promo-close')[0];
if (isMobile()) {
setTimeout(() => {
promoPopup.style.display = 'table';
}, 20000);
}
promoPopupClose.addEventListener('click', e => {
promoPopup.style.display = 'none';
});
const appleLink = document.getElementById('apple_link');
appleLink.addEventListener('click', e => {
ga('send', 'event', 'link promo', 'app');
window.open('https://apps.apple.com/us/app/fluid-simulation/id1443124993');
});
const googleLink = document.getElementById('google_link');
googleLink.addEventListener('click', e => {
ga('send', 'event', 'link promo', 'app');
window.open('https://play.google.com/store/apps/details?id=games.paveldogreat.fluidsimfree');
});
// Simulation section
const canvas = document.getElementsByTagName('canvas')[0];
resizeCanvas();
let config = {
SIM_RESOLUTION: 128,
DYE_RESOLUTION: 1024,
CAPTURE_RESOLUTION: 512,
DENSITY_DISSIPATION: 1,
VELOCITY_DISSIPATION: 0.2,
PRESSURE: 0.8,
PRESSURE_ITERATIONS: 20,
CURL: 30,
SPLAT_RADIUS: 0.25,
SPLAT_FORCE: 6000,
SHADING: true,
COLORFUL: true,
COLOR_UPDATE_SPEED: 10,
PAUSED: false,
BACK_COLOR: { r: 0, g: 0, b: 0 },
TRANSPARENT: false,
BLOOM: true,
BLOOM_ITERATIONS: 8,
BLOOM_RESOLUTION: 256,
BLOOM_INTENSITY: 0.8,
BLOOM_THRESHOLD: 0.6,
BLOOM_SOFT_KNEE: 0.7,
SUNRAYS: true,
SUNRAYS_RESOLUTION: 196,
SUNRAYS_WEIGHT: 1.0,
}
function pointerPrototype () {
this.id = -1;
this.texcoordX = 0;
this.texcoordY = 0;
this.prevTexcoordX = 0;
this.prevTexcoordY = 0;
this.deltaX = 0;
this.deltaY = 0;
this.down = false;
this.moved = false;
this.color = [30, 0, 300];
}
let pointers = [];
let splatStack = [];
pointers.push(new pointerPrototype());
const { gl, ext } = getWebGLContext(canvas);
if (isMobile()) {
config.DYE_RESOLUTION = 512;
}
if (!ext.supportLinearFiltering) {
config.DYE_RESOLUTION = 512;
config.SHADING = false;
config.BLOOM = false;
config.SUNRAYS = false;
}
startGUI();
function getWebGLContext (canvas) {
const params = { alpha: true, depth: false, stencil: false, antialias: false, preserveDrawingBuffer: false };
let gl = canvas.getContext('webgl2', params);
const isWebGL2 = !!gl;
if (!isWebGL2)
gl = canvas.getContext('webgl', params) || canvas.getContext('experimental-webgl', params);
let halfFloat;
let supportLinearFiltering;
if (isWebGL2) {
gl.getExtension('EXT_color_buffer_float');
supportLinearFiltering = gl.getExtension('OES_texture_float_linear');
} else {
halfFloat = gl.getExtension('OES_texture_half_float');
supportLinearFiltering = gl.getExtension('OES_texture_half_float_linear');
}
gl.clearColor(0.0, 0.0, 0.0, 1.0);
const halfFloatTexType = isWebGL2 ? gl.HALF_FLOAT : halfFloat.HALF_FLOAT_OES;
let formatRGBA;
let formatRG;
let formatR;
if (isWebGL2)
{
formatRGBA = getSupportedFormat(gl, gl.RGBA16F, gl.RGBA, halfFloatTexType);
formatRG = getSupportedFormat(gl, gl.RG16F, gl.RG, halfFloatTexType);
formatR = getSupportedFormat(gl, gl.R16F, gl.RED, halfFloatTexType);
}
else
{
formatRGBA = getSupportedFormat(gl, gl.RGBA, gl.RGBA, halfFloatTexType);
formatRG = getSupportedFormat(gl, gl.RGBA, gl.RGBA, halfFloatTexType);
formatR = getSupportedFormat(gl, gl.RGBA, gl.RGBA, halfFloatTexType);
}
ga('send', 'event', isWebGL2 ? 'webgl2' : 'webgl', formatRGBA == null ? 'not supported' : 'supported');
return {
gl,
ext: {
formatRGBA,
formatRG,
formatR,
halfFloatTexType,
supportLinearFiltering
}
};
}
function getSupportedFormat (gl, internalFormat, format, type)
{
if (!supportRenderTextureFormat(gl, internalFormat, format, type))
{
switch (internalFormat)
{
case gl.R16F:
return getSupportedFormat(gl, gl.RG16F, gl.RG, type);
case gl.RG16F:
return getSupportedFormat(gl, gl.RGBA16F, gl.RGBA, type);
default:
return null;
}
}
return {
internalFormat,
format
}
}
function supportRenderTextureFormat (gl, internalFormat, format, type) {
let texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
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, 4, 4, 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);
let status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
return status == gl.FRAMEBUFFER_COMPLETE;
}
function startGUI () {
var gui = new dat.GUI({ width: 300 });
gui.add(config, 'DYE_RESOLUTION', { 'high': 1024, 'medium': 512, 'low': 256, 'very low': 128 }).name('quality').onFinishChange(initFramebuffers);
gui.add(config, 'SIM_RESOLUTION', { '32': 32, '64': 64, '128': 128, '256': 256 }).name('sim resolution').onFinishChange(initFramebuffers);
gui.add(config, 'DENSITY_DISSIPATION', 0, 4.0).name('density diffusion');
gui.add(config, 'VELOCITY_DISSIPATION', 0, 4.0).name('velocity diffusion');
gui.add(config, 'PRESSURE', 0.0, 1.0).name('pressure');
gui.add(config, 'CURL', 0, 50).name('vorticity').step(1);
gui.add(config, 'SPLAT_RADIUS', 0.01, 1.0).name('splat radius');
gui.add(config, 'SHADING').name('shading').onFinishChange(updateKeywords);
gui.add(config, 'COLORFUL').name('colorful');
gui.add(config, 'PAUSED').name('paused').listen();
gui.add({ fun: () => {
splatStack.push(parseInt(Math.random() * 20) + 5);
} }, 'fun').name('Random splats');
let bloomFolder = gui.addFolder('Bloom');
bloomFolder.add(config, 'BLOOM').name('enabled').onFinishChange(updateKeywords);
bloomFolder.add(config, 'BLOOM_INTENSITY', 0.1, 2.0).name('intensity');
bloomFolder.add(config, 'BLOOM_THRESHOLD', 0.0, 1.0).name('threshold');
let sunraysFolder = gui.addFolder('Sunrays');
sunraysFolder.add(config, 'SUNRAYS').name('enabled').onFinishChange(updateKeywords);
sunraysFolder.add(config, 'SUNRAYS_WEIGHT', 0.3, 1.0).name('weight');
let captureFolder = gui.addFolder('Capture');
captureFolder.addColor(config, 'BACK_COLOR').name('background color');
captureFolder.add(config, 'TRANSPARENT').name('transparent');
captureFolder.add({ fun: captureScreenshot }, 'fun').name('take screenshot');
let github = gui.add({ fun : () => {
window.open('https://github.com/PavelDoGreat/WebGL-Fluid-Simulation');
ga('send', 'event', 'link button', 'github');
} }, '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 : () => {
ga('send', 'event', 'link button', 'twitter');
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';
let discord = gui.add({ fun : () => {
ga('send', 'event', 'link button', 'discord');
window.open('https://discordapp.com/invite/CeqZDDE');
} }, 'fun').name('Discord');
discord.__li.className = 'cr function bigFont';
discord.__li.style.borderLeft = '3px solid #8C8C8C';
let discordIcon = document.createElement('span');
discord.domElement.parentElement.appendChild(discordIcon);
discordIcon.className = 'icon discord';
let app = gui.add({ fun : () => {
ga('send', 'event', 'link button', 'app');
window.open('http://onelink.to/5b58bn');
} }, 'fun').name('Check out mobile app');
app.__li.className = 'cr function appBigFont';
app.__li.style.borderLeft = '3px solid #00FF7F';
let appIcon = document.createElement('span');
app.domElement.parentElement.appendChild(appIcon);
appIcon.className = 'icon app';
if (isMobile())
gui.close();
}
function isMobile () {
return /Mobi|Android/i.test(navigator.userAgent);
}
function captureScreenshot () {
let res = getResolution(config.CAPTURE_RESOLUTION);
let target = createFBO(res.width, res.height, ext.formatRGBA.internalFormat, ext.formatRGBA.format, ext.halfFloatTexType, gl.NEAREST);
render(target);
let texture = framebufferToTexture(target);
texture = normalizeTexture(texture, target.width, target.height);
let captureCanvas = textureToCanvas(texture, target.width, target.height);
let datauri = captureCanvas.toDataURL();
downloadURI('fluid.png', datauri);
URL.revokeObjectURL(datauri);
}
function framebufferToTexture (target) {
gl.bindFramebuffer(gl.FRAMEBUFFER, target.fbo);
let length = target.width * target.height * 4;
let texture = new Float32Array(length);
gl.readPixels(0, 0, target.width, target.height, gl.RGBA, gl.FLOAT, texture);
return texture;
}
function normalizeTexture (texture, width, height) {
let result = new Uint8Array(texture.length);
let id = 0;
for (let i = height - 1; i >= 0; i--) {
for (let j = 0; j < width; j++) {
let nid = i * width * 4 + j * 4;
result[nid + 0] = clamp01(texture[id + 0]) * 255;
result[nid + 1] = clamp01(texture[id + 1]) * 255;
result[nid + 2] = clamp01(texture[id + 2]) * 255;
result[nid + 3] = clamp01(texture[id + 3]) * 255;
id += 4;
}
}
return result;
}
function clamp01 (input) {
return Math.min(Math.max(input, 0), 1);
}
function textureToCanvas (texture, width, height) {
let captureCanvas = document.createElement('canvas');
let ctx = captureCanvas.getContext('2d');
captureCanvas.width = width;
captureCanvas.height = height;
let imageData = ctx.createImageData(width, height);
imageData.data.set(texture);
ctx.putImageData(imageData, 0, 0);
return captureCanvas;
}
function downloadURI (filename, uri) {
let link = document.createElement('a');
link.download = filename;
link.href = uri;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
class Material {
constructor (vertexShader, fragmentShaderSource) {
this.vertexShader = vertexShader;
this.fragmentShaderSource = fragmentShaderSource;
this.programs = [];
this.activeProgram = null;
this.uniforms = [];
}
setKeywords (keywords) {
let hash = 0;
for (let i = 0; i < keywords.length; i++)
hash += hashCode(keywords[i]);
let program = this.programs[hash];
if (program == null)
{
let fragmentShader = compileShader(gl.FRAGMENT_SHADER, this.fragmentShaderSource, keywords);
program = createProgram(this.vertexShader, fragmentShader);
this.programs[hash] = program;
}
if (program == this.activeProgram) return;
this.uniforms = getUniforms(program);
this.activeProgram = program;
}
bind () {
gl.useProgram(this.activeProgram);
}
}
class Program {
constructor (vertexShader, fragmentShader) {
this.uniforms = {};
this.program = createProgram(vertexShader, fragmentShader);
this.uniforms = getUniforms(this.program);
}
bind () {
gl.useProgram(this.program);
}
}
function createProgram (vertexShader, fragmentShader) {
let program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS))
console.trace(gl.getProgramInfoLog(program));
return program;
}
function getUniforms (program) {
let uniforms = [];
let uniformCount = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS);
for (let i = 0; i < uniformCount; i++) {
let uniformName = gl.getActiveUniform(program, i).name;
uniforms[uniformName] = gl.getUniformLocation(program, uniformName);
}
return uniforms;
}
function compileShader (type, source, keywords) {
source = addKeywords(source, keywords);
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS))
console.trace(gl.getShaderInfoLog(shader));
return shader;
};
function addKeywords (source, keywords) {
if (keywords == null) return source;
let keywordsString = '';
keywords.forEach(keyword => {
keywordsString += '#define ' + keyword + '\n';
});
return keywordsString + source;
}
const baseVertexShader = compileShader(gl.VERTEX_SHADER, `
precision highp float;
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 blurVertexShader = compileShader(gl.VERTEX_SHADER, `
precision highp float;
attribute vec2 aPosition;
varying vec2 vUv;
varying vec2 vL;
varying vec2 vR;
uniform vec2 texelSize;
void main () {
vUv = aPosition * 0.5 + 0.5;
float offset = 1.33333333;
vL = vUv - texelSize * offset;
vR = vUv + texelSize * offset;
gl_Position = vec4(aPosition, 0.0, 1.0);
}
`);
const blurShader = compileShader(gl.FRAGMENT_SHADER, `
precision mediump float;
precision mediump sampler2D;
varying vec2 vUv;
varying vec2 vL;
varying vec2 vR;
uniform sampler2D uTexture;
void main () {
vec4 sum = texture2D(uTexture, vUv) * 0.29411764;
sum += texture2D(uTexture, vL) * 0.35294117;
sum += texture2D(uTexture, vR) * 0.35294117;
gl_FragColor = sum;
}
`);
const copyShader = compileShader(gl.FRAGMENT_SHADER, `
precision mediump float;
precision mediump sampler2D;
varying highp vec2 vUv;
uniform sampler2D uTexture;
void main () {
gl_FragColor = texture2D(uTexture, vUv);
}
`);
const clearShader = compileShader(gl.FRAGMENT_SHADER, `
precision mediump float;
precision mediump sampler2D;
varying highp vec2 vUv;
uniform sampler2D uTexture;
uniform float value;
void main () {
gl_FragColor = value * texture2D(uTexture, vUv);
}
`);
const colorShader = compileShader(gl.FRAGMENT_SHADER, `
precision mediump float;
uniform vec4 color;
void main () {
gl_FragColor = color;
}
`);
const checkerboardShader = compileShader(gl.FRAGMENT_SHADER, `
precision highp float;
precision highp sampler2D;
varying vec2 vUv;
uniform sampler2D uTexture;
uniform float aspectRatio;
#define SCALE 25.0
void main () {
vec2 uv = floor(vUv * SCALE * vec2(aspectRatio, 1.0));
float v = mod(uv.x + uv.y, 2.0);
v = v * 0.1 + 0.8;
gl_FragColor = vec4(vec3(v), 1.0);
}
`);
const displayShaderSource = `
precision highp float;
precision highp sampler2D;
varying vec2 vUv;
varying vec2 vL;
varying vec2 vR;
varying vec2 vT;
varying vec2 vB;
uniform sampler2D uTexture;
uniform sampler2D uBloom;
uniform sampler2D uSunrays;
uniform sampler2D uDithering;
uniform vec2 ditherScale;
uniform vec2 texelSize;
vec3 linearToGamma (vec3 color) {
color = max(color, vec3(0));
return max(1.055 * pow(color, vec3(0.416666667)) - 0.055, vec3(0));
}
void main () {
vec3 c = texture2D(uTexture, vUv).rgb;
#ifdef SHADING
vec3 lc = texture2D(uTexture, vL).rgb;
vec3 rc = texture2D(uTexture, vR).rgb;
vec3 tc = texture2D(uTexture, vT).rgb;
vec3 bc = texture2D(uTexture, vB).rgb;
float dx = length(rc) - length(lc);
float dy = length(tc) - length(bc);
vec3 n = normalize(vec3(dx, dy, length(texelSize)));
vec3 l = vec3(0.0, 0.0, 1.0);
float diffuse = clamp(dot(n, l) + 0.7, 0.7, 1.0);
c *= diffuse;
#endif
#ifdef BLOOM
vec3 bloom = texture2D(uBloom, vUv).rgb;
#endif
#ifdef SUNRAYS
float sunrays = texture2D(uSunrays, vUv).r;
c *= sunrays;
#ifdef BLOOM
bloom *= sunrays;
#endif
#endif
#ifdef BLOOM
float noise = texture2D(uDithering, vUv * ditherScale).r;
noise = noise * 2.0 - 1.0;
bloom += noise / 255.0;
bloom = linearToGamma(bloom);
c += bloom;
#endif
float a = max(c.r, max(c.g, c.b));
gl_FragColor = vec4(c, a);
}
`;
const bloomPrefilterShader = compileShader(gl.FRAGMENT_SHADER, `
precision mediump float;
precision mediump sampler2D;
varying vec2 vUv;
uniform sampler2D uTexture;
uniform vec3 curve;
uniform float threshold;
void main () {
vec3 c = texture2D(uTexture, vUv).rgb;
float br = max(c.r, max(c.g, c.b));
float rq = clamp(br - curve.x, 0.0, curve.y);
rq = curve.z * rq * rq;
c *= max(rq, br - threshold) / max(br, 0.0001);
gl_FragColor = vec4(c, 0.0);
}
`);
const bloomBlurShader = compileShader(gl.FRAGMENT_SHADER, `
precision mediump float;
precision mediump sampler2D;
varying vec2 vL;
varying vec2 vR;
varying vec2 vT;
varying vec2 vB;
uniform sampler2D uTexture;
void main () {
vec4 sum = vec4(0.0);
sum += texture2D(uTexture, vL);
sum += texture2D(uTexture, vR);
sum += texture2D(uTexture, vT);
sum += texture2D(uTexture, vB);
sum *= 0.25;
gl_FragColor = sum;
}
`);
const bloomFinalShader = compileShader(gl.FRAGMENT_SHADER, `
precision mediump float;
precision mediump sampler2D;
varying vec2 vL;
varying vec2 vR;
varying vec2 vT;
varying vec2 vB;
uniform sampler2D uTexture;
uniform float intensity;
void main () {
vec4 sum = vec4(0.0);
sum += texture2D(uTexture, vL);
sum += texture2D(uTexture, vR);
sum += texture2D(uTexture, vT);
sum += texture2D(uTexture, vB);
sum *= 0.25;
gl_FragColor = sum * intensity;
}
`);
const sunraysMaskShader = compileShader(gl.FRAGMENT_SHADER, `
precision highp float;
precision highp sampler2D;
varying vec2 vUv;
uniform sampler2D uTexture;
void main () {
vec4 c = texture2D(uTexture, vUv);
float br = max(c.r, max(c.g, c.b));
c.a = 1.0 - min(max(br * 20.0, 0.0), 0.8);
gl_FragColor = c;
}
`);
const sunraysShader = compileShader(gl.FRAGMENT_SHADER, `
precision highp float;
precision highp sampler2D;
varying vec2 vUv;
uniform sampler2D uTexture;
uniform float weight;
#define ITERATIONS 16
void main () {
float Density = 0.3;
float Decay = 0.95;
float Exposure = 0.7;
vec2 coord = vUv;
vec2 dir = vUv - 0.5;
dir *= 1.0 / float(ITERATIONS) * Density;
float illuminationDecay = 1.0;
float color = texture2D(uTexture, vUv).a;
for (int i = 0; i < ITERATIONS; i++)
{
coord -= dir;
float col = texture2D(uTexture, coord).a;
color += col * illuminationDecay * weight;
illuminationDecay *= Decay;
}
gl_FragColor = vec4(color * Exposure, 0.0, 0.0, 1.0);
}
`);
const splatShader = compileShader(gl.FRAGMENT_SHADER, `
precision highp float;
precision highp 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 advectionShader = compileShader(gl.FRAGMENT_SHADER, `
precision highp float;
precision highp sampler2D;
varying vec2 vUv;
uniform sampler2D uVelocity;
uniform sampler2D uSource;
uniform vec2 texelSize;
uniform vec2 dyeTexelSize;
uniform float dt;
uniform float dissipation;
vec4 bilerp (sampler2D sam, vec2 uv, vec2 tsize) {
vec2 st = uv / tsize - 0.5;
vec2 iuv = floor(st);
vec2 fuv = fract(st);
vec4 a = texture2D(sam, (iuv + vec2(0.5, 0.5)) * tsize);
vec4 b = texture2D(sam, (iuv + vec2(1.5, 0.5)) * tsize);
vec4 c = texture2D(sam, (iuv + vec2(0.5, 1.5)) * tsize);
vec4 d = texture2D(sam, (iuv + vec2(1.5, 1.5)) * tsize);
return mix(mix(a, b, fuv.x), mix(c, d, fuv.x), fuv.y);
}
void main () {
#ifdef MANUAL_FILTERING
vec2 coord = vUv - dt * bilerp(uVelocity, vUv, texelSize).xy * texelSize;
vec4 result = bilerp(uSource, coord, dyeTexelSize);
#else
vec2 coord = vUv - dt * texture2D(uVelocity, vUv).xy * texelSize;
vec4 result = texture2D(uSource, coord);
#endif
float decay = 1.0 + dissipation * dt;
gl_FragColor = result / decay;
}`,
ext.supportLinearFiltering ? null : ['MANUAL_FILTERING']
);
const divergenceShader = compileShader(gl.FRAGMENT_SHADER, `
precision mediump float;
precision mediump sampler2D;
varying highp vec2 vUv;
varying highp vec2 vL;
varying highp vec2 vR;
varying highp vec2 vT;
varying highp vec2 vB;
uniform sampler2D uVelocity;
void main () {
float L = texture2D(uVelocity, vL).x;
float R = texture2D(uVelocity, vR).x;
float T = texture2D(uVelocity, vT).y;
float B = texture2D(uVelocity, vB).y;
vec2 C = texture2D(uVelocity, vUv).xy;
if (vL.x < 0.0) { L = -C.x; }
if (vR.x > 1.0) { R = -C.x; }
if (vT.y > 1.0) { T = -C.y; }
if (vB.y < 0.0) { B = -C.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 mediump float;
precision mediump sampler2D;
varying highp vec2 vUv;
varying highp vec2 vL;
varying highp vec2 vR;
varying highp vec2 vT;
varying highp 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(0.5 * vorticity, 0.0, 0.0, 1.0);
}
`);
const vorticityShader = compileShader(gl.FRAGMENT_SHADER, `
precision highp float;
precision highp 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).x;
float R = texture2D(uCurl, vR).x;
float T = texture2D(uCurl, vT).x;
float B = texture2D(uCurl, vB).x;
float C = texture2D(uCurl, vUv).x;
vec2 force = 0.5 * vec2(abs(T) - abs(B), abs(R) - abs(L));
force /= length(force) + 0.0001;
force *= curl * C;
force.y *= -1.0;
vec2 velocity = texture2D(uVelocity, vUv).xy;
velocity += force * dt;
velocity = min(max(velocity, -1000.0), 1000.0);
gl_FragColor = vec4(velocity, 0.0, 1.0);
}
`);
const pressureShader = compileShader(gl.FRAGMENT_SHADER, `
precision mediump float;
precision mediump sampler2D;
varying highp vec2 vUv;
varying highp vec2 vL;
varying highp vec2 vR;
varying highp vec2 vT;
varying highp vec2 vB;
uniform sampler2D uPressure;
uniform sampler2D uDivergence;
void main () {
float L = texture2D(uPressure, vL).x;
float R = texture2D(uPressure, vR).x;
float T = texture2D(uPressure, vT).x;
float B = texture2D(uPressure, 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 mediump float;
precision mediump sampler2D;
varying highp vec2 vUv;
varying highp vec2 vL;
varying highp vec2 vR;
varying highp vec2 vT;
varying highp vec2 vB;
uniform sampler2D uPressure;
uniform sampler2D uVelocity;
void main () {
float L = texture2D(uPressure, vL).x;
float R = texture2D(uPressure, vR).x;
float T = texture2D(uPressure, vT).x;
float B = texture2D(uPressure, 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 (target, clear = false) => {
if (target == null)
{
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
}
else
{
gl.viewport(0, 0, target.width, target.height);
gl.bindFramebuffer(gl.FRAMEBUFFER, target.fbo);
}
if (clear)
{
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
}
// CHECK_FRAMEBUFFER_STATUS();
gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);
}
})();
function CHECK_FRAMEBUFFER_STATUS () {
let status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
if (status != gl.FRAMEBUFFER_COMPLETE)
console.trace("Framebuffer error: " + status);
}
let dye;
let velocity;
let divergence;
let curl;
let pressure;
let bloom;
let bloomFramebuffers = [];
let sunrays;
let sunraysTemp;
let ditheringTexture = createTextureAsync('LDR_LLL1_0.png');
const blurProgram = new Program(blurVertexShader, blurShader);
const copyProgram = new Program(baseVertexShader, copyShader);
const clearProgram = new Program(baseVertexShader, clearShader);
const colorProgram = new Program(baseVertexShader, colorShader);
const checkerboardProgram = new Program(baseVertexShader, checkerboardShader);
const bloomPrefilterProgram = new Program(baseVertexShader, bloomPrefilterShader);
const bloomBlurProgram = new Program(baseVertexShader, bloomBlurShader);
const bloomFinalProgram = new Program(baseVertexShader, bloomFinalShader);
const sunraysMaskProgram = new Program(baseVertexShader, sunraysMaskShader);
const sunraysProgram = new Program(baseVertexShader, sunraysShader);
const splatProgram = new Program(baseVertexShader, splatShader);
const advectionProgram = new Program(baseVertexShader, advectionShader);
const divergenceProgram = new Program(baseVertexShader, divergenceShader);
const curlProgram = new Program(baseVertexShader, curlShader);
const vorticityProgram = new Program(baseVertexShader, vorticityShader);
const pressureProgram = new Program(baseVertexShader, pressureShader);
const gradienSubtractProgram = new Program(baseVertexShader, gradientSubtractShader);
const displayMaterial = new Material(baseVertexShader, displayShaderSource);
function initFramebuffers () {
let simRes = getResolution(config.SIM_RESOLUTION);
let dyeRes = getResolution(config.DYE_RESOLUTION);
const texType = ext.halfFloatTexType;
const rgba = ext.formatRGBA;
const rg = ext.formatRG;
const r = ext.formatR;
const filtering = ext.supportLinearFiltering ? gl.LINEAR : gl.NEAREST;
gl.disable(gl.BLEND);
if (dye == null)
dye = createDoubleFBO(dyeRes.width, dyeRes.height, rgba.internalFormat, rgba.format, texType, filtering);
else
dye = resizeDoubleFBO(dye, dyeRes.width, dyeRes.height, rgba.internalFormat, rgba.format, texType, filtering);
if (velocity == null)
velocity = createDoubleFBO(simRes.width, simRes.height, rg.internalFormat, rg.format, texType, filtering);
else
velocity = resizeDoubleFBO(velocity, simRes.width, simRes.height, rg.internalFormat, rg.format, texType, filtering);
divergence = createFBO (simRes.width, simRes.height, r.internalFormat, r.format, texType, gl.NEAREST);
curl = createFBO (simRes.width, simRes.height, r.internalFormat, r.format, texType, gl.NEAREST);
pressure = createDoubleFBO(simRes.width, simRes.height, r.internalFormat, r.format, texType, gl.NEAREST);
initBloomFramebuffers();
initSunraysFramebuffers();
}
function initBloomFramebuffers () {
let res = getResolution(config.BLOOM_RESOLUTION);
const texType = ext.halfFloatTexType;
const rgba = ext.formatRGBA;
const filtering = ext.supportLinearFiltering ? gl.LINEAR : gl.NEAREST;
bloom = createFBO(res.width, res.height, rgba.internalFormat, rgba.format, texType, filtering);
bloomFramebuffers.length = 0;
for (let i = 0; i < config.BLOOM_ITERATIONS; i++)
{
let width = res.width >> (i + 1);
let height = res.height >> (i + 1);
if (width < 2 || height < 2) break;
let fbo = createFBO(width, height, rgba.internalFormat, rgba.format, texType, filtering);
bloomFramebuffers.push(fbo);
}
}
function initSunraysFramebuffers () {
let res = getResolution(config.SUNRAYS_RESOLUTION);
const texType = ext.halfFloatTexType;
const r = ext.formatR;
const filtering = ext.supportLinearFiltering ? gl.LINEAR : gl.NEAREST;
sunrays = createFBO(res.width, res.height, r.internalFormat, r.format, texType, filtering);
sunraysTemp = createFBO(res.width, res.height, r.internalFormat, r.format, texType, filtering);
}
function createFBO (w, h, internalFormat, format, type, param) {
gl.activeTexture(gl.TEXTURE0);
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);
let texelSizeX = 1.0 / w;
let texelSizeY = 1.0 / h;
return {
texture,
fbo,
width: w,
height: h,
texelSizeX,
texelSizeY,
attach (id) {
gl.activeTexture(gl.TEXTURE0 + id);
gl.bindTexture(gl.TEXTURE_2D, texture);
return id;
}
};
}
function createDoubleFBO (w, h, internalFormat, format, type, param) {
let fbo1 = createFBO(w, h, internalFormat, format, type, param);
let fbo2 = createFBO(w, h, internalFormat, format, type, param);
return {
width: w,
height: h,
texelSizeX: fbo1.texelSizeX,
texelSizeY: fbo1.texelSizeY,
get read () {
return fbo1;
},
set read (value) {
fbo1 = value;
},
get write () {
return fbo2;
},
set write (value) {
fbo2 = value;
},
swap () {
let temp = fbo1;
fbo1 = fbo2;
fbo2 = temp;
}
}
}
function resizeFBO (target, w, h, internalFormat, format, type, param) {
let newFBO = createFBO(w, h, internalFormat, format, type, param);
copyProgram.bind();
gl.uniform1i(copyProgram.uniforms.uTexture, target.attach(0));
blit(newFBO);
return newFBO;
}
function resizeDoubleFBO (target, w, h, internalFormat, format, type, param) {
if (target.width == w && target.height == h)
return target;
target.read = resizeFBO(target.read, w, h, internalFormat, format, type, param);
target.write = createFBO(w, h, internalFormat, format, type, param);
target.width = w;
target.height = h;
target.texelSizeX = 1.0 / w;
target.texelSizeY = 1.0 / h;
return target;
}
function createTextureAsync (url) {
let texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, 1, 1, 0, gl.RGB, gl.UNSIGNED_BYTE, new Uint8Array([255, 255, 255]));
let obj = {
texture,
width: 1,
height: 1,
attach (id) {
gl.activeTexture(gl.TEXTURE0 + id);
gl.bindTexture(gl.TEXTURE_2D, texture);
return id;
}
};
let image = new Image();
image.onload = () => {
obj.width = image.width;
obj.height = image.height;
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image);
};
image.src = url;
return obj;
}
function updateKeywords () {
let displayKeywords = [];
if (config.SHADING) displayKeywords.push("SHADING");
if (config.BLOOM) displayKeywords.push("BLOOM");
if (config.SUNRAYS) displayKeywords.push("SUNRAYS");
displayMaterial.setKeywords(displayKeywords);
}
updateKeywords();
initFramebuffers();
multipleSplats(parseInt(Math.random() * 20) + 5);
let lastUpdateTime = Date.now();
let colorUpdateTimer = 0.0;
update();
function update () {
const dt = calcDeltaTime();
if (resizeCanvas())
initFramebuffers();
updateColors(dt);
applyInputs();
if (!config.PAUSED)
step(dt);
render(null);
requestAnimationFrame(update);
}
function calcDeltaTime () {
let now = Date.now();
let dt = (now - lastUpdateTime) / 1000;
dt = Math.min(dt, 0.016666);
lastUpdateTime = now;
return dt;
}
function resizeCanvas () {
let width = scaleByPixelRatio(canvas.clientWidth);
let height = scaleByPixelRatio(canvas.clientHeight);
if (canvas.width != width || canvas.height != height) {
canvas.width = width;
canvas.height = height;
return true;
}
return false;
}
function updateColors (dt) {
if (!config.COLORFUL) return;
colorUpdateTimer += dt * config.COLOR_UPDATE_SPEED;
if (colorUpdateTimer >= 1) {
colorUpdateTimer = wrap(colorUpdateTimer, 0, 1);
pointers.forEach(p => {
p.color = generateColor();
});
}
}
function applyInputs () {
if (splatStack.length > 0)
multipleSplats(splatStack.pop());
pointers.forEach(p => {
if (p.moved) {
p.moved = false;
splatPointer(p);
}
});
}
function step (dt) {
gl.disable(gl.BLEND);
curlProgram.bind();
gl.uniform2f(curlProgram.uniforms.texelSize, velocity.texelSizeX, velocity.texelSizeY);
gl.uniform1i(curlProgram.uniforms.uVelocity, velocity.read.attach(0));
blit(curl);
vorticityProgram.bind();
gl.uniform2f(vorticityProgram.uniforms.texelSize, velocity.texelSizeX, velocity.texelSizeY);
gl.uniform1i(vorticityProgram.uniforms.uVelocity, velocity.read.attach(0));
gl.uniform1i(vorticityProgram.uniforms.uCurl, curl.attach(1));
gl.uniform1f(vorticityProgram.uniforms.curl, config.CURL);
gl.uniform1f(vorticityProgram.uniforms.dt, dt);
blit(velocity.write);
velocity.swap();
divergenceProgram.bind();
gl.uniform2f(divergenceProgram.uniforms.texelSize, velocity.texelSizeX, velocity.texelSizeY);
gl.uniform1i(divergenceProgram.uniforms.uVelocity, velocity.read.attach(0));
blit(divergence);
clearProgram.bind();
gl.uniform1i(clearProgram.uniforms.uTexture, pressure.read.attach(0));
gl.uniform1f(clearProgram.uniforms.value, config.PRESSURE);
blit(pressure.write);
pressure.swap();
pressureProgram.bind();
gl.uniform2f(pressureProgram.uniforms.texelSize, velocity.texelSizeX, velocity.texelSizeY);
gl.uniform1i(pressureProgram.uniforms.uDivergence, divergence.attach(0));
for (let i = 0; i < config.PRESSURE_ITERATIONS; i++) {
gl.uniform1i(pressureProgram.uniforms.uPressure, pressure.read.attach(1));
blit(pressure.write);
pressure.swap();
}
gradienSubtractProgram.bind();
gl.uniform2f(gradienSubtractProgram.uniforms.texelSize, velocity.texelSizeX, velocity.texelSizeY);
gl.uniform1i(gradienSubtractProgram.uniforms.uPressure, pressure.read.attach(0));
gl.uniform1i(gradienSubtractProgram.uniforms.uVelocity, velocity.read.attach(1));
blit(velocity.write);
velocity.swap();
advectionProgram.bind();
gl.uniform2f(advectionProgram.uniforms.texelSize, velocity.texelSizeX, velocity.texelSizeY);
if (!ext.supportLinearFiltering)
gl.uniform2f(advectionProgram.uniforms.dyeTexelSize, velocity.texelSizeX, velocity.texelSizeY);
let velocityId = velocity.read.attach(0);
gl.uniform1i(advectionProgram.uniforms.uVelocity, velocityId);
gl.uniform1i(advectionProgram.uniforms.uSource, velocityId);
gl.uniform1f(advectionProgram.uniforms.dt, dt);
gl.uniform1f(advectionProgram.uniforms.dissipation, config.VELOCITY_DISSIPATION);
blit(velocity.write);
velocity.swap();
if (!ext.supportLinearFiltering)
gl.uniform2f(advectionProgram.uniforms.dyeTexelSize, dye.texelSizeX, dye.texelSizeY);
gl.uniform1i(advectionProgram.uniforms.uVelocity, velocity.read.attach(0));
gl.uniform1i(advectionProgram.uniforms.uSource, dye.read.attach(1));
gl.uniform1f(advectionProgram.uniforms.dissipation, config.DENSITY_DISSIPATION);
blit(dye.write);
dye.swap();
}
function render (target) {
if (config.BLOOM)
applyBloom(dye.read, bloom);
if (config.SUNRAYS) {
applySunrays(dye.read, dye.write, sunrays);
blur(sunrays, sunraysTemp, 1);
}
if (target == null || !config.TRANSPARENT) {
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
gl.enable(gl.BLEND);
}
else {
gl.disable(gl.BLEND);
}
if (!config.TRANSPARENT)
drawColor(target, normalizeColor(config.BACK_COLOR));
if (target == null && config.TRANSPARENT)
drawCheckerboard(target);
drawDisplay(target);
}
function drawColor (target, color) {
colorProgram.bind();
gl.uniform4f(colorProgram.uniforms.color, color.r, color.g, color.b, 1);
blit(target);
}
function drawCheckerboard (target) {
checkerboardProgram.bind();
gl.uniform1f(checkerboardProgram.uniforms.aspectRatio, canvas.width / canvas.height);
blit(target);
}
function drawDisplay (target) {
let width = target == null ? gl.drawingBufferWidth : target.width;
let height = target == null ? gl.drawingBufferHeight : target.height;
displayMaterial.bind();
if (config.SHADING)
gl.uniform2f(displayMaterial.uniforms.texelSize, 1.0 / width, 1.0 / height);
gl.uniform1i(displayMaterial.uniforms.uTexture, dye.read.attach(0));
if (config.BLOOM) {
gl.uniform1i(displayMaterial.uniforms.uBloom, bloom.attach(1));
gl.uniform1i(displayMaterial.uniforms.uDithering, ditheringTexture.attach(2));
let scale = getTextureScale(ditheringTexture, width, height);
gl.uniform2f(displayMaterial.uniforms.ditherScale, scale.x, scale.y);
}
if (config.SUNRAYS)
gl.uniform1i(displayMaterial.uniforms.uSunrays, sunrays.attach(3));
blit(target);
}
function applyBloom (source, destination) {
if (bloomFramebuffers.length < 2)
return;
let last = destination;
gl.disable(gl.BLEND);
bloomPrefilterProgram.bind();
let knee = config.BLOOM_THRESHOLD * config.BLOOM_SOFT_KNEE + 0.0001;
let curve0 = config.BLOOM_THRESHOLD - knee;
let curve1 = knee * 2;
let curve2 = 0.25 / knee;
gl.uniform3f(bloomPrefilterProgram.uniforms.curve, curve0, curve1, curve2);
gl.uniform1f(bloomPrefilterProgram.uniforms.threshold, config.BLOOM_THRESHOLD);
gl.uniform1i(bloomPrefilterProgram.uniforms.uTexture, source.attach(0));
blit(last);
bloomBlurProgram.bind();
for (let i = 0; i < bloomFramebuffers.length; i++) {
let dest = bloomFramebuffers[i];
gl.uniform2f(bloomBlurProgram.uniforms.texelSize, last.texelSizeX, last.texelSizeY);
gl.uniform1i(bloomBlurProgram.uniforms.uTexture, last.attach(0));
blit(dest);
last = dest;
}
gl.blendFunc(gl.ONE, gl.ONE);
gl.enable(gl.BLEND);
for (let i = bloomFramebuffers.length - 2; i >= 0; i--) {
let baseTex = bloomFramebuffers[i];
gl.uniform2f(bloomBlurProgram.uniforms.texelSize, last.texelSizeX, last.texelSizeY);
gl.uniform1i(bloomBlurProgram.uniforms.uTexture, last.attach(0));
gl.viewport(0, 0, baseTex.width, baseTex.height);
blit(baseTex);
last = baseTex;
}
gl.disable(gl.BLEND);
bloomFinalProgram.bind();
gl.uniform2f(bloomFinalProgram.uniforms.texelSize, last.texelSizeX, last.texelSizeY);
gl.uniform1i(bloomFinalProgram.uniforms.uTexture, last.attach(0));
gl.uniform1f(bloomFinalProgram.uniforms.intensity, config.BLOOM_INTENSITY);
blit(destination);
}
function applySunrays (source, mask, destination) {
gl.disable(gl.BLEND);
sunraysMaskProgram.bind();
gl.uniform1i(sunraysMaskProgram.uniforms.uTexture, source.attach(0));
blit(mask);
sunraysProgram.bind();
gl.uniform1f(sunraysProgram.uniforms.weight, config.SUNRAYS_WEIGHT);
gl.uniform1i(sunraysProgram.uniforms.uTexture, mask.attach(0));
blit(destination);
}
function blur (target, temp, iterations) {
blurProgram.bind();
for (let i = 0; i < iterations; i++) {
gl.uniform2f(blurProgram.uniforms.texelSize, target.texelSizeX, 0.0);
gl.uniform1i(blurProgram.uniforms.uTexture, target.attach(0));
blit(temp);
gl.uniform2f(blurProgram.uniforms.texelSize, 0.0, target.texelSizeY);
gl.uniform1i(blurProgram.uniforms.uTexture, temp.attach(0));
blit(target);
}
}
function splatPointer (pointer) {
let dx = pointer.deltaX * config.SPLAT_FORCE;
let dy = pointer.deltaY * config.SPLAT_FORCE;
splat(pointer.texcoordX, pointer.texcoordY, dx, dy, pointer.color);
}
function multipleSplats (amount) {
for (let i = 0; i < amount; i++) {
const color = generateColor();
color.r *= 10.0;
color.g *= 10.0;
color.b *= 10.0;
const x = Math.random();
const y = Math.random();
const dx = 1000 * (Math.random() - 0.5);
const dy = 1000 * (Math.random() - 0.5);
splat(x, y, dx, dy, color);
}
}
function splat (x, y, dx, dy, color) {
splatProgram.bind();
gl.uniform1i(splatProgram.uniforms.uTarget, velocity.read.attach(0));
gl.uniform1f(splatProgram.uniforms.aspectRatio, canvas.width / canvas.height);
gl.uniform2f(splatProgram.uniforms.point, x, y);
gl.uniform3f(splatProgram.uniforms.color, dx, dy, 0.0);
gl.uniform1f(splatProgram.uniforms.radius, correctRadius(config.SPLAT_RADIUS / 100.0));
blit(velocity.write);
velocity.swap();
gl.uniform1i(splatProgram.uniforms.uTarget, dye.read.attach(0));
gl.uniform3f(splatProgram.uniforms.color, color.r, color.g, color.b);
blit(dye.write);
dye.swap();
}
function correctRadius (radius) {
let aspectRatio = canvas.width / canvas.height;
if (aspectRatio > 1)
radius *= aspectRatio;
return radius;
}
canvas.addEventListener('mousedown', e => {
let posX = scaleByPixelRatio(e.offsetX);
let posY = scaleByPixelRatio(e.offsetY);
let pointer = pointers.find(p => p.id == -1);
if (pointer == null)
pointer = new pointerPrototype();
updatePointerDownData(pointer, -1, posX, posY);
});
canvas.addEventListener('mousemove', e => {
let pointer = pointers[0];
if (!pointer.down) return;
let posX = scaleByPixelRatio(e.offsetX);
let posY = scaleByPixelRatio(e.offsetY);
updatePointerMoveData(pointer, posX, posY);
});
window.addEventListener('mouseup', () => {
updatePointerUpData(pointers[0]);
});
canvas.addEventListener('touchstart', e => {
e.preventDefault();
const touches = e.targetTouches;
while (touches.length >= pointers.length)
pointers.push(new pointerPrototype());
for (let i = 0; i < touches.length; i++) {
let posX = scaleByPixelRatio(touches[i].pageX);
let posY = scaleByPixelRatio(touches[i].pageY);
updatePointerDownData(pointers[i + 1], touches[i].identifier, posX, posY);
}
});
canvas.addEventListener('touchmove', e => {
e.preventDefault();
const touches = e.targetTouches;
for (let i = 0; i < touches.length; i++) {
let pointer = pointers[i + 1];
if (!pointer.down) continue;
let posX = scaleByPixelRatio(touches[i].pageX);
let posY = scaleByPixelRatio(touches[i].pageY);
updatePointerMoveData(pointer, posX, posY);
}
}, false);
window.addEventListener('touchend', e => {
const touches = e.changedTouches;
for (let i = 0; i < touches.length; i++)
{
let pointer = pointers.find(p => p.id == touches[i].identifier);
if (pointer == null) continue;
updatePointerUpData(pointer);
}
});
window.addEventListener('keydown', e => {
if (e.code === 'KeyP')
config.PAUSED = !config.PAUSED;
if (e.key === ' ')
splatStack.push(parseInt(Math.random() * 20) + 5);
});
function updatePointerDownData (pointer, id, posX, posY) {
pointer.id = id;
pointer.down = true;
pointer.moved = false;
pointer.texcoordX = posX / canvas.width;
pointer.texcoordY = 1.0 - posY / canvas.height;
pointer.prevTexcoordX = pointer.texcoordX;
pointer.prevTexcoordY = pointer.texcoordY;
pointer.deltaX = 0;
pointer.deltaY = 0;
pointer.color = generateColor();
}
function updatePointerMoveData (pointer, posX, posY) {
pointer.prevTexcoordX = pointer.texcoordX;
pointer.prevTexcoordY = pointer.texcoordY;
pointer.texcoordX = posX / canvas.width;
pointer.texcoordY = 1.0 - posY / canvas.height;
pointer.deltaX = correctDeltaX(pointer.texcoordX - pointer.prevTexcoordX);
pointer.deltaY = correctDeltaY(pointer.texcoordY - pointer.prevTexcoordY);
pointer.moved = Math.abs(pointer.deltaX) > 0 || Math.abs(pointer.deltaY) > 0;
}
function updatePointerUpData (pointer) {
pointer.down = false;
}
function correctDeltaX (delta) {
let aspectRatio = canvas.width / canvas.height;
if (aspectRatio < 1) delta *= aspectRatio;
return delta;
}
function correctDeltaY (delta) {
let aspectRatio = canvas.width / canvas.height;
if (aspectRatio > 1) delta /= aspectRatio;
return delta;
}
function generateColor () {
let c = HSVtoRGB(Math.random(), 1.0, 1.0);
c.r *= 0.15;
c.g *= 0.15;
c.b *= 0.15;
return c;
}
function HSVtoRGB (h, s, v) {
let r, g, b, i, f, p, q, t;
i = Math.floor(h * 6);
f = h * 6 - i;
p = v * (1 - s);
q = v * (1 - f * s);
t = v * (1 - (1 - f) * s);
switch (i % 6) {
case 0: r = v, g = t, b = p; break;
case 1: r = q, g = v, b = p; break;
case 2: r = p, g = v, b = t; break;
case 3: r = p, g = q, b = v; break;
case 4: r = t, g = p, b = v; break;
case 5: r = v, g = p, b = q; break;
}
return {
r,
g,
b
};
}
function normalizeColor (input) {
let output = {
r: input.r / 255,
g: input.g / 255,
b: input.b / 255
};
return output;
}
function wrap (value, min, max) {
let range = max - min;
if (range == 0) return min;
return (value - min) % range + min;
}
function getResolution (resolution) {
let aspectRatio = gl.drawingBufferWidth / gl.drawingBufferHeight;
if (aspectRatio < 1)
aspectRatio = 1.0 / aspectRatio;
let min = Math.round(resolution);
let max = Math.round(resolution * aspectRatio);
if (gl.drawingBufferWidth > gl.drawingBufferHeight)
return { width: max, height: min };
else
return { width: min, height: max };
}
function getTextureScale (texture, width, height) {
return {
x: width / texture.width,
y: height / texture.height
};
}
function scaleByPixelRatio (input) {
let pixelRatio = window.devicePixelRatio || 1;
return Math.floor(input * pixelRatio);
}
function hashCode (s) {
if (s.length == 0) return 0;
let hash = 0;
for (let i = 0; i < s.length; i++) {
hash = (hash << 5) - hash + s.charCodeAt(i);
hash |= 0; // Convert to 32bit integer
}
return hash;
};
gitextract_spjirf4e/ ├── .github/ │ └── FUNDING.yml ├── LICENSE ├── README.md ├── index.html └── script.js
SYMBOL INDEX (63 symbols across 1 files)
FILE: script.js
function pointerPrototype (line 87) | function pointerPrototype () {
function getWebGLContext (line 118) | function getWebGLContext (canvas) {
function getSupportedFormat (line 170) | function getSupportedFormat (gl, internalFormat, format, type)
function supportRenderTextureFormat (line 191) | function supportRenderTextureFormat (gl, internalFormat, format, type) {
function startGUI (line 208) | function startGUI () {
function isMobile (line 283) | function isMobile () {
function captureScreenshot (line 287) | function captureScreenshot () {
function framebufferToTexture (line 301) | function framebufferToTexture (target) {
function normalizeTexture (line 309) | function normalizeTexture (texture, width, height) {
function clamp01 (line 325) | function clamp01 (input) {
function textureToCanvas (line 329) | function textureToCanvas (texture, width, height) {
function downloadURI (line 342) | function downloadURI (filename, uri) {
class Material (line 351) | class Material {
method constructor (line 352) | constructor (vertexShader, fragmentShaderSource) {
method setKeywords (line 360) | setKeywords (keywords) {
method bind (line 379) | bind () {
class Program (line 384) | class Program {
method constructor (line 385) | constructor (vertexShader, fragmentShader) {
method bind (line 391) | bind () {
function createProgram (line 396) | function createProgram (vertexShader, fragmentShader) {
function getUniforms (line 408) | function getUniforms (program) {
function compileShader (line 418) | function compileShader (type, source, keywords) {
function addKeywords (line 431) | function addKeywords (source, keywords) {
function CHECK_FRAMEBUFFER_STATUS (line 944) | function CHECK_FRAMEBUFFER_STATUS () {
function initFramebuffers (line 982) | function initFramebuffers () {
function initBloomFramebuffers (line 1012) | function initBloomFramebuffers () {
function initSunraysFramebuffers (line 1034) | function initSunraysFramebuffers () {
function createFBO (line 1045) | function createFBO (w, h, internalFormat, format, type, param) {
function createDoubleFBO (line 1079) | function createDoubleFBO (w, h, internalFormat, format, type, param) {
function resizeFBO (line 1108) | function resizeFBO (target, w, h, internalFormat, format, type, param) {
function resizeDoubleFBO (line 1116) | function resizeDoubleFBO (target, w, h, internalFormat, format, type, pa...
function createTextureAsync (line 1128) | function createTextureAsync (url) {
function updateKeywords (line 1160) | function updateKeywords () {
function update (line 1176) | function update () {
function calcDeltaTime (line 1188) | function calcDeltaTime () {
function resizeCanvas (line 1196) | function resizeCanvas () {
function updateColors (line 1207) | function updateColors (dt) {
function applyInputs (line 1219) | function applyInputs () {
function step (line 1231) | function step (dt) {
function render (line 1296) | function render (target) {
function drawColor (line 1319) | function drawColor (target, color) {
function drawCheckerboard (line 1325) | function drawCheckerboard (target) {
function drawDisplay (line 1331) | function drawDisplay (target) {
function applyBloom (line 1350) | function applyBloom (source, destination) {
function applySunrays (line 1396) | function applySunrays (source, mask, destination) {
function blur (line 1408) | function blur (target, temp, iterations) {
function splatPointer (line 1421) | function splatPointer (pointer) {
function multipleSplats (line 1427) | function multipleSplats (amount) {
function splat (line 1441) | function splat (x, y, dx, dy, color) {
function correctRadius (line 1457) | function correctRadius (radius) {
function updatePointerDownData (line 1526) | function updatePointerDownData (pointer, id, posX, posY) {
function updatePointerMoveData (line 1539) | function updatePointerMoveData (pointer, posX, posY) {
function updatePointerUpData (line 1549) | function updatePointerUpData (pointer) {
function correctDeltaX (line 1553) | function correctDeltaX (delta) {
function correctDeltaY (line 1559) | function correctDeltaY (delta) {
function generateColor (line 1565) | function generateColor () {
function HSVtoRGB (line 1573) | function HSVtoRGB (h, s, v) {
function normalizeColor (line 1597) | function normalizeColor (input) {
function wrap (line 1606) | function wrap (value, min, max) {
function getResolution (line 1612) | function getResolution (resolution) {
function getTextureScale (line 1626) | function getTextureScale (texture, width, height) {
function scaleByPixelRatio (line 1633) | function scaleByPixelRatio (input) {
function hashCode (line 1638) | function hashCode (s) {
Condensed preview — 5 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (63K chars).
[
{
"path": ".github/FUNDING.yml",
"chars": 852,
"preview": "# These are supported funding model platforms\n\ngithub: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [u"
},
{
"path": "LICENSE",
"chars": 1072,
"preview": "MIT License\n\nCopyright (c) 2017 Pavel Dobryakov\n\nPermission is hereby granted, free of charge, to any person obtaining a"
},
{
"path": "README.md",
"chars": 433,
"preview": "# WebGL Fluid Simulation\n\n[Play here](https://paveldogreat.github.io/WebGL-Fluid-Simulation/)\n\n<img src=\"/screenshot.jpg"
},
{
"path": "index.html",
"chars": 6735,
"preview": "<!DOCTYPE html>\n<html>\n <head>\n <meta charset=\"utf-8\">\n <meta http-equiv=\"Cache-Control\" content=\"no-ca"
},
{
"path": "script.js",
"chars": 51849,
"preview": "/*\nMIT License\n\nCopyright (c) 2017 Pavel Dobryakov\n\nPermission is hereby granted, free of charge, to any person obtainin"
}
]
About this extraction
This page contains the full source code of the PavelDoGreat/WebGL-Fluid-Simulation GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 5 files (59.5 KB), approximately 16.5k tokens, and a symbol index with 63 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.