瀏覽代碼

添加动画相关知识点

usuiforhe 2 年之前
父節點
當前提交
e191e167e4
共有 25 個文件被更改,包括 478 次插入5 次删除
  1. 二進制
      104/Image/016.png
  2. 二進制
      104/Image/017.png
  3. 二進制
      104/Image/018.png
  4. 二進制
      104/Image/019.png
  5. 二進制
      104/Image/020.png
  6. 二進制
      104/Image/021.png
  7. 二進制
      104/Image/022.png
  8. 二進制
      104/Image/023.png
  9. 二進制
      104/Image/024.png
  10. 二進制
      104/Image/025.png
  11. 二進制
      104/Image/026.png
  12. 二進制
      104/Image/027.png
  13. 二進制
      104/Image/028.png
  14. 二進制
      104/Image/029.png
  15. 二進制
      104/Image/030.png
  16. 二進制
      104/Image/031.png
  17. 二進制
      104/Image/032.png
  18. 二進制
      104/Image/033.png
  19. 二進制
      104/Image/034.png
  20. 二進制
      104/Image/035.png
  21. 二進制
      104/Image/036.png
  22. 二進制
      104/Image/037.png
  23. 二進制
      104/Image/038.png
  24. 二進制
      104/Image/039.png
  25. 478 5
      104/README.md

二進制
104/Image/016.png


二進制
104/Image/017.png


二進制
104/Image/018.png


二進制
104/Image/019.png


二進制
104/Image/020.png


二進制
104/Image/021.png


二進制
104/Image/022.png


二進制
104/Image/023.png


二進制
104/Image/024.png


二進制
104/Image/025.png


二進制
104/Image/026.png


二進制
104/Image/027.png


二進制
104/Image/028.png


二進制
104/Image/029.png


二進制
104/Image/030.png


二進制
104/Image/031.png


二進制
104/Image/032.png


二進制
104/Image/033.png


二進制
104/Image/034.png


二進制
104/Image/035.png


二進制
104/Image/036.png


二進制
104/Image/037.png


二進制
104/Image/038.png


二進制
104/Image/039.png


+ 478 - 5
104/README.md

