PDA

View Full Version : OpenGL 3.2 and bitmap fonts



Brainer
28-03-2010, 10:25 PM
Hello there.

I was wondering - the documentation says that display lists and bitmaps are deprecated in OpenGL 3.2 core profile. Well then, how do we render bitmap fonts? ??? I can't find any "new code" doing this - only the old snippets where display lists are involved.

Do you happen to know anything in this matter?

paul_nicholls
29-03-2010, 02:17 AM
Hi Brainer,
I've never used display lists for fonts.

I have taken an existing font (ttf, or other), and rendered it to a texture (using either Font Studio 4, or my own programs). I have then loaded into OpenGL the texture and just drawn quads or triangle strips (2 for each character) to the screen representing each character.

I haven't used VBOs or vertex buffers like you should use in OpenGL 3+ (only used OpenGL 1.1 or 1.2 myself ATM), but only the usual glVertex2f() calls...

cheers,
Paul

Brainer
29-03-2010, 06:08 AM
Thanks for the idea, Paul. :) If you could paste some code here, I'd be glad.

paul_nicholls
29-03-2010, 10:54 AM
The quickest way to do bitmapped fonts would be to get Font Studio 4.1

http://web.archive.org/web/20080616193316/www.nitrogen.za.org/projectinfo.asp?id=12

You should be also able to get the Font4.pas file from that site too so you can load and draw in OpenGL the font textures created by Font Studio :)

If not, I can load the Font4.pas file somewhere so you can use it ;)

cheers,
Paul

Brainer
29-03-2010, 10:59 AM
I can't find it, Paul. Can you please upload it somewhere?

User137
29-03-2010, 11:21 AM
I'm using built in bitmap generator myself so i don't have to provide applications with font picture files. Maybe you can pull something out of this function:

// You can call this with parameters such as
// 'Courier',10,256
// if fontSize is much bigger it will not fit well in 256 sized
// texture, that is when you need to increase TexSize to 512
procedure TNXFont.CreateBMP(fontName: string; fontSize,
_TexSize: integer);
var i,n,x,y: integer; b: TBitmap;
begin
self.TexSize:=_TexSize;
nxTex.Options:=[toColorKey];
nxTex.TransparentColor.r:=0;
nxTex.TransparentColor.g:=0;
nxTex.TransparentColor.b:=0;
TextureI:=nxTex.AddTexture2('$'+fontname,'',true);
b:=TBitmap.Create;
b.Width:=TexSize; b.Height:=TexSize;
sx:=texSize div 16; sy:=texSize div 14;
with b.Canvas do begin
pen.Color:=clBlack; brush.Color:=clBlack;
rectangle(0,0,texSize,texSize);
font.Name:=fontName; font.Size:=fontSize; font.Color:=clWhite;
brush.Style:=bsClear; n:=0;
pen.Color:=clRed;
// First 32 characters (0..31) in ascii table are meaningless
// They don't contain any characters used in texts.
for i:=0 to 255-32 do begin
// I save width in pixels of each character in charW[] array
// This is used in rendering process
charW[i+32]:=TextWidth(chr(i+32));
if charW[i+32]>sx-1 then charW[i+32]:=sx-1;
n:=n+TextHeight(chr(i+32));
x:=sx*(i mod 16); y:=sy*(i div 16);
TextRect(bounds(x,y,sx,sy),x+1,y,chr(i+32));
end;
CenterH:=n div ((255-31)*2);
end;
// At this point TBitmap b contains full font texture
nxTex.LoadBMPData(@nxTex.texture[textureI],b);
b.Free;
end;

paul_nicholls
29-03-2010, 07:42 PM
I can't find it, Paul. Can you please upload it somewhere?


Here is the Font4.pas file:

http://pastebin.com/eMgUHWRR

I did comment out the loadjpgs method so I could get it running under Lazarus/freepascal though (I didn't need, or want it anyway)...

I hope this helps :)

