언리얼 애니메이션 - FABRIK

yys·2026년 7월 28일

TIL

목록 보기
76/85

문제 상황


이번에 FPS 내용을 공부하다가, 새로운 애니메이션 개념을 학습하게 되어서 작성하게 되었다.

Aim Offset을 적용하면 조준 방향에 따라 상체와 오른손, 그리고 오른손에 부착된 무기가 함께 움직인다.
하지만 왼손은 기존 애니메이션 포즈만 따라가기 때문에 무기의 손잡이에서 떨어질 수 있다.

무기와 조준 각도마다 별도의 애니메이션을 만드는 대신, 왼손이 무기의 특정 지점을 실시간으로 따라가도록 FABRIK을 적용해보려고 한다.

FABRIK


FABRIK은 Forward And Backward Reaching Inverse Kinematics의 약자로, 여러 본으로 이루어진 체인의 끝을 목표 위치로 이동시키는 IK 솔버다.

일반 애니메이션은 어깨와 팔꿈치의 회전을 정해 손의 위치를 결정한다.
반대로 IK는 손이 도착해야 할 위치를 먼저 지정하고, 그 위치에 도달하도록 어깨와 팔꿈치의 회전을 역으로 계산한다.

FABRIK 노드를 추가해서 다음과 같이 설정해주었다.

FABRIK 노드 옵션

End Effector

옵션현재 값의미
Effector TransformPinTip Bone이 도달해야 할 목표 위치와 회전이다. Pin으로 노출해 외부에서 계산한 Transform을 전달받는다.
Effector Targethand_rEffector Transform의 기준으로 사용할 본이다. 현재 왼손의 목표 위치를 오른손 기준으로 계산한다.
Effector Transform SpaceBone SpaceEffector Transform을 hand_r 본의 상대 좌표로 해석한다. 따라서 월드 공간의 무기 소켓도 hand_r Bone Space로 변환해서 전달해야 한다.
Effector Rotation SourceCopy Target RotationTip Bone의 회전을 Effector Transform에 들어 있는 목표 회전과 일치시킨다. 왼손의 위치뿐 아니라 손목 방향도 소켓에 맞춘다.

Solver

옵션현재 값의미
Tip Bonehand_l목표 위치로 이동시킬 체인의 끝 본이다. 현재는 왼손 본이 무기의 소켓을 따라간다.
Root Boneupperarm_lFABRIK 계산을 시작하는 체인의 루트 본이다. upperarm_l부터 hand_l까지의 본이 목표 위치에 맞게 조정된다.

여기서 Effector는 IK 체인의 끝부분이 도달해야 할 목표 위치와 회전을 의미하며, 사람의 팔로 비유하면 손이 End Effector에 해당하고, “손을 어디에 둘 것인가”를 나타내는 목표 Transform이 Effector Transform이다.

Solver는 Tip Bone이 Effector에 도달하도록 본 체인을 계산하는 알고리즘 이다.
즉, Root BoneTip Bone으로 계산 범위를 지정하면 FABRIK Solver가 그 사이에 있는 어깨, 팔꿈치, 손목의 위치와 회전을 반복적으로 조정한다.

오른손은 무기를 들고 있으므로 hand_r을 기준으로 삼고, 왼손인 hand_l이 무기의 손잡이를 따라가도록 만드는 구조다.

FABRIK 소켓


먼저 3인칭 무기 메시에 FABRIK_Socket을 추가하고 왼손이 잡아야 할 위치에 배치해야 한다.
내가 받은 에셋 기준에서는 소켓이 이미 할당되어 있어서 수정할 필요가 없었다.

이때 소켓은 단순히 손바닥이 닿는 지점이 아니라, 최종적으로 hand_l 본의 기준점인 손목이 도착해야 할 위치와 회전에 맞추는 것이 좋다.

하지만 이 소켓은 무기 메시의 공간에 있고, FABRIK 노드는 캐릭터의 hand_r Bone Space를 사용한다. 서로 다른 좌표계를 그대로 전달하면 왼손이 엉뚱한 곳으로 이동한다.

따라서 다음 순서로 좌표를 변환한다.

  1. 무기의 FABRIK_Socket Transform을 World Space로 가져온다.
  2. World Space Transform을 캐릭터의 hand_r Bone Space로 변환한다.
  3. 변환된 Transform을 FABRIK 노드의 Effector로 전달한다.

소켓 Transform 계산


소켓은 조준에 따라 계속 움직이므로 Tick에서 매 프레임 계산한다.

void AShooterCharacter::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);

    CalculateFABRIKSocketTransform();
}

void AShooterCharacter::CalculateFABRIKSocketTransform()
{
    if (IsValid(Combat) &&
        IsValid(Combat->CurrentWeapon) &&
        IsValid(Combat->CurrentWeapon->GetMesh3P()))
    {
        // 무기 소켓의 월드 Transform
        FABRIK_SocketTransform =
            Combat->CurrentWeapon->GetMesh3P()->GetSocketTransform(
                TEXT("FABRIK_Socket"),
                RTS_World
            );

        FVector OutLocation;
        FRotator OutRotation;

        // 월드 공간의 소켓 Transform을
        // hand_r 기준 Bone Space로 변환
        GetMesh()->TransformToBoneSpace(
            TEXT("hand_r"),
            FABRIK_SocketTransform.GetLocation(),
            FABRIK_SocketTransform.GetRotation().Rotator(),
            OutLocation,
            OutRotation
        );

        FABRIK_SocketTransform.SetLocation(OutLocation);
        FABRIK_SocketTransform.SetRotation(OutRotation.Quaternion());
    }
}

TransformToBoneSpace()는 입력받은 월드 위치와 회전을 지정한 본의 상대 좌표로 변환한다.

즉, 변환된 FABRIK_SocketTransformhand_r에서 바라봤을 때 왼손 그립 소켓이 어디에 있고, 어떤 방향을 향하고 있는가? 를 알려주는 Transform을 지닌다.

이 값을 BlueprintReadOnly로 노출한 뒤 3인칭 AnimBP의 FABRIK 노드에 전달해주려고 한다.

UPROPERTY(BlueprintReadOnly, Category = "FPS|FABRIK")
FTransform FABRIK_SocketTransform;

AnimBP 적용


Aim Offset을 적용한 Cached Pose를 FABRIK 노드에 연결하고, 앞에서 계산한 FABRIK_SocketTransform을 Effector Transform으로 전달한다.

FABRIK은 Skeletal Control 노드이므로 입력 포즈가 Component Space로 변환되며, 처리 후 다시 Local Space로 변환되어 최종 포즈로 출력된다.

이제 조준 방향에 따라 무기가 움직여도 왼손이 FABRIK_Socket을 따라간다.
손의 위치만 고정하는 것이 아니라 upperarm_l부터 hand_l까지의 본을 함께 계산하므로 팔꿈치도 비교적 자연스럽게 움직인다.

profile
게임 개발 지망생

0개의 댓글