@@ -220,11 +220,11 @@ void tickMain(float delta_time) {
 4. 植被
 5. 天空
 
-游戏中所有的物体都可以抽象成GO->**GameObject**,游戏最重要的就是管理这些GameObject
+游戏中所有的物体都可以抽象成 GO -> **GameObject**,游戏最重要的就是管理这些GameObject
 
 那么如何定义游戏中的对象?
 
-最经典的做法就是**继承**,鸭子=>会叫的鸭子=>会飞的鸭子=>...
+最经典的做法就是**继承**,鸭子 => 会叫的鸭子 => 会飞的鸭子 => ...
 
 但是随着游戏越做越复杂,很多东西并没有清晰的父子关系:水陆两栖坦克(坦克+巡逻艇, 坦克派生自汽车,巡逻艇派生自船)
 
@@ -295,7 +295,7 @@ void Bomb::OnExpode(){
 
 此时,比较好的解决方案其实是 **事件**(Event) 机制
 
-参考设计模式:观察者模式,使用事件的好处就是只是通知被影响的物体发生了何种事情,由被通知的对象自行做后续处理
+参考设计模式:**观察者模式**,使用事件的好处就是只是通知被影响的物体发生了何种事情,由被通知的对象自行做后续处理
 
 ```cpp
 void Bomb::OnExpode(){
@@ -337,9 +337,9 @@ DECLARE_MULTICAST_DELEGATE_OneParam(FActorComponentGlobalDestroyPhysicsSignature
 
 ### 其他
 
-当子GameObject绑定到父GameObject后,tick时要先计算父GameObject,再计算子GameObject
+当子 GameObject 绑定到父 GameObject 后,tick 时要先计算父 GameObject ,再计算子 GameObject
 
-再比如消息的传递,A给B发送信息,同时B也给A发送了信息,此时微观上其实是由先后顺序的,但是如果信息的处理是交给两个核心处理,那么可能这次是A先收到信息,下次是B先收到信息,这样程序的运行结果可能不同,导致严重的问题出现
+再比如消息的传递,A  B 发送信息,同时 B 也给 A 发送了信息,此时微观上其实是由先后顺序的,但是如果信息的处理是交给两个核心处理,那么可能这次是 A 先收到信息,下次是 B 先收到信息,这样程序的运行结果可能不同,导致严重的问题出现
 
 所以,很多时候会有信息管理器,将信息统一发送到一个管理器中,再由管理器去根据顺序统一发送信息
 
@@ -410,3 +410,476 @@ struct Triangle {
 ![](./Image/104_15.png)
 
 > 参考设计模式中的享元模式
+
+## 动画系统
+
+游戏动画的挑战
+
+- 复杂的动画状态并且与游戏的其他系统交互
+  1. 在复杂环境中的动态调整
+  2. 与其他游戏系统耦合
+  3. 需要监听玩家输入
+  4. ...
+
+- 动画是实时渲染的
+  1. 所有的计算都是实时的
+  2. 动画数据庞大,导致数据读写消耗甚至打到读写瓶颈
+
+- 游戏动画自然感和流畅度要求越来越高
+  1. 更多的体验
+  2. 更多生动的表达
+
+### 2D动画技术 - 2D Animation
+
+![](./Image/016.png)
+
+最常见的就是 `Sprite Animation` 精灵帧动画,就是把游戏中的角色一帧一帧的循环播放
+
+| 帧动画 | 各角度 |
+| --- | --- |
+| ![](./Image/017.png) | ![](./Image/018.png) |
+
+为了让表现更加真实,记录多个角度的帧动画,能够用 2D 模拟出 3D 的感觉
+
+虽然 2D 动画技术很古老,但是现在仍然会在游戏中频繁使用
+
+![](./Image/019.png)
+
+> 通过帧动画模拟粒子爆炸特效
+
+除了前面介绍的 `Sprite Animation` 之外,经常使用的还有 `Live2D`
+
+![](./Image/020.png)
+
+通过将图片旋转、变形、扭曲、编辑来实现角色动作
+
+![](./Image/021.png)
+
+将角色身上的所有元素:眉毛、眼睛、头发、手、脚、胳膊等变成一个个小的**图元**,最后将图元变成大的资源
+
+对于角色身上的每个图元都可以通过旋转、放缩、变形等操作可以做出非常鲜活的动作效果
+
+`Live2D` 可以设置每个图元的深度保证每个图元动起来的时候不会互相影响导致穿帮; 其次 `Live2D` 可以给每个图元设置**控制网格**,通过控制网格可以更加细腻的控制图元的扭曲
+
+除了 `Live2D` 之外还有 Spine 和 龙骨动画,通过绑定骨骼的方式来操作图元的移动,以此来模拟 3D 的骨骼蒙皮操作
+
+![](Image/026.png)
+
+### 3D动画技术 - 3D Animation
+
+DoF (Degrees of Freedom) 自由度
+
+![](Image/022.png)
+
+对于一个在3维空间的刚体来说,它在整个空间运动的自由度就是6个自由度:平移的三个自由度、旋转的三个自由度
+
+那么对于3D动画来说,其核心就是对刚体运动的表达。在游戏中,做3D动画最基础的就是**基于层次结构的刚体的动画**,类似皮影戏
+
+![](Image/023.png)
+
+因为关节骨骼是树状结构,所以也叫 `Hierarchical`,也就是一层一层一级一级的分布下来
+
+![](Image/024.png)
+
+如果只是单纯的将 Mesh 和 骨骼 进行绑定,那么很容易出现上图的的问题,那就是 Mesh 与 Mesh 之间出现重合,也就是俗称的穿模
+
+由于单纯的将 Mesh 与 骨骼 进行绑定会出现很多问题,因此引入了 **蒙皮动画** `Skin Animation`
+
+![](Image/025.png)
+
+参考人体的骨骼和皮肤,模型的一个点的位置可能由多个骨骼同时作用,可以尽可能保证角色运动的时候模型不会互相穿插,会比前面的刚体动画更加合适
+
+除了上述的两种 3D 动画技术之外,还有一个 `Physics-based Animation` 基于物理的动画
+
+比如常见的 `Ragdoll` **布娃娃系统**;`Cloth and Fluid simulation` 布料、流体模拟;`Iverse Kinamatics (IK)` 反向动力学,就是给定某个点让身体运动更加自然
+
+至于如何创建 3D 动画,一般就两种方法:动画师在软件中**K帧** 和 **动作捕捉**
+
+### 蒙皮动画 - Skinned Animation Implementation
+
+让 Mesh 动起来
+
+1. 创建一个模型
+2. 给模型创建一个配套骨骼
+3. 给每一个骨骼刷蒙皮,就是上模型的顶点与骨骼进行关联
+4. 给骨骼制作对应的动画
+5. 骨骼运动带动对应的模型顶点运动
+
+![](Image/027.png)
+
+在骨骼运动的时候,所有动画系统的计算都是在骨骼各自的空间中计算的
+
+| **World Space** | **Model Space** | **Local Space** |
+| --- | --- | --- |
+| 世界坐标系 | 模型坐标系 | 骨骼的本地坐标系 |
+
+![](Image/028.png)
+
+也就是骨骼在自己的局部坐标系下的移动,通过一级一级的向上传递,从而计算得到模型坐标系下的移动信息
+
+因为骨骼是个层级的结构,比如你的手腕转了30度,这是相对于局部坐标系的。对于世界坐标系,手腕可能转动的就不是30度了,所以用局部坐标系更方便
+
+![](Image/029.png)
+
+Joint 是由动画师直接操纵来控制运动的物体, Bone 是关节之间的空隙;而局部坐标系的原点在 Joint 上
+
+> Joint 是有很多自由度的,但是 Bone 是没有自由度的
+
+游戏中除了与人体相关的骨骼之外,还会添加很多其他的骨骼用于控制模型
+
+![](Image/030.png)
+
+与人体骨骼类似,披风上的骨骼可以控制披风的运动和变形
+
+![](Image/031.png)
+
+通过将人和马两个 Object 上对应的两个骨骼 Attach,保证坐标和旋转相同,然后人播放人的动画,马播放马的动画,就可以实现人骑马的效果
+
+![](Image/032.png)
+
+关于模型的姿势,一般分两种 `T-Pose` 和 `A-Pose`
+
+> T 和 A 表示人物的站姿
+
+早期模型一般都是 `T-Pose` 的,后面发现 `T-Pose` 的模型在肩膀会出现挤压的情况,这样会导致做肩甲处的动作时精度不够的情况,所以后面改为使用 `A-Pose` 这样肩膀处的精度就足够了
+
+对于角色某一个固定的动作可以称之为 Pose,把很多的 Pose 连到一起就形成了动画
+
+对于骨骼来说有9个自由度:平移、旋转、缩放,对于骨骼缩放可以实现人脸的变形、弹性变化等
+
+### 3D 旋转的数学
+
+每个关节做的最多的运动其实是旋转,手臂的旋转带动了手掌的移动,手腕的旋转带动了手指的移动
+
+那么如何在 3D 空间表示旋转?
+
+![](Image/033.png)
+
+![](Image/034.png)
+
+> 绕单个轴旋转时点的计算公式
+
+![](Image/035.png)
+
+> 同时绕 x、y、z 三轴旋转时的计算公式
+
+上述就是对欧拉角的计算公式,但是使用欧拉角会存在很多问题,比如:万向锁、需要严格按照顺序执行、难以插值计算、难以计算叠加
+
+![](Image/036.png)
+
+如上图所示,对同一个物体进行旋转操作时,会根据操作顺序的不同导致结果不同
+
+由于如上欧拉角的一些问题,导致在动画中计算时会出现很多问题,所以一般动画都使用**四元数**
+
+[四元数与三维旋转](https://github.com/Krasjet/quaternion)
+
+#### 复数的计算
+
+**复数**可以表示为 `a + bi` 的形式,其中 a 是实数部分,b 是虚数部分,i 是虚数单位
+
+> 复数也可以用来表示二维空间中的点或向量,也可以用来表示二维空间中的旋转变换
+> 复数有自己的运算法则,包括加法,减法,乘法,除法等,它们满足一些代数性质,如交换律,结合律,分配律等
+> 复数还有一些特殊的表示形式,如指数形式,三角形式,极坐标形式等,它们可以方便地进行一些复数的运算和分析
+
+因为 𝑧 = 𝑎 + 𝑏𝑖 其实就是对于 {1, 𝑖} 这个基(Basis)的线性组合(Linear Combination),我们也可以用向量来表示一个复数:
+
+$$
+z = 
+\begin{bmatrix}
+    a \\
+    b
+\end{bmatrix}
+$$
+
+因为这个向量有两个元素,我们可以使用复平面上的一个点来表示一个复数, 复平面的横坐标 𝑅𝑒 代表它的实部,纵坐标 𝐼𝑚 代表它的虚部:
+
+![](Image/037.png)
+
+复数加法,他们的和就是分量相加的结果
+
+$$
+z_1 = a + bi \\
+z_2 = c + di \\
+z_1 + z_2 = (a + c) + (b + d)i
+$$
+
+复数减法,他们的差就是分量相减的结果
+
+$$
+z_1 - z_2 = (a - c) + (b - d)i
+$$
+
+复数乘法,通过分配律来计算乘积
+
+$$
+z_1 * z_2 = ac - bd + adi + bci = (ac - bd) + (bc + ad)i
+$$
+
+> 也就是说乘积的结果就是一个新的复数,其实部为 `ac - bd`,虚部为 `bc + ad`
+
+通过上述计算结果不难发现,其实复数的乘法符合矩阵与向量的相乘的规则
+
+$$
+z_1 * z_2 = bc - bd + (bc + ad)i = \begin{bmatrix}
+    a & -b \\ b & a
+\end{bmatrix} * \begin{bmatrix}
+    c \\ d
+\end{bmatrix}
+$$
+
+右侧的 $
+\begin{bmatrix}
+    c \\ d
+\end{bmatrix}
+$ 是用向量的形式来表示 $z_2$ 
+
+左侧的 $
+\begin{bmatrix}
+    a & -b \\ b & a
+\end{bmatrix}
+$ 则是用矩阵的形式来表示 $z_1$
+
+> 问题:为什么复数的矩阵可以这么表示?
+
+那么,在矩阵形式下,复数与复数相乘可以表示为矩阵相乘
+
+$$
+z_1 = a + bi \\
+z_2 = c + di \\
+z_1 * z_2 = \begin{bmatrix}
+    a & -b \\
+    b & a
+\end{bmatrix} * \begin{bmatrix}
+    c & -d \\
+    d & c
+\end{bmatrix} = \begin{bmatrix}
+    ac - bd & -(bc+ad) \\
+    bc + ad & ac - bd
+\end{bmatrix}
+$$
+
+并且复数的乘法是满足交换律的
+
+$$
+z_2 * z_1 = \begin{bmatrix}
+    c & -d \\
+    d & c
+\end{bmatrix} * \begin{bmatrix}
+    a & -b \\
+    b & a
+\end{bmatrix} = \begin{bmatrix}
+    ac - bd & -(bc+ad) \\
+    bc + ad & ac - bd
+\end{bmatrix}
+$$
+
+一般称 I 为单位矩阵, 也就是 
+
+$$
+I = \begin{bmatrix}
+    1 & 0 \\
+    0 & 1
+\end{bmatrix}
+$$
+
+虚数单位 i 则等价于 
+
+$$
+\begin{bmatrix}
+    0 & -1 \\
+    1 & 0
+\end{bmatrix}
+$$
+
+可以发现 
+
+$$
+i^2 = i * i = \begin{bmatrix}
+    0 & -1 \\
+    1 & 0
+\end{bmatrix} * \begin{bmatrix}
+    0 & -1 \\
+    1 & 0
+\end{bmatrix} = \begin{bmatrix}
+    -1 & 0 \\
+    0 & -1
+\end{bmatrix} = -I = -1
+$$
+
+复数的模长
+
+如果 `z = a + bi`,那么其模长为 $||z|| = \sqrt{a^2 + b^2}$
+
+复数的共轭
+
+如果 `z = a + bi`,那么其共轭为 $\overline{z} = a - bi$ ,也就是反转 z 虚部的符号
+
+尝试计算 $z * \overline{z}$ 可以发现
+
+$$
+z \overline{z} = (a + bi)(a - bi) = a^2 - abi + abi + b^2 = a^2 + b^2 = ||z||^2
+$$
+
+因此得出结论 $||z|| = \sqrt{z*\overline{z}}$
+
+#### 复数相乘与2D旋转
+
+既然与复数相乘代表与 $\begin{bmatrix}
+    a & -b \\
+    b & a
+\end{bmatrix}$ 矩阵所作出的变换,那么这种变换代表什么?
+
+$$
+\begin{bmatrix}
+    a & -b \\
+    b & a
+\end{bmatrix} = \sqrt{a^2 + b^2} * \begin{bmatrix}
+    \frac{a}{\sqrt{a^2 + b^2}} & \frac{-b}{\sqrt{a^2 + b^2}} \\
+    \frac{b}{\sqrt{a^2 + b^2}} & \frac{a}{\sqrt{a^2 + b^2}} 
+\end{bmatrix}
+$$
+
+将矩阵的每个元素都处于模场 $||z|| = \sqrt{a^a + b^2}$ 并将其提到矩阵外面
+
+![](Image/038.png)
+
+如上图 `||z||` 正是复数 z 与坐标轴所形成的三角形的斜边长,而 a、b 分别为三角形的两个直角边,如果将斜边与实数轴 Re 正方向的夹角记为 $\theta$ ,按照三角函数的定义可以得出 $\frac{a}{\sqrt{a^2 + b^2}} = \cos\theta$ 且 $\frac{b}{\sqrt{a^2 + b^2}} = \sin\theta$,因此原矩阵可以变换成
+
+$$
+\begin{bmatrix}
+    a & -b \\
+    b & a
+\end{bmatrix} = \sqrt{a^2 + b^2} * \begin{bmatrix}
+    \cos\theta & -\sin\theta \\
+    \sin\theta & \cos\theta
+\end{bmatrix} \\ = ||z|| * \begin{bmatrix}
+    \cos\theta & -\sin\theta \\
+    \sin\theta & \cos\theta 
+\end{bmatrix} \\ = || zz || * I * \begin{bmatrix}
+    \cos\theta & -\sin\theta \\
+    \sin\theta & \cos\theta 
+\end{bmatrix} \\ =\begin{bmatrix}
+    ||z|| & 0 \\
+    0 & ||z||
+\end{bmatrix} * \begin{bmatrix}
+    \cos\theta & -\sin\theta \\
+    \sin\theta & \cos\theta 
+\end{bmatrix}
+$$
+
+> 上面的 I 就是单位矩阵的意思
+
+而 $\begin{bmatrix} \cos\theta & -\sin\theta \\ \sin\theta & \cos\theta \end{bmatrix}$ 又是经典的 2D 旋转矩阵
+
+用 $\begin{bmatrix} 1 \\ 0 \end{bmatrix}$ 和 $\begin{bmatrix}  0 \\ 1 \end{bmatrix}$ 分别带入计算一下
+
+$$
+\begin{bmatrix}
+    a & -b \\
+    b & a
+\end{bmatrix} \begin{bmatrix}
+    1 \\ 0
+\end{bmatrix} = \begin{bmatrix}
+    ||z|| & 0 \\
+    0 & ||z||
+\end{bmatrix} \begin{bmatrix}
+    \cos\theta & -\sin\theta \\
+    \sin\theta & \cos\theta
+\end{bmatrix} \begin{bmatrix}
+    1 \\ 0
+\end{bmatrix} = \begin{bmatrix}
+    ||z|| & 0 \\
+    0 & ||z||
+\end{bmatrix} \begin{bmatrix}
+    \cos\theta \\
+    \sin\theta
+\end{bmatrix} = \begin{bmatrix}
+    ||z||\cos\theta \\
+    ||z||\sin\theta
+\end{bmatrix}
+$$
+
+首先将 $\begin{bmatrix} 1 \\ 0 \end{bmatrix}$ 变换到 $\begin{bmatrix} \cos\theta \\ \sin\theta \end{bmatrix}$ 的位置,也就是逆时针旋转了 $\theta$ 度,接下来将矩阵 $\begin{bmatrix} \cos\theta \\ \sin\theta \end{bmatrix}$ 缩放了 ||z|| 倍
+
+$$
+\begin{bmatrix}
+    a & -b \\
+    b & a
+\end{bmatrix} \begin{bmatrix}
+    0 \\ 1
+\end{bmatrix} = \begin{bmatrix}
+    ||z|| & 0 \\
+    0 & ||z||
+\end{bmatrix} \begin{bmatrix}
+    \cos\theta & -\sin\theta \\
+    \sin\theta & \cos\theta
+\end{bmatrix} \begin{bmatrix}
+    0 \\ 1
+\end{bmatrix} = \begin{bmatrix}
+    ||z|| & 0 \\
+    0 & ||z||
+\end{bmatrix} \begin{bmatrix}
+    -\sin\theta \\
+    \cos\theta
+\end{bmatrix} = \begin{bmatrix}
+    -||z||\sin\theta \\
+    ||z||\cos\theta
+\end{bmatrix}
+$$
+
+这里同样是将 $\begin{bmatrix}  0 \\ 1 \end{bmatrix}$ 变换到了  $\begin{bmatrix}  -\sin\theta \\ \cos\theta \end{bmatrix}$ 位置,也就是逆时针旋转了 $\theta$ 度
+
+上述的计算过程大概就是下面的表现效果
+
+![](Image/039.png)
+
+所以,复数的相乘其实是**旋转与缩放变换的复合**
+
+如果有一个复数 `z = a + bi`, 那么 z 与任何一个复数 c 相乘都会将 c 逆时针旋转 $\theta$ 度,并将其缩放 $||z|| = \sqrt{a^2 + b^2}$ 倍
+
+> $\theta = \arctan{\frac{b}{a}}$
+
+如果复数的 $a^2 + b^2 = 1$,即这个复数可以用一个单位想来来表示,那么这个复数所代表的几何意义就只有旋转了,只留下单纯的旋转矩阵 
+
+$$
+V' = \begin{bmatrix}
+    \cos\theta & -\sin\theta \\
+    \sin\theta & \cos\theta
+\end{bmatrix} V
+$$
+
+其实 $\begin{bmatrix} \cos\theta & -\sin\theta \\ \sin\theta & \cos\theta \end{bmatrix}$ 这个旋转矩阵如果写成复数形式的话就是 $\cos\theta + i\sin\theta$
+
+如果将向量 $v = \begin{bmatrix} x \\ y \end{bmatrix}$ 看作是一个复数 $v = x + yi$ 其中实部为 x,虚部为 y。可以构造一个复数 $z = \cos\theta + i \sin\theta$ 并将它与 v 相乘来进行旋转,旋转 $\theta$ 度之后的向量 $v'$ 可以用等价的复数乘法来表示
+
+$$
+v' = zv = (\cos\theta + i\sin\theta)v
+$$ 
+
+又根据**欧拉公式**,可以得到 $\cos\theta + i\sin\theta = e^{i\theta}$,可以可以将复数进一步表示为
+
+$$
+z = ||z||\begin{bmatrix}
+    \cos\theta & -\sin\theta \\
+    \sin\theta & \cos\theta
+\end{bmatrix} = ||z||(\cos\theta + i\sin\theta) = ||z||e^{i\theta}
+$$
+
+如果定义 `r = ||z||` 那么就可以得到复数的极坐标公式 $z = re^{i\theta}$
+
+$$
+v' = re^{i\theta}v
+$$
+
+如果有两个代表 2D 旋转的单位复数 $z_1 = \cos\theta + i\sin\theta$ 和 $z_2 = \cos\gamma + i\sin\gamma$ 以及一个向量 `v = x + yi`,可以先对 v 进行 $z_1$ 的旋转 $v' = z_1 v$ 然后对 v' 进行 $z_2$ 的旋转 $v'' = z_2(z_1v)$
+
+定义 $z_{net} = z_1 z_2$, 那么 $v'' = z_{net}v$
+
+又因为复数的乘法符合交换律,所以 $z_{net} = z_1z_2 = z_2z_1$
+
+#### 四元数
+
+四元数的定义和复数非常相似,唯一的区别就是四元数一共有三个虚部,而复数只有一个 $q = a + bi + cj + dk$ 其中 $i^2 = j^2 = k^2 = ijk = -1$
+
+与复数类似,四元素其实就是对于基`{1, i, j, l}`的线性组合,四元数也可以写成向量形式 $q=\begin{bmatrix} a \\ b \\ c \\ d \end{bmatrix}$
+