const canvas = document.getElementById('glCanvas');
const gl = canvas.getContext('webgl');
gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
const vsSource = `
attribute vec3 aPosition;
attribute vec3 aVelocity;
attribute float aLife;
uniform float uTime;
varying lowp float vLife;
void main() {
vec3 pos = aPosition + aVelocity * uTime;
pos.y -= 0.5 * 9.8 * uTime * uTime;
gl_Position = vec4(pos, 1.0);
gl_PointSize = 6.0 * aLife;
vLife = aLife;
}
`;
const fsSource = `
varying lowp float vLife;
void main() {
lowp vec2 coord = gl_PointCoord - vec2(0.5);
lowp float dist = length(coord);
if (dist > 0.5) discard;
lowp vec3 color = mix(
vec3(1.0, 0.3, 0.0),
vec3(1.0, 1.0, 0.0),
vLife
);
lowp float alpha = (1.0 - dist * 2.0) * vLife;
gl_FragColor = vec4(color, alpha);
}
`;
function initShaderProgram(gl, vsSource, fsSource) {
const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vsSource);
const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource);
const shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);
return shaderProgram;
}
function loadShader(gl, type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
return shader;
}
const numParticles = 200;
const particles = [];
class Particle {
constructor() {
this.reset();
}
reset() {
this.position = [0, -0.5, 0];
const angle = Math.random() * Math.PI * 2;
const speed = 0.5 + Math.random() * 0.5;
this.velocity = [
Math.cos(angle) * speed,
2 + Math.random(),
Math.sin(angle) * speed * 0.3
];
this.life = 1.0;
this.age = 0;
}
update(dt) {
this.age += dt;
this.life = Math.max(0, 1 - this.age / 2);
if (this.life === 0) this.reset();
}
}
for (let i = 0; i < numParticles; i++) {
particles.push(new Particle());
}
const positionBuffer = gl.createBuffer();
const velocityBuffer = gl.createBuffer();
const lifeBuffer = gl.createBuffer();
const shaderProgram = initShaderProgram(gl, vsSource, fsSource);
const positionAttrib = gl.getAttribLocation(shaderProgram, 'aPosition');
const velocityAttrib = gl.getAttribLocation(shaderProgram, 'aVelocity');
const lifeAttrib = gl.getAttribLocation(shaderProgram, 'aLife');
const timeUniform = gl.getUniformLocation(shaderProgram, 'uTime');
let lastTime = 0;
function render(time) {
time *= 0.001;
const dt = time - lastTime;
lastTime = time;
particles.forEach(p => p.update(dt));
const positions = [];
const velocities = [];
const lives = [];
particles.forEach(p => {
positions.push(...p.position);
velocities.push(...p.velocity);
lives.push(p.life);
});
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.DYNAMIC_DRAW);
gl.vertexAttribPointer(positionAttrib, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(positionAttrib);
gl.bindBuffer(gl.ARRAY_BUFFER, velocityBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(velocities), gl.DYNAMIC_DRAW);
gl.vertexAttribPointer(velocityAttrib, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(velocityAttrib);
gl.bindBuffer(gl.ARRAY_BUFFER, lifeBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(lives), gl.DYNAMIC_DRAW);
gl.vertexAttribPointer(lifeAttrib, 1, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(lifeAttrib);
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.useProgram(shaderProgram);
gl.uniform1f(timeUniform, 0);
gl.drawArrays(gl.POINTS, 0, numParticles);
requestAnimationFrame(render);
}
render(0);