Github : https://github.com/CatDarkGame/UE4SimpleFootIKcpp

Kinematics(운동역학) 이란 ?

IK를 설명하기전에 우선 애니메이션 분야에서 Kinematics(운동역학)이라는 개념이 존재한다.

모델에 리깅을 하여 관절부 움직임을 묘사함에 있어 역학관계 계산이 들어가는 것을 애니메이션 Kinematics이라고 한다.

 

Kinematics의 종류는 FK(Forward Kinematics), IK(Inverse Kinematic) 두 가지가 있다.

좌 - FK,  우 - IK

 

영어 뜻과 기능을 생각하며 직역을 하면 FK는 전방 운동역학이고 IK는 역방향 운동역학이다.

쉽게 설명하자면 

FK는 사람 관절로 주먹을 뻗는 동작이 있다고 하면 어깨 -> 팔꿈치 -> 손목 순으로 운동 에너지를 표현하고 

IK는 반대로 물건을 집는 동작이면 손목 -> 팔굼치 -> 어깨 순으로 운동 에너지가 표현 될것이다.

 

이런 개념으로 Forward Kinematics(전방 운동역학), Inverse Kinematics(역방향 운동역학)이라고 이름 명시된다.

 

IK(Inverse Kinematics)

IK는 1998년 GDC에서 처음 소개 되었으며 게임에서 애니메이션 동작중 역방향 운동 개념이 들어간

고개 돌릴때 목 관절 처리, 물건 집기, 발을 땅에 딛기 등 환경요소와 상호작용이 되는 애니메이션으로

더 역동적인 표현을 위해 사용된다.

 

UE4 C++을 이용하여 IK 구현하기

1. 프로젝트 제작

이 튜토리얼은 Unrea4 4.21.2 버전으로 만들었다.

우선 새 프로젝트에 C++ 삼인칭 예제 프로젝트를 생성한다.

 

2. Foot IK Actor Component 제작

 

-기본 코드 세팅

Cpt_FootIK라는 이름으로 ActorComponent C++ 클래스를 제작한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#pragma once
 
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "Cpt_FootIK.generated.h"
 
 
UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class IKTEST_API UCpt_FootIK : public UActorComponent
{
    GENERATED_BODY()
 
public:    
    
    
public:    
    UCpt_FootIK();
    virtual void BeginPlay() override;
    virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
    virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
 
        
};
 
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4f; text-decoration:none">Colored by Color Scripter
http://colorscripter.com/info#e" target="_blank" style="text-decoration:none; color:white">cs

.h

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include "Cpt_FootIK.h"
 
UCpt_FootIK::UCpt_FootIK()
{
    PrimaryComponentTick.bCanEverTick = true;
 
}
 
void UCpt_FootIK::BeginPlay()
{
    Super::BeginPlay();
 
    
}
 
void UCpt_FootIK::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
    Super::EndPlay(EndPlayReason);
 
}
 
 
void UCpt_FootIK::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
    Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
 
}
 
 
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4f; text-decoration:none">Colored by Color Scripter
http://colorscripter.com/info#e" target="_blank" style="text-decoration:none; color:white">cs

.cpp

 

기존 코드에서 EndPlay 함수를 추가하여 기본 세팅을 한다.

 

 

-IK LineTrace 함수 제작

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
ST_IK_TraceInfo UCpt_FootIK::IK_FootTrace(float fTraceDistance, FName sSocket)
{
    ST_IK_TraceInfo pTraceInfo;
 
    //! Set Linetraces startpoint and end point
    FVector pSocketLocation = m_pCharacter->GetMesh()->GetSocketLocation(sSocket);
    FVector pLine_Start = FVector(pSocketLocation.X, pSocketLocation.Y, m_pCharacter->GetActorLocation().Z);
    FVector pLine_End = FVector(pSocketLocation.X, pSocketLocation.Y
        , (m_pCharacter->GetActorLocation().Z - m_fIKCapsuleHalkHeight) - fTraceDistance);
 
    //! Process Line Trace
    FHitResult pHitResult;
    TArray<AActor*> pIgnore;
 
    bool bDebug = true;
    EDrawDebugTrace::Type eDebug = EDrawDebugTrace::None;
    if (bDebug == true) eDebug = EDrawDebugTrace::ForOneFrame;
 
    bool bResult = UKismetSystemLibrary::LineTraceSingle(GetWorld(), pLine_Start, pLine_End,
        UEngineTypes::ConvertToTraceType(ECC_Visibility), true, pIgnore, eDebug, pHitResult, true);
 
    //! Set ImpactNormal and Offset from HitResult
    pTraceInfo.pImpactLocation = pHitResult.ImpactNormal;
    if (pHitResult.IsValidBlockingHit() == true)
    {
        float fImpactLegth = (pHitResult.ImpactPoint - pHitResult.TraceEnd).Size();
        pTraceInfo.fOffset = 5.0f + (fImpactLegth - fTraceDistance);
    }
    else
    {
        pTraceInfo.fOffset = 0.0f;
    }
 
    return pTraceInfo;
}
 
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4f; text-decoration:none">Colored by Color Scripter
http://colorscripter.com/info#e" target="_blank" style="text-decoration:none; color:white">cs

