Welcome to the first blog post on shaderfun.com. It probably won’t be all shaders, and probably not always fun, but I promise lots of interesting code and stuff. Signed distance fields are so awesome that my friends claim I bring them up every time I go to the pub. Whilst this is not true (and neither is the fact that I never buy a pint), they are one of my favourite programming tools these days, so I’ve decided to start this blog off with some signed distance field fun (SDFF).
(Not Signed) Distance Fields
Typically when we create a texture we think of it as a grid of numbers, each of which represents a ‘brightness’. 0 is black, 1 is white. Coloured textures simply extend this concept to store 3 values (or 4 if transparency is included).
Whilst typical textures are very useful for general purpose imagery, they are not particularly good at expressing geometric shapes. This is predominantly down to 2 problems:
- If the geometry is not perfectly aligned to the pixel grid, it must be approximated, resulting in aliasing effects
- Low resolution textures scale up very badly, forcing us to store high res textures if we want an accurate representation of our geometry
Distance fields are another way of utilising textures that aim to describe geometry (such as a circle, line or more complex combination of shapes). To achieve this, the value of each pixel represents the distance from the centre of that pixel to the nearest geometry.
In this diagram we can see the distance field for a simple line segment at the centre of a grid:
The same image can be better visualised using a colour to represent the distance of a pixel from our geometry:
This technique, originally popularised in games for font rendering has some real benefits. With access to this texture a shader has a lot of extra information about the geometry it contains, and we can use that extra information to achieve a variety of effects.
Before proceeding, another aspect of shaders and textures needs a mention – texture sampling. Thus far in the above examples we’ve been using a 16×16 texture in point sampling mode. That is, when the GPU wants to get the colour at a given coordinate in the texture, it simply reads the value of the closest pixel, resulting in a blocky effect.
These days most of the time we use textures in bilinear (or trilinear) sampling mode, in which the GPU automatically blends across pixels to get a smoother texture:
In the case of a distance field, this is equivalent to smoothly sampling the distances stored in our texture:
Rendering The Geometry
Ok! Armed with a distance field, and a GPU that utilises bilinear filtering to smoothly sample the pixels of that field, lets see what we can build. Our first and simplest shader samples the distance, and outputs a ‘solid’ colour if the pixel is close enough to the edge of the geometry.
To start with, here’s the whole unity shader just to put everything in context:
I won’t go into huge detail, as most of it is boiler plate unity shader code. The interesting bit is inside our ‘frag’ function (the fragment shader):
Here we sample our texture as normal, but instead of outputting a colour, we interpret the red channel as a distance. If the distance is lower than the supplied
_BorderWidth parameter, we output the
_Border colour, otherwise we output the
The result of this extremely simple shader is our distance field ‘line’ in all its glory:
Here we have rendered the line segment geometry with a radius of 0.5 pixels in the texture (known as texels). Visually this yields a solid line 1 texel wide with 2 rounded ends where the bilinear texture sampling has blended the distances stored in neighbouring pixels.
Next, we can add an extra ‘offset’ value to the shader to bias the sampled distance, allowing us to increase or decrease the distance used by our calculations:
The resulting visual effect is that, by adjusting the offset, we can create thinner and fatter lines.
Something really worth remembering here is that the above texture is a mere 16×16 pixels in size, yet the level of detail achieved appears to be much greater. This extremely powerful feature is what makes signed distance fields so great for font rendering – with relatively small amounts of texture space we can efficiently render very highly detailed geometric shapes. The same principle allows us to achieve many other fun useful effects such as outlines, morphing and bloom at a low GPU/memory cost.
Just to finish off this first part, here’s a nice fat line without the nasty grid for you to gaze at:
Stay tuned for the next parts of this blog, in which I’m aiming to cover:
- Full signed distance fields
- Different techniques (and code!) for generating signed distance fields
- Some fun effects you can achieve with them
All the code for this series can be found at: https://github.com/chriscummings100/signeddistancefields