前言
自此已是我关于Opengl ES系列入门教程的第16篇文章了,虽然写的不咋的,文章产出量也不高,但是这个系列从2022年8月底开始持续到现在也坚持了比较久,
每一篇文章都是经过自己的精心调试通过后再写文总结发布,这个过程耗费了我很多的私人时间,其实一开始想写这个系列就是为了想看看自己不能系统性地把一件事情做好,
现在看来依然比较难,因为自己平常除了工作以外,偶尔也会去峡谷浪一浪,本身自己就是个不靠谱的宝爸,所以经常周末还得往老家跑。
目前这个系列还有滤镜、转场动画等一些内容没有写完,大部分写博文的童鞋都知道,这大概就是一个用爱发电的过程,没有利益,也没有营收,离谱一点写的不好,可能还会被喷…
本来打算是这个系列完善后再放出源码的,但是有不少好学的童鞋加了V信索要,同时也不知道后续这个系列自己还会不会继续完善下去,因此这次就把这个系列的源码放出来啦,仅供参考。
所谓纸上得来总觉浅,绝知此事要躬行
,其实在之前的博文中,我都贴上了关键的代码,如果真有时间又想学习的话还是建议自己亲手敲一遍学得牢,理解得透切。
今天的主要内容是使用Opengl ES搭配FreeType进行文字渲染。
FreeType
文字渲染是做音视频开发逃不掉的一个需求,实际上 OpenGL 并没有定义渲染文字的方式,但是我们可以通过纹理贴图的方式实现文字渲染,就是将带有文字的图像上传到纹理,然后进行纹理贴图。
因此在Android平台是使用Opengl ES进行文字渲染就有两个不同的方案,一个是使用Canvas绘制文字生产Bitmap,然后将带文字的Bitmap进行渲染;另外一个是使用跨平台的字体解析库FreeType实现。
FreeType是一个基于 C 语言实现的用于文字渲染的跨平台开源库,它小巧、高效、高度可定制,主要用于加载字体并将其渲染到位图,支持多种字体的相关操作。
FreeType 官网地址:
https://www.freetype.org/
更多关于FreeType的介绍以及使用文档请自行参考官网教程。
FreeType编译
一贯原则,要将一个跨平台C/C++库移植到Android,首先第一步就是先进行交叉编译,对交叉编译不了解的童鞋们请参考之前的文章:
FreeType很贴心地为我们提供了configure
文件,因此我们只需配置一下NDK的相关环境即可编译成功,更多关于configure
可配置参数,
可通过命令行configure --help
查看。
下面是笔者在mac上编译FreeType的一个脚本:
#!/bin/bash
# 这里需要配置你的NDK路径
NDK_PATH="/Users/fly/Documents/Android/SDK/ndk/21.4.7075529/"
# 注意我这里是苹果系统,如果是Linux系统是不一样的
HOST_PLATFORM="darwin-x86_64"
COMMON_OPTIONS="
--with-zlib=no
--with-bzip2=no
--with-png=no
--with-harfbuzz=no
--with-brotli=no
--with-sysroot=${SYSROOT}
"
TOOLCHAIN_PREFIX="${NDK_PATH}/toolchains/llvm/prebuilt/${HOST_PLATFORM}/bin"
SYSROOT="${NDK_PATH}/toolchains/llvm/prebuilt/${HOST_PLATFORM}/sysroot"
make clean
FOLDER=`pwd`/android-libs/arm64-v8a
mkdir -p ${FOLDER}
CC=${TOOLCHAIN_PREFIX}/aarch64-linux-android21-clang \
./configure \
--prefix=${FOLDER} \
--libdir=${FOLDER} \
--host=aarch64-linux-android \
--enable-debug \
${COMMON_OPTIONS}
make -j4 && make install
编译成功后即可将静态库引入到Android Studio中使用了,引入的步骤也很简单,主要是配置一下头文件的搜索路径和动态库的查找路径即可,详情可参考之前的博文:
FreeType的使用
有关FreeType的使用,官网有比较详细的介绍:https://freetype.org/freetype2/docs/tutorial/step1.html
首先引入的头文件是:
#include "ft2build.h"
#include <freetype/ftglyph.h>
在FreeType中每个字体库加载后就是一个叫做FT_Face的东西,后续通过FT_Face这个结构图可以获取到文字的一系列信息,比如图像宽高等。
首先使用FT_Init_FreeType
进行初始化,然后通过函数FT_New_Face
得到一个FT_Face。
下面这段代码的意思就是将128个ASCII,并将相关字体图像信息缓存起来:
void TextRenderOpengl::LoadFacesByASCII() {
// FreeType
FT_Library ft;
FT_Face face;
do {
if (FT_Init_FreeType(&ft)) {
LOGD("FT_Init_FreeType fail");
break;
}
// FONT_FILE_PATH替换成自己的字体文件路径
if (FT_New_Face(ft, FONT_FILE_PATH, 0, &face)) {
LOGD("FT_New_Face fail");
break;
}
// 设置文字的大小,此函数设置了字体面的宽度和高度
// 将宽度值设为0表示我们要从字体面通过给出的高度中动态计算出字形的宽度。
FT_Set_Pixel_Sizes(face, 0, 90);
// 字节对齐
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
// Load first 128 characters of ASCII set
for (unsigned char c = 0; c < 128; c++) {
// Load character glyph
if (FT_Load_Char(face, c, FT_LOAD_RENDER)) {
LOGD("FT_Load_Char: Failed to load Glyph");
continue;
}
// 纹理
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(
GL_TEXTURE_2D,
0,
GL_LUMINANCE,
face->glyph->bitmap.width,
face->glyph->bitmap.rows,
0,
GL_LUMINANCE,
GL_UNSIGNED_BYTE,
face->glyph->bitmap.buffer
);
// Set texture options
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);
// Now store character for later use
Character character = {
texture,
glm::ivec2(face->glyph->bitmap.width, face->glyph->bitmap.rows),
glm::ivec2(face->glyph->bitmap_left, face->glyph->bitmap_top),
static_cast<GLuint>(face->glyph->advance.x)
};
m_Characters.insert(std::pair<GLint, Character>(c, character));
}
break;
} while (0);
glBindTexture(GL_TE