游戏人生
首页
(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
点赞、收藏、关注
目录
<< 18.2 Blender导出骨骼动画
18.4 矩阵的主序 >>
## 18.3 解析骨骼动画 ```text 「游戏引擎 浅入浅出」是一本开源电子书,PDF/随书代码/资源下载: https://github.com/ThisisGame/cpp-game-engine-book ``` ```bash CLion项目文件位于 samples\skeleton_animation\load_skeleton_animation ``` 上一节从Blender中导出骨骼动画数据到自定义文件,这一节解析这个自定义文件,并播放骨骼动画。 解析自定义文件,就是加载进内存,并且按照上一节设计的文件格式,读取并存储到不同的变量。 例如读取骨骼数量保存到变量`bone_num`,读取关键帧数据保存到变量`all_frame_data`中。 播放骨骼动画,就是获取一帧的数据,计算出这一帧骨骼所在的位置。 每一帧都有新的位置变化,一秒钟播放24帧,就形成了动画。 下面来看具体实现吧。 ### 1. 读取骨骼动画文件 新建类`AnimationClip`,创建以下成员变量存储骨骼动画数据, ```c++ //file:source/renderer/animation_clip.h line:43 private: /// 所有骨骼名字 std::vector<std::string> bone_names_; /// 骨骼的子节点 std::vector<std::vector<unsigned short>> bone_children_vector_; /// 骨骼T-pose std::vector<glm::mat4> bone_t_pose_vector_; /// 持续时间 float duration_=0.0f; /// 总帧数 unsigned short frame_count_=0; /// 每一帧每一个骨骼的位移矩阵 std::vector<std::vector<glm::mat4>> bone_matrix_frames_vector_; /// 骨骼动画开始播放时间 float start_time_=0.0f; /// 骨骼动画是否在播放 bool is_playing_=false; /// 当前帧 unsigned short current_frame_=-1; ``` 在`void AnimationClip::LoadFromFile(const char *file_path)`中读取文件到内存,并按照文件格式进行解析,读取数据存储到成员变量中,为下一步计算骨骼位置坐准备。 ```c++ //file:source/renderer/animation_clip.cpp line:27 /// 加载动画片段 /// \param file_path void AnimationClip::LoadFromFile(const char *file_path) { //读取文件头 ifstream input_file_stream(Application::data_path()+file_path,ios::in | ios::binary); if(!input_file_stream.is_open()) { DEBUG_LOG_ERROR("AnimationClip::LoadFromFile: open file failed,file_path:{}",file_path); return; } char file_head[14]; input_file_stream.read(file_head,13); file_head[13]='\0'; if(strcmp(file_head,SKELETON_ANIMATION_HEAD) != 0) { DEBUG_LOG_ERROR("AnimationClip::LoadFromFile: file head error,file_head:{},the right is:{}",file_head,SKELETON_ANIMATION_HEAD); return; } //读取骨骼数量 unsigned short bone_count=0; input_file_stream.read(reinterpret_cast<char *>(&bone_count), sizeof(unsigned short)); //读取骨骼名字数组 for(unsigned short i=0;i<bone_count;i++) { //读取骨骼名字长度 unsigned short bone_name_size=0; input_file_stream.read(reinterpret_cast<char *>(&bone_name_size), sizeof(unsigned short)); char* bone_name=new char[bone_name_size+1]; input_file_stream.read(bone_name,bone_name_size); bone_name[bone_name_size]='\0'; bone_names_.push_back(bone_name); delete[] bone_name; } //读取骨骼子节点 for(unsigned short bone_index=0; bone_index < bone_count; bone_index++) { //读取骨骼子节点数量 unsigned short child_count=0; input_file_stream.read(reinterpret_cast<char *>(&child_count), sizeof(unsigned short)); //读取骨骼子节点名字,在名字数组的序号。 std::vector<unsigned short> child_indexes; for(unsigned short j=0;j<child_count;j++) { unsigned short child_index=0; input_file_stream.read(reinterpret_cast<char *>(&child_index), sizeof(unsigned short)); child_indexes.push_back(child_index); } bone_children_vector_.push_back(child_indexes); } //读取骨骼T-pose for(unsigned short bone_index=0; bone_index < bone_count; bone_index++) { //读取骨骼T-pose glm::mat4 bone_t_pose; input_file_stream.read(reinterpret_cast<char *>(&bone_t_pose), sizeof(float) * 16); bone_t_pose_vector_.push_back(bone_t_pose); } //读取帧数 input_file_stream.read(reinterpret_cast<char *>(&frame_count_), sizeof(unsigned short)); //读取骨骼动画 for (int frame_index = 0; frame_index < frame_count_; frame_index++) { //读取一帧的骨骼矩阵 std::vector<glm::mat4> bone_matrices; for (unsigned short bone_index = 0; bone_index < 2; bone_index++) { glm::mat4 bone_matrix; input_file_stream.read(reinterpret_cast<char *>(&bone_matrix), sizeof(float) * 16); bone_matrices.push_back(bone_matrix); } bone_matrix_frames_vector_.push_back(bone_matrices); } input_file_stream.close(); Bake(); } ``` 这其实就是上一节`18.2 Blender导出骨骼动画`的逆过程。 ### 2. 骨骼动画刷帧计算 骨骼动画的本质就是帧动画,和Unity的Animation是一样的。 之所以是动画,是因为每一帧都修改了骨骼的位置。 在引擎中,要让骨骼动起来,就要在每一帧都计算出骨骼的正确位置。 即根据读取的T-Pos矩阵 以及 当前帧最新的offset矩阵,计算出当前位置矩阵。 因为父骨骼的位移会影响到子骨骼,所以需要用递归来将父骨骼矩阵传递下去。 具体实现如下:<a id="antiCollectorAdTxt" href="https://github.com/ThisisGame/cpp-game-engine-book">「游戏引擎 浅入浅出」是一本开源电子书,PDF/随书代码/资源下载: https://github.com/ThisisGame/cpp-game-engine-book</a> ```c++ //file:source/renderer/animation_clip.cpp line:97 /// 预计算骨骼矩阵 void AnimationClip::Bake() { for (int i = 0; i < frame_count_; ++i) { DEBUG_LOG_INFO("AnimationClip::Bake: frame_index:{}",i); //计算当前帧的骨骼矩阵 std::vector<glm::mat4>& current_frame_bone_matrices=bone_matrix_frames_vector_[i]; CalculateBoneMatrix(current_frame_bone_matrices,0,glm::mat4(1.0f)); } } /// 递归计算骨骼矩阵,从根节点开始。Blender导出的时候要确保先导出父节点。 /// \param bone_name /// \param parent_matrix /// \param bone_matrix void AnimationClip::CalculateBoneMatrix(std::vector<glm::mat4>& current_frame_bone_matrices,unsigned short bone_index, const glm::mat4 &parent_matrix) { glm::mat4 bone_matrix=current_frame_bone_matrices[bone_index]; glm::mat4 bone_t_pos_matrix=bone_t_pose_vector_[bone_index]; glm::mat4 bone_matrix_with_parent=parent_matrix*bone_t_pos_matrix*bone_matrix; DEBUG_LOG_INFO("{} bone_matrix:{}",bone_names_[bone_index],glm::to_string_beauty(bone_matrix_with_parent)); current_frame_bone_matrices[bone_index]=bone_matrix_with_parent; std::vector<unsigned short> child_indexes=bone_children_vector_[bone_index]; for(unsigned short child_index:child_indexes) { CalculateBoneMatrix(current_frame_bone_matrices,child_index,bone_matrix_with_parent); } } ``` ### 3. 测试 工程已经转向Lua了,所以后续所有新增类都要注册到Lua。 ```c++ //file:source/lua_binding/lua_binding.cpp line:476 sol_state_.new_usertype<AnimationClip>("AnimationClip",sol::call_constructor,sol::constructors<AnimationClip()>(), "LoadFromFile", &AnimationClip::LoadFromFile, "duration", &AnimationClip::duration, "Play", &AnimationClip::Play, "Stop", &AnimationClip::Stop, "Update", &AnimationClip::Update ); ``` 后续章节我就不再提这个了。 修改`login_scene.lua`,在`Awake`中加载解析骨骼动画文件,然后在`Update`中计算帧数据。 ```lua --file:example/login_scene.lua line:14 function LoginScene:Awake() print("LoginScene Awake") LoginScene.super.Awake(self) self.animation_clip_=AnimationClip.new() self.animation_clip_:LoadFromFile("animation/export.skeleton_anim") self.animation_clip_:Play() end ...... ...... function LoginScene:Update() --print("LoginScene Update") LoginScene.super.Update(self) self.animation_clip_:Update() end ``` 在Blender中我导出了`[1,40]`范围的帧数据,`[1-20]`变化,`[21,40]`还原,第20帧状态如下: ![](md/cpp-game-engine-book/imgs/skeleton_animation/load_skeleton_animation/blender_bone001_tail_pos.jpg) 以第20帧来验证代码计算数据是否正确。 运行工程,log会输出每一帧每个骨骼最新的矩阵,查看第20帧的Log: ```log [source animation_clip.cpp] [function AnimationClip::Bake] [line 99] [info] AnimationClip::Bake: frame_index:19 [source animation_clip.cpp] [function AnimationClip::CalculateBoneMatrix] [line 111] [info] Bone bone_matrix:mat4x4: (0.866025, -0.500000, 0.000000, 0.000000), (0.500000, 0.866025, -0.000000, 0.000000), (0.000000, 0.000000, 1.000000, 0.000000), (0.000000, 0.000000, 0.000000, 1.000000) [source animation_clip.cpp] [function AnimationClip::CalculateBoneMatrix] [line 111] [info] Bone.001 bone_matrix:mat4x4: (0.000000, -1.000000, 0.000000, 0.000000), (1.000000, 0.000000, -0.000000, 0.000000), (0.000000, 0.000000, 1.000000, 0.000000), (1.000000, 1.732051, -0.000000, 1.000000) ``` 所谓的骨骼矩阵,指的是骨骼的首端关节点矩阵。 拿`Bone.001`首端矩阵,乘以`Bone.001`末端的相对位移,计算得到`Bone.001`末端在第20帧的坐标,如下: ![](md/cpp-game-engine-book/imgs/skeleton_animation/load_skeleton_animation/calculate_bone_001_tail_success.jpg) 计算得到结果与Blender中一致,解析骨骼动画成功。 ### 4. 怎么没有渲染出骨骼? 问:骨骼动画解析成功了,但是却没有渲染出来骨骼。难道骨骼动画就是这些东西? 你是对的,骨骼动画就是这些东西,本质就是父子之间的相对变换,是纯数据。 问:游戏中的骨骼动画不是会渲染出人物动画吗? 我们口头说的游戏中的骨骼动画,叫骨骼蒙皮动画。 骨骼动画代表动画数据。 蒙皮就是Mesh,每个顶点与一个骨骼绑定,当骨骼变换时,带动顶点变化,这样就有了每帧变化的Mesh,渲染形成动画。 下一章就介绍骨骼蒙皮动画。 ### 5. 其他 有一些东西需要注意: 1. 绕Z轴旋转,从Y转向X,角度是负数。 ![](md/cpp-game-engine-book/imgs/skeleton_animation/blender_export/rotate_z_from_y_to_x.jpg) 2. Blender是行主序,和数学上的表现一致,可以直接用作代数运算。 3. GLM是列主序,做运算的时候记得转换。 一个矩阵乘法动画演示的网站:`http://matrixmultiplication.xyz/`
<< 18.2 Blender导出骨骼动画
18.4 矩阵的主序 >>
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 猎人开发后记