Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to render text in modern OpenGL with GLSL

I want to render text in LWJGL by using modern OpenGL (rendering with VBO and shader) but I have no idea how to do it.

like image 312
Thomas Flynn Avatar asked Feb 27 '14 21:02

Thomas Flynn


People also ask

Does OpenGL use GLSL?

The OpenGL Shading Language (GLSL) is the principal shading language for OpenGL. While, thanks to OpenGL Extensions, there are several shading languages available for use in OpenGL, GLSL (and SPIR-V) are supported directly by OpenGL without extensions.

How does text rendering work?

The process of transforming font outlines into pixels is called rasterization. The operating system's text-rendering engine places the outline (ie the shape) of each character at the desired font size on a pixel grid. Next, it colours all the pixels whose centre is inside the outline (see image below).


1 Answers

Here is an approach:

  • Create a texture containing all your characters, rasterized at a certain size.
  • For each character, store the location of the patch of the texture containing the character

class CharCoords {
    public int x, y, width, height;
}
  • Upload to the GPU a 2D square geometry with vertices (0,0), (0,1), (1,0), (1,1)
  • The vertex shader could look as follows

#version 120

uniform mat4 PVMmat;        // The projection-view-model matrix
uniform vec4 charCoords;    // The CharCoord struct for the character you are rendering, {x, y, w, h}
uniform float texSize;      // The size of the texture which contains the rasterized characters (assuming it is square)
uniform vec2 offset;        // The offset at which to paint, w.r.t the first character

attribute vec2 vertex;

varying vec2 tc;

void main(){
    // Transform from absolute texture coordinates to normalized texture coordinates
    // This works because the rectangle spans [0,1] x [0,1]
    // Depending on where the origin lies in your texture (i.e. topleft or bottom left corner), you need to replace "1. - vertex.y" with just "vertex.y"
    tc = (charCoords.xy + charCoords.zw * vec2(vertex.x, 1. - vertex.y)) / texSize;

    // Map the vertices of the unit square to a rectangle with correct aspect ratio and positioned at the correct offset
    float x = (charCoords[2] * vertex.x + offset.x) / charCoords[3];
    float y = vertex.y + offset.y / charCoords[3];

    // Apply the model, view and projection transformations
    gl_Position = PVMmat * vec4(x, y, 0., 1.);
}
  • The fragment shader is trivial:

#version 120

uniform vec4 color;
uniform sampler2D tex;

varying vec2 tc;

void main() {
    gl_FragColor = color * texture2D(tex, tc);
}
  • Your drawing function could then look as follows (note: the code is using a shader class with some convenience methods, but the idea should be clear):

public void drawString(Matrix4f PVMmat, String text, Color color, HAlign halign, VAlign valign) {
    Vector2f offset = new Vector2f();

    // Font alignment
    if(halign == HAlign.Center){
        offset.x = -(int) (0.5f * getWidth(text));
    }else if(halign == HAlign.Right){
        offset.x = -getWidth(text);
    }
    if(valign == VAlign.Middle){
        offset.y = -(int) (0.5f * getHeight());
    }else if(valign == VAlign.Top){
        offset.y = -getHeight();
    }

    m_shader.bind();        
    m_shader.setAttributeBuffer("vertex", m_vertexBuffer, 2);
    m_shader.setUniformMatrix("PVMmat", PVMmat);
    m_shader.setUniformVector("color", color);
    m_shader.setUniformScalar("texSize", (float)m_textureSize);
    m_shader.setTexture("tex", m_fontTexture, GL11.GL_TEXTURE_2D);
    GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, m_model.getIndexBuffer());
    for(int i = 0; i < text.length(); ++i) {
        CharCoords coo = m_charMap.get(text.charAt(i));
        m_shader.setUniformVector("charCoords", new Vector4f(coo.x, coo.y, coo.width, coo.height));
        m_shader.setUniformVector("offset", offset);
        GL11.glDrawElements(GL11.GL_TRIANGLES, m_indexCount, GL11.GL_UNSIGNED_INT, 0);
        offset.x += coo.width;
    }
    GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
    m_shader.unbind();
}

where the functions getHeigth and getWidth are:

public int getWidth(String text) {
    int totalwidth = 0;
    for (int i = 0; i < text.length(); i++) {
        CharCoords coo = m_charMap.get(text.charAt(i));
        totalwidth += coo.width;
    }
    return totalwidth;
}

public int getHeight() {
    return m_fontMetrics.getHeight();
}
  • Note: to set the scale and position of your text, modify the model matrix accordingly.
like image 162
smani Avatar answered Oct 26 '22 19:10

smani