前言
之前还在头疼着,游戏里显示文字怎么办,文字?你可能会说,前面不都是已经显示文字了。确实,有显示出来的文字,但那只是做好的图片上有文字而已,我希望能在代码里输入文字,屏幕上就能显示文字。解决起来真的是麻烦,不过我还是克服了各种困难,实现了文字显示。
正题
首先,需要知道一个东西,truetype2.0,这个是用来生成字体纹理的。这个东西不太好下载,慢慢下吧,我是下载了好多次才成功。
我的设计思路就是,利用truetype加载一个字体文件,一般是TTF,TTC格式的字体文件,如果你用的是windows系统,在C盘windows文件夹下有个Font文件夹,里面都是这样的文件。加载好文件后,将文件里的字体绘制到一张大的纹理上,效果就是一个大纹理上有各种文字,然后显示文字的时候,就从这张纹理上找到对应文字的位置绘制出来。
bool GLTextureFont::openFont(const char * filename,int _fontSize)
{
//设置字号
fontSize = _fontSize;
//清空字库
memset(characters,0,sizeof(TexChar)*(1<<16));
//初始化字库
if(FT_Init_FreeType( &fontLibrary ))
return false;
//设置字体
if(FT_New_Face(fontLibrary,filename,0,&fontFace))
return false;
//设置字号
FT_Set_Char_Size( fontFace, fontSize << 6, fontSize << 6, 72, 72);
//生成字纹理
glGenTextures(1,&fontTexture);
glBindTexture(GL_TEXTURE_2D, fontTexture);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
glTexImage2D( GL_TEXTURE_2D, 0, GL_ALPHA,1024,1024,0, GL_ALPHA, GL_UNSIGNED_BYTE,0);
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
return true;
}
这个函数的参数,一个是字体文件名,另一个是字号。函数作用就是加载一个文字文件,创建一个纹理,还有绘制纹理的VAO,VBO。
TexChar * GLTextureFont::getWchar(wchar_t wchar)
{
if(characters[wchar].x1==0 && characters[wchar].y1==0&&characters[wchar].x2==0&&characters[wchar].y2==0)
{
//换行
if(x + fontSize>1024)
{
x = 0;
y = y + fontSize;
}
//获取字体贴图到纹理上
FT_Load_Glyph( fontFace, FT_Get_Char_Index(fontFace, wchar), FT_LOAD_DEFAULT );
FT_Glyph glyph;
FT_Get_Glyph( fontFace->glyph, &glyph );
FT_Glyph_To_Bitmap( &glyph, ft_render_mode_normal, 0, 1 );
FT_BitmapGlyph bitmap_glyph = (FT_BitmapGlyph)glyph;
FT_Bitmap& bitmap = bitmap_glyph->bitmap;
//把文字绘制在纹理上
if (bitmap.width == 0 || bitmap.rows == 0)
{
x += fontSize/2;
characters[wchar].x1 = x;
characters[wchar].y1 = y;
characters[wchar].x2 = x + bitmap.width;
characters[wchar].y2 = y + bitmap.rows;
characters[wchar].offsetY = 0;
characters[wchar].offsetX = 0;
}
else
{
glBindTexture(GL_TEXTURE_2D,fontTexture);
characters[wchar].x1 = x;
characters[wchar].y1 = y;
characters[wchar].x2 = x + bitmap.width;
characters[wchar].y2 = y + bitmap.rows;
characters[wchar].offsetY = bitmap_glyph->top;;
characters[wchar].offsetX = bitmap_glyph->left;
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexSubImage2D (GL_TEXTURE_2D,0,x,y,bitmap.width,bitmap.rows,GL_ALPHA,GL_UNSIGNED_BYTE,bitmap.buffer);
x += bitmap.width + 1;
}
}
return &characters[wchar];
}
这里借鉴了一篇博客的思想,我拿来进行了引用,大概就是显示一个字符的时候,去一个字典中查找这个字是不是已经映射好了文字的位置,如果映射好了,直接返回这个文字的网格信息,然后绘制,如果没有映射过,就去生成的字体中去查找这个文字所在的位置,将其复制出来贴到你的字典里,这里的字典就是你创建的大纹理,并且还有一个数组来记录对应的字在大纹理上的位置,而输入的文字作为数组的索引。
最后,在显示的时候,我自己改编了一下方法,利用VBO存储每句画,用着色器来变换文字颜色和文字的位置。
void GLTextureFont::draw()
{
int length = wcslen(_wstring);
int index = 0;
int screenX=0;
for(int i=0;i<length;i++)
{
//取一个字符的纹理
TexChar*texChar = getWchar(_wstring[i]);
int textHeight = texChar->y2-texChar->y1;
int textWidth = texChar->x2-texChar->x1;
float offset =
tDrawChar[index + 0].x = (screenX)*1.0/GLSTD_TOOLS::view_width;
tDrawChar[index + 0].y =(textHeight)*1.0/GLSTD_TOOLS::view_height;
tDrawChar[index + 0].z = 0;
tDrawChar[index + 0].u = texChar->x1/1024.0;
tDrawChar[index + 0].v = texChar->y1/1024.0;
//顶点2
tDrawChar[index + 1].x = (textWidth+screenX)*1.0/GLSTD_TOOLS::view_width;
tDrawChar[index + 1].y = (textHeight)*1.0/GLSTD_TOOLS::view_height;
tDrawChar[index + 1].z = 0;
tDrawChar[index + 1].u = texChar->x2/1024.0;
tDrawChar[index + 1].v = texChar->y1/1024.0;
//顶点3
tDrawChar[index + 2].x = (textWidth+screenX)*1.0/GLSTD_TOOLS::view_width;
tDrawChar[index + 2].y = (0)*1.0/GLSTD_TOOLS::view_height;
tDrawChar[index + 2].z = 0;
tDrawChar[index + 2].u = texChar->x2/1024.0;
tDrawChar[index + 2].v = texChar->y2/1024.0;
//顶点4
tDrawChar[index + 3].x = (screenX)*1.0/GLSTD_TOOLS::view_width;
tDrawChar[index + 3].y = (0)*1.0/GLSTD_TOOLS::view_height;
tDrawChar[index + 3].z = 0;
tDrawChar[index + 3].u = texChar->x1/1024.0;
tDrawChar[index + 3].v = texChar->y2/1024.0;
index += 4;
screenX = screenX+textWidth + texChar->offsetX;
}
//绑定VAO
glBindVertexArray(VAO);
//绑定VBO,复制顶点
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat)* 5*index, tDrawChar, GL_DYNAMIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), 0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
glEnableVertexAttribArray(1);
glBindVertexArray(0);
glBindTexture(GL_TEXTURE_2D,fontTexture);
glUseProgram(program);
glUniformMatrix4fv(position_ID, 1, GL_FALSE, (GLfloat*)glm::value_ptr(position_mat));
glUniform3f(color_ID,color.x,color.y,color.z);
glBindVertexArray(VAO);
glDrawArrays(GL_QUADS,0,index);
glBindVertexArray(0);
}
看似简单的三段代码,其实我连研究再改写用了一整天。
看起来效果还不错。有了文字显示,那么我们就可以完成更多事情了。