2025-11-30 · game_math
【游戏中的数学】游戏中的数学 (12) - 曲线数学
游戏曲线完全指南:贝塞尔曲线 (Bezier)、De Casteljau 算法、Catmull-Rom 样条、曲线选型对比,以及闭合路径的实现。
骨骼动画是现代 3D 游戏角色的标准。它基于层级变换 (Hierarchical Transformations)。 每个骨骼(Bone)都是一个坐标系,子骨骼的变换是相对于父骨骼的。
这是最自然的动画方式:旋转关节,末端随之移动。 例如:旋转肩膀 -> 旋转手肘 -> 手掌移动。
数学上,这是矩阵乘法的链式反应: \[ M_{hand} = M_{shoulder} \times M_{elbow} \times M_{wrist} \]
这是 FK 的逆问题:已知末端目标位置,求各关节的角度。 例如:手要放在门把手上,肩膀和手肘应该怎么转?
这是一个非线性方程组求解问题,通常有多个解(或无解)。
CCD 是一种迭代算法,思路非常直观:从末端关节开始,逐个调整父关节,使末端指向目标。
算法步骤: 1. 遍历骨骼链,从最后一个骨骼(末端)到根骨骼。 2. 对于每个骨骼,计算”当前末端位置”到”目标位置”的向量。 3. 旋转当前骨骼,使得末端位置尽可能接近目标。 4. 重复上述过程多次,直到误差小于阈值。
代码片段 (伪代码):
void SolveCCD(Transform[] bones, Vector3 target, int iterations = 10) {
for (int i = 0; i < iterations; i++) {
// 从倒数第二个骨骼开始(末端本身不需要旋转)
for (int j = bones.Length - 2; j >= 0; j--) {
Transform bone = bones[j];
Transform endEffector = bones[bones.Length - 1];
Vector3 toEnd = endEffector.position - bone.position;
Vector3 toTarget = target - bone.position;
// 计算旋转:将 toEnd 旋转到 toTarget
Quaternion rotation = Quaternion.FromToRotation(toEnd, toTarget);
bone.rotation = rotation * bone.rotation;
}
if (Vector3.Distance(bones[bones.Length-1].position, target) < 0.01f)
break;
}
}FABRIK (Forward And Backward Reaching Inverse Kinematics) 不使用旋转角度,而是直接操作关节位置。 1. 前向: 将末端拉到目标点,然后拉直骨骼链。 2. 后向: 将根节点拉回原点,再次拉直骨骼链。 3. 迭代: 重复直到收敛。 它比 CCD 更快且更自然,避免了奇怪的扭曲。
当目标点超出骨骼链的最大伸展长度时,IK 算法无解。实际工程中需要处理这种情况:
float maxReach = bones.Sum(b => b.length); // 骨骼链总长度
float targetDist = Vector3.Distance(bones[0].position, target);
if (targetDist > maxReach) {
// 方案 A:将目标拉到最大可达距离
Vector3 direction = (target - bones[0].position).normalized;
target = bones[0].position + direction * maxReach * 0.99f;
// 方案 B:完全伸直指向目标(无需迭代)
// StretchTowards(bones, target);
}选择哪种方案取决于游戏需求:抓取类动作通常用方案 A(手尽量伸向目标),脚部 IK 通常直接忽略不可达的目标。
骨骼动了,网格(Mesh)怎么动? 每个顶点通常受多个骨骼影响(权重 Weight)。 \[ P_{final} = \sum (w_i \times M_i \times P_{bind}) \] 这称为线性混合蒙皮 (Linear Blend Skinning - LBS)。
线性混合蒙皮有一个经典问题:当关节弯曲角度较大时(如手肘弯曲 > 90°),网格会出现塌陷 (Candy Wrapper) 或膨胀现象。这是因为 LBS 对变换矩阵做线性插值,而旋转的线性插值并不正确。
双四元数蒙皮 (Dual Quaternion Skinning, DQS) 使用双四元数来插值变换,能更好地保持体积:
| 方法 | 性能 | 弯曲质量 | 实现复杂度 |
|---|---|---|---|
| LBS | 快 | 大角度有塌陷 | 简单 |
| DQS | 略慢(约 10-15%) | 体积保持好 | 中等 |
现代引擎通常默认使用 LBS,但提供 DQS 选项用于需要高质量蒙皮的角色。
< 上一篇: 曲线数学 | 回到目录 | 下一篇: 随机与噪声 >
把当前热点继续串成多页阅读,而不是停在单篇消费。
2025-11-30 · game_math
游戏曲线完全指南:贝塞尔曲线 (Bezier)、De Casteljau 算法、Catmull-Rom 样条、曲线选型对比,以及闭合路径的实现。
2025-11-30 · game_math
详解游戏引擎中常用的标量数学函数:Lerp, InverseLerp, Remap, Clamp, SmoothStep 等。不仅有公式,更有实际应用场景。
2025-11-30 · game_math
深入浅出地讲解游戏开发中的三角函数:Sin, Cos, Atan2。从单位圆原理到圆周运动、波浪动画和朝向计算的实际应用。
2025-11-30 · game_math
深入理解游戏开发中的矩阵:TRS 变换与代码实践、矩阵乘法与组合变换、MVP 管线详解、逆矩阵、法线变换,以及旋转表示方式对比。
骨骼动画是现代 3D 游戏角色的标准。它基于层级变换 (Hierarchical Transformations)。 每个骨骼(Bone)都是一个坐标系,子骨骼的变换是相对于父骨骼的。
这是最自然的动画方式:旋转关节,末端随之移动。 例如:旋转肩膀 -> 旋转手肘 -> 手掌移动。
数学上,这是矩阵乘法的链式反应: \[ M_{hand} = M_{shoulder} \times M_{elbow} \times M_{wrist} \]
这是 FK 的逆问题:已知末端目标位置,求各关节的角度。 例如:手要放在门把手上,肩膀和手肘应该怎么转?
这是一个非线性方程组求解问题,通常有多个解(或无解)。
CCD 是一种迭代算法,思路非常直观:从末端关节开始,逐个调整父关节,使末端指向目标。
算法步骤: 1. 遍历骨骼链,从最后一个骨骼(末端)到根骨骼。 2. 对于每个骨骼,计算”当前末端位置”到”目标位置”的向量。 3. 旋转当前骨骼,使得末端位置尽可能接近目标。 4. 重复上述过程多次,直到误差小于阈值。
代码片段 (伪代码):
void SolveCCD(Transform[] bones, Vector3 target, int iterations = 10) {
for (int i = 0; i < iterations; i++) {
// 从倒数第二个骨骼开始(末端本身不需要旋转)
for (int j = bones.Length - 2; j >= 0; j--) {
Transform bone = bones[j];
Transform endEffector = bones[bones.Length - 1];
Vector3 toEnd = endEffector.position - bone.position;
Vector3 toTarget = target - bone.position;
// 计算旋转:将 toEnd 旋转到 toTarget
Quaternion rotation = Quaternion.FromToRotation(toEnd, toTarget);
bone.rotation = rotation * bone.rotation;
}
if (Vector3.Distance(bones[bones.Length-1].position, target) < 0.01f)
break;
}
}FABRIK (Forward And Backward Reaching Inverse Kinematics) 不使用旋转角度,而是直接操作关节位置。 1. 前向: 将末端拉到目标点,然后拉直骨骼链。 2. 后向: 将根节点拉回原点,再次拉直骨骼链。 3. 迭代: 重复直到收敛。 它比 CCD 更快且更自然,避免了奇怪的扭曲。
当目标点超出骨骼链的最大伸展长度时,IK 算法无解。实际工程中需要处理这种情况:
float maxReach = bones.Sum(b => b.length); // 骨骼链总长度
float targetDist = Vector3.Distance(bones[0].position, target);
if (targetDist > maxReach) {
// 方案 A:将目标拉到最大可达距离
Vector3 direction = (target - bones[0].position).normalized;
target = bones[0].position + direction * maxReach * 0.99f;
// 方案 B:完全伸直指向目标(无需迭代)
// StretchTowards(bones, target);
}选择哪种方案取决于游戏需求:抓取类动作通常用方案 A(手尽量伸向目标),脚部 IK 通常直接忽略不可达的目标。
骨骼动了,网格(Mesh)怎么动? 每个顶点通常受多个骨骼影响(权重 Weight)。 \[ P_{final} = \sum (w_i \times M_i \times P_{bind}) \] 这称为线性混合蒙皮 (Linear Blend Skinning - LBS)。
线性混合蒙皮有一个经典问题:当关节弯曲角度较大时(如手肘弯曲 > 90°),网格会出现塌陷 (Candy Wrapper) 或膨胀现象。这是因为 LBS 对变换矩阵做线性插值,而旋转的线性插值并不正确。
双四元数蒙皮 (Dual Quaternion Skinning, DQS) 使用双四元数来插值变换,能更好地保持体积:
| 方法 | 性能 | 弯曲质量 | 实现复杂度 |
|---|---|---|---|
| LBS | 快 | 大角度有塌陷 | 简单 |
| DQS | 略慢(约 10-15%) | 体积保持好 | 中等 |
现代引擎通常默认使用 LBS,但提供 DQS 选项用于需要高质量蒙皮的角色。
< 上一篇: 曲线数学 | 回到目录 | 下一篇: 随机与噪声 >
把当前热点继续串成多页阅读,而不是停在单篇消费。
2025-11-30 · game_math
游戏曲线完全指南:贝塞尔曲线 (Bezier)、De Casteljau 算法、Catmull-Rom 样条、曲线选型对比,以及闭合路径的实现。
2025-11-30 · game_math
详解游戏引擎中常用的标量数学函数:Lerp, InverseLerp, Remap, Clamp, SmoothStep 等。不仅有公式,更有实际应用场景。
2025-11-30 · game_math
深入浅出地讲解游戏开发中的三角函数:Sin, Cos, Atan2。从单位圆原理到圆周运动、波浪动画和朝向计算的实际应用。
2025-11-30 · game_math
深入理解游戏开发中的矩阵:TRS 变换与代码实践、矩阵乘法与组合变换、MVP 管线详解、逆矩阵、法线变换,以及旋转表示方式对比。