1

I'm quite new to font rendering and I'm trying to generate signed distance field with freetype so that it can be used in fragment shader in OpenGL. Here is the code that I tried:

           error = FT_Load_Glyph(face, glyph_index, FT_LOAD_DEFAULT);
           if (error)
           {
              // Handle error
           }
 
           
           error = FT_Render_Glyph(face->glyph, FT_RENDER_MODE_SDF);
           if (error)
           {
              // Handle error
           }

Maybe I completly misunderstand the idea of SDF, but my thought was that I could give freetype a ttf file and with FT_RENDER_MODE_SDF it should produce a buffer with signed distances. But FT_Render_Glyph returns an error (19) which happens to be "cannot render this glyph format".

Rabbid76
  • 202,892
  • 27
  • 131
  • 174
Farvater
  • 13
  • 4

1 Answers1

7

SDF support was added at the end of 2020, with a new module in the second half of 2021, so make sure you have a more recent version than that. For example, 2.6 is older than 2.12.0 (the newest at the time of writing).

With that out of the way, let's get started.

I'm assuming you've completed the font rendering tutorial from LearnOpenGL and you can successfully render text on the screen. You should have something like this (notice the new additions):

glPixelStorei(GL_UNPACK_ALIGNMENT, 1); // Disable byte-alignment restriction

FT_GlyphSlot slot = face->glyph; // <-- This is new

for (unsigned char c = 0; c < 128; c++)
{
    // Load character glyph 
    if (FT_Load_Char(face, c, FT_LOAD_RENDER))
    {
        // error message
        continue;
    }

    FT_Render_Glyph(slot, FT_RENDER_MODE_SDF); // <-- And this is new

    // Generate texture
    GLuint texture;
    glGenTextures(1, &texture);
    glBindTexture(GL_TEXTURE_2D, texture);
    glTexImage2D( ... );
    ...
}

When rendering the text, you have to tell OpenGL not to write the fragments of the quads to the depth buffer, otherwise adjacent glyphs will overlap and start to flicker:

glDepthMask(GL_FALSE); // Don't write into the depth buffer
RenderText(pTextShader, text, 25.0f, 25.0f, 1.0f, glm::vec3(0.5, 0.8f, 0.2f));
glDepthMask(GL_TRUE); // Re-enable writing to the depth buffer

If you want to place the text as an object in your scene, in world-space, then in the vertex shader you can use:

gl_Position = uVp * uModel * vec4(vertex.xy, 0.0, 1.0); // uVp is "projection * view" on the CPU side

However, this is a bit outside the scope of your question. It just makes it easier to inspect the text from all angles by circling the camera around it. Make sure you run glDisable(GL_CULL_FACE) before drawing the glyphs, to disable backface culling, so they're visible from both sides.

As for the fragment shader I suggest you watch this video.

The bare minimum would be:

void main()
{    
    float glyphShape = texture(uGlyphTexture, TexCoords).r;

    if (glyphShape < 0.5)
        discard;

    oFragColor = vec4(uTextColor, 1.0);
}

Result:

enter image description here

I think there's a pretty stark difference between them, wouldn't you say?

Have fun!

Andrei Despinoiu
  • 370
  • 3
  • 17
  • Thank you! Now it works, but there is another problem. I need to use unicode inside my application and it renders sdf at startup for every codepoint inside ttf. I noticed it runs too slow: several minutes for one ttf. So, is this sdf functionality meant for "offline" usage - just to generate something like an atlas that can be used at runtime, or I'm just doing something wrong and there is some better way to use sdf generation at runtime ? – Farvater May 14 '22 at 12:02
  • You can skip glyphs 0-31 since they're not found in most fonts. If you look at the generated textures on the GPU using RenderDoc or Nsight Graphics you'll see them as squares. The smaller the range, the faster it will be done processing it (less data). "Several minutes for one ttf" means you're definitely doing something wrong. Maybe you set a resolution that's too large with FT_Set_Pixel_Sizes()? You generally don't need much res with SDFs, 64 is probably the highest I would go with, even for huge text. Either way, your question is outside the scope of your original question. Open a new one. – Andrei Despinoiu May 15 '22 at 13:50
  • You can probably skip `FT_LOAD_RENDER` if you are calling `FT_Render_Glyph` manually. – Osyotr Dec 03 '22 at 19:16