Sharp scalable bitmap font and other applications of SDF

This is scaled bitmap, not vector graphics. Can you guess how big the source bitmap is?

SDFFont-anim

Source bitmap

A
16x16 pixels RGB565 bitmap

Each pixel of the source bitmap contains information about the distance to the closest edge of the letter or symbol. The technique is called signed distance fields. It is used e.g. in 3D games for rendering sharp decals without the need for high resolution images. I don’t know if this has any use on Pokitto, just thought it’d be interesting to test.

I used the more advanced RGB color method. You could also use a grayscale source bitmap if you want to save memory and don’t need that sharp corners.

Source code: SDFFont.zip (8.3 KB)

More about SDF fonts: https://computergraphics.stackexchange.com/questions/306/sharp-corners-with-signed-distance-fields-fonts

4 Likes

An interesting technique!

That is really neat. I am guessing if you had a lot of text to display and you wanted scaling - say for the opening scene of a star wars game - this would be ideal!

2 Likes

I wonder if this could be useful for things other than text rendering. Such as storing the game map as a low resolution SDF bitmap which then gets rendered in higher resolution. For example in a lemmings/worms like game where you can’t use tiles, or a top-down racing game where you could store the track in a very low resolution bitmap and then render it with much higher resolution.

4 Likes

I implemented it in opengl as a shader, it’s quite clever (iirc it was first used by people from Valve for HL2)

how fast does it run? it does requires an interpolation and other calculations for each pixels.
A lot of fun can be done once you get the distance to the edge, like borders, various effects and so on

I agree, it’d a very interesting technique for maps! It’d be too far from procedural generation actually. Since SDFs can gives you a distance to an edge -or a dot-, another use could be to have a space/speed trade for having paths and AIs - or a player! - trying to follow them. You could even have some gameplay where, the closest your are from the path, the highest the score you get, for a race-type game!

I haven’t tested on hw, but on emulator fps drops to 17 when drawing fullscreen image. Bilinear interpolation is the most expensive operation, and it must be done three times per pixel for RGB bitmap. In the current demo there is not much else, just median() to pick one of the three components, plus scaling and clamp() to map the value to palette index.

Using grayscale bitmap instead would reduce number of bilinear interpolations to one per pixel if performance is an issue.

1 Like

Here’s what a racing game could look like when using SDF to store the track bitmap.

sdf_racer

Original high resolution track image is 1024x1024 black&white. I use a script to convert it to grayscale distance field and then scale it down to 64x64 8bit bitmap. Grass, sand, track edges and asphalt are created by mapping different distance values to palette indices.

This is the 64x64 SDF bitmap of Suzuka Circuit:
out

7 Likes

Looks great! Where is the demo running?

Pokitto emulator, fps counter says 30.

4 Likes

Worms-like destructible terrain and collisions.

SDF-worms

Map size is 704x176 pixels, and can be adjusted depending on how much detail you want. The actual signed distance field bitmap takes only 128x32 bytes in memory. Each byte of the SDF is divided into two channels, 5 bits for the terrain and 3 bits for the excavations. Single 8 bit channel would work too, but two channels allow the use of different texture for excavations instead of them being just through holes.

Collisions are relatively easy to implement with SDF since it directly gives you the distance to the closest wall (D0=distanceAt(x, y)). For the collision surface normal, you need two more distance values: Dx=distanceAt(x+delta, y) and Dy=distanceAt(x, y+delta). Now the normal vector is simply n=(D0-Dx, D0-Dy) normalized.

9 Likes

This tech seems to be very handy. Could you elaborate a bit.

The terrain looks to be very detailed. What is the tile size? How do you mark in the tilemap when there are excavations? Can the excavations be other than a circle shape.

2 Likes

Terrain uses a texture that is 32x64 and tiled over the whole terrain, but there is no tilemap, only the 128x32 signed distance field. The SDF is used as a mask to decide whether to draw sky or textured terrain. Similarily in the racing game demo above I could have used asphalt and grass textures instead of solid gray and green colors.

The SDF has two channels, 3 bits for excavations and 5 bits for terrain. Excavations are added by modifying the 3 bit channel. You can think of it as two separate SDFs. For the small and large hole, I have precomputed distance field bitmaps, which are then copied to the desired coordinates in the excavations SDF. When rendering, the two channels are basically ANDed together. Holes can be any size and shape.

2 Likes

This is one of the most promising new ideas in a long time. @FManga was struggling to get enough map data for his pod racer clone, i think this could be used in that context also

2 Likes

That clarifies it a bit.

What area that 128x32 sdf bitmap covers in screen pixels?

In the demo I chose it to cover 704x176 pixels.

SDFs are very handy indeed, they’re also used e.g. in 3D engines (godot) for reflections. Great work @jpfli, I really like the demo gifs. Vector representation of fonts/maps could also be considered, that would probably take even less space, but I think it’s more work to create a vector shape than to generate an SDF from a bitmap (plus you can nicely utilize the distance like in your racing demo). Even in cases when real-time processing is too slow, this can still serve as a compression to save space in ROM.

3 Likes