基于OpenGL编写一个简易的2D渲染框架-05 渲染文本
阅读文章前需要了解的知识:文本渲染 https://learnopengl-cn.github.io/06%20In%20Practice/02%20Text%20Rendering/
简要步骤:
获取要绘制的字符的 Unicode 码,使用 FreeType 库获取对应的位图数据,添加到字符表中(后面同样的字符可以再表中直接索引),将字符表上的字符填充到一张纹理上。计算每个字符的纹理坐标,使用渲染器绘制
注意的问题:
对于中英文混合的字符串,使用 char 存储时,英文字符占 1 个字节,而中文字符占 2 个字符。必须转换为宽字符,即中英文字符都占 2 个字节。
void TextRender::toWchar(wchar_t* dest, const char* src, int size)
{
const char* old_local = setlocale(LC_CTYPE, "chs");
mbstowcs(dest, src, size);
setlocale(LC_CTYPE, old_local);
}
通过上面的函数,可以将 char 转为 wchar_t。
添加 FreeType 库到工程
注意添加新的包含路径就好了,我把 External 目录也设置为包含路径,否则使用 FreeType 库会发生错误
渲染文本
首先,对 FreeType 库初始化
size = ;
/* 初始化 FreeType 库 */
assert(FT_Init_FreeType(&ftLibrary) == );
/* 加载字体 */
assert(FT_New_Face(ftLibrary, PathHelper::fullPath("Font/msyh.ttc").c_str(), , &ftFace) == );
/* 设定为 UNICODE,默认也是 */
FT_Select_Charmap(ftFace, FT_ENCODING_UNICODE);
/* 定义字体大小 */
FT_Set_Pixel_Sizes(ftFace, size, size);
字体在 Font 文件夹中,为 微软雅黑
定义一个字符结构
struct Character
{
Vec2 texcoord[]; /* 纹理坐标 */
Vec2 size; /* 字型大小 */
Vec2 bearing; /* 从基准线到字形左部/顶部的偏移值 */
int advance; /* 原点距下一个字形原点的距离 */
bool space; std::vector<unsigned char> data;
};
包含渲染字符所需要的数据,对于空白字符(没有位图数据,只有到下一个字符的偏移量)需要特别处理。 data 存储位图数据。
上面只是绘制一个字符的数据,对于一串字符,还需要定义一个文本结构
struct Text
{
Vec2 pos;
float scale;
Color color;
std::vector<Character*> vCharacters;
};
包含绘制文本的坐标,缩放比,颜色以及字符的索引。
创建一个新类 TextRender 使用渲染文本
调用函数 DrawText 直接绘制文本
void TextRender::drawText(int x, int y, float scale, const std::string& text, Color& color)
{
static wchar_t buffer[];
this->toWchar(buffer, text.c_str(), text.size()); int count = ;
for ( int i = ; i < ; i++ ) {
if ( buffer[i] == ) break;
count++;
} Text t = { Vec2(x, y), scale, color };
Character* character = nullptr; for ( int i = ; i < count; i++ ) {
int index = buffer[i]; auto it = characterMap.find(index);
if ( it == characterMap.end() ) {
character = new Character();
character->space = (index == ' ' );
this->loadCharacter(character, index);
characterMap.insert(CharacterMap::value_type(index, character));
}
else {
character = it->second;
}
t.vCharacters.push_back(character);
}
vTexts.push_back(t);
}
遍历所有要绘制的字符,查找字符表,如果字符表存则返回字符。最后把所有字符存储到 Text 中,而 Text 存储在一个数组中(数组存储一帧需要绘制的字符串)。
如果字符表中没有该字符,则加载该字符
void TextRender::loadCharacter(Character* character, unsigned long id)
{
if ( bUpdateTexture == false ) bUpdateTexture = true; FT_Load_Char(ftFace, id, FT_LOAD_RENDER); /* 填充结构数据 */
character->size.set(ftFace->glyph->bitmap.width, ftFace->glyph->bitmap.rows);
character->bearing.set(ftFace->glyph->bitmap_left, ftFace->glyph->bitmap_top);
character->advance = ftFace->glyph->advance.x; if ( character->space ) return; /* 储存位图数据 */
character->data.resize(character->size.x * character->size.y);
memcpy(&character->data[], ftFace->glyph->bitmap.buffer, character->data.size());
}
函数中使用 FreeType 库加载字符位图数据,填充绘制字符的数据信息。一旦这个函数被调用,证明字符表需要添加新的字符了,在渲染文本前需要更新纹理以及纹理坐标。这里使用 bool 值的 bUpdateTexture 标志,待会要更新纹理。
更新纹理的函数
void TextRender::updateTextTexture()
{
glPixelStorei(GL_UNPACK_ALIGNMENT, ); if ( texture.texture != - ) {
glDeleteTextures(, &texture.texture);
}
glGenTextures(, &texture.texture);
glBindTexture(GL_TEXTURE_2D, texture.texture); int count = characterMap.size();
int col, row; if ( count < nRowCharCount ) {
col = count;
row = ;
}
else {
col = nRowCharCount;
row = ceilf(float(count) / col);
}
textureData.resize(row * col * size * size);
for ( auto &ele : textureData ) ele = ; int tex_size_w = col * size;
int tex_size_h = row * size; /* 合并所有字符的位图数据 */
int characterCount = ;
for ( auto it = characterMap.begin(); it != characterMap.end(); ++it ) {
this->copyCharacterData(characterCount / col, characterCount % col, size * col, it->second, tex_size_w, tex_size_h);
characterCount++;
} /* 设置纹理数据 */
glTexImage2D(GL_TEXTURE_2D, , GL_RED, col * size, row * size, , GL_RED, GL_UNSIGNED_BYTE, &textureData[]); /* 设置纹理选项 */
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); /* 解绑纹理 */
glBindTexture(GL_TEXTURE_2D, );
}
函数中的重点是如何把字符表中字符的位图数据合并的一张纹理上。方法是创建一张足够大的纹理,再将纹理切分为 M x N 的块