cheers,
Paul

Brainer
30-03-2010, 06:55 PM
Ok, thanks Paul - I'll take a rain check, 'cause another problem appeared.

Before, I was using Vampire Imaging Lib for loading textures, but apperently it doesn't work with D2010. :( Is there a version for D2010? Or maybe you can suggest me another lib?

paul_nicholls
30-03-2010, 07:50 PM
Perhaps you could try the freeimage image loading lib?

http://freeimage.sourceforge.net/

It is also free :)

don't know if it is D2010 compatible though...

cheers,
Paul

Brainer
30-03-2010, 07:57 PM
I figured out that Vampyre is actually D2010 compatible... ::)

I have a question regarding rendering a quad in OGL 3.0. Let's say I have a quad like this:


glVertex3f(x, y, z);
glVertex3f(x, y - pCharacter^.dy, z);
glVertex3f(x + pCharacter^.dx, y - pCharacter^.dy, z);
glVertex3f(x + pCharacter^.dx, y, z);

Since quads are deprecated in OGL 3.0, I need to convert it to two triangles. How do I do that?

User137
30-03-2010, 08:22 PM
Could you use a TRIANGLE_STRIP? That way you would still manage it with 4 vertices.


0---2
| / |
1/_3

Andreaz
31-03-2010, 05:55 AM
Triangle strips is not the best way as they are continues over more then 2 triangles. Better to draw using index buffer.

In other words draw the verticies as you would normaly do but into a VBO with GL_STREAM_DRAW and then generate a index buffer:




0---2 4---6
| / | | / |
1---3 5---7

// Triangle 1
indicies[0]:= 0;
indicies[0]:= 2;
indicies[0]:= 1;
// Triangle 2
indicies[0]:= 2;
indicies[0]:= 3;
indicies[0]:= 1;

// Triangle 3
indicies[0]:= 4;
indicies[0]:= 6;
indicies[0]:= 5;
// Triangle 4
indicies[0]:= 6;
indicies[0]:= 7;
indicies[0]:= 5;


and so on...

User137
31-03-2010, 10:25 AM
I don't know OpenGL 3 that well but figures described here (http://openbve.trainsimcentral.co.uk/common/faces.png) are basics in all graphics API's? However you can use indexes with triangle_strips too. Only point of indexes is to save memory by reducing vertex calls. But if you compare rendering speed of GL_TRIANGLES and GL_TRIANGLE_STRIP, then strip is always faster.

Now that i think about it, it depends if you render the font characters continuous or 1 by 1. You actually need unique texture coordinate per vertex so continuous is out of question. I guess in this case indexes wouldn't do any good.

Brainer
02-04-2010, 10:44 PM
I'm still trying to convert a quad into a triangle. I got this code (it's being worked on, hence it looks so ugly):


{ .: DrawQuad :. }
procedure DrawQuad(X, Y, Wid, Hgt, Lev, Tu, Tu2, Tv, Tv2: Single);
var
V: array[0..3] of TBrainVector;
begin
Tv := 1 - Tv;
Tv2 := 1 - Tv2;

TexCoords.Add(TexCoord(Tu, Tv));
TexCoords.Add(TexCoord(Tu2, Tv));
TexCoords.Add(TexCoord(Tu2, Tv2));
TexCoords.Add(TexCoord(Tu, Tv2));
V[0] := Vec3(X, Y, -Lev);
V[1] := Vec3(X + Wid, Y, -Lev);
V[2] := Vec3(X + Wid, Y - Hgt, -Lev);
V[3] := Vec3(X, Y - Hgt, -Lev);

Vertices.Add(V[0]);
Vertices.Add(V[1]);
Vertices.Add(V[2]);

Vertices.Add(V[2]);
Vertices.Add(V[3]);
Vertices.Add(V[0]);
end;

The quad is drawn correctly, but there's something wrong with its texture coordinates. Can you point me out, what's wrong?

