OpenGL文字渲染

一、简介
由于OpenGL本身并没有定义如何渲染文字到屏幕,也没有用于表示文字的基本图形,我们必须自己定义一套全新的方式才能让OpenGL来绘制文字。目前一些技术包括:通过GL_LINES来绘制字形、创建文字的3D网格、将带有文字的纹理渲染到一个2D方块中。

二、文字渲染
在早期渲染文字时,选择你应用程序的字体(或者创建你自己的字体)来绘制文字是通过将所有用到的文字加载在一张大纹理图中来实现的。这张纹理贴图我们把它叫做位图字体(Bitmap Font),它包含了所有我们想要使用的字符。这些字符被称为字形(Glyph)。每个字形根据他们的编号被放到位图字体中的确切位置,在渲染这些字形的时候根据这些排列规则将他们取出并贴到指定的2D方块中。

现代文字渲染:FreeType
FreeType是一个能够用于加载字体并将他们渲染到位图以及提供多种字体相关的操作的软件开发库。它是一个非常受欢迎的跨平台字体库,被用于 Mac OSX、Java、PlayStation主机、Linux、Android等。FreeType的真正吸引力在于它能够加载TrueType字体。
TrueType字体不采用像素或其他不可缩放的方式来定义,而是一些通过数学公式(曲线的组合)。这些字形,类似于矢量图像,可以根据你需要的字体大小来生成像素图像。通过使用TrueType字体可以轻易呈现不同大小的字符符号并且没有任何质量损失。
#include <ft2build.h>
#include FT_FREETYPE_H
FreeType要做的事就是加载TrueType字体并为每一个字形生成位图和几个度量值。我们可以取出它生成的位图作为字形的纹理,将这些度量值用作字形纹理的位置、偏移等描述。
要加载一个字体,我们需要做的是初始化FreeType并且将这个字体加载为FreeType称之为面(Face)的东西。这里为我们加载一个从Windows/Fonts目录中拷贝来的TrueType字体文件arial.ttf。
FT_Library ft;
if (FT_Init_FreeType(&ft))
std::cout << “ERROR::FREETYPE: Could not init FreeType Library” << std::endl;
FT_Face face;
if (FT_New_Face(ft, “fonts/arial.ttf”, 0, &face))
std::cout << “ERROR::FREETYPE: Failed to load font” << std::endl;
这些FreeType函数在出现错误的情况下返回一个非零整数值。
一旦我们加载字体面完成,我们还要定义文字大小,这表示着我们要从字体面中生成多大的字形:
FT_Set_Pixel_Sizes(face, 0, 48);
此函数设置了字体面的宽度和高度,将宽度值设为0表示我们要从字体面通过给出的高度中动态计算出字形的宽度。
一个字体面中包含了所有字形的集合。我们可以通过调用FT_Load_Char函数来激活当前要表示的字形。这里我们选在加载字母字形’X’:
if (FT_Load_Char(face, ‘X’, FT_LOAD_RENDER))
std::cout << “ERROR::FREETYTPE: Failed to load Glyph” << std::endl;
通过将FT_LOAD_RENDER设为一个加载标识,我们告诉FreeType去创建一个8位的灰度位图,我们可以通过face->glyph->bitmap来取得这个位图。

着色器:
我们需要使用下面的顶点着色器来渲染字形:

#version 330 core
layout (location = 0) in vec4 vertex; // <vec2 pos, vec2 tex>
out vec2 TexCoords;
uniform mat4 projection;
void main()
{
gl_Position = projection * vec4(vertex.xy, 0.0, 1.0);
TexCoords = vertex.zw;
}
我们将位置和纹理纹理坐标的数据合起来存在一个vec4中。顶点着色器将会将位置坐标与投影矩阵相乘,并将纹理坐标转发给片段着色器:
#version 330 core
in vec2 TexCoords;
out vec4 color;
uniform sampler2D text;
uniform vec3 textColor;
void main()
{
vec4 sampled = vec4(1.0, 1.0, 1.0, texture(text, TexCoords).r);
color = vec4(textColor, 1.0) * sampled;
}
片段着色器有两个uniform变量:一个是单颜色通道的字形位图纹理,另一个是文字的颜色,我们可以同调整它来改变最终输出的字体颜色。我们首先从位图纹理中采样,由于纹理数据中仅存储着红色分量,我们就通过r分量来作为取样颜色的aplpha值。结果值是一个字形背景为纯透明,而字符部分为不透明的白色的颜色。我们将此颜色与字体颜色uniform值相乘就得到了要输出的字符颜色了。
当然我们必需开启混合才能让这一切行之有效:
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
至于投影矩阵我们将使用一个正交投影矩阵。对于文字渲染我们通常都不需要进行透视,使用正交投影也能保证我们所有的顶点坐标设置有效:
glm::mat4 projection = glm::ortho(0.0f, 800.0f, 0.0f, 600.0f);
我们设置投影矩阵的底部参数为0.0f并将顶部参数设置为窗口的高度。这样做的结果是我们可以通过设置0.0f~600.0f的纵坐标来表示顶点在窗口中的垂直位置。这意味着现在点(0.0,0.0)表示左下角而不再是窗口正中间。
最后要做的事是创建一个VBO和VAO用来渲染方块。现在我们分配足够的内存来初始化VBO然后在我们渲染字符的时候再来更新VBO的内存。
GLuint VAO, VBO;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 6 * 4, NULL, GL_DYNAMIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);

每个2D方块需要6个顶点,每个顶点又是由一个4维向量(一个纹理坐标和一个顶点坐标)组成,因此我们将VBO的内存分配为6*4个float的大小。由于我们会在绘制字符时更新这断内存,所以我们将内存类型设置为GL_DYNAMIC_DRAW。

发布了146 篇原创文章 · 获赞 28 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/weixin_38498942/article/details/104069365