游戏人生
首页
(current)
GameDevTools
登陆
|
注册
个人中心
注销
游戏引擎 浅入浅出
Introduction
Introduction
前言
前言
1. 游戏引擎框架介绍
1. 游戏引擎框架介绍
1.1 Unity的组成
1.2 游戏引擎组成
2. Opengl开发环境搭建
2. Opengl开发环境搭建
2.1 Opengl到底是什么
2.2 搭建Opengl开发环境
2.3 使用VisualStudio开发
3. 绘制多边形
3. 绘制多边形
3.1 画个三角形
3.2 画个正方形
3.3 画个立方体
4. 着色器
4. 着色器
4.1 Unity Shader和OpenGL Shader
4.2 顶点着色器
4.3 片段着色器
5. 绘制贴图
5. 绘制贴图
5.1 颜色和贴图
5.2 贴图文件介绍
5.3 CPU与GPU的通信方式
5.4 使用stb_image解析图片
5.5 绘制带贴图的立方体盒子
5.6 压缩纹理
5.7 图片压缩工具
5.8 使用压缩纹理
5.9 DXT压缩纹理扩展
6. 索引与缓冲区对象
6. 索引与缓冲区对象
6.1 顶点索引
6.2 缓冲区对象
6.3 OpenGL Core Profile
6.4 顶点数组对象
7. 绘制Mesh和材质
7. 绘制Mesh和材质
7.1 导出Mesh文件
7.2 使用Mesh文件
7.3 Shader文件创建与使用
7.4 创建材质
7.5 使用材质
7.6 MeshRenderer
8. 绘制静态模型
8. 绘制静态模型
8.1 Blender安装与配置
8.2 Blender制作模型
8.3 Blender Python设置开发环境
8.4 Blender Python创建物体
8.5 Blender Python导出顶点数据
8.6 加载导出的Mesh
9. 基于组件开发
9. 基于组件开发
9.1 基于RTTR实现反射
9.2 实现GameObject-Component
10. 相机
10. 相机
10.1 最简单的相机
10.2 多相机渲染
10.3 相机排序
10.4 CullingMask
11. 控制系统
11. 控制系统
11.1 键盘控制
11.2 鼠标控制
12. 拆分引擎和项目
12. 拆分引擎和项目
13. 绘制文字
13. 绘制文字
13.1 TrueType简介
13.2 绘制单个字符
13.3 绘制多个文字
13.4 彩色字
14. GUI
14. GUI
14.1 正交相机
14.2 UIImage
14.3 UIMask
14.4 UIText
14.5 UIButton
15. 播放音效
15. 播放音效
15.1 播放2D音效
15.2 播放3D音效
15.3 使用FMOD Studio音频引擎
16. Profiler
16. Profiler
16.1 初识easy_profiler
16.2 集成easy_profiler
17. 嵌入Lua
17. 嵌入Lua
17.1 Sol2与C++交互
17.2 更加友好的Lua框架设计
17.3 引擎集成sol2
17.4 调试Lua
18. 骨骼动画
18. 骨骼动画
18.1 Blender制作骨骼动画
18.2 Blender导出骨骼动画
18.3 解析骨骼动画
18.4 矩阵的主序
19. 骨骼蒙皮动画
19. 骨骼蒙皮动画
19.1 骨骼蒙皮动画实现
19.2 骨骼权重
19.3 Blender蒙皮刷权重
19.4 Blender导出蒙皮权重
19.5 加载权重文件
20. 解析FBX文件
20. 解析FBX文件
20.1 导出Mesh
20.2 导出骨骼动画
20.3 导出权重
20.4 渲染骨骼蒙皮动画
21. 多线程渲染
21. 多线程渲染
21.1 GLFW多线程渲染
21.2 基于任务队列的多线程渲染
21.3 完全异步的多线程模型
21.4 引擎支持多线程渲染
22. Physx物理引擎
22. Physx物理引擎
22.1 Physx实例-小球掉落
22.2 物理材质
22.3 碰撞检测
22.4 连续碰撞检测
22.5 场景查询
22.6 引擎集成Physx
23. 经典光照
23. 经典光照
23.1 环境光
23.2 漫反射光照模型
23.3 镜面高光光照模型
23.4 高光贴图
23.5 Shader结构体
23.6 Uniform Buffer Object
23.7 方向光
23.8 点光源
23.9 多光源
24. 引擎编辑器的实现
24. 引擎编辑器的实现
24.1 分析Godot引擎编辑器
24.2 FBO RenderTexture GameTurbo DLSS
24.3 ImGui介绍与使用
24.4 分离引擎核心层和应用层
24.5 使用ImGui实现引擎编辑器
24.6 Hierarchy与Inspector面板
24.7 Geometry Buffer
25. Shadow Mapping
25. Shadow Mapping
25.1 深度图
25.2 简单阴影
88. VSCode扩展开发与定制
88. VSCode扩展开发与定制
88.1 第一个VSCode扩展程序
88.2 从源码编译VSCode
88.3 打包VSCode内置扩展
88.4 打包LuaHelper到Code-OSS
89. Doxygen生成API文档
89. Doxygen生成API文档
90. GPU分析工具
90. GPU分析工具
90.1 RenderDoc分析不显示bug
98. SubstancePainter插件开发
98. SubstancePainter插件开发
98.1 SP插件开发环境
98.2 开发SP功能性插件
98.3 开发SP渲染插件
99. Toolbag插件开发
99. Toolbag插件开发
99.1 插件开发环境
99.2 API介绍
99.3 命令行调用Toolbag
99.4 更多实现
99.5 代码参考
附录1. Wwise音频引擎
附录1. Wwise音频引擎
1.1 Wwise名词概念
1.2 Wwise制作音效导出SoundBank
1.3 集成Wwise
1.4 封装Wwise播放3D音效
1.5 Wwise性能分析器介绍
1.6 猎人开发后记
代码资源下载
点我下载
Github
点赞、收藏、关注
目录
<< 13.1 TrueType简介
13.3 绘制多个文字 >>
## 13.2 绘制单个字符 ```text 「游戏引擎 浅入浅出」是一本开源电子书,PDF/随书代码/资源下载: https://github.com/ThisisGame/cpp-game-engine-book ``` ```bash CLion项目文件位于 samples\draw_font\draw_ttf_font_freetype ``` 这一节主要讲解使用`freetype`解析ttf,对字符生成bitmap的流程。 ### 1. 引入freetype到项目 `freetype`很庞大,一般来说都是编译成library到项目中link。 但是我想参考SDL,尽量不使用library,所以将`freetype`直接编译。 由于`freetype`代码很多,所以单独创建了`CMakeLists.txt.FreeType`,然后在项目`CMakeLists.txt`进行引用。 ![](md/cpp-game-engine-book/imgs/draw_font/draw_ttf_font_freetype/freetype_cmakelists.jpg) ### 2. freetype流程 使用`freetype`对字符生成bitmap的主要流程如下图。 ![](md/cpp-game-engine-book/imgs/draw_font/draw_ttf_font_freetype/freetype_flow.jpg) 主要分2步: 1. 解析ttf字体 2. 对单个字符生成bitmap 下面进行介绍。 ### 3. 解析ttf字体 游戏中可能使用多种字体,这里创建`Font`类,加载解析字体文件,然后以字体文件路径为key进行存储。 ```c++ //file:source/renderer/font.cpp line:17 /// 加载一个字体文件并解析 /// \param image_file_path ttf字体文件路径 /// \param font_size 默认文字尺寸 /// \return Font* Font::LoadFromFile(std::string font_file_path,unsigned short font_size){ Font* font=GetFont(font_file_path); if(font!= nullptr){ return font; } //读取 ttf 字体文件 ifstream input_file_stream(Application::data_path()+ font_file_path,ios::in | ios::binary); input_file_stream.seekg(0,std::ios::end); int len = input_file_stream.tellg(); input_file_stream.seekg(0,std::ios::beg); char *font_file_buffer = new char[len]; input_file_stream.read(font_file_buffer , len); //将ttf 传入FreeType解析 FT_Library ft_library= nullptr; FT_Face ft_face= nullptr; FT_Init_FreeType(&ft_library);//FreeType初始化; FT_Error error = FT_New_Memory_Face(ft_library, (const FT_Byte*)font_file_buffer, len, 0, &ft_face); if (error != 0){ spdlog::error("FT_New_Memory_Face return error {}!",error); return nullptr; } FT_Select_Charmap(ft_face, FT_ENCODING_UNICODE); FT_F26Dot6 ft_size = (FT_F26Dot6)(font_size*(1 << 6)); FT_Set_Char_Size(ft_face, ft_size, 0, 72, 72); if (ft_face == nullptr){ spdlog::error("FT_Set_Char_Size error!"); return nullptr; } //创建Font实例,保存Freetype解析字体结果。 font=new Font(); font->font_size_=font_size; font->font_file_buffer_=font_file_buffer; font->ft_library_=ft_library; font->ft_face_=ft_face; font_map_[font_file_path]=font; //创建空白的、仅Alpha通道纹理,用于生成文字。 unsigned char * pixels = (unsigned char *)malloc(font->font_texture_size_ * font->font_texture_size_); memset(pixels, 0,font->font_texture_size_*font->font_texture_size_); font->font_texture_=Texture2D::Create(font->font_texture_size_,font->font_texture_size_,GL_RED,GL_RED,GL_UNSIGNED_BYTE,pixels); delete pixels; return font; } ``` `freetype`解析ttf字体文件之后,我创建了一张1024的Texture:`font->font_texture_`。 这其实就是一张图集,和普通的小图打包成大图唯一的不同之处就是,这里的小图是`freetype`动态生成的bitmap。 当`freetype`为字符动态生成bitmap后,可以使用`OpenGL`提供的API,对图集进行局部更新。 ```c Unity中对UI做DrawCall合并时,也有动态图集的做法,差不多的逻辑。 ``` 下面就来看下如何为字符动态生成bitmap吧。<a id="antiCollectorAdTxt" href="https://github.com/ThisisGame/cpp-game-engine-book">「游戏引擎 浅入浅出」是一本开源电子书,PDF/随书代码/资源下载: https://github.com/ThisisGame/cpp-game-engine-book</a> ### 4. 对单个字符生成bitmap ```c++ //file:source/renderer/font.cpp line:70 /// freetype为字符生成bitmap /// \param c void Font::LoadCharacter(char ch) { //加载这个字的字形,加载到 m_FTFace上面去;Glyph:字形,图形字符 [glif]; FT_Load_Glyph(ft_face_, FT_Get_Char_Index(ft_face_, ch), FT_LOAD_DEFAULT); //从 FTFace上面读取这个字形 到 ft_glyph 变量; FT_Glyph ft_glyph; FT_Get_Glyph(ft_face_->glyph, &ft_glyph); //渲染为256级灰度图 FT_Glyph_To_Bitmap(&ft_glyph, ft_render_mode_normal, 0, 1); FT_BitmapGlyph ft_bitmap_glyph = (FT_BitmapGlyph)ft_glyph; FT_Bitmap& ft_bitmap = ft_bitmap_glyph->bitmap; font_texture_->UpdateSubImage(0, 0, ft_bitmap.width, ft_bitmap.rows, GL_RED, GL_UNSIGNED_BYTE, ft_bitmap.buffer); } ``` `FT_Bitmap`结构里就保存着freetype为字符生成的bitmap,从它里面拿到bitmap尺寸、二进制数据,就可以上传到GPU对大的图集局部更新。 ### 5. 创建材质 创建一个新的材质`data/material/quad_draw_font.mat`。 使用图片进行渲染时,需要在材质中设置图片文件路径。 而字体是生成的bitmap,所以在材质中将图片文件路径留空:`image=""`。 ```xml <material shader="shader/unlit"> <texture name="u_diffuse_texture" image=""/> </material> ``` ### 6. 渲染文字 有了文字`Texture`,也有了`Material`,只需要创建顶点数据、索引数据就可以渲染了。 ```c++ //file:example/login_scene.cpp line:70 /// 创建文字 void LoginScene::CreateFont() { vector<MeshFilter::Vertex> vertex_vector={ { {-1.0f, -1.0f, 1.0f}, {1.0f,1.0f,1.0f,1.0f}, {0.0f, 0.0f} }, { { 1.0f, -1.0f, 1.0f}, {1.0f,1.0f,1.0f,1.0f}, {1.0f, 0.0f} }, { { 1.0f, 1.0f, 1.0f}, {1.0f,1.0f,1.0f,1.0f}, {1.0f, 1.0f} }, { {-1.0f, 1.0f, 1.0f}, {1.0f,1.0f,1.0f,1.0f}, {0.0f, 1.0f} } }; vector<unsigned short> index_vector={ 0,1,2, 0,2,3 }; //创建模型 GameObject auto go=new GameObject("quad_draw_font"); go->set_layer(0x01); //挂上 Transform 组件 auto transform=dynamic_cast<Transform*>(go->AddComponent("Transform")); transform->set_position({2.f,0.f,0.f}); //挂上 MeshFilter 组件 auto mesh_filter=dynamic_cast<MeshFilter*>(go->AddComponent("MeshFilter")); mesh_filter->CreateMesh(vertex_vector,index_vector); //创建 Material material=new Material();//设置材质 material->Parse("material/quad_draw_font.mat"); //挂上 MeshRenderer 组件 auto mesh_renderer=dynamic_cast<MeshRenderer*>(go->AddComponent("MeshRenderer")); mesh_renderer->SetMaterial(material); //生成文字贴图 Font* font=Font::LoadFromFile("font/hkyuan.ttf",500); font->LoadCharacter('A'); //使用文字贴图 material->SetTexture("u_diffuse_texture", font->font_texture()); } ``` ### 7. 测试 运行项目测试,结果如下图: ![](md/cpp-game-engine-book/imgs/draw_font/draw_ttf_font_freetype/draw_font_texture.jpg) 可以看到茶壶旁边的大A,不过它是上下颠倒的,是哪里出错了吗? 打开`RenderDoc`对程序进行截屏分析,就可以看到显存里的文字图集,它确实是反的。 ![](md/cpp-game-engine-book/imgs/draw_font/draw_ttf_font_freetype/renderdoc_freetype_font_texture.png) 这是因为`FreeType`将左上角作为坐标原点,而`OpenGL`是左下角。 所以需要将UV坐标翻转,这里就不做了,下一节会翻转过来。 另外一个问题是,为什么文字是红色的? 这是因为FreeType生成的是bitmap,作为单通道即可,而OpenGL对单通道可选的format只有GL_RED,所以在创建Texture的时候就使用了GL_RED,那么在片段Shader中就读取到R通道,就是RED,红色通道。 ```c++ font->font_texture_=Texture2D::Create(font->font_texture_size_,font->font_texture_size_,GL_RED,GL_RED,GL_UNSIGNED_BYTE,pixels); ``` ### 8. 相关参考 主要参考OpenGL API文档。 1. https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glTexImage2D.xhtml 2. https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glTexSubImage2D.xhtml ### 9. RenderDoc分析不显示bug 在编写本节教程时,遇到了文字不显示的Bug,最终使用RenderDoc分析出是C++临时变量的问题。 具体分析过程查看后面章节:[90.1 RenderDoc分析不显示bug](?book=cpp-game-engine-book&lang=zh&md=90. gpu_analysis_tools/90.1 renderdoc_analysis_not_show_bugs.md)
<< 13.1 TrueType简介
13.3 绘制多个文字 >>
12
代码资源下载
点我下载
Github
点赞、收藏、关注
目录
Introduction
Introduction
前言
前言
1. 游戏引擎框架介绍
1. 游戏引擎框架介绍
1.1 Unity的组成
1.2 游戏引擎组成
2. Opengl开发环境搭建
2. Opengl开发环境搭建
2.1 Opengl到底是什么
2.2 搭建Opengl开发环境
2.3 使用VisualStudio开发
3. 绘制多边形
3. 绘制多边形
3.1 画个三角形
3.2 画个正方形
3.3 画个立方体
4. 着色器
4. 着色器
4.1 Unity Shader和OpenGL Shader
4.2 顶点着色器
4.3 片段着色器
5. 绘制贴图
5. 绘制贴图
5.1 颜色和贴图
5.2 贴图文件介绍
5.3 CPU与GPU的通信方式
5.4 使用stb_image解析图片
5.5 绘制带贴图的立方体盒子
5.6 压缩纹理
5.7 图片压缩工具
5.8 使用压缩纹理
5.9 DXT压缩纹理扩展
6. 索引与缓冲区对象
6. 索引与缓冲区对象
6.1 顶点索引
6.2 缓冲区对象
6.3 OpenGL Core Profile
6.4 顶点数组对象
7. 绘制Mesh和材质
7. 绘制Mesh和材质
7.1 导出Mesh文件
7.2 使用Mesh文件
7.3 Shader文件创建与使用
7.4 创建材质
7.5 使用材质
7.6 MeshRenderer
8. 绘制静态模型
8. 绘制静态模型
8.1 Blender安装与配置
8.2 Blender制作模型
8.3 Blender Python设置开发环境
8.4 Blender Python创建物体
8.5 Blender Python导出顶点数据
8.6 加载导出的Mesh
9. 基于组件开发
9. 基于组件开发
9.1 基于RTTR实现反射
9.2 实现GameObject-Component
10. 相机
10. 相机
10.1 最简单的相机
10.2 多相机渲染
10.3 相机排序
10.4 CullingMask
11. 控制系统
11. 控制系统
11.1 键盘控制
11.2 鼠标控制
12. 拆分引擎和项目
12. 拆分引擎和项目
13. 绘制文字
13. 绘制文字
13.1 TrueType简介
13.2 绘制单个字符
13.3 绘制多个文字
13.4 彩色字
14. GUI
14. GUI
14.1 正交相机
14.2 UIImage
14.3 UIMask
14.4 UIText
14.5 UIButton
15. 播放音效
15. 播放音效
15.1 播放2D音效
15.2 播放3D音效
15.3 使用FMOD Studio音频引擎
16. Profiler
16. Profiler
16.1 初识easy_profiler
16.2 集成easy_profiler
17. 嵌入Lua
17. 嵌入Lua
17.1 Sol2与C++交互
17.2 更加友好的Lua框架设计
17.3 引擎集成sol2
17.4 调试Lua
18. 骨骼动画
18. 骨骼动画
18.1 Blender制作骨骼动画
18.2 Blender导出骨骼动画
18.3 解析骨骼动画
18.4 矩阵的主序
19. 骨骼蒙皮动画
19. 骨骼蒙皮动画
19.1 骨骼蒙皮动画实现
19.2 骨骼权重
19.3 Blender蒙皮刷权重
19.4 Blender导出蒙皮权重
19.5 加载权重文件
20. 解析FBX文件
20. 解析FBX文件
20.1 导出Mesh
20.2 导出骨骼动画
20.3 导出权重
20.4 渲染骨骼蒙皮动画
21. 多线程渲染
21. 多线程渲染
21.1 GLFW多线程渲染
21.2 基于任务队列的多线程渲染
21.3 完全异步的多线程模型
21.4 引擎支持多线程渲染
22. Physx物理引擎
22. Physx物理引擎
22.1 Physx实例-小球掉落
22.2 物理材质
22.3 碰撞检测
22.4 连续碰撞检测
22.5 场景查询
22.6 引擎集成Physx
23. 经典光照
23. 经典光照
23.1 环境光
23.2 漫反射光照模型
23.3 镜面高光光照模型
23.4 高光贴图
23.5 Shader结构体
23.6 Uniform Buffer Object
23.7 方向光
23.8 点光源
23.9 多光源
24. 引擎编辑器的实现
24. 引擎编辑器的实现
24.1 分析Godot引擎编辑器
24.2 FBO RenderTexture GameTurbo DLSS
24.3 ImGui介绍与使用
24.4 分离引擎核心层和应用层
24.5 使用ImGui实现引擎编辑器
24.6 Hierarchy与Inspector面板
24.7 Geometry Buffer
25. Shadow Mapping
25. Shadow Mapping
25.1 深度图
25.2 简单阴影
88. VSCode扩展开发与定制
88. VSCode扩展开发与定制
88.1 第一个VSCode扩展程序
88.2 从源码编译VSCode
88.3 打包VSCode内置扩展
88.4 打包LuaHelper到Code-OSS
89. Doxygen生成API文档
89. Doxygen生成API文档
90. GPU分析工具
90. GPU分析工具
90.1 RenderDoc分析不显示bug
98. SubstancePainter插件开发
98. SubstancePainter插件开发
98.1 SP插件开发环境
98.2 开发SP功能性插件
98.3 开发SP渲染插件
99. Toolbag插件开发
99. Toolbag插件开发
99.1 插件开发环境
99.2 API介绍
99.3 命令行调用Toolbag
99.4 更多实现
99.5 代码参考
附录1. Wwise音频引擎
附录1. Wwise音频引擎
1.1 Wwise名词概念
1.2 Wwise制作音效导出SoundBank
1.3 集成Wwise
1.4 封装Wwise播放3D音效
1.5 Wwise性能分析器介绍
1.6 猎人开发后记