然后将字符表中字符的位图数据拷贝到对应的格子上,计算字符的纹理坐标
void TextRender::copyCharacterData(int row, int col, int stride, Character* character, float sizew, float sizeh)
{
int w = character->size.x;
int h = character->size.y;
int index = ; if ( character->space ) return; for ( int i = ; i < h; i++ ) {
for ( int j = ; j < w; j++ ) {
index = (row * size + i) * stride + (col * size + j);
textureData[index] = character->data[i * w + j];
}
}
character->texcoord[].set(float(col * size + ) / sizew, float(row * size + h) / sizeh);
character->texcoord[].set(float(col * size + ) / sizew, float(row * size + ) / sizeh);
character->texcoord[].set(float(col * size + w) / sizew, float(row * size + ) / sizeh);
character->texcoord[].set(float(col * size + w) / sizew, float(row * size + h) / sizeh);
}
最后就是渲染了
void TextRender::render()
{
/* 更新纹理 */
if ( bUpdateTexture ) {
bUpdateTexture = false;
this->updateTextTexture();
} /* 获取顶点数据 */
for ( auto& ele : vTexts ) {
GLfloat x = ele.pos.x;
GLfloat y = ele.pos.y; int positionCount = ele.vCharacters.size() * ;
int indexCount = ele.vCharacters.size() * ;
if ( vPositions.size() < positionCount ) {
vPositions.resize(positionCount);
vTexCoords.resize(positionCount);
vIndices.resize(indexCount);
}
nPositionIndex = nIndexIndex = ; int beginIndex = ;
for ( auto& character : ele.vCharacters ) {
GLfloat xpos = x + character->bearing.x * ele.scale;
GLfloat ypos = y - (character->size.y - character->bearing.y) * ele.scale; x += (character->advance >> ) * ele.scale; if ( character->space ) continue; GLfloat w = character->size.x * ele.scale;
GLfloat h = character->size.y * ele.scale; vPositions[nPositionIndex + ].set(xpos + , ypos + , );
vPositions[nPositionIndex + ].set(xpos + , ypos + h, );
vPositions[nPositionIndex + ].set(xpos + w, ypos + h, );
vPositions[nPositionIndex + ].set(xpos + w, ypos + , ); vTexCoords[nPositionIndex + ] = (character->texcoord[]);
vTexCoords[nPositionIndex + ] = (character->texcoord[]);
vTexCoords[nPositionIndex + ] = (character->texcoord[]);
vTexCoords[nPositionIndex + ] = (character->texcoord[]); vIndices[nIndexIndex + ] = ( * beginIndex + );
vIndices[nIndexIndex + ] = ( * beginIndex + );
vIndices[nIndexIndex + ] = ( * beginIndex + );
vIndices[nIndexIndex + ] = ( * beginIndex + );
vIndices[nIndexIndex + ] = ( * beginIndex + );
vIndices[nIndexIndex + ] = ( * beginIndex + ); beginIndex++;
nPositionIndex += ;
nIndexIndex += ;
} static RenderUnit unit;
unit.pPositions = &vPositions[];
unit.nPositionCount = nPositionIndex;
unit.pTexcoords = &vTexCoords[];
unit.pIndices = &vIndices[];
unit.nIndexCount = nIndexIndex;
unit.color = ele.color;
unit.texture = &texture;
unit.renderType = RENDER_TYPE_TEXTURE; pRenderer->pushRenderUnit(unit);
nPositionIndex = nIndexIndex = ;
}
vTexts.clear();
}
在主函数绘制文本
textRender.drawText(, , 0.8, "基于 OpenGL 编写简易游戏框架 Simple2D", Color(, , , ));
textRender.drawText(, , 0.8, "基于 OpenGL 编写简易游戏框架 Simple2D", Color(, , , ));
textRender.drawText(, , 0.8, "基于 OpenGL 编写简易游戏框架 Simple2D", Color(, , , ));
textRender.drawText(, , 0.8, "基于 OpenGL 编写简易游戏框架 Simple2D", Color(, , , ));
textRender.drawText(, , 0.8, "基于 OpenGL 编写简易游戏框架 Simple2D", Color(, , , ));
textRender.drawText(, , 0.8, "基于 OpenGL 编写简易游戏框架 Simple2D", Color(, , , ));
textRender.drawText(, , 0.45, buffer, Color(, , , ));
textRender.render();
运行程序后的结果

