DEV Community

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

Posted on

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

Heroku

Tired of jumping between terminals, dashboards, and code?

Check out this demo showcasing how tools like Cursor can connect to Heroku through the MCP, letting you trigger actions like deployments, scaling, or provisioning—all without leaving your editor.

Learn More

Top comments (0)

Hosting.com image

Your VPS. Your rules.

No bloat, no shortcuts. Just raw VPS power with full root, NVMe storage, and AMD EPYC performance. Ready when you are.

Learn more

👋 Kindness is contagious

Take a moment to explore this thoughtful article, beloved by the supportive DEV Community. Coders of every background are invited to share and elevate our collective know-how.

A heartfelt "thank you" can brighten someone's day—leave your appreciation below!

On DEV, sharing knowledge smooths our journey and tightens our community bonds. Enjoyed this? A quick thank you to the author is hugely appreciated.

Okay