paul_nicholls
03-04-2010, 12:49 AM
I don't know if it helps, but here is how I draw a quad using a triangle strip:

Procedure TParticleSystem.Draw;
Var
i : Integer;
Particle : TParticle;
Begin
glPushAttrib(GL_ALL_ATTRIB_BITS);
glDisable(GL_DEPTH_TEST);
glDisable(GL_LIGHTING);
glEnable(GL_TEXTURE_2D);
glEnable(GL_BLEND);
glEnable(GL_ALPHA);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
For i := FActiveParticles.Count - 1 Downto 0 Do
Begin
Particle := TParticle(FActiveParticles.Items[i]);
glPushMatrix;
glTranslatef(Particle.X,Particle.Y,Particle.Z);
glScalef(FScale,FScale,1);
glColor4f(Particle.R,Particle.G,Particle.B,Particl e.A);
glBegin(GL_TRIANGLE_STRIP);
glTexCoord2fv(@FImageRect.tRect.t3); glVertex3f(+Particle.Size/2,+Particle.Size/2,0); // Top Right
glTexCoord2fv(@FImageRect.tRect.t2); glVertex3f(-Particle.Size/2,+Particle.Size/2,0); // Top Left
glTexCoord2fv(@FImageRect.tRect.t4); glVertex3f(+Particle.Size/2,-Particle.Size/2,0); // Bottom Right
glTexCoord2fv(@FImageRect.tRect.t1); glVertex3f(-Particle.Size/2,-Particle.Size/2,0); // Bottom Left
glEnd;
glPopMatrix;
End;
glPopAttrib;
End;

cheers,
Paul

User137
03-04-2010, 01:21 AM
The quad is drawn correctly, but there's something wrong with its texture coordinates. Can you point me out, what's wrong?
You are adding only 4 texture coordinates (instead of 6) but you use 6 vertices. If you used index buffer you would manage with only 4 texture and vertex coordinates.

Brainer
06-04-2010, 11:29 AM
Ok, I got over the problem with rendering quads. :)

But now, I'd like to know, how do I put the text in front of the camera in the 2D space? I'm using this code to set the projection matrix:


FProjMat := Mat4CreateOrtho(0, FWidth, 0, FHeight, -1.0, 1.0);

And this snippet to render the font:


glEnable(GL_BLEND);
glDisable(GL_DEPTH_TEST);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

Camera.ProjectionType := ptOrthogonal;

Texture.Bind(0);
Self.DoDefaultRender(Camera);
Texture.UnBind();

Camera.ProjectionType := ptPerspective;

glDisable(GL_BLEND);
glEnable(GL_DEPTH_TEST);

But the text is not visible in the orthogonal view. It is in the perspective, tho'. What am I doing wrong? Maybe it's something wrong with the shader? ???


#version 150

precision highp float;

uniform mat4 proj;
uniform mat4 modelview;

in vec4 vertex;
in vec3 normal;
in vec2 texCoord;

out vec3 fragmentNormal;
out vec2 textureCoord;

void main(void) {

fragmentNormal = (modelview * vec4(normal, 0.0)).xyz;
textureCoord = texCoord;

gl_Position = proj * modelview * vertex;
}

chronozphere
06-04-2010, 01:36 PM
Could be a few things:

- You are not using the right Z value
- You are rendering the letters in the wrong position (try (0,0) )
- You are not using the right scale of the letters
- You are rendering the triangles in the wrong order (unlikely though, since it works in perspective view).

WILL asked me to write a little article for PascalGamer that explains how to do both 2D and 3D rendering at the same time. I might do that in the future, if I find the time. ;)

