[UE4][Animation]IK Related
keywords: [UE4][Animation]IK Related
Examples
工程1
在油管上看到一个UE4 IK动画的demo工程示例,该示例作者的主页:
https://www.patreon.com/unrealcg
演示视频:Advanced foot IK for Unreal Engine 4 - (100% Free)
https://www.youtube.com/watch?v=XetC9ivIXFc
demo工程下载地址(4.19):
https://pan.baidu.com/s/1mlcM0cseKWpprnISVM0P5Q
工程2
该工程除了IK,还包括动画融合、物理等功能
演示视频:UE4 - Advanced Locomotion System V3 - Features
https://www.youtube.com/watch?v=yTniZCOCY7o
下载地址:Unreal商城,60刀
Advanced Locomotion System V3
https://www.unrealengine.com/marketplace/advanced-locomotion-system-v1
IK AnimNode
FABRIK (Forward And Backward Reaching Inverse Kinematics)
FABRIK
https://docs.unrealengine.com/en-us/Engine/Animation/NodeReference/Fabrik
Adding of a rifle and fabrik node to fix left hand
https://www.youtube.com/watch?v=49MJWjlSHcw
Look At
Look At
https://docs.unrealengine.com/en-us/Engine/Animation/NodeReference/SkeletalControls/LookAt
CCDIK (Cyclic Coordinate Descent Inverse Kinematics)
CCDIK
https://docs.unrealengine.com/en-us/Engine/Animation/NodeReference/SkeletalControls/CCDIK
Hand IK Retargeting
Hand IK Retargeting
https://docs.unrealengine.com/en-us/Engine/Animation/NodeReference/SkeletalControls/HandIKRetargeting
Two Bone IK
Two Bone IK
https://docs.unrealengine.com/en-us/Engine/Animation/NodeReference/SkeletalControls/TwoBoneIK
Spline IK
Spline IK
https://docs.unrealengine.com/en-us/Engine/Animation/NodeReference/SkeletalControls/SplineIK
IK Engine Source
CCDIK
Path:
Engine\Source\Runtime\AnimGraphRuntime\Private\BoneControllers\AnimNode_CCDIK.cpp
void FAnimNode_CCDIK::EvaluateSkeletalControl_AnyThread(FComponentSpacePoseContext& Output, TArray<FBoneTransform>& OutBoneTransforms)
{
const FBoneContainer& BoneContainer = Output.Pose.GetPose().GetBoneContainer();
// Update EffectorLocation if it is based off a bone position
FTransform CSEffectorTransform = GetTargetTransform(Output.AnimInstanceProxy->GetComponentTransform(), Output.Pose, EffectorTarget, EffectorLocationSpace, EffectorLocation);
FVector const CSEffectorLocation = CSEffectorTransform.GetLocation();
// Gather all bone indices between root and tip.
TArray<FCompactPoseBoneIndex> BoneIndices;
{
const FCompactPoseBoneIndex RootIndex = RootBone.GetCompactPoseIndex(BoneContainer);
FCompactPoseBoneIndex BoneIndex = TipBone.GetCompactPoseIndex(BoneContainer);
do
{
BoneIndices.Insert(BoneIndex, 0);
BoneIndex = Output.Pose.GetPose().GetParentBoneIndex(BoneIndex);
} while (BoneIndex != RootIndex);
BoneIndices.Insert(BoneIndex, 0);
}
// Gather transforms
int32 const NumTransforms = BoneIndices.Num();
OutBoneTransforms.AddUninitialized(NumTransforms);
// Gather chain links. These are non zero length bones.
TArray<CCDIKChainLink> Chain;
Chain.Reserve(NumTransforms);
// Start with Root Bone
{
const FCompactPoseBoneIndex& RootBoneIndex = BoneIndices[0];
const FTransform& LocalTransform = Output.Pose.GetLocalSpaceTransform(RootBoneIndex);
const FTransform& BoneCSTransform = Output.Pose.GetComponentSpaceTransform(RootBoneIndex);
OutBoneTransforms[0] = FBoneTransform(RootBoneIndex, BoneCSTransform);
Chain.Add(CCDIKChainLink(BoneCSTransform, LocalTransform, 0));
}
// Go through remaining transforms
for (int32 TransformIndex = 1; TransformIndex < NumTransforms; TransformIndex++)
{
const FCompactPoseBoneIndex& BoneIndex = BoneIndices[TransformIndex];
const FTransform& LocalTransform = Output.Pose.GetLocalSpaceTransform(BoneIndex);
const FTransform& BoneCSTransform = Output.Pose.GetComponentSpaceTransform(BoneIndex);
FVector const BoneCSPosition = BoneCSTransform.GetLocation();
OutBoneTransforms[TransformIndex] = FBoneTransform(BoneIndex, BoneCSTransform);
// Calculate the combined length of this segment of skeleton
float const BoneLength = FVector::Dist(BoneCSPosition, OutBoneTransforms[TransformIndex - 1].Transform.GetLocation());
if (!FMath::IsNearlyZero(BoneLength))
{
Chain.Add(CCDIKChainLink(BoneCSTransform, LocalTransform, TransformIndex));
}
else
{
// Mark this transform as a zero length child of the last link.
// It will inherit position and delta rotation from parent link.
CCDIKChainLink & ParentLink = Chain[Chain.Num() - 1];
ParentLink.ChildZeroLengthTransformIndices.Add(TransformIndex);
}
}
// solve
bool bBoneLocationUpdated = AnimationCore::SolveCCDIK(Chain, CSEffectorLocation, Precision, MaxIterations, bStartFromTail, bEnableRotationLimit, RotationLimitPerJoints);
// If we moved some bones, update bone transforms.
if (bBoneLocationUpdated)
{
int32 NumChainLinks = Chain.Num();
// First step: update bone transform positions from chain links.
for (int32 LinkIndex = 0; LinkIndex < NumChainLinks; LinkIndex++)
{
CCDIKChainLink const & ChainLink = Chain[LinkIndex];
OutBoneTransforms[ChainLink.TransformIndex].Transform = ChainLink.Transform;
// If there are any zero length children, update position of those
int32 const NumChildren = ChainLink.ChildZeroLengthTransformIndices.Num();
for (int32 ChildIndex = 0; ChildIndex < NumChildren; ChildIndex++)
{
OutBoneTransforms[ChainLink.ChildZeroLengthTransformIndices[ChildIndex]].Transform = ChainLink.Transform;
}
}
#if WITH_EDITOR
DebugLines.Reset(OutBoneTransforms.Num());
DebugLines.AddUninitialized(OutBoneTransforms.Num());
for (int32 Index = 0; Index < OutBoneTransforms.Num(); ++Index)
{
DebugLines[Index] = OutBoneTransforms[Index].Transform.GetLocation();
}
#endif // WITH_EDITOR
}
}
Path:
Engine\Source\Runtime\AnimationCore\Private\CCDIK.cpp
namespace AnimationCore
{
bool SolveCCDIK(TArray<CCDIKChainLink>& InOutChain, const FVector& TargetPosition, float Precision, int32 MaxIteration, bool bStartFromTail, bool bEnableRotationLimit, const TArray<float>& RotationLimitPerJoints)
{
struct Local
{
static bool UpdateChainLink(TArray<CCDIKChainLink>& Chain, int32 LinkIndex, const FVector& TargetPos, bool bInEnableRotationLimit, const TArray<float>& InRotationLimitPerJoints)
{
int32 const TipBoneLinkIndex = Chain.Num() - 1;
ensure(Chain.IsValidIndex(TipBoneLinkIndex));
CCDIKChainLink& CurrentLink = Chain[LinkIndex];
// update new tip pos
FVector TipPos = Chain[TipBoneLinkIndex].Transform.GetLocation();
FTransform& CurrentLinkTransform = CurrentLink.Transform;
FVector ToEnd = TipPos - CurrentLinkTransform.GetLocation();
FVector ToTarget = TargetPos - CurrentLinkTransform.GetLocation();
ToEnd.Normalize();
ToTarget.Normalize();
float RotationLimitPerJointInRadian = FMath::DegreesToRadians(InRotationLimitPerJoints[LinkIndex]);
float Angle = FMath::ClampAngle(FMath::Acos(FVector::DotProduct(ToEnd, ToTarget)), -RotationLimitPerJointInRadian, RotationLimitPerJointInRadian);
bool bCanRotate = (FMath::Abs(Angle) > KINDA_SMALL_NUMBER) && (!bInEnableRotationLimit || RotationLimitPerJointInRadian > CurrentLink.CurrentAngleDelta);
if (bCanRotate)
{
// check rotation limit first, if fails, just abort
if (bInEnableRotationLimit)
{
if (RotationLimitPerJointInRadian < CurrentLink.CurrentAngleDelta + Angle)
{
Angle = RotationLimitPerJointInRadian - CurrentLink.CurrentAngleDelta;
if (Angle <= KINDA_SMALL_NUMBER)
{
return false;
}
}
CurrentLink.CurrentAngleDelta += Angle;
}
// continue with rotating toward to target
FVector RotationAxis = FVector::CrossProduct(ToEnd, ToTarget);
if (RotationAxis.SizeSquared() > 0.f)
{
RotationAxis.Normalize();
// Delta Rotation is the rotation to target
FQuat DeltaRotation(RotationAxis, Angle);
FQuat NewRotation = DeltaRotation * CurrentLinkTransform.GetRotation();
NewRotation.Normalize();
CurrentLinkTransform.SetRotation(NewRotation);
// if I have parent, make sure to refresh local transform since my current transform has changed
if (LinkIndex > 0)
{
CCDIKChainLink const & Parent = Chain[LinkIndex - 1];
CurrentLink.LocalTransform = CurrentLinkTransform.GetRelativeTransform(Parent.Transform);
CurrentLink.LocalTransform.NormalizeRotation();
}
// now update all my children to have proper transform
FTransform CurrentParentTransform = CurrentLinkTransform;
// now update all chain
for (int32 ChildLinkIndex = LinkIndex + 1; ChildLinkIndex <= TipBoneLinkIndex; ++ChildLinkIndex)
{
CCDIKChainLink& ChildIterLink = Chain[ChildLinkIndex];
const FTransform LocalTransform = ChildIterLink.LocalTransform;
ChildIterLink.Transform = LocalTransform * CurrentParentTransform;
ChildIterLink.Transform.NormalizeRotation();
CurrentParentTransform = ChildIterLink.Transform;
}
return true;
}
}
return false;
}
};
bool bBoneLocationUpdated = false;
int32 const NumChainLinks = InOutChain.Num();
// iterate
{
int32 const TipBoneLinkIndex = NumChainLinks - 1;
// @todo optimize locally if no update, stop?
bool bLocalUpdated = false;
// check how far
const FVector TargetPos = TargetPosition;
FVector TipPos = InOutChain[TipBoneLinkIndex].Transform.GetLocation();
float Distance = FVector::Dist(TargetPos, TipPos);
int32 IterationCount = 0;
while ((Distance > Precision) && (IterationCount++ < MaxIteration))
{
// iterate from tip to root
if (bStartFromTail)
{
for (int32 LinkIndex = TipBoneLinkIndex - 1; LinkIndex > 0; --LinkIndex)
{
bLocalUpdated |= Local::UpdateChainLink(InOutChain, LinkIndex, TargetPos, bEnableRotationLimit, RotationLimitPerJoints);
}
}
else
{
for (int32 LinkIndex = 1; LinkIndex < TipBoneLinkIndex; ++LinkIndex)
{
bLocalUpdated |= Local::UpdateChainLink(InOutChain, LinkIndex, TargetPos, bEnableRotationLimit, RotationLimitPerJoints);
}
}
Distance = FVector::Dist(InOutChain[TipBoneLinkIndex].Transform.GetLocation(), TargetPosition);
bBoneLocationUpdated |= bLocalUpdated;
// no more update in this iteration
if (!bLocalUpdated)
{
break;
}
}
}
return bBoneLocationUpdated;
}
}
References
使用 Skeletal Control 动态控制头部IK运动
http://blog.csdn.net/sb978433018/article/details/77429762
认识自己的无知是认识世界的最可靠的方法。---米歇尔·蒙田《随笔集》