twgl.js/examples/gpgpu-particles.html
Gregg Tavares 67551111b4 Fix CSS
There was a time when 100vh was correct but mobile browsers changed.
2025-10-13 14:54:12 -07:00

244 lines
6.9 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<meta property="og:image:height" content="419">
<meta property="og:image:width" content="800">
<meta property="og:title" content="TWGL.js - GPGPU Particles">
<meta property="og:description" content="This example shows how to use GPGPU in WebGL with the twgl.js library.">
<meta property="og:url" content="http://twgljs.org/examples/gpgpu-particles.html">
<meta property="og:image" content="http://twgljs.org/examples/screenshots/gpgpu-particles.png">
<link href="/resources/images/twgljs-icon.png" rel="shortcut icon" type="image/png">
<title>GPGPU Particles.</title>
<style>
html, body {
margin: 0;
font-family: monospace;
height: 100%;
}
canvas {
width: 100%;
height: 100%;
display: block;
}
</style>
</head>
<body>
<canvas id="canvasgl"></canvas>
<div id="b" style="position: absolute; top: 10px; width: 100%; text-align: center; z-index: 2;"><a href="http://twgljs.org">twgl.js</a> - GPGPU Particles.</div>
<script src="../dist/7.x/twgl.min.js"></script>
<script>
'use strict';
// particle initialization
const vInit = `
precision mediump float;
attribute vec2 position;
varying vec2 v_position;
void main() {
v_position = 0.5 * position + 0.5;
gl_Position = vec4(position, 0.0, 1.0);
}`;
const fInit = `
precision mediump float;
varying vec2 v_position;
void main() {
gl_FragColor = vec4(v_position, 0.0, 0.0);
}`;
// particle physics in a cycle
const vPhysics = `
precision mediump float;
attribute vec2 position;
varying vec2 v_texcoord;
void main() {
v_texcoord = 0.5 * position + 0.5;
gl_Position = vec4(position, 0.0, 1.0);
}`;
const fPhysics = `
precision mediump float;
varying vec2 v_texcoord;
uniform sampler2D u_texture;
uniform vec2 gravity_center;
uniform float off_gravity;
uniform float restore_colors;
uniform float dt;
vec2 n2rand() {
return vec2(fract(sin(dot(v_texcoord.xy, vec2(12.9898, 78.233))) * 43758.5453),
fract(sin(dot(v_texcoord.xy * 1.61803, vec2(12.9898, 78.233))) * 43758.5453));
}
void main() {
vec4 particle = texture2D(u_texture, v_texcoord);
vec2 r, d, a, x, v;
x = particle.xy;
v = particle.zw;
r = x - gravity_center;
d = x - v_texcoord;
// physics emulation
a = (1.0 - off_gravity) * ( -normalize(r) * dt * clamp(3. / dot(r, r), 0., 2.) + 0.03 * (n2rand() - 0.5) );
x += (1.0 - restore_colors) * (v - 0.5) * dt;
x += restore_colors * -d;
v += a;
// mirror edge
v -= 2.0 * (v - 0.5) * step(0.0, abs(x - 0.5) - 0.5);
x = abs(abs(abs(x) - 1.0) - 1.0);
gl_FragColor = vec4(x, v);
}`;
// drawing particles
const vDraw = `
precision mediump float;
attribute vec2 v_texcoord;
uniform sampler2D u_texture;
varying vec3 color;
vec3 hsv2rgb(vec3 c) {
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}
void main () {
vec4 particle = texture2D(u_texture, v_texcoord);
color = hsv2rgb(vec3(0.5 * v_texcoord + 0.4, 0.9));
gl_Position = vec4(particle.xy * 2.0 - 1.0, 0.0, 1.0);
gl_PointSize = 1.0;
}`;
const fDraw = `
precision mediump float;
varying vec3 color;
void main () {
gl_FragColor = vec4(color, 1.);
}`;
const mousepos = [0.5, 0.5];
const canvas = document.getElementById('canvasgl');
const gl = canvas.getContext('webgl', { depth: false, antialiasing: false });
const programInit = twgl.createProgramInfo(gl, [vInit, fInit]);
const programPhysics = twgl.createProgramInfo(gl, [vPhysics, fPhysics]);
const programDraw = twgl.createProgramInfo(gl, [vDraw, fDraw]);
const n = 256;
const m = 256;
let fb1 = twgl.createFramebufferInfo(gl, undefined, n, m);
let fb2 = twgl.createFramebufferInfo(gl, undefined, n, m);
const positionObject = { position: { data: [1, 1, 1, -1, -1, -1, -1, 1], numComponents: 2 } };
const positionBuffer = twgl.createBufferInfoFromArrays(gl, positionObject);
const pointData = [];
for (let i = 0; i < n; i++) {
for (let j = 0; j < m; j++) {
pointData.push(i / (n - 1));
pointData.push(j / (m - 1));
}
}
const pointsObject = { v_texcoord: { data: pointData, numComponents: 2 } };
const pointsBuffer = twgl.createBufferInfoFromArrays(gl, pointsObject);
// particle initialization
gl.useProgram(programInit.program);
twgl.setBuffersAndAttributes(gl, programInit, positionBuffer);
twgl.bindFramebufferInfo(gl, fb1);
twgl.drawBufferInfo(gl, positionBuffer, gl.TRIANGLE_FAN);
let dt;
let prevTime;
let temp;
let offGravity = 0;
let restoreColors = 0;
function draw(time) {
twgl.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
dt = (prevTime) ? time - prevTime : 0;
prevTime = time;
// particle physics
gl.useProgram(programPhysics.program);
twgl.setBuffersAndAttributes(gl, programPhysics, positionBuffer);
twgl.setUniforms(programPhysics, {
u_texture: fb1.attachments[0],
gravity_center: mousepos,
off_gravity: offGravity,
restore_colors: restoreColors,
dt: 2.5 * dt,
});
twgl.bindFramebufferInfo(gl, fb2);
twgl.drawBufferInfo(gl, positionBuffer, gl.TRIANGLE_FAN);
// drawing particles
gl.useProgram(programDraw.program);
twgl.setBuffersAndAttributes(gl, programDraw, pointsBuffer);
twgl.setUniforms(programDraw, { u_texture: fb2.attachments[0] });
twgl.bindFramebufferInfo(gl, null);
twgl.drawBufferInfo(gl, pointsBuffer, gl.POINTS);
// ping-pong buffers
temp = fb1;
fb1 = fb2;
fb2 = temp;
}
(function animate(now) {
draw(now / 1000);
requestAnimationFrame(animate);
})(0);
function setMousePos(e) {
mousepos[0] = e.clientX / gl.canvas.clientWidth;
mousepos[1] = 1 - e.clientY / gl.canvas.clientHeight;
}
canvas.addEventListener('mousemove', setMousePos);
canvas.addEventListener('mouseleave', () => {
mousepos[0] = 0.5;
mousepos[1] = 0.5;
});
canvas.addEventListener('mousedown', e => {
if (e.button === 0) {
offGravity = 1;
} else {
restoreColors = 1;
}
});
window.addEventListener('mouseup', () => {
offGravity = 0;
restoreColors = 0;
});
function handleTouch(e) {
e.preventDefault();
setMousePos(e.touches[0]);
}
canvas.addEventListener('contextmenu', e => e.preventDefault());
canvas.addEventListener('touchstart', handleTouch, {passive: false});
canvas.addEventListener('touchmove', handleTouch, {passive: false});
</script>
</body>
</html>