User137
06-04-2010, 04:20 PM
If your triangles are rendered 1-sided (GL_CULL_FACE enabled) then by default ortho is like the backside of perspective. If you turn camera 180 degrees or draw vertices in opposite order it may help. (or coordinates just aren't right...)

Brainer
07-04-2010, 11:03 PM
I tried both chronozphere and User137 solutions, but none worked.

Hm, I can't understand why it doesn't work. The quads are not displayed. You said the coords may not be right. Here's how I specify them:


procedure TBrain2DFont.BuildObject;
const
Txt: String = 'BrainEngine';
Size: Single = 0.025;
Y: Single = 0.0;
var
CurX: single;
Ch: Widechar;
Chaar, I, Ind: integer;

{ .: DrawQuad :. }
procedure DrawQuad(X, Y, Wid, Hgt, Lev, Tu, Tu2, Tv, Tv2: Single);
var
V: array[0..3] of TBrainVector;
begin
TexCoords.Add(TexCoord(Tu, Tv));
TexCoords.Add(TexCoord(Tu2, Tv));
TexCoords.Add(TexCoord(Tu2, Tv2));
TexCoords.Add(TexCoord(Tu2, Tv2));
TexCoords.Add(TexCoord(Tu, Tv2));
TexCoords.Add(TexCoord(Tu, Tv));

V[0] := Vec3(-X, Y, Lev);
V[1] := Vec3(-(X + Wid), Y, Lev);
V[2] := Vec3(-(X + Wid), Y - Hgt, Lev);
V[3] := Vec3(-X, Y - Hgt, Lev);

Vertices.Add(V[0]);
Vertices.Add(V[1]);
Vertices.Add(V[2]);
Vertices.Add(V[2]);
Vertices.Add(V[3]);
Vertices.Add(V[0]);
end;

begin
CurX := 0.0;
if (Length(F) = 0) then
exit;

for I := 1 to length(Txt) do
begin
Ch := Txt[I];
Chaar := integer(ch);

if Chaar = 32 then
begin
Ind := -1;
CurX := CurX + SpaceWidth*Size;
end
else
begin
Ind := CharLookup[Chaar];
end;

if ind > -1 then
begin
CurX := CurX + F[Ind].A*Size;


DrawQuad(CurX, Y, F[ind].Wid*Size, F[ind].Hgt*Size, 0.0, F[ind].x1,
F[ind].x2, F[ind].y1, F[ind].y2);

CurX := CurX + F[Ind].C*Size;
end;
end;
end;

This code works, but only in the perspective view. The font is displayed correctly: http://i44.tinypic.com/25sara0.png

So what's wrong? ???

User137
08-04-2010, 10:04 AM
I'm guessing you are drawing it so small it's hardly 1 pixel. Ortho screen coordinates go from 0 to window's width etc. You are scaling the render by 0.025, try leave it out for ortho. Each character should be at least 6x10 in numeric coordinates.

Brainer
08-04-2010, 06:53 PM
Each character should be at least 6x10 in numeric coordinates.

What do you mean?

I tried leaving it out, but still no success. :(

User137
08-04-2010, 08:22 PM
Vec3(-X, ...
X is always negative? This would draw it outside the screen.

By 6x10 i mean that it would be roughly the size of font on this forum page, in pixels. You just need to take into account that perspective view goes by default from ~ -2 to 2 when ortho is 0 to 800 or something, that is hundreds of times bigger number scale.

Brainer
09-04-2010, 03:52 PM
I use a negative X to draw the triangles in reverse order. Is that a bad idea?

chronozphere
09-04-2010, 05:25 PM
I use a negative X to draw the triangles in reverse order. Is that a bad idea?


It doesn't really work like that. If you want to render text at (300, 300), you'll end up with text being rendered at (-300, 300) which is out of the screen.

If you want to reverse the triangles, you should alter the order of your vertices:



Vertices.Add(V[0]);
Vertices.Add(V[2]);
Vertices.Add(V[1]);
Vertices.Add(V[3]);
Vertices.Add(V[2]);
Vertices.Add(V[0]);


This is not tested. I hope you get what I mean. :)

P.S: You are right about the fact that flipping an axis changes the rotation order. However, flipping X has some undesirable effects in this case.

Brainer
10-04-2010, 07:55 AM
P.S: You are right about the fact that flipping an axis changes the rotation order. However, flipping X has some undesirable effects in this case.

Even if I use positive values on the X axis, the text is not rendered.

Hm, I've heard there's a need to map the text coordinates to the screen's coordinates before drawing in 2D mode. So it seems my current matrix calculations lead to no good, since there's a need to, I dunno how to call it, "project" the matrix onto a 2D plane? Maybe that's what should be done, what do you think? ???

Here's the quote:


if you were using a shader, you could just send vec2 with the screen size, then (vertex.x/(screen.x*0.5))-1 would scale the screen pixel to the correct place.. eg. (300/(600*0.5))-1 = 0 which would be the center of the screen.. in other words, you just need to send the vertex in screen coordinates and apply this scale instead of transforming it with the matrix in the shader.. it should work that way..

User137
10-04-2010, 07:58 AM
Can you do this first. Go in ortho mode and just draw a line from 50,50 to 100,50. If it is not showing then something is wrong with the setup. (GL_LINES)

Brainer
10-04-2010, 09:48 AM
Ok.

I tried it and the line is not shown in orthographic projection.

Brainer
10-04-2010, 11:32 AM
I figured it out!

I tried once again to turn the camera by 180 degrees and it worked. :) I had to change the matrix I used to create the final model-view matrix so that it could work. Here's how I do it.

Calculating the projection matrix


FProjMat := Mat4CreateOrtho(0, FWidth, 0, FHeight, -1.0, 1.0);

Calculating the view matrix


CachedMatrix := Mat4Rotate(Mat4Identity(), Euler(0.0, 180.0, 0.0))

Rendering the text


procedure TBrain2DText.RenderText(const Camera: TBrainCamera;
const AText: String; const ATextSize: Single; const AFont: TBrainFont);
var
CurX: Single;
Ch: Char;
Chaar, I, Ind: Integer;
ProjMat, ModelMat, ModelView: TBrainMatrix;

{ .: AddQuad :. }
procedure AddQuad(X, Y, Wid, Hgt, Tu, Tu2, Tv, Tv2: Single);
var
V: array [0 .. 3] of TBrainVector;
begin
TexCoords.Add(TexCoord(Tu, Tv));
TexCoords.Add(TexCoord(Tu2, Tv));
TexCoords.Add(TexCoord(Tu2, Tv2));
TexCoords.Add(TexCoord(Tu2, Tv2));
TexCoords.Add(TexCoord(Tu, Tv2));
TexCoords.Add(TexCoord(Tu, Tv));

V[0] := Vec3(-X, Y, 0.0);
V[1] := Vec3(-(X + Wid), Y, 0.0);
V[2] := Vec3(-(X + Wid), Y - Hgt, 0.0);
V[3] := Vec3(-X, Y - Hgt, 0.0);

Vertices.Add(V[0]);
Vertices.Add(V[1]);
Vertices.Add(V[2]);
Vertices.Add(V[2]);
Vertices.Add(V[3]);
Vertices.Add(V[0]);
end;

begin
// Before we begin, check if we have everything we need
if not Assigned(Camera) then
exit;
if not Assigned(Shader) then
exit;
if (AText = '') then
exit;
if not Assigned(AFont) then
exit;
if (ATextSize = 0.0) then
exit;

// If there are any leftovers from previous text, remove it
Vertices.Clear();
TexCoords.Clear();

// We are provided with parameters, so it's time to render
// the text
CurX := -Position.X;
if not (AFont.FontLoaded) then
exit;

// Iterate through the text
for I := 1 to Length(AText) do
begin
// Get the current character
Ch := AText[I];
Chaar := Integer(Ch);

// Check if the current character is a legal one
// (i.e. neither #10 nor #13). If it is, also check
// if it's not the Space character. If so, move the cursor
Ind := -1;
case Chaar of
32:
CurX := CurX + AFont.SpaceWidth * ATextSize;
10, 13: // No line-breaks supported
break;
else
Ind := AFont.CharacterLookup[Chaar];
end;

// If we have a valid character...
if (Ind > -1) then
begin
// Setup the cursor
CurX := CurX + AFont.CharacterInfo[Ind].A * ATextSize;

// Add a new quad which contains the character
AddQuad(CurX, Position.Y, AFont.CharacterInfo[Ind].Width * ATextSize,
AFont.CharacterInfo[Ind].Height * ATextSize, AFont.CharacterInfo[Ind].x1,
AFont.CharacterInfo[Ind].x2, AFont.CharacterInfo[Ind].y1,
AFont.CharacterInfo[Ind].y2);

// Move the cursor to the next character
CurX := CurX + AFont.CharacterInfo[Ind].C * ATextSize;
end;
end;
// Notify the object that its structure has changed
Self.StructureChanged();

// Enable blending
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

// Switch to the orthographic projection, enable the shader,
// and bind the texture
Camera.ProjectionType := ptOrthogonal2D;
Camera.ForceMatrixRecalc();

Shader.Enable();
AFont.FontTexture.Bind(0);
AFont.FontTexture.Apply();

// Calculate the matrices
ProjMat := Camera.ProjectionMatrix;
ModelMat := Mat4Translate(Mat4Identity(), Vec3(Position.X, Position.Y, 0.0));
ModelView := Mat4Multiply(ModelMat, Camera.Matrix);

// Update the shader
Shader.SetMatrix(ProjectionMatrixUniformName, @ProjMat);
Shader.SetMatrix(ModelViewMatrixUniformName, @ModelView);

// Finally, render the text using triangle strips
glBindVertexArray(GetVAO());
glDrawArrays(GL_TRIANGLE_STRIP, 0, Vertices.Count);
glBindVertexArray(0);

// Restore to defaults
Camera.ProjectionType := ptPerspective;
Camera.ForceMatrixRecalc();

glDisable(GL_BLEND);
end;


Hope this helps. :)

