Easy Filters - Intro to GPU Pixel Shaders

Image Manipulation (Contrast, Brightness, Blur, Pixellation)

In today’s article, we are going to explore the basics of low-level GPU computations used for rendering graphics. In order to do so, we are going to dive into the world of GPU Shaders and perform some image manipulations like contrast, brightness, blur, and pixellation. By the end of this article, you will be able to perform basic pixel operations using top graphics libraries like and (iOS, macOS).

Easy Filters (source: )

If you want to perform real-time image filtering like in the above app preview you can download the iOS app

and continue reading to implement such effects by yourself!

Why GPU?

Before we start with implementing GPU shaders, let’s start by asking ourselves - why do we need GPUs in the first place?

Regardless of the fact whether we are dealing with raster or vector graphics,

Pixel vs Raster Graphics (source: )

at the end of the day, our screens are just matrixes of pixels, where each pixel is defined by its RGB value.

Screen Pixels Closeup (source: )

For example, a FullHD screen contains 1920×1080=2073600 pixels.

So to have a smooth display, we need to find a way to efficiently compute over 2 millions of pixel values for every frame as fast as possible.

It’s possible to perform such computations on CPUs but it wouldn’t be optimal. CPUs are suited for heavy serial tasks but we need to compute many light tasks that can be computed in parallel and this where GPUs stand out.

CPU vs GPU (source: )

What’s more, GPUs have built-in hardware acceleration for some linear algebra functions and can compute many of them at once - this is why GPUs are heavily leveraged in Graphics, Machine Learning and Blockchain worlds.

Rendering Graphics

We need to compute many pixel values for each frame as fast as possible and now we know that in order to do it, we should use GPUs.

But how can we do it?

Let’s leverage GPU’s parallelism and create relatively small low-level programs (kernels) that can compute multiple pixel values simultaneously. Such programs all called Shaders.

Pixel Shaders

To get a better understanding of pixel shaders, let’s use the analogy to the from the ‘’ which I highly recommend.

Gutenberg’s Press

Gutenberg’s Press (source: )

The main idea behind the Gutenberg’s Press was to draw the whole page at once in a repetitive way.

Press Closeup (source: )

This is almost exactly what we are doing in GPU shaders but instead of using types for drawing characters on paper, we are computing RGBA values for screen displays.

Pixel Shader is a program that is executed for every pixel of the drawable to calculate its RGBA value.

Hello World

Usually, when learning a new technology/language we start with a Hello World program that outputs ‘Hello World’ to the console. It would be an overkill to start with such example in learning GPU Shaders so let’s start with a simple example that displays the red color on the screen.

Line 1

For a given input vec2 fragCoord with coordinates, we output vec4 fragColor with corresponding RGBA value that we would like to show on the screen.

Line 2

We define red color as

vec3(1.0, 0.0, 0.0);

which is nothing else as

RED=255, GREEN=0, BLUE=0

FYI: color values in shaders are normalized (0.0–1.0)

Line 3

Since we want a red color for every pixel, we simply output our redColor vector and with an additional alpha channel of 1.0 to fragColor.

Ultimately, we are getting a red display. Pretty simple, huh?

Feel free to play with this basic example in your web browser!

Passthrough

Let’s proceed with something slightly more advanced and create a shader that presents a texture on the screen.

Line 2

We need to calculate normalized coordinates.

Line 3

For each pixel from the drawable, we are taking the corresponding pixel from the iChannel0 texture and assign its color to the originalColor variable.

Line 4

Finally we are outputting the originalColor. Which results in simply presenting the iChannel0 texture on the screen.

Brightness

Now let’s proceed with some image manipulation and perform some brightness adjustment to the input texture.

Line 3

Let’s define our brightness adjustment value.

Line 5

Then, we need to increment each RGB channel with the brightness value.

Line 6

Finally, we need to output our new brightness adjusted color values and apply clamping because we don’t want to go over 1.0 and below 0.0.

Contrast

In this example let’s play with contrast.

Line 3

Let’s define our contrast adjustment value.

Line 5

From every RGB value, we are subtracting 0.5 and then multiply the result by the contrast value. Finally, we are adding 0.5 to even out the initial subtraction.

BoxBlur

Let’s move on to something more advanced - BoxBlur. The idea behind it is simple. For every pixel, we are going to calculate the average value of all surrounding pixels in a defined radius.

BoxBlur of Size 3 (source: )

Line 4

Let’s define our blurSize value.

Line 5

Let’s calculate our range i.e how far should we look for every pixel in every direction (left, right, up, down)

Line 7

Let’s define empty vec4 colors container.

Lines 9–15

The core of the BoxBlur algorithm. For every pixel, we are going to scan surrounding pixels within the defined range and sum their color values.

FYI: we are going to include source pixels as well

For blurSize of 25 we are going to check 12 pixels to the left, 12 pixels to the right, 12 pixels up and 12 pixels down. It will give us a sum of 25*25=625 values for every pixel.

Line 17

Finally, we are calculating the final average color so we need to divide our vec4 colors by the blurSize squared (625 in our example).

Pixellation

Finally, let’s implement a pixellation effect.

Line 3

Let’s define our size value.

Line 4

We need to set our normalized square pixelSize.

Line 6

We need to calculate the position to read the color from the input texture. To achieve the pixellation effect we can take every n value (which is defined by pixelSize) from the input texture and we can do it with the floor function.

What’s Next?

In this tutorial, we’ve learned how to leverage GPUs for rendering graphics and simple image manipulations with and . However, we can achieve way more with GPU shaders and I encourage you to play with them yourself. You can start right away in your browser and

Shadertoy WebGL Kernels

Project’s GitHub page.

Corresponding iOS app

Questions? Comments? Feel free to leave your feedback in the comments section or contact me directly at .

And don’t forget to 👏 if you enjoyed this article 🙂.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store