Public
Edited
Dec 29
3 forks
20 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
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);

const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
return status == gl.FRAMEBUFFER_COMPLETE;
}
Insert cell
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);

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);
}
}
Insert cell
function compileShader(type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
return shader;
}
Insert cell
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);
}
`)
Insert cell
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];
}
Insert cell
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;
}
}
}
Insert cell
textureWidth = gl.drawingBufferWidth >> downsample
Insert cell
textureHeight = gl.drawingBufferHeight >> downsample
Insert cell
Insert cell
density = createDoubleFBO(0, textureWidth, textureHeight, ext.internalFormat, gl.RGBA, ext.halfFloatTexType, ext.supportLinearFloat ? gl.LINEAR : gl.NEAREST)
Insert cell
velocity = createDoubleFBO(2, textureWidth, textureHeight, ext.internalFormatRG, ext.formatRG, ext.halfFloatTexType, ext.supportLinearFloat ? gl.LINEAR : gl.NEAREST)
Insert cell
divergence = createFBO(4, textureWidth, textureHeight, ext.internalFormatRG, ext.formatRG, ext.halfFloatTexType, gl.NEAREST)
Insert cell
curl = createFBO(5, textureWidth, textureHeight, ext.internalFormatRG, ext.formatRG, ext.halfFloatTexType, gl.NEAREST)
Insert cell
pressure = createDoubleFBO(6, textureWidth, textureHeight, ext.internalFormatRG, ext.formatRG, ext.halfFloatTexType, gl.NEAREST)
Insert cell
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);
}
}
Insert cell
clearProgram = new GLProgram(
baseVertexShader,
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);
}
`
)
)
Insert cell
splatProgram = new GLProgram(
baseVertexShader,
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);
}
`
)
)
Insert cell
advectionProgram = new GLProgram(
baseVertexShader,
ext.supportLinearFloat
? 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);
}
`
)
: 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;
}
`
)
)
Insert cell
divergenceProgram = new GLProgram(
baseVertexShader,
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);
}
`
)
)
Insert cell
curlProgram = new GLProgram(
baseVertexShader,
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);
}
`
)
)
Insert cell
vorticityProgram = new GLProgram(
baseVertexShader,
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);
}
`
)
)
Insert cell
pressureProgram = new GLProgram(
baseVertexShader,
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);
}
`
)
)
Insert cell
gradienSubtractProgram = new GLProgram(
baseVertexShader,
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);
}
`
)
)
Insert cell
displayProgram = new GLProgram(
baseVertexShader,
compileShader(
gl.FRAGMENT_SHADER,
`
precision highp float;
precision mediump sampler2D;
varying vec2 vUv;
uniform sampler2D uTexture;
void main () {
vec4 textureColor = texture2D(uTexture, vUv);
gl_FragColor = vec4(1.0 - textureColor.r, 1.0 - textureColor.g,1.0 - textureColor.b,1.0);
}
`
)
)
Insert cell
function splat(x, y, dx, dy, color, p) {
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, 0.0001);
blit(velocity.second[1]);
velocity.swap();

gl.uniform1i(splatProgram.uniforms.uTarget, density.first[2]);
gl.uniform3f(
splatProgram.uniforms.color,
0.3 * (256 - color[0]),
0.3 * (256 - color[1]),
0.3 * (256 - color[2])
);
blit(density.second[1]);
density.swap();

gl.uniform1i(splatProgram.uniforms.uTarget, pressure.first[2]);
gl.uniform3f(splatProgram.uniforms.color, p, p, p);
blit(pressure.second[1]);
pressure.swap();
}
Insert cell
mainLoop = {
let i = 0;
while (true) {
gl.viewport(0, 0, textureWidth, textureHeight);

if (mutable mouse) mouseSplat(mutable mouse);

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, velocityDissipation);
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, densityDissipation);
blit(density.second[1]);
density.swap();

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, curlDegree);
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();

let pressureTexId = pressure.first[2];
gl.activeTexture(gl.TEXTURE0 + pressureTexId);
gl.bindTexture(gl.TEXTURE_2D, pressure.first[0]);
gl.uniform1i(clearProgram.uniforms.uTexture, pressureTexId);
gl.uniform1f(clearProgram.uniforms.value, 0.8);
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]);
pressureTexId = pressure.first[2];
gl.uniform1i(pressureProgram.uniforms.uPressure, pressureTexId);
gl.activeTexture(gl.TEXTURE0 + pressureTexId);
for (let i = 0; i < 25; i++) {
gl.bindTexture(gl.TEXTURE_2D, pressure.first[0]);
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);

yield ++i;
}
}
Insert cell
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more