phibermon
10-04-2010, 05:24 PM
*sigh* it was so much easier with display lists :(

Brainer
11-04-2010, 08:05 AM
Maybe true, but using VBOs you have more control over the fonts you're displaying. :) Besides, the method I know which used display lists was based on Windows-specific functions. This way you can make your code more portable + you can do whatever you want with every character of a font (i.e. rotate it, etc.). :)

EDIT
I've just discovered a bug in my previous code, here's a quick fix:


// Finally, render the text using triangles
glBindVertexArray(GetVAO());
glDrawArrays(GL_TRIANGLES, 0, Vertices.Count);
glBindVertexArray(0);


EDIT2
Hm, I found some bugs while testing the code further. First of all, the projection matrix should be calculated as follows:


FProjMat := Mat4CreateOrtho(0, FWidth, FHeight, 0, -1.0, 1.0);

The difference is that now the origin stays at the top-left corner of the screen instead of the bottom-left.

Of course, I needed to do changes in the drawing itself. Here's the full code now:


procedure TBrain2DText.RenderText(const Camera: TBrainCamera;
const AText: String; const ATextSize: Single; const AFont: TBrainFont);
var
CurX: Single;
Ch: Char;
Chaar, I, Ind: Integer;
ProjMat, ModelView: TBrainMatrix;
CameraProjection: TBrainCameraProjectionType;

{ .: AddQuad :. }
procedure AddQuad(X, Y, Wid, Hgt, Tu, Tu2, Tv, Tv2: Single);
var
V: array [0 .. 3] of TBrainVector;
begin
TexCoords.Add(TexCoord(Tu, Tv));
TexCoords.Add(TexCoord(Tu2, Tv));
TexCoords.Add(TexCoord(Tu2, Tv2));
TexCoords.Add(TexCoord(Tu2, Tv2));
TexCoords.Add(TexCoord(Tu, Tv2));
TexCoords.Add(TexCoord(Tu, Tv));

V[0] := Vec3(-X, Y, 0.0);
V[1] := Vec3(-(X + Wid), Y, 0.0);
V[2] := Vec3(-(X + Wid), Y + Hgt, 0.0);
V[3] := Vec3(-X, Y + Hgt, 0.0);

Vertices.Add(V[0]);
Vertices.Add(V[1]);
Vertices.Add(V[2]);
Vertices.Add(V[2]);
Vertices.Add(V[3]);
Vertices.Add(V[0]);
end;

begin
// Before we begin, check if we have everything we need
if not Assigned(Camera) then
exit;
if not Assigned(Shader) then
exit;
if (AText = '') then
exit;
if not Assigned(AFont) then
exit;
if (ATextSize = 0.0) then
exit;

// Switch to the orthographic projection
CameraProjection := Camera.ProjectionType;
Camera.ProjectionType := ptOrthographic2D;
Camera.ForceMatrixRecalc();

// If the desired text is different from the last one rendered, we need to
// rebuild the object. If not, just render what we already have
if (LastText <> AText) then
begin
// If there are any leftovers from previous text, remove them
Vertices.Clear();
TexCoords.Clear();

// We are provided with parameters, so it's time to render
// the text. But first, check if the selected font has been loaded
CurX := 0.0;
if not (AFont.FontLoaded) then
exit;

// Iterate through the text
for I := 1 to Length(AText) do
begin
// Get the current character
Ch := AText[I];
Chaar := Integer(Ch);

// Check if the current character is a legal one
// (i.e. neither #10 nor #13). If it is, also check
// if it's not the Space character. If so, move the cursor.
// Otherwise, just retrieve the character from array
Ind := -1;
case Chaar of
32:
CurX := CurX + AFont.SpaceWidth * ATextSize;
10, 13: // No line-breaks supported
break;
else
Ind := AFont.CharacterLookup[Chaar];
end;

// If we have a valid character...
if (Ind > -1) then
begin
// Setup the cursor
CurX := CurX + AFont.CharacterInfo[Ind].A * ATextSize;

// Add a new quad which contains the character
AddQuad(CurX, 0.0, AFont.CharacterInfo[Ind].Width * ATextSize,
AFont.CharacterInfo[Ind].Height * ATextSize, AFont.CharacterInfo[Ind].x1,
AFont.CharacterInfo[Ind].x2, AFont.CharacterInfo[Ind].y1,
AFont.CharacterInfo[Ind].y2);

// Move the cursor to the next character
CurX := CurX + AFont.CharacterInfo[Ind].C * ATextSize;
end;
end;
// Notify the object that its structure has changed
Self.StructureChanged();

// Remember what the last text was
LastText := AText;
end;

// Enable blending and disable the depth buffer
glDisable(GL_DEPTH_TEST);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

// Enable the shader and bind the texture
Shader.Enable();
AFont.FontTexture.Bind(0);
AFont.FontTexture.Apply();

// Calculate the matrices
ProjMat := Camera.ProjectionMatrix;
ModelView := Mat4Translate(Camera.Matrix, Vec3(Position.X, Position.Y, 0.0));

// Update the shader
Shader.SetMatrix(ProjectionMatrixUniformName, @ProjMat);
Shader.SetMatrix(ModelViewMatrixUniformName, @ModelView);

// Finally, render the text using triangles
glBindVertexArray(GetVAO());
glDrawArrays(GL_TRIANGLES, 0, Vertices.Count);
glBindVertexArray(0);

// Restore the projection
Camera.ProjectionType := CameraProjection;
Camera.ForceMatrixRecalc();

// Turn OpenGL's state machine back to what we had before
glDisable(GL_BLEND);
glEnable(GL_DEPTH_TEST);
end;