Rendering TG Character Test.fs in the ISF Editor utility.
While GLSL is not a particularly great language for rendering text strings, many shader developers have found it a fun challenge to do so anyway. Over the years we’ve run into several interesting blog posts and examples from people showing off different techniques that they’ve come up with. In this write up we are going to look at three ‘common’ approaches to drawing text in GLSL along with how the concepts can be adapted when making ISF shaders:
Using a bitmap image of characters as a lookup.
Encoding the bytes from a bitmap font in an array as part of the shader.
Individually drawing each character in GLSL.
As with most coding problems even within these general techniques there are several ways the objective can be solved, and no doubt other clever developers out there will continue to come up with other approaches that will leave our mouths agape.
Rendering text in GLSL using fonts encoded in bitmap images
The first technique we will look at is very well covered in the blog post Text Rendering by Jon Baker. describes how a monospaced font can be encoded into a bitmap image which is read from as part of the shader: “we're using a bitmapped monospaced font, where every font glyph is 6 pixels wide and 8 pixels tall.”
Within the shader itself each “cell” can determine its own local pixel coordinates and then look up the corresponding pixel from the provided bitmap image for the desired ASCII character to display.
ISF allows us to include images and reference them in the JSON blob, which you can find in the top portion of the JB Monospace Character Test.fs example.
/*{ "CATEGORIES": [ "Text" ], "CREDIT": "Jon Baker, adapted by VIDVOX", "IMPORTED": { "fontImage": { "PATH": "jbakermonospaced.png" } }, "DESCRIPTION": "Test for drawing characters from a bitmap font stored as an image.", "INPUTS": [ ... ], "ISFVSN": "2" } */
The bitmap image containing the font.
This basic test shader provides us with a slider (float) with a range of 0 to 255 for selecting which character we want to repeat in each cell. It also includes a toggle button (boolean) for enabling the debug mode for visualizing the local pixel coordinates for each cell. When loaded into a host app like VDMX, it looks like this:
Rendering JB Monospace Character Test.fs in VDMX6 with the debugOverlay enabled to show the local pixel coordinates for each cell.
Building on the test shader we can create something more complex, such as the JB Monospace Random Characters.fs example which renders an image full of random characters each with a random color. For this ISF the debug option has been extended to show either the local coordinates for each cell or the coordinates of each cell within the whole grid.
Rendering JB Monospace Random Characters.fs in VDMX. Includes sliders for adjusting the random seeds of the characters independently from the hues.
Encoding the bytes from a bitmap font in an array as part of the shader
The character A encoded in 16 bytes, stored as an uvec4, that’s 4 uints with each 4 bytes, from Texture-less Text Rendering.
Another approach that is well documented involves taking the data from a bitmap font file and encoding the byte values directly into the shader as an array. Here we are going to mainly look at two blog posts:
Tim Gfrerer’s wonderful post Texture-less Text Rendering uses the PSF1 and an in depth explanation of their implementation as a GLSL shader.
Jon Baker has made a beautiful adaptation of Code page 437, an extended ASCII table which shipped with the IBM PC, written as a GLSL shader. In the blog post Siren: Masks Planes he details the process of how he came up with and implemented the idea.
The Texture-less Text Rendering approach described by Tim has four steps:
Get the bitmap data from the font file.
Embed the byte data as an array in the shader file.
Use another array to hold the character code values for a word.
Looking up the character data for each index in the word array.
Using the ISF Editor to translate syntax between variations of GLSL.
While ISF allows for a custom vertex shader, to keep things simple for future remixing, for the TG Character Test.fs adaptation we put everything in the fragment shader. Starting from the debug_text.frag in the GitHub repository referenced in the blog post, there were a number of minor syntax changes that were needed to adapt this for ISF. Fortunately they were all fairly easy to handle by hand using the error messages in the ISF Editor utility.
Like with the basic test shader using the bitmap lookup, before getting into Tim’s approach for displaying full words and phrases, it is useful to start with an example that just generates a display for every possible character, along with the ability to test displaying a specific character. This makes it easy to verify each part of the shader is working as expected and validate the drawing.
With this working, creating an extra constant to hold the full phrase for display and swapping it out for our debug code, we now have a full implementation of Tim’s technique in TG Message Test.fs. This trick for holding a phrase as character codes in an array can also be used along with the bitmap image approach above.
Jon Baker uses a similar approach for adapting the Code page 437 font to use a single fragment shader. The blog post diving into how it was developed includes example code shared on ShaderToy. Converting this to ISF is a fun exercise. The big detail is that when using this in VDMX6, the ‘char’ variable is a restricted name by Metal, so we have to change that to something else. Fortunately the error log points this out for us when we try to load it. The adapted shader, JB Code Page 437 Character Test.fs, is set up to just repeat the same character in each cell.
JB Code Page 437 Character Test.fs loaded in VDMX.
Drawing individual characters with GLSL code
Perhaps the most tedious version approach that we will look at in this discussion is using code to render out specific characters that are needed. Two examples of this are included in the standard set of ISF shaders:
Digital Clock.fs
ASCII Art.fs
In Digital Clock.fs, only the digits 0-9 and a colon are needed for display, so while this can be time consuming to write, there are only a handful of cases to deal with. The functions found in this example can be very useful in situations where only numbers are appropriate to be rendered.
Function for drawing a digit in Digital Clock.fs.
Rendering Digital Clock.fs in VDMX with the Bad TV effect added.
In ASCII Art.fs, the code for each character rendered was created by movAX13h using a custom tool to generate the code snippets for the supported characters: “: * o & 8 @ #” – while this is not useful for general purpose text writing, it works great in this use case where only a few are needed to match up with different brightness levels.
Another example of this approach in action can be found in the Text with Truchets Demo on ShaderToy.
Why convert these to ISF?
ISF (the Interactive Shader Format) is a useful standard for creating ‘write once’ shaders that can be used across different host applications as generators and filters. It allows for playing with and remixing GLSL based compositions without the extra effort of having to build an environment for rendering shaders and all the related code to get a basic pipeline running.
Other approaches…
These are of course not the only ways to go about drawing text in GLSL, for example Jazz Mickle has a great post describing their bitmap font renderer for Unity that is worth reading. If you have seen any others that we should check out, please send us an email with a link!