IK를 하기 위해서는 우선 발 밑으로 LineTrace를 하여 땅위치, Normal Vector를 알아야 한다.

IK_FootTrace는 그 정보를 알아오는 함수이다.

 

IK_FootTrace에서 반환 되는 구조체는 LineTrace의 HitPoint와 발, 땅의 Offset값이 들어있다.

 

TickComponent에서 Trace감지할 거리값, Trace를 시행할 본 이름을 입력하여 호출한다.

 

-Foot Rotation

1
2
3
4
5
6
7
8
9
FRotator UCpt_FootIK::NormalToRotator(FVector pVector)
{
    float fAtan2_1 = UKismetMathLibrary::DegAtan2(pVector.Y, pVector.Z);
    float fAtan2_2 = UKismetMathLibrary::DegAtan2(pVector.X, pVector.Z);
    fAtan2_2 *= -1.0f;
    FRotator pResult = FRotator(fAtan2_2, 0.0f, fAtan2_1);
 
    return pResult;
}
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4f; text-decoration:none">Colored by Color Scripter
http://colorscripter.com/info#e" target="_blank" style="text-decoration:none; color:white">cs

IK_FootTrace함수에서 나온 땅위치 값 바탕으로 바닥 Normal Vector를 알 수 있다. 위 함수는 해당 기능을 한다.

 

1
2
3
4
5
6
void UCpt_FootIK::IK_Update_FootRotation(float fDeltaTime, FRotator pTargetValue, FRotator * pFootRotatorValue, float fInterpSpeed)
{
    //! Set Foot Rotation value with FInterpTo
    FRotator pInterpRotator = UKismetMathLibrary::RInterpTo(*pFootRotatorValue, pTargetValue, fDeltaTime, fInterpSpeed);
    *pFootRotatorValue = pInterpRotator;
}
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4f; text-decoration:none">Colored by Color Scripter
http://colorscripter.com/info#e" target="_blank" style="text-decoration:none; color:white">cs

NormalToRotator에서 나온 값을 RInterpTo함수를 이용해 값이 보간하며 변하게 하는 함수이다.

 

TickComponent에서 DeltaTime, Normal값, 값을 저장할 변수 포인터, Interp속도 값을 넣어 호출한다.

 

-Foot Rotator : Animation Blueprint 적용

캐릭터의 Animation Blueprint를 열고 이벤트 그래프에서 위 노드 사진과 같이 FootRotatorL,R변수를 만들고

캐릭터의 FootIK Component에 접근하여 아까 IK_Update_FootRotation함수에 넣은 변수를 Set한다.

 

 

다음 애님 그래프로 이동한다.

본 트랜스폼 노드 2개를 만든 뒤 아래 와 같이 노드를 세팅한다.

 

최종적으로 위 형태로 노드를 구성하면 Foot Rotation 동작한다.

앞으로 Foot Offset, HipOffset을 추가로 구현할 예정인데 C++에서 구현 이후 Animation Blueprint에서 동일하게

구현하여 적용한다.

 

