151 lines
4.3 KiB
JavaScript
151 lines
4.3 KiB
JavaScript
import { RenderTarget } from '../core/RenderTarget.js';
|
|
import { Program } from '../core/Program.js';
|
|
import { Mesh } from '../core/Mesh.js';
|
|
import { Vec2 } from '../math/Vec2.js';
|
|
import { Triangle } from './Triangle.js';
|
|
|
|
export class Flowmap {
|
|
constructor(
|
|
gl,
|
|
{
|
|
size = 128, // default size of the render targets
|
|
falloff = 0.3, // size of the stamp, percentage of the size
|
|
alpha = 1, // opacity of the stamp
|
|
dissipation = 0.98, // affects the speed that the stamp fades. Closer to 1 is slower
|
|
type, // Pass in gl.FLOAT to force it, defaults to gl.HALF_FLOAT
|
|
} = {}
|
|
) {
|
|
const _this = this;
|
|
this.gl = gl;
|
|
|
|
// output uniform containing render target textures
|
|
this.uniform = { value: null };
|
|
|
|
this.mask = {
|
|
read: null,
|
|
write: null,
|
|
|
|
// Helper function to ping pong the render targets and update the uniform
|
|
swap: () => {
|
|
let temp = _this.mask.read;
|
|
_this.mask.read = _this.mask.write;
|
|
_this.mask.write = temp;
|
|
_this.uniform.value = _this.mask.read.texture;
|
|
},
|
|
};
|
|
|
|
{
|
|
createFBOs();
|
|
|
|
this.aspect = 1;
|
|
this.mouse = new Vec2();
|
|
this.velocity = new Vec2();
|
|
|
|
this.mesh = initProgram();
|
|
}
|
|
|
|
function createFBOs() {
|
|
// Requested type not supported, fall back to half float
|
|
if (!type) type = gl.HALF_FLOAT || gl.renderer.extensions['OES_texture_half_float'].HALF_FLOAT_OES;
|
|
|
|
let minFilter = (() => {
|
|
if (gl.renderer.isWebgl2) return gl.LINEAR;
|
|
if (gl.renderer.extensions[`OES_texture_${type === gl.FLOAT ? '' : 'half_'}float_linear`]) return gl.LINEAR;
|
|
return gl.NEAREST;
|
|
})();
|
|
|
|
const options = {
|
|
width: size,
|
|
height: size,
|
|
type,
|
|
format: gl.RGBA,
|
|
internalFormat: gl.renderer.isWebgl2 ? (type === gl.FLOAT ? gl.RGBA32F : gl.RGBA16F) : gl.RGBA,
|
|
minFilter,
|
|
depth: false,
|
|
};
|
|
|
|
_this.mask.read = new RenderTarget(gl, options);
|
|
_this.mask.write = new RenderTarget(gl, options);
|
|
_this.mask.swap();
|
|
}
|
|
|
|
function initProgram() {
|
|
return new Mesh(gl, {
|
|
// Triangle that includes -1 to 1 range for 'position', and 0 to 1 range for 'uv'.
|
|
geometry: new Triangle(gl),
|
|
|
|
program: new Program(gl, {
|
|
vertex,
|
|
fragment,
|
|
uniforms: {
|
|
tMap: _this.uniform,
|
|
|
|
uFalloff: { value: falloff * 0.5 },
|
|
uAlpha: { value: alpha },
|
|
uDissipation: { value: dissipation },
|
|
|
|
// User needs to update these
|
|
uAspect: { value: 1 },
|
|
uMouse: { value: _this.mouse },
|
|
uVelocity: { value: _this.velocity },
|
|
},
|
|
depthTest: false,
|
|
}),
|
|
});
|
|
}
|
|
}
|
|
|
|
update() {
|
|
this.mesh.program.uniforms.uAspect.value = this.aspect;
|
|
|
|
this.gl.renderer.render({
|
|
scene: this.mesh,
|
|
target: this.mask.write,
|
|
clear: false,
|
|
});
|
|
this.mask.swap();
|
|
}
|
|
}
|
|
|
|
const vertex = /* glsl */ `
|
|
attribute vec2 uv;
|
|
attribute vec2 position;
|
|
|
|
varying vec2 vUv;
|
|
|
|
void main() {
|
|
vUv = uv;
|
|
gl_Position = vec4(position, 0, 1);
|
|
}
|
|
`;
|
|
|
|
const fragment = /* glsl */ `
|
|
precision highp float;
|
|
|
|
uniform sampler2D tMap;
|
|
|
|
uniform float uFalloff;
|
|
uniform float uAlpha;
|
|
uniform float uDissipation;
|
|
|
|
uniform float uAspect;
|
|
uniform vec2 uMouse;
|
|
uniform vec2 uVelocity;
|
|
|
|
varying vec2 vUv;
|
|
|
|
void main() {
|
|
vec4 color = texture2D(tMap, vUv) * uDissipation;
|
|
|
|
vec2 cursor = vUv - uMouse;
|
|
cursor.x *= uAspect;
|
|
|
|
vec3 stamp = vec3(uVelocity * vec2(1, -1), 1.0 - pow(1.0 - min(1.0, length(uVelocity)), 3.0));
|
|
float falloff = smoothstep(uFalloff, 0.0, length(cursor)) * uAlpha;
|
|
|
|
color.rgb = mix(color.rgb, stamp, vec3(falloff));
|
|
|
|
gl_FragColor = color;
|
|
}
|
|
`;
|