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


认识自己的无知是认识世界的最可靠的方法。---米歇尔·蒙田《随笔集》