-Hip(Pelvis) Offset, Capsule Height Set 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
void UCpt_FootIK::IK_Update_CapsuleHalfHeight(float fDeltaTime, float fHipsShifts, bool bResetDefault)
{
    UCapsuleComponent* pCapsule = m_pCharacter->GetCapsuleComponent();
    if (pCapsule == nullptr)
    {
        UE_LOG(LogTemp, Warning, TEXT("IK : Capsule is NULL"));
        return;
    }
 
    //! Get Half Height of capsule component
    float fCapsuleHalf = 0.0f;
    if (bResetDefault == true)
    {
        fCapsuleHalf = m_fIKCapsuleHalkHeight;
    }
    else
    {
        float fHalfAbsSize = UKismetMathLibrary::Abs(fHipsShifts) * 0.5f;
        fCapsuleHalf = m_fIKCapsuleHalkHeight - fHalfAbsSize;
    }
 
    //! Set capsule component height with FInterpTo 
    float fScaledCapsuleHalfHeight = pCapsule->GetScaledCapsuleHalfHeight();
    float fInterpValue = UKismetMathLibrary::FInterpTo(fScaledCapsuleHalfHeight, fCapsuleHalf, fDeltaTime, 13.0f);
 
    pCapsule->SetCapsuleHalfHeight(fInterpValue, true);
}
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4f; text-decoration:none">Colored by Color Scripter
http://colorscripter.com/info#e" target="_blank" style="text-decoration:none; color:white">cs

발의 높이에 따라 Capsule Collider 위치를 조절하는 함수이다.

1
2
3
4
5
6
void UCpt_FootIK::IK_Update_FootOffset(float fDeltaTime, float fTargetValue, float* fEffectorValue, float fInterpSpeed)
{
    float fInterpValue = UKismetMathLibrary::FInterpTo(*fEffectorValue, fTargetValue, fDeltaTime, fInterpSpeed);
    *fEffectorValue = fInterpValue;
}
 
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4f; text-decoration:none">Colored by Color Scripter
http://colorscripter.com/info#e" target="_blank" style="text-decoration:none; color:white">cs

이전에는 Rotator값을 InterpTo로 값을 보간했는데 이 함수는 Vector값을 보간한다.

이 함수를 통해서 발 위치, Hip위치를 조절한다.

 

1
2
3
4
5
6
 
    float fHipsOffset = UKismetMathLibrary::Min(pTrace_Left.fOffset, pTrace_Right.fOffset);
    if (fHipsOffset < 0.0f == false) fHipsOffset = 0.0f;
    IK_Update_FootOffset(DeltaTime, fHipsOffset, &m_fHipOffset, 13.0f);
    IK_Update_CapsuleHalfHeight(DeltaTime, fHipsOffset, false);
 
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4f; text-decoration:none">Colored by Color Scripter
http://colorscripter.com/info#e" target="_blank" style="text-decoration:none; color:white">cs

TickComponent함수에 추가하는 코드로 이전에 양발에 LineTrace했던 데이터를 토대로

낮은 위치의 발 기준으로 Hip위치를 조절하고 CapsuleComponent위치를 조정한다.

 

-Hip(Pelvis) Offset, Capsule Height Set : Animation Blueprint 적용

C++에서 기능을 만들었으니 AnimationBlueprint에서 값을 받아서 실제 애니메이션에 적용해야한다.

이벤트 그래프에서 위 사진 처럼 HipOffset을 Set한다.

 

다음 애님그래프에서 이전에 Foot Rotation 처리한 부분 다음 노드를 이어서 본 트랜스 폼 노드를 만들어

이번엔 pelvis(hip) 본을 설정하고 HipOffset값을 넣는다.

 

-Foot Offset

FootOffset은 이전에 만든 IK_Update_FootOffset함수를 에 LineTrace의 Offset값과 이전에 구한 HipOffset값을

이용하여 발 위치를 구할 수 있다.

 

-Foot Offset : Animation Blueprint

Animation Blueprint에서 이벤트 그래프에서 이전에 값을 Set한거와 마찬가지로 FootOffset L, R 값을 세팅한다.

 

다음 애님그래프에서 2본 IK 노드를 설치한다. 이 노드는 캐릭터 사지(팔, 다리)와 같은 3조인트로 이어진 본을 IK 할 수 있게 하는 노드이다.

위와 같이 세팅하면 끝.

 

 


WRITTEN BY
CatDarkGame
Technical Artist dhwlgn12@gmail.com

,