ProductsResearchServicesPartner with UsAboutBlogContact

Designed in California

Game DevelopmentAugust 12, 2016 3 min readUpdated: July 1, 2026

WebGL Shaders: GPU-Accelerated Particle Systems (2016)

AunimedaAunimeda
📋 Table of Contents ▼

WebGL Shaders: GPU-Accelerated Particle Systems

If you're still iterating over an array of particle objects in JavaScript and updating their x and y positions every frame, you're doing it wrong. It's 2016, and the bottleneck is the CPU-GPU bridge. We need to move the entire simulation loop into the shaders.

The Bottleneck: Buffer SubData

Calling gl.bufferSubData every frame for 100,000 particles is a performance killer. Instead, we store the state (position, velocity, age) in textures or use Transform Feedback (if you're on the bleeding edge of WebGL 2, though WebGL 1 is still king for compatibility).

Vertex Shader Logic

We can calculate the new position based on the initial position and the elapsed time directly in the vertex shader.

attribute vec3 a_initialPosition;
attribute vec3 a_velocity;
attribute float a_startTime;

uniform float u_time;
uniform float u_duration;

varying float v_age;

void main() {
    float elapsed = u_time - a_startTime;
    v_age = elapsed / u_duration;

    if (v_age < 1.0) {
        // Simple physics: p = p0 + v*t + 0.5*a*t^2
        vec3 gravity = vec3(0.0, -9.8, 0.0);
        vec3 currentPosition = a_initialPosition + (a_velocity * elapsed) + (0.5 * gravity * elapsed * elapsed);
        
        gl_Position = u_projectionMatrix * u_modelViewMatrix * vec4(currentPosition, 1.0);
        gl_PointSize = 2.0 * (1.0 - v_age);
    } else {
        // Move off-screen
        gl_Position = vec4(999.0, 999.0, 999.0, 1.0);
    }
}

Fragment Shader Beauty

To make them look like real fire or smoke, we use the gl_PointCoord to draw a procedural circle or sample a noise texture.

precision mediump float;
varying float v_age;

void main() {
    float dist = distance(gl_PointCoord, vec2(0.5, 0.5));
    if (dist > 0.5) discard;

    float alpha = 1.0 - v_age;
    gl_FragColor = vec4(1.0, 0.5, 0.2, alpha * (0.5 - dist) * 2.0);
}

CPU-Side Setup

The key is to upload the data once and just update a single u_time uniform.

const particles = new Float32Array(count * 7); // x, y, z, vx, vy, vz, startTime
// ... fill data ...
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, particles, gl.STATIC_DRAW);

function render(now) {
    gl.uniform1f(u_timeLocation, now / 1000);
    gl.drawArrays(gl.POINTS, 0, count);
    requestAnimationFrame(render);
}

By leveraging the parallel nature of the GPU, we can easily hit 60fps with millions of particles, something that would melt a mobile CPU if done in JS.


Aunimeda develops mobile and PC games - from casual hyper-casual titles to mid-core games with complex progression systems.

Contact us to discuss your game project. See also: Game Development, Mobile Game Development

Read Also

WebGPU: The Era of Compute Shaders and GPGPU on the Web (2024)aunimeda
Game Development

WebGPU: The Era of Compute Shaders and GPGPU on the Web (2024)

WebGL served us well, but it's time for the next level. WebGPU brings raw GPU power and compute shaders to the browser in 2024.

How to Develop a Mobile Game in 2026: From Idea to App Storeaunimeda
Game Development

How to Develop a Mobile Game in 2026: From Idea to App Store

Unity vs Unreal, monetization models, porting to iOS and Android, App Store submission - everything you need to know to take a mobile game from concept to launch in 2026.

WebGL: High-Performance Data Visualization on the Web (2021)aunimeda
Frontend Engineering

WebGL: High-Performance Data Visualization on the Web (2021)

When SVG and Canvas 2D hit their limits, WebGL is the answer. In 2021, we are visualizing millions of data points with the GPU.

Need IT development for your business?

We build websites, mobile apps and AI solutions. Prices are in the calculator.

Game Development →

Get Consultation All articles