How do you implement MSDF text rendering on OpenGL ?

Started by
3 comments, last by Alberth 4 years, 5 months ago

I have a game. and I want to make a text. so I decided to use the SDF approach. but when I scale the text, the text is rounded at the corners. After research, I found the msdf (https://github.com/Chlumsky/msdfgen). This is the way I'm trying to display text.

I am generating a 96x96 png image for the letters "g", " V " and "A".

This was the command I ended up using:

./msdfgen msdf -font Arial.ttf 'g' -size 96 96 -pxrange 12 -scale 2  -o a.png -translate 3 20

I got the images and store the images in an array of 2D textures

Then I do the Kerning. I used FreeType to provide the kerning (8px size).

I then rendered each character with a sprite/plane and set it’s width and height to the character size provided to FreeType in the kerning section above.

My shaders

vertex

#version 430 core
layout(location = 0) in vec2 position;
layout(location = 1) in vec2 texCoord;
layout(location = 2) in float layer;

out vec2 uv;
out float layer_get;

void main()
{
    gl_Position = vec4(position, 0.0, 1.0);
    uv = texCoord;
    layer_get = layer;
}

fragment

#version 430 core
out vec4 color_output;

in vec2 uv;

layout (binding=0) uniform sampler2DArray textureArray;
in float layer_get;

float median(float r, float g, float b) {
    return max(min(r, g), min(max(r, g), b));
}

void main()
{   
    vec3 flipped_texCoords = vec3(uv.x, 1.0 - uv.y, layer_get);
    vec2 pos = flipped_texCoords.xy;
    vec3 distance = texture(textureArray, flipped_texCoords).rgb;
    ivec2 sz = textureSize(textureArray, 0).xy;
    float dx = dFdx(pos.x) * sz.x; 
    float dy = dFdy(pos.y) * sz.y;
    float sigDist = median(distance.r, distance.g, distance.b);
    float w = fwidth(sigDist);
    float opacity = smoothstep(0.5 - w, 0.5 + w, sigDist);
    color_output = vec4(0.0,0.0,0.0, opacity);
}

example in this thread.

My result

Post image

It looks absolutely not natural.

To give you a feel of how I calculated the squares, I disable blending to see what the actual rendered squares look like:

Post image

Kerning (I think I'm doing something wrong in that phrase).

fn kerning(text: &Vec<&str>, face: FontFace) -> Vec<f32> {
    let mut vec: Vec<f32> = Vec::new();

    let mut x = 0.0;

    let scale_factor = 26.0 / 8.0; // Initially calculated as 8px, then I try to scale to 26px

    for (i, l) in text.iter().enumerate() {
        let res = face.chars.get(&l.to_string()).unwrap();

        let xpos = x + res.bearX as f32 * scale_factor;
        
        /*
        I know that the OpenGL coordinate system is different from the coordinate system I'm used to.this function just makes a square starting from the top left corner
        */
        vec.append(&mut make_square_px(
            res.width as f32 * scale_factor,
            res.height as f32 * scale_factor,
            x, // X
            0.0, // Y i am not calculating the baseline
            300.0,    // Screen width
            300.0,    // Screen height
            i as f32, // layer
        ));

        // Now advance cursors for next glyph
        x += ((res.advance as f32 / 64.0) * scale_factor);
    }
    vec
}

My knowledge of OpenGL is small . so I hope someone will help me solve this problem.

Advertisement

This is not a Writing question. Moving this to a programming forum.

-- Tom Sloper -- sloperama.com

Scale factor shouldn't be needed, the proper solution is to ask letters from a bigger font. Fonts are designed for a particular size, scaling them makes then look less good.

This topic is closed to new replies.

Advertisement