Vignette

This is part of the WebGL image processing series and it relies on information in previous articles. See all articles here.

It is designed to be used on desktop.

What is vignetting?

Vignetting is the reduction of an image's brightness or saturation towards its edges. While this was historically an undesired effect in photography – an indicator of incorrect camera settings or issues with the lens – it can also be used to lead the viewer's eye to a specific point on an image.

We look at code from Tyler Lindberg's glsl-vignette here, which is licensed on a MIT license.

In its simplest form we can take the distance from the center of the image and create a radial gradient that darkerns towards the edges. I've included an additional preview of the vignette over a white background to get a complete picture of the vignette's shape.

float vignette(vec2 uv, float radius){
  return radius - distance(uv, vec2(0.5));
}

void main() {
  // map uv between 0 -> 1
  vec2 uv = gl_FragCoord.xy/u_resolution;
  // load image
  vec4 texel = texture(u_image, uv);
  // apply vignette to texel (image)
  outColor = texel * vignette(uv, u_radius);
}

Smoothstep

The main issue with the above approach is that we lose a lot of detail from the center of the image. We can use smoothstep to control how smooth the fade is between our image and the darkness at the edges.

// Based on https://github.com/TyLindberg/glsl-vignette/blob/master/simple.glsl

float vignette(
  vec2 uv, 
  float radius, // (0 - 1)
  float smoothness // (0 - 1)
) {
  float diff = radius - distance(uv, vec2(0.5));
  return smoothstep(-smoothness, smoothness, diff);
}

void main() {
  // map uv between 0 -> 1
  vec2 uv = gl_FragCoord.xy/u_resolution;
  // load image
  vec4 texel = texture(u_image, uv);
  // apply vignette to texel (image)
  outColor = texel * vignette(uv, u_radius, u_smoothness);
}

Vignette shape

We can also use separate values for our width and height to control the shape of our vignette.

// Based on https://github.com/TyLindberg/glsl-vignette/blob/master/advanced.glsl

// Based on distance functions found at:
// http://iquilezles.org/www/articles/distfunctions/distfunctions.htm
float sdSquare(vec2 point, float width) {
  vec2 d = abs(point) - width;
  return min(max(d.x, d.y), 0.0) + length(max(d, 0.0));
}

float vignette(
  vec2 uv, 
  vec2 size, // (0 - 0.5)
  float roundness, // (0 - 1)
  float smoothness // (0 - 1)
) {
  // Center UVs
  uv -= 0.5;

  // Shift UVs based on the larger of width or height
  float minWidth = min(size.x, size.y);
  uv.x = sign(uv.x) * clamp(abs(uv.x) - abs(minWidth - size.x), 0.0, 1.0);
  uv.y = sign(uv.y) * clamp(abs(uv.y) - abs(minWidth - size.y), 0.0, 1.0);

  // Signed distance calculation
  float boxSize = minWidth * (1.0 - roundness);
  float dist = sdSquare(uv, boxSize) - (minWidth * roundness);

  return 1.0 - smoothstep(0.0, smoothness, dist);
}

void main() {
  // map uv between 0 -> 1
  vec2 uv = gl_FragCoord.xy/u_resolution;
  vec4 texel = texture(u_image, uv);
  outColor = texel * vignette(uv, vec2(u_width, u_height), u_roundness, u_smoothness);
}

In the next article, we'll be exploring how to simulate the effect of chromatic aberration.

Feedback and suggestions

Have a suggestion or want to show me your work?
Get in touch at via email or Twitter @maximmcnair.