'use strict';
function main() {
const gl = document.querySelector('canvas').getContext('webgl2')
if (!gl) {
return alert('sorry, need webgl2');
}
const ext = gl.getExtension('EXT_color_buffer_float');
if (!ext) {
return alert('sorry, need EXT_color_buffer_float');
}
const {width, height} = gl.canvas;
const numParticles = 128;
const particleParameters = []; // info that does not change
let currentParticleState = []; // info that does change
let nextParticleState = []; // computed from currentState
for (let i = 0; i < numParticles; ++i) {
particleParameters.push(rand(-100, 100), rand(-100, 100), 0, 0);
currentParticleState.push(rand(0, width), rand(0, height), 0, 0);
}
function rand(min, max) {
return Math.random() * (max - min) + min;
}
const particleParamsTex = twgl.createTexture(gl, {
src: new Float32Array(particleParameters),
internalFormat: gl.RGBA32F,
width: numParticles,
height: 1,
minMax: gl.NEAREST,
});
const currentStateTex = twgl.createTexture(gl, {
src: new Float32Array(currentParticleState),
internalFormat: gl.RGBA32F,
width: numParticles,
height: 1,
minMax: gl.NEAREST,
});
const nextStateTex = twgl.createTexture(gl, {
internalFormat: gl.RGBA32F,
width: numParticles,
height: 1,
minMax: gl.NEAREST,
});
// create a framebuffer with 1 attachment (currentStateTex)
// and record that it's numParticles wide and 1 pixel tall
let currentStateFBI = twgl.createFramebufferInfo(gl, [
{ attachment: currentStateTex, },
], numParticles, 1);
// create a framebuffer with 1 attachment (nextStateTex)
// and record that it's numParticles wide and 1 pixel tall
let nextStateFBI = twgl.createFramebufferInfo(gl, [
{ attachment: nextStateTex, },
], numParticles, 1);
const particleVS = `
#version 300 es
in vec4 position;
void main() {
gl_Position = position;
}
`;
const particleFS = `
#version 300 es
precision highp float;
uniform vec2 resolution;
uniform float deltaTime;
uniform sampler2D particleParamsTex;
uniform sampler2D currentStateTex;
out vec4 outColor;
vec4 euclideanModulo(vec4 n, vec4 m) {
return mod(mod(n, m) + m, m);
}
void main() {
int i = int(gl_FragCoord.x);
vec4 curPos = texelFetch(currentStateTex, ivec2(i, 0), 0);
vec4 velocity = texelFetch(particleParamsTex, ivec2(i, 0), 0);
outColor = euclideanModulo(curPos + velocity * deltaTime, vec4(resolution, 1, 1));
}
`;
const drawVS = `
#version 300 es
uniform sampler2D currentStateTex;
uniform vec2 resolution;
void main() {
gl_PointSize = 3.0;
// we calculated pos in pixel coords
vec4 pos = texelFetch(currentStateTex, ivec2(gl_VertexID, 0), 0);
gl_Position = vec4(
pos.xy / resolution * 2. - 1., // convert to clip space
0,
1);
}
`;
const drawFS = `
#version 300 es
precision mediump float;
out vec4 outColor;
void main() {
outColor = vec4(0, 0, 0, 1);
}
`;
// compile shaders, link program, look up locations.
const particleProgramInfo = twgl.createProgramInfo(gl, [particleVS, particleFS]);
const drawProgramInfo = twgl.createProgramInfo(gl, [drawVS, drawFS]);
// create a -1 to +1 quad vertices and put in a buffer.
const quadBufferInfo = twgl.primitives.createXYQuadBufferInfo(gl, 2);
let then = 0;
function render(now) {
now *= 0.001; // convert to seconds
const deltaTime = now - then;
then = now;
// bind the framebuffer and set the viewport to match
twgl.bindFramebufferInfo(gl, nextStateFBI);
gl.useProgram(particleProgramInfo.program);
twgl.setBuffersAndAttributes(gl, particleProgramInfo, quadBufferInfo);
twgl.setUniformsAndBindTextures(particleProgramInfo, {
resolution: [width, height],
deltaTime: deltaTime,
currentStateTex: currentStateFBI.attachments[0],
particleParamsTex,
});
// call drawArrays or drawBuffers
twgl.drawBufferInfo(gl, quadBufferInfo);
const t = nextStateFBI;
nextStateFBI = currentStateFBI;
currentStateFBI = t;
// bind the canvas and set the viewport to match
twgl.bindFramebufferInfo(gl, null);
gl.useProgram(drawProgramInfo.program);
twgl.setUniforms(drawProgramInfo, {
resolution: [width, height],
currentStateTex: currentStateFBI.attachments[0],
});
gl.drawArrays(gl.POINTS, 0, numParticles);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
}
main();
canvas { border: 1px solid black; }
<canvas></canvas>
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
newState = oldState.map(computeNewStateFromOld);
and your GPU version of the same thing? In both cases particle index 0 is always particle 0, particle index 1 is always particle 1. – gman