源码下载:http://pan.baidu.com/s/1skOmP21
基于OpenGL编写一个简易的2D渲染框架-05 渲染文本的更多相关文章
- 基于OpenGL编写一个简易的2D渲染框架-06 编写一个粒子系统
在这篇文章中,我将详细说明如何编写一个简易的粒子系统. 粒子系统可以模拟许多效果,下图便是这次的粒子系统的显示效果.为了方便演示,就弄成了一个动图. 图中,同时显示了 7 种不同粒子效果,看上去效果挺 ...
- 基于OpenGL编写一个简易的2D渲染框架-01 创建窗口
最近正在学习OpenGL,我认为学习的最快方法就是做一个小项目了. 如果对OpenGL感兴趣的话,这里推荐一个很好的学习网站 https://learnopengl-cn.github.io/ 我用的 ...
- 基于OpenGL编写一个简易的2D渲染框架-04 绘制图片
阅读文章前需要了解的知识,纹理:https://learnopengl-cn.github.io/01%20Getting%20started/06%20Textures/ 过程简述:利用 FreeI ...
- 基于OpenGL编写一个简易的2D渲染框架-03 渲染基本几何图形
阅读文章前需要了解的知识,你好,三角形:https://learnopengl-cn.github.io/01%20Getting%20started/04%20Hello%20Triangle/ 要 ...
- 基于OpenGL编写一个简易的2D渲染框架-02 搭建OpenGL环境
由于没有使用GLFW库,接下来得费一番功夫. 阅读这篇文章前请看一下这个网页:https://learnopengl-cn.github.io/01%20Getting%20started/02%20 ...
- 基于OpenGL编写一个简易的2D渲染框架-11 重构渲染器-Renderer
假如要渲染一个纯色矩形在窗口上,应该怎么做? 先确定顶点的格式,一个顶点应该包含位置信息 vec3 以及颜色信息 vec4,所以顶点的结构体定义可以这样: struct Vertex { Vec3 p ...
- 基于OpenGL编写一个简易的2D渲染框架-08 重构渲染器-整体架构
事实上,前面编写的渲染器 Renderer 非常简陋,虽然能够进行一些简单的渲染,但是它并不能满足我们的要求. 当渲染粒子系统时,需要开启混合模式,但渲染其他顶点时却不需要开启混合模式.所以同时渲染粒 ...
- 基于OpenGL编写一个简易的2D渲染框架-09 重构渲染器-Shader
Shader 只是进行一些简单的封装,主要功能: 1.编译着色程序 2.绑定 Uniform 数据 3.根据着色程序的顶点属性传递顶点数据到 GPU 着色程序的编译 GLuint Shader::cr ...
- 基于OpenGL编写一个简易的2D渲染框架-10 重构渲染器-Pass
Pass,渲染通路,一个渲染通路指的是一次像素处理和一次顶点处理,也就是指的是一次绘制.简单来说就是顶点数据在渲染管线中走一遍最后绘制. 渲染粒子系统的粒子时,需要开启 OpenGL 的混合模式,并使 ...
随机推荐
- Linux高级文本处理命令
cut 一.cut命令 功能:cut命令可以从一个文本文件/文本流中提取文本列 语法: cut -d '分割字符' -f fields ##用于有特定分割字符 cut -c 字符区间 ##用于排列整齐 ...
- php单例模式实现对象只被创建一次 mysql单例操作类
这是我在php面试题中遇到的一道试题,单例模式按字面来看就是某一个类只有一个实例,这样做的好处还是很大的,比如说数据库的连接,我们只需要实例化一次,不需要每次都去new了,这样极大的降低了资源的耗费. ...
- 未能正确加载“VSTS for Database Professionals Sql Server Data-tier Application”包。(转)
今天费了九牛二虎之力,重转好了vs2010之后,打开解决方案,报出下面的错误: ---------------------------Microsoft Visual Studio---------- ...
- C# 正则表达式 判断各种字符串(如手机号)
using System; using System.Text.RegularExpressions; namespace MetarCommonSupport { /// <summary&g ...
- JZ2440 裸机驱动 第6章 存储控制器
本章目标: 了解S3C2410/S3C2440地址空间的布局 掌握如何通过总线形式访问扩展的外设,比如内存.NOR Flash.网卡等 ························ ...
- Exchange 2003服务器中如何在公司资料夹中设置共享行事历
Exchange 2003服务器中如何在公司资料夹中设置共享行事历 编写人:左丘文 2018-2-23 春节假期归来,开工第一天,感觉还没有从假期中恢复及调整过来.突然想到了我已经荒废了近一年的园子, ...
- Oracle环境变量设置脚本
每次都傻乎乎的往bashrc里面写环境变量,感觉不任性.于是,看了本书了解了/etc/oratab这个东东后,参考着书也写了一个设置Oracle环境变量的脚本. 在/etc/下创建oraset,权限设 ...
- C#连接Oracle数据库的连接字符串
来源:http://blog.csdn.net/superhoy/article/details/8108037 两种方式:1.IP+SID方式 2.配置链接方式 1..IP+SID方式 DbHelp ...
- 无需公众号PHP微信登录微信授权微信第三方登录微信开发php开发
无论是个人还是公司,无需申请公众号,无需申请微信开放平台,即可实现手机网站和PC网站的微 信登录!!! 正常的微信登录是这样的: 1.手机版网站做微信登录,需要申请一个认证公众号,认证不仅需要公司营 ...
- Eclipse里面的Maven项目打包(Maven build)
eclipse里面执行maven build打包的时候,如何设置参数? 主要就是设置一个goals