阅读文章前需要了解的知识:文本渲染 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 渲染文本的更多相关文章

  1. 基于OpenGL编写一个简易的2D渲染框架-06 编写一个粒子系统

    在这篇文章中,我将详细说明如何编写一个简易的粒子系统. 粒子系统可以模拟许多效果,下图便是这次的粒子系统的显示效果.为了方便演示,就弄成了一个动图. 图中,同时显示了 7 种不同粒子效果,看上去效果挺 ...

  2. 基于OpenGL编写一个简易的2D渲染框架-01 创建窗口

    最近正在学习OpenGL,我认为学习的最快方法就是做一个小项目了. 如果对OpenGL感兴趣的话,这里推荐一个很好的学习网站 https://learnopengl-cn.github.io/ 我用的 ...

  3. 基于OpenGL编写一个简易的2D渲染框架-04 绘制图片

    阅读文章前需要了解的知识,纹理:https://learnopengl-cn.github.io/01%20Getting%20started/06%20Textures/ 过程简述:利用 FreeI ...

  4. 基于OpenGL编写一个简易的2D渲染框架-03 渲染基本几何图形

    阅读文章前需要了解的知识,你好,三角形:https://learnopengl-cn.github.io/01%20Getting%20started/04%20Hello%20Triangle/ 要 ...

  5. 基于OpenGL编写一个简易的2D渲染框架-02 搭建OpenGL环境

    由于没有使用GLFW库,接下来得费一番功夫. 阅读这篇文章前请看一下这个网页:https://learnopengl-cn.github.io/01%20Getting%20started/02%20 ...

  6. 基于OpenGL编写一个简易的2D渲染框架-11 重构渲染器-Renderer

    假如要渲染一个纯色矩形在窗口上,应该怎么做? 先确定顶点的格式,一个顶点应该包含位置信息 vec3 以及颜色信息 vec4,所以顶点的结构体定义可以这样: struct Vertex { Vec3 p ...

  7. 基于OpenGL编写一个简易的2D渲染框架-08 重构渲染器-整体架构

    事实上,前面编写的渲染器 Renderer 非常简陋,虽然能够进行一些简单的渲染,但是它并不能满足我们的要求. 当渲染粒子系统时,需要开启混合模式,但渲染其他顶点时却不需要开启混合模式.所以同时渲染粒 ...

  8. 基于OpenGL编写一个简易的2D渲染框架-09 重构渲染器-Shader

    Shader 只是进行一些简单的封装,主要功能: 1.编译着色程序 2.绑定 Uniform 数据 3.根据着色程序的顶点属性传递顶点数据到 GPU 着色程序的编译 GLuint Shader::cr ...

  9. 基于OpenGL编写一个简易的2D渲染框架-10 重构渲染器-Pass

    Pass,渲染通路,一个渲染通路指的是一次像素处理和一次顶点处理,也就是指的是一次绘制.简单来说就是顶点数据在渲染管线中走一遍最后绘制. 渲染粒子系统的粒子时,需要开启 OpenGL 的混合模式,并使 ...

随机推荐

  1. jquery.ellipsis根据宽度(不是字数)进行内容截断,支持多行内容

    jquery.ellipsis 自动计算内容宽度(不是字数)截断,并加上省略号,内容不受中英文或符号限制. 如果根据字数来计算的话,因为不同字符的宽度并不相同,比如l和W,特别是中英文,最终内容宽度会 ...

  2. greasemonkey修改网页url

    // ==UserScript== // @name JSHE_ModifyFunction // @namespace jshe // @include http://localhost/* // ...

  3. Apache Derby数据库 安装、知识点

    Apache Derby数据库 安装: 下载路径:http://archive.apache.org/dist/db/derby/ 出处:http://www.yiibai.com/hive/hive ...

  4. python Django Nginx+ uWSGI 安装配置

    环境: CentOS7.python-3.5.3.Nignx 1.10.3 .Django 1.10.6.uWSGI 2.0.14 django项目目录:/var/webRoot/p1 项目结构: 基 ...

  5. AppBox中,如何在用户管理页面显示用户所属的多个角色?

    <f:TemplateField Width="200px" HeaderText="角色">    <ItemTemplate>    ...

  6. bzoj 2784 [JLOI2012]时间流逝——树上高斯消元

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=2784 一个状态可以加很多个能量圈,但减少能量圈的情况只有一种.所以可以用树来刻画. 然后就变 ...

  7. 线性代数之SVD与PCA

    [作者:byeyear    Email:east3@163.com    首发www.cnblogs.com    转载请注明] 回忆学校的美好时光,一起来复习下曾经的课程吧. 1. SVD推荐am ...

  8. js轮播插件

    // Tween算法 var Tween = { // t:当前步数 // b:初始位置 // c:总距离 // d:总步数 // Linear:匀速 Linear: function(t,b,c,d ...

  9. 用PNG作为Texture创建Material

    转自:http://aigo.iteye.com/blog/2279512 1,导入一张png素材作为Texture 2,新建一个Material,设置Blend Mode为Translucent,连 ...

  10. EC20 MODULE serial com log in passwd

    ec20 module would print debug info via debug uart, and you can log in by user root, the passwd is qu ...