如果捕捉对象的动作导致身体核心大幅移动,而你希望显示的3D模型小幅移动或几乎不移动,可以通过以下方法实现:
1. 核心思路
- 固定核心位置:将角色的根骨骼(通常是髋部)固定在某个位置,避免大幅移动。
- 动作归一化:将捕捉到的动作数据归一化到局部坐标系中,使其相对于核心位置的变化较小。
- 限制移动范围:通过数学方法限制角色核心的移动范围,使其仅在小范围内移动。
2. 实现方法
2.1 固定核心位置
在Unity中,将角色的根骨骼(如髋部)固定在一个位置,同时将捕捉到的动作数据转换为局部坐标系。
-
获取核心位置:
- 使用髋部中点(MediaPipe索引23和24的平均值)作为核心位置。
- 将核心位置固定在某个点(如原点)。
- 转换到局部坐标系:
- 将所有关键点坐标减去核心位置,使其相对于核心位置的变化较小。
2.2 限制移动范围
通过数学方法(如插值或限制函数)限制核心位置的移动范围,使其仅在小范围内移动。
3. 代码实现
3.1 修改IKController.cs
在IKController.cs中添加核心位置固定和动作归一化的逻辑:
using UnityEngine;
using RootMotion.FinalIK;
public class IKController : MonoBehaviour
{
public UDPReceiver udpReceiver;
// Final IK组件
public LimbIK leftArmIK;
public LimbIK rightArmIK;
public LimbIK leftLegIK;
public LimbIK rightLegIK;
// IK目标物体
public Transform leftHandTarget;
public Transform rightHandTarget;
public Transform leftFootTarget;
public Transform rightFootTarget;
// 躯干和头部骨骼
public Transform spine; // 躯干骨骼
public Transform head; // 头部骨骼
// 核心位置固定
private Vector3 coreOffset; // 核心位置偏移
private bool isCoreInitialized = false;
void Update()
{
if (udpReceiver.receivedLandmarks == null) return;
// 初始化核心位置
if (!isCoreInitialized)
{
InitializeCorePosition();
isCoreInitialized = true;
}
// 更新四肢IK目标
UpdateLimbs();
// 更新躯干和头部
UpdateTorso();
UpdateHead();
}
void InitializeCorePosition()
{
// 获取初始核心位置(髋部中点)
Vector3 leftHip = udpReceiver.receivedLandmarks[23];
Vector3 rightHip = udpReceiver.receivedLandmarks[24];
coreOffset = (leftHip + rightHip) / 2;
}
void UpdateLimbs()
{
// 将关键点坐标转换为局部坐标系
Vector3 leftHandLocal = udpReceiver.receivedLandmarks[15] - coreOffset;
Vector3 rightHandLocal = udpReceiver.receivedLandmarks[16] - coreOffset;
Vector3 leftFootLocal = udpReceiver.receivedLandmarks[27] - coreOffset;
Vector3 rightFootLocal = udpReceiver.receivedLandmarks[28] - coreOffset;
// 更新IK目标位置
leftHandTarget.position = leftHandLocal;
rightHandTarget.position = rightHandLocal;
leftFootTarget.position = leftFootLocal;
rightFootTarget.position = rightFootLocal;
// 更新IK计算
leftArmIK.solver.Update();
rightArmIK.solver.Update();
leftLegIK.solver.Update();
rightLegIK.solver.Update();
}
void UpdateTorso()
{
// 获取髋部中点(索引23和24)
Vector3 leftHip = udpReceiver.receivedLandmarks[23] - coreOffset;
Vector3 rightHip = udpReceiver.receivedLandmarks[24] - coreOffset;
Vector3 hipCenter = (leftHip + rightHip) / 2;
// 获取肩膀中点(索引11和12)
Vector3 leftShoulder = udpReceiver.receivedLandmarks[11] - coreOffset;
Vector3 rightShoulder = udpReceiver.receivedLandmarks[12] - coreOffset;
Vector3 shoulderCenter = (leftShoulder + rightShoulder) / 2;
// 计算躯干方向
Vector3 torsoDirection = (shoulderCenter - hipCenter).normalized;
Quaternion targetRotation = Quaternion.LookRotation(torsoDirection, Vector3.up);
// 应用旋转到躯干骨骼
spine.rotation = Quaternion.Slerp(spine.rotation, targetRotation, Time.deltaTime * 10);
}
void UpdateHead()
{
// 获取鼻子(索引0)和眼睛中点(索引2和5)
Vector3 nose = udpReceiver.receivedLandmarks[0] - coreOffset;
Vector3 leftEye = udpReceiver.receivedLandmarks[2] - coreOffset;
Vector3 rightEye = udpReceiver.receivedLandmarks[5] - coreOffset;
Vector3 eyeCenter = (leftEye + rightEye) / 2;
// 计算头部方向
Vector3 headDirection = (nose - eyeCenter).normalized;
Quaternion targetRotation = Quaternion.LookRotation(headDirection, Vector3.up);
// 应用旋转到头部骨骼
head.rotation = Quaternion.Slerp(head.rotation, targetRotation, Time.deltaTime * 10);
}
}
3.2 限制核心移动范围
如果需要进一步限制核心位置的移动范围,可以在UpdateLimbs方法中添加限制逻辑:
void UpdateLimbs()
{
// 将关键点坐标转换为局部坐标系
Vector3 leftHandLocal = udpReceiver.receivedLandmarks[15] - coreOffset;
Vector3 rightHandLocal = udpReceiver.receivedLandmarks[16] - coreOffset;
Vector3 leftFootLocal = udpReceiver.receivedLandmarks[27] - coreOffset;
Vector3 rightFootLocal = udpReceiver.receivedLandmarks[28] - coreOffset;
// 限制移动范围(例如:限制在±0.5单位内)
leftHandLocal = ClampPosition(leftHandLocal, 0.5f);
rightHandLocal = ClampPosition(rightHandLocal, 0.5f);
leftFootLocal = ClampPosition(leftFootLocal, 0.5f);
rightFootLocal = ClampPosition(rightFootLocal, 0.5f);
// 更新IK目标位置
leftHandTarget.position = leftHandLocal;
rightHandTarget.position = rightHandLocal;
leftFootTarget.position = leftFootLocal;
rightFootTarget.position = rightFootLocal;
// 更新IK计算
leftArmIK.solver.Update();
rightArmIK.solver.Update();
leftLegIK.solver.Update();
rightLegIK.solver.Update();
}
Vector3 ClampPosition(Vector3 position, float maxDistance)
{
// 限制位置在maxDistance范围内
return Vector3.ClampMagnitude(position, maxDistance);
}
4. 场景配置
-
角色根骨骼:
- 确保角色的根骨骼(通常是髋部)固定在场景中的某个位置。
- 将
coreOffset初始化为捕捉对象的初始核心位置。
- Final IK配置:
- 确保四肢的
LimbIK组件和对应的目标物体已正确设置。
- 确保四肢的
5. 优化与注意事项
- 平滑处理:添加滤波(如指数平滑)减少抖动:
Vector3 smoothedPosition = Vector3.Lerp(currentPosition, targetPosition, smoothFactor); - 动态调整核心位置:如果希望核心位置可以缓慢移动,可以使用插值方法动态调整
coreOffset:coreOffset = Vector3.Lerp(coreOffset, newCoreOffset, Time.deltaTime * smoothFactor); - 性能优化:确保Python和Unity的帧率匹配,避免数据堆积。
通过以上方法,你可以实现3D模型的核心位置固定或小幅移动,从而满足你的需求。