DEV Community

Cover image for How to Build a GPU-Accelerated Image Filter with WebGL and JavaScript
HexShift
HexShift

Posted on

2 2 1

How to Build a GPU-Accelerated Image Filter with WebGL and JavaScript

WebGL is a JavaScript API that enables rendering high-performance 2D and 3D graphics directly in the browser using the GPU. In this article, we'll walk through building an image filter (like grayscale or blur) using WebGL shaders and JavaScript. This allows for real-time processing and smooth performance, even with large images.

1. Why Use WebGL for Image Filtering?

Traditional image manipulation using Canvas or JavaScript loops is CPU-bound and slow for high-res images. WebGL leverages GPU shaders to handle pixel-level transformations in parallel, delivering speed and responsiveness crucial for modern UI effects and photo editors.

2. Basic HTML Setup

Start with a simple page layout and an image to filter:

<canvas id="glcanvas" width="500" height="500"></canvas>
<img id="source" src="image.jpg" crossorigin="anonymous" style="display:none;" />

3. WebGL Shader Basics

You’ll need a vertex shader and a fragment shader. The vertex shader maps the image onto a rectangle, and the fragment shader processes each pixel.

const vertexShaderSource = `
attribute vec2 a_position;
attribute vec2 a_texCoord;
varying vec2 v_texCoord;

void main() {
  gl_Position = vec4(a_position, 0, 1);
  v_texCoord = a_texCoord;
}`;
const fragmentShaderSource = `
precision mediump float;
varying vec2 v_texCoord;
uniform sampler2D u_image;

void main() {
  vec4 color = texture2D(u_image, v_texCoord);
  float gray = dot(color.rgb, vec3(0.299, 0.587, 0.114));
  gl_FragColor = vec4(vec3(gray), 1.0);
}`;

4. Initializing WebGL Context

Set up the WebGL rendering context and compile shaders:

const canvas = document.getElementById('glcanvas');
const gl = canvas.getContext('webgl');

function createShader(gl, type, source) {
  const shader = gl.createShader(type);
  gl.shaderSource(shader, source);
  gl.compileShader(shader);
  return shader;
}

const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);

5. Rendering the Image with Filter

Create and link the WebGL program, load the image texture, and draw the filtered output:

function createProgram(gl, vShader, fShader) {
  const program = gl.createProgram();
  gl.attachShader(program, vShader);
  gl.attachShader(program, fShader);
  gl.linkProgram(program);
  return program;
}

const program = createProgram(gl, vertexShader, fragmentShader);
gl.useProgram(program);

const positionLocation = gl.getAttribLocation(program, "a_position");
const texCoordLocation = gl.getAttribLocation(program, "a_texCoord");

const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
  -1, -1, 1, -1, -1, 1,
  -1, 1, 1, -1, 1, 1,
]), gl.STATIC_DRAW);

Then load the image and draw:

const image = document.getElementById('source');
image.onload = () => {
  const tex = gl.createTexture();
  gl.bindTexture(gl.TEXTURE_2D, tex);
  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA,
                gl.UNSIGNED_BYTE, image);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
  
  gl.viewport(0, 0, canvas.width, canvas.height);
  gl.drawArrays(gl.TRIANGLES, 0, 6);
};

6. Going Further

  • Add a UI to switch filters (blur, sepia, invert, etc.)
  • Support video textures for real-time webcam effects
  • Export the filtered image using canvas.toDataURL()

Conclusion

WebGL is a powerful tool for building high-performance, interactive visual effects in the browser. This simple grayscale filter can be extended into a full-featured image editor or dynamic UI component, all running smoothly thanks to GPU acceleration.

If this post helped you, consider supporting me here: buymeacoffee.com/hexshift

Tiugo image

Modular, Fast, and Built for Developers

CKEditor 5 gives you full control over your editing experience. A modular architecture means you get high performance, fewer re-renders and a setup that scales with your needs.

Start now

Top comments (0)

Postmark Image

20% off for developers who'd rather build features than debug email

Stop wrestling with email delivery and get back to the code you love. Postmark handles the complexities of email infrastructure so you can ship your product faster.

Start free

👋 Kindness is contagious

Value this insightful article and join the thriving DEV Community. Developers of every skill level are encouraged to contribute and expand our collective knowledge.

A simple “thank you” can uplift someone’s spirits. Leave your appreciation in the comments!

On DEV, exchanging expertise lightens our path and reinforces our bonds. Enjoyed the read? A quick note of thanks to the author means a lot.

Okay