mirror of
https://github.com/greggman/twgl.js.git
synced 2025-12-08 19:26:07 +00:00
244 lines
6.9 KiB
HTML
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> |