In this series of articles we’re going to look at image processing with the GPU. We’ll use WebGL (Web Graphics Library) and GLSL (OpenGL Shading Language) shaders for this, which will enable interactive examples throughout the series.
While the theory behind image processing is quite math-heavy, this series will focus on the code for producing these effects and the theory behind them.
We’ll start off with a basic introduction to WebGL and GLSL, and then move onto simple color correction algorithms that you’d find in software such as Lightroom, VSCO or Instagram. We will then take a tour through numerous filters and effects that would be at home in Photoshop.
Articles in this series:
- Intro to WebGL (this article)
- Color Correction
- Hue
- Film Grain
- Duotone
- Pixelation
- Thresholding
- Dithering
- Vignette
- Chromatic Aberration
Here's a preview of what we'll build over the course of these articles. You can also view the full screen version here.
Basic WebGL concepts
WebGL is a JavaScript API for rendering high-performance, interactive, 3D and 2D graphics in the browser. It uses your GPU to compute your programs in parallel rather than the linear functionality of the CPU.
WebGL enables us to send data to, and run programs (shaders) on, the GPU. This series will focus on a type of shader program known as a fragment shader. A fragment shader will run once for each pixel in the rendered image. These shader programs run in parallel so you can't have any shared state between them.
Shader programs are written in a C-inspired language called GLSL (OpenGL Shading Language). Graphics cards are extremely fast in comparison to the CPU, and have many mathematical functions built in that GLSL enables us to take advantage of from our shader programs.
We pass data to our fragment shader as a Uniform. A Uniform is a variable that is the same for every fragment shader in a render pass. As this series will focus on editing images, we'll pass this into our shader program as a special kind of Uniform known as a Texture. Textures are WebGL's built in support for images. It stores the image data as a 2x2 matrix (row x column) of RGBA (red, green, blue, alpha) color values.
A brief recap:
- Shader: a program that runs on the GPU, as opposed to the CPU
- Fragment shader: a program (on the GPU) that runs for every pixel in your image in parallel
- Uniform: a variable that is the same for every fragment shader in a render pass
- Texture: WebGL’s built-in support for images
GLSL colors
Colors in GLSL are a 4-value vector, containing the red, green, blue, alpha channels. These map to the RGBA values you know in CSS, but are 0.0 to 1.0 instead of 0 to 255. To get a feel for how these relate to each other, have a play around with the values below using your arrow keys:
Our foundational shader
The foundations of our shader lie in a few input uniform
s and one color value output
. Let's have a quick look, then I'll break it down.
#version 300 es
precision highp float;
uniform vec2 u_resolution;
uniform sampler2D u_image;
out vec4 outColor;
void main() {
vec2 uv = gl_FragCoord.xy/u_resolution;
vec4 texel = texture(u_image, uv);
outColor = texel;
}
Starting from the top, we have #version 300 es
. This tells WebGL which version of GLSL our shader is written in. This is quite important as there are many breaking changes between version 100
and 300
.
The precision
value tells our program how many decimals a floating point can contain. The more decimals, the more data can be stored from a calculation.
We pass our image in as a uniform u_image
, which we can read with GLSL's texture
function. We also need the resolution (width and height) of our canvas to correctly map our image data to our canvas size, passing this into the texture
function.
The texture
function outputs a texel
: a texture element rather than a pixel. We can then assign this to our outColor
.
This program doesn't do much at the moment. It simply outputs the image we pass in, as we can see above. In the next article we’ll expand on this basic program to support color correction methods such as brightness, exposure and saturation.