Using OpenGL shaders to make glyphs for an OpenType font, the easy way

Fredrick Brennan
6 min readJan 2, 2020

Or, in which a fat idiot learns that OpenGL isn’t actually that hard.

The goal of this tutorial/essay is to generate glyphs like these below, from my font «TT2020 Style G Italic»:

Now that I released v1 of TT2020, my typewriter font family which has nine versions of each glyph to break up monotony and look more authentic, I can explore optimization. After all, the programming prophets say that such is the ideal order of operations —

We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil.

It is a self-imposed requirement that I only use libre software in my projects. I don’t see this changing unless I suffer some catastrophic brain damage. (Perhaps some anon will make good on their desire to throw me down the stairs for telling them 8chan’s admins failed to do anything concrete to stop their idiot friends shooting up mosques.)

All of my fonts, for example, are developed with FontForge (although sometimes, in a pinch, I will use Adobe’s open source AFDKO toolkit, ttx, or Google's fontmake as well).

This has many advantages but is often quite a slog. Such, unfortunately, is the case with TT2020’s build system: the glyphs take a very long time indeed to build.

All of TT2020’s styles, as of this writing, are generated with GIMP scripts. While many of GIMP’s operations seem to have OpenCL support, the operation I most commonly use, «Newsprint», which takes the longest, especially combined with Perlin noise, seems to have its OpenCL support disabled. Perlin noise doesn’t have any support at all.

“Me IRL,” as the kids say.

I could just give up and use Photoshop CC. It probably has good GPU support, perhaps even CUDA support. (I have an NVIDIA card.) I could also start smoking crack. Or maybe experience life in Guam, after all I do have such strong opinions on it, and as an imperial citizen the Ninth Circuit recently ruled I have the right to vote there (Davis v. Guam), but Guamanians don’t have the right to take out marginally more money in loans (Limtiaco v. Camacho)! Thank you, racist American judiciary! Truly, there is no freer or more democratic country on Earth.

Oh dear, there go my strong opinions. Technical writing, Fred, technical writing! Keep it together!

Let’s get to the point: why not see if I can figure out how to make use of Øyvind Kolås’ disabled «Newsprint» code? He calls it a «Spachrotyzer», whatever on Earth that’s supposed to mean. Apparently it has something to do with the word spatiochromatic, again, whatever that means. Despite the obtuse name, the code looks really simple.

One problem: I’m a fat idiot that doesn’t know OpenGL, or GLSL, and despite literally being a programmer for ten years, I have never done any GPU anything. I played around with Godot once years ago before deciding (a) it sucked, and (b) I don’t want to make a game that badly anyway (of course, due to my aforementioned autism, also known as good taste, I refuse to use Unity.) I’m determined to make an effort though. So I find a book with a delightful cover:

I am ready to begin writing C. #include <GL/gl.h>! glfwInit! glfwCreateWindow! #define GLSL(src)! Execute, spirit evolution!

Yeah, no, this isn’t fun at all. I stumble upon a video of a proprietary software sold on the Steam store (apparently non-video games are sold on it, who knew?) named «Shadron».

I want that, but am not about to make my build system dependent on a proprietary program other future developers aren’t likely to have (or even be able to find, depending on how long from now it is).

Fortunately, there is a very similar free program: «SHADERed»! It seems not very well known which is a shame, so I hope this brings it a little much needed attention.

It is newish, the part we care about has only existed since August 2019. (Apparently a DirectX version existed before then. I’m sure its author worked hard on it, but I have no interest in programs reliant on proprietary APIs.)

It is honestly almost complete. The author seems to be focusing on making a shader debugger right now instead (that’s a shame if you ask me, but I can’t complain about how people spend their free time), so it’s not quite complete. However, in two hours I was able to achieve this, with no GLSL experience, just from the examples distributed with the program, a bit of Stack Overflow, and when push came to shove, reading SHADERed’s source code:

That’s not bad, and I was over the moon having achieved it (I thought OpenGL was supposed to be hard, you see!), but we’re not even using Kolås’ code yet. The next day I hacked that in and achieved this:

I remembered to mention I’m a fat idiot, right? This program is really something very, very impressive. (As is Mr. Kolås, but, he knows.) The speedup here, I timed it, is at least 64x! Remember we only need our graphics card to produce a single frame, we’re not making a videogame here. (Thank God, that industry is for men much more patient with capitalism than I am.)

After much experimentation over the next day, I came upon this result:

I’m very happy with it. They say the hardest thing about writing code is reading code, and that’s definitely true for this project. The whole code is here, but I really only wrote this petit fragment:

    float ll;
float l = fragColor.r * fragColor.g * fragColor.b;
ll = l;
float pn = Perlin3D(vec3(uvn*7.7, 6));
// if (l<0.45) l-= (0.4-pn)*4;
// if (l<0.99) { l-= (1.2*pn); }
{ l-= (2*pn); }
if (ll>0.9) l = 1;
// if (-(l-pn)<=0&&l>0.51) l-=0.22;
// l+=0.22;
float cc = abs(fragColor.r-fragColor.g);
float a = abs(fragColor.b-fragColor.g);
// x,y,part_white,offset,hue, pattern,period,tubrulence,blocksize,angleboost,twist
float g = 1-spachrotyze(fragCoord[0], fragCoord[1]/0.5, 1-l,cc,a, 1, 15, 0, 0,0,degrees_to_radians(45));
// Output to screen
outColor = vec4(vec3(g,g,g),1);

Further work

I mentioned missing features earlier, and here is the most important one: the only “output” is GLSL; no C or C++. :-(

Meaning, to turn this into a script I can run on an input bitmap, and get my nine output bitmaps written to the disk, I still need to write some C to use this shader — SHADERed cannot even currently take a filename on the command line.

If its author were to offer an API like:

./bin/SHADERed -render TT2020.sprj -o ./bin/render.png

Then I could open SHADERed once for each glyph, (I currently run GIMP once for each version of each glyph, so there is already 9x fewer invocations,) and use another program to split up the output bitmap into the nine versions.

I’m going to give him a chance to implement that before I figure out how to proceed. I have another project I want to finish in the mean time…follow me on Twitter.

--

--