3줄 요약

  1. 애니메이션 프로그래머 Daniel Holden이 2026년 7월 30일 자신의 블로그에 발 미끄러짐(foot sliding)을 없애는 레시피 네 가지를 올렸다. 오래도록 요청받아 온 주제라고 하며, 예제의 소스 코드 전체를 GitHub에 함께 열어 두었다.
  2. 레시피는 발끝을 원하는 자리에 놓는 다리 체인 솔버와, 접촉 중에 발끝 목표를 지면에 고정하는 런타임 잠금으로 시작한다. 이어서 발끝 속도로 접촉 구간을 자동 표시하고, 클립 전체를 제약 조건으로 푸는 오프라인 방법으로 나아간다.
  3. 저자는 발 미끄러짐을 마찰의 위반이 아닌 속도의 오차로 보라고 말한다. 잠글 곳도 뒤꿈치보다 발끝 쪽이다. 다리가 완전히 펴지는 것을 피하려고 골반을 끌어내리면 캐릭터가 티라노사우루스처럼 걷게 된다.

저자는 서두에서 이 주제가 과학보다 예술에 가깝다고 밝힌다. 애니메이션 프로그래머 두 사람에게 물으면 최선의 해법에 대한 답이 둘로 갈리고, 그래서 참고할 자료를 찾기도 쉽지 않으며 학계의 관심도 적다는 것이다. 그는 자신이 내놓는 방법이 증명된 최선은 아니며 이보다 잘 해내는 게임도 많을 것이라고 미리 말해 둔다. 그래도 여러 해 동안 자신에게는 잘 작동했으니, 이 주제에 발을 들이는 사람에게 출발점은 되리라고 한다.

다리 체인 풀기

문제 설정은 이렇다. 캐릭터의 다리 관절 체인이 어떤 자세로 놓여 있고, 우리는 그 자세를 최대한 보존하면서 발끝만 원하는 위치로 옮기고 싶다. 그러려면 관절의 로컬 회전을 고쳐야 한다.

절차는 네 단계로 나뉜다.

  1. 발끝의 목표 위치에서 뒤꿈치의 목표 위치를 계산한다.
  2. 두 관절 역운동학을 풀어 뒤꿈치를 그 목표에 놓는다.
  3. 뒤꿈치 관절을 회전시켜 발끝이 목표를 향하게 한다.
  4. 필요하면 발끝 말단 관절을 회전시켜 지면 관통을 없앤다.

첫 단계는 간단하다. 입력 자세에서 발끝으로부터 뒤꿈치로 가는 벡터를 구한 다음 발끝 목표에 더하면 그것이 뒤꿈치 목표다.

*targetHeel = Vector3Add(*targetToe, Vector3Subtract(
    globalTransforms[heelBoneIndex].translation, 
    globalTransforms[toeBoneIndex].translation));

두 번째 단계에서 저자는 자신이 예전에 공개했던 두 관절 역운동학 코드를 조금 고쳐 쓴다. 고친 부분은 목표 지점을 부드럽게 잡아당기는 처리다. 목표가 다리로 닿을 수 있는 한계에 가까워지면, 지수 함수로 포화시켜 안쪽으로 끌어당긴다. 한계에서 딱 잘라내면 그 순간 자세가 튀기 때문이다.

세 번째와 네 번째 단계는 회전을 하나씩 주는 일이다. 두 벡터 사이의 회전을 구하는 함수로 뼈가 목표를 바라보도록 돌린다. 지면 충돌은 바인드 포즈에서 뒤꿈치와 발끝, 발끝 말단의 높이를 미리 재서 처리한다. 목표의 높이가 그 값보다 낮아지지 못하게 막는 방식이다. 지형이 평평하지 않다면 발밑으로 레이캐스트를 쏘아 실제 지면 높이를 가져와야 한다고 저자는 덧붙인다.

이 단계를 마치면 블랙박스 하나가 손에 들어온다. 캐릭터의 기존 자세와 새 발끝 목표를 넣으면 언제나 그럴듯한 자세를 돌려주는 함수다. 여기까지 오면 발 잠금은 발끝 목표를 미끄러지지 않게 붙드는 문제로 좁혀진다.

접촉 중에 발끝 잠그기

잠금 레시피 자체는 단순하다. 접촉이 없는 동안에는 입력 애니메이션의 발끝 위치를 그대로 따라간다. 접촉이 시작되면 그 순간의 발끝 위치를 지면 위의 한 점으로 기록한 뒤, 그 점을 목표로 삼는다. 두 상태 사이는 이너셜라이제이션(inertialization)으로 잇는데, 저자는 3차 함수를 쓰는 방식을 택했다.

각 접촉점에는 여러 상태를 따로 저장해야 한다. 현재 위치와 속도, 입력 소스의 위치와 속도, 이너셜라이제이션 오프셋과 그 속도, 전환 이후 경과 시간, 접촉 지점, 그리고 지금 잠겨 있는지 여부다.

잠금과 해제에는 거리 조건이 붙는다. 입력이 접촉을 알리더라도 현재 출력과 입력 사이의 거리가 잠금 임계값보다 멀면 잠그지 않는다. 반대로 잠긴 상태에서 그 거리가 해제 임계값을 넘으면, 입력이 접촉을 유지하더라도 잠금을 푼다. 발이 비현실적으로 끌려다니는 것을 막는 안전장치다.

이렇게 만든 발끝 목표를 앞서 만든 솔버에 넣으면, 접촉 구간 동안 발끝이 제자리에 고정된다.

접촉 구간을 자동으로 표시하기

앞의 코드는 입력 애니메이션의 어느 구간에서 발끝이 지면에 닿아 있는지를 알아야 돌아간다. 저자는 손으로 라벨을 다는 것이 유일한 정답이라고 인정하면서도, 간단한 휴리스틱으로 90% 지점까지는 갈 수 있다고 한다.

저자가 보는 신호는 발끝 관절의 전역 속도 크기다.

발끝 관절의 전역 속도 크기 그래프

속도가 낮게 내려앉는 구간이 눈에 띄게 드러나므로, 여기에 임계값을 걸면 접촉 여부를 대체로 나타내는 이진 신호를 얻는다.

속도 신호에 임계값을 적용해 접촉 구간을 표시한 그래프

저자는 발 높이를 신호로 쓰는 쪽을 권하지 않는다. 사람은 걸을 때 발을 그리 높이 들지 않고 접촉 중에도 높이가 조금씩 오르내리기 때문에, 임계값을 어디에 둘지 정하기가 어렵다.

발끝 높이 그래프. 접촉 구간의 경계가 속도만큼 뚜렷하지 않다

그래도 높이는 보조 검사로 쓸모가 있다. 발이 공중에 멈춰 있는 구간을 접촉으로 잘못 표시하는 일을 막아 주기 때문이다. 그래서 두 신호에 임계값을 함께 걸 수도 있다. 품질이 좋은 애니메이션 데이터에서 저자가 쓰는 값은 속도가 초당 0.1미터에서 0.5미터 사이, 높이가 0.1미터다. 물론 스켈레톤에서 발끝 관절이 어디에 붙어 있느냐에 따라 달라진다.

임계값을 건 신호에는 후처리를 붙인다. 첫째는 다수결 필터다. 짧은 창을 미끄러뜨리며 창 한가운데 프레임의 값을 다수결로 정한다. 접촉이 한 프레임만 켜지거나 꺼지는 일을 막아 준다. 저자는 넘파이에서 중앙값 필터를 쓰는데 이진 신호에서는 결과가 같다고 하며, 초당 60프레임(60Hz) 데이터에서 창 너비 5프레임을 출발점으로 삼는다.

다수결 필터를 적용하기 전과 후의 접촉 신호 비교

둘째는 가우시안 스무딩이다. 이진 신호가 연속 신호로 바뀌므로, 런타임에서 임계값을 조절해 접촉이 얼마나 이르게 또는 늦게 켜질지를 다룰 여유가 생긴다.

가우시안 스무딩으로 이진 접촉 신호를 연속 신호로 바꾼 그래프

저자는 이 휴리스틱이 완벽과는 거리가 멀다고 덧붙인다. 특히 달리기 애니메이션에서는 깨끗한 라벨을 얻기가 여전히 어렵다. 빠르게 달릴 때는 신발과 발의 변형이 제법 커서 발이 위아래로 움직이고, 접촉 시간 자체도 짧다.

초당 30프레임에서 달리기 접촉은 한두 프레임에 그치는 일이 잦다. 여기에 다수결 필터까지 얹으면 조금만 어긋나도 접촉을 놓친다. 그래서 애니메이션 데이터는 초당 60프레임으로 유지할수록 도움이 된다. 초당 30프레임 신호를 업샘플링해야 한다면, 선형 보간보다 3차 보간으로 속도를 구하는 편이 임계값을 걸기 쉽다고 한다. 저자는 자동 접촉 표시를, 데이터를 정성껏 손볼수록 이득이 커지는 사례로 든다.

클립 전체를 한 번에 푸는 방법

애니메이션 클립 전체를 볼 수 있고 계산 시간에도 여유가 있다면, 런타임과는 다른 방법을 쓸 수 있다. 저자는 발 미끄러짐을 제약 조건의 집합으로 세우고 위치 기반 동역학(position-based dynamics)에 가까운 방법으로 푸는 쪽에서 더 좋은 결과를 얻었다고 한다. 이론은 복잡하게 들리지만 구현 자체는 아주 단순하다는 것이 저자의 말이다.

발상은 이렇다. 모든 프레임에서 골반과 양쪽 발끝의 위치를 구해, 이것들을 서로 연결된 입자 무리로 본다. 프레임 사이의 상대 위치와 한 프레임 안에서의 상대 위치가 입력 애니메이션과 비슷하게 유지되도록 부드러운 용수철 연결을 건다. 그 위에 더 단단한 연결을 하나 얹는다. 접촉으로 표시된 발끝 입자는 프레임 사이의 거리가 0이어야 한다. 그러면 접촉 구간의 발끝 입자들이 한 점으로 뭉치고, 그 보정의 여파가 나머지 애니메이션 전체에 고르게 퍼진다.

제약을 지키게 만드는 방법도 단순하다. 애니메이션 전체를 여러 번 돌면서, 제약마다 관련된 입자를 조금씩 움직여 조건에 더 가깝게 만든다. 접촉이 이어진 두 프레임의 발끝 입자는 그 중점으로 끌어당기고, 높이는 지면에 맞춘다. 접촉이 아니면 원본 애니메이션에서의 상대 이동량을 유지하는 위치로 부드럽게 민다. 한 프레임 안에서는 골반과 발끝 사이의 거리가 원본과 같아지도록 양쪽을 조금씩 당긴다. 강도는 두 개의 계수로 조절하는데, 미끄러짐 제약을 얼마나 강하게 걸 것인지와 소스 애니메이션을 얼마나 따라갈 것인지의 균형을 여기서 잡는다. 반복 횟수는 늘릴수록 결과가 좋아지지만 너무 높이면 계산이 오래 걸린다.

저자는 루트 이동을 1.25배로 늘려 일부러 미끄러짐을 만든 클립과, 0.75배로 줄인 클립을 준비했다. 두 클립에서 보정하지 않은 결과와 런타임 이너셜라이제이션, 오프라인 방법을 나란히 놓고 비교한 영상을 실었다. 오프라인 방법은 클립 전체를 미리 보기 때문에, 접촉이 들어올 때마다 반응하는 대신 보정을 고르게 나누고 입력 애니메이션을 더 잘 지킨다.

저자가 남긴 네 가지 조언

마지막 장에서 저자는 발 미끄러짐이 어디에서 오는지부터 다시 살핀다. 원인은 적어도 두 가지다. 하나는 캐릭터의 루트 움직임이 몸에 재생 중인 애니메이션과 맞지 않는 경우다. 이때 미끄러지는 대상은 발에 그치지 않고 캐릭터 전체다. 발이 지면에 가장 가까우니 우리 눈에 발만 들어올 뿐이다. 저자는 고쳐야 할 대상이 다리의 움직임이 아닌 애니메이션 전체라는 점을 기억하라고 강조한다. 다른 하나는 스켈레톤의 로컬 회전을 보간하거나 블렌딩하거나 수정할 때, 관절 체인의 끝인 발이 자연스럽지 않게 움직이는 경우다.

발 미끄러짐은 속도의 문제다

많은 사람이 발 높이를 보고 접촉을 자동으로 표시하려 든다. 발 미끄러짐을 지면 마찰력에 대한 위반으로 여기기 때문이다. 저자가 보기에 이 관점은 핵심을 놓친다. 런타임의 발 속도를 소스 데이터의 발 속도에 맞추는 것이 목표다. 그 일을 접촉 구간에서만 하는 이유는 현실적인 타협에 가깝다. 모든 순간에 속도를 구속하면 나머지 애니메이션을 따라갈 수 없기 때문이다. 루트 모션을 키우면 발은 세계 안에서 더 빨리 움직이고, 이 속도 오차는 접촉 여부와 상관없이 속도가 클수록 뚜렷하게 보인다.

잠가야 할 것은 발끝이다

로코모션 데이터를 보면 전체 시간의 90퍼센트 동안 실제로 바닥에 닿아 있는 부위는 발끝 관절이 붙은 영역이다. 운동성이 큰 동작에서 사람은 우리 예상보다 훨씬 앞발로 착지하며, 뒤꿈치는 닿았는데 발끝은 떠 있는 시간이 아주 짧다. 게다가 뒤꿈치만 닿은 자세는 안정적인 경우가 드물다. 반대로 발끝만 닿은 자세는 대단히 흔하고, 발끝을 축으로 도는 동작이나 뒤꿈치를 들었다 내리는 동작도 자주 나온다.

뒤꿈치는 떠 있고 발끝만 바닥에 닿아 있는 캐릭터의 자세

뒤꿈치가 바닥을 파고드는 나쁜 애니메이션을 만나면 그 자세에서 뒤꿈치를 잠그고 싶어지지만, 저자는 그러지 말라고 한다. 뒤꿈치에 움직일 자유를 많이 줄수록 결과가 보기 좋아진다는 것이다.

역운동학은 갈아 끼우는 도구가 아니다

두 관절 역운동학을 떠올릴 때 사람들은 보통 폴 벡터 같은 절차적 규칙으로 세 관절의 자세를 통째로 갈아 끼우는 과정을 생각한다. 리깅에서는 대개 그렇게 설정하기도 한다. 그러나 역운동학을 꼭 그렇게 쓸 필요는 없다. 기존 애니메이션에 최소한의 수정으로 적용해서, 관절을 목표 위치로 옮기되 뼈의 회전은 최대한 보존할 수도 있다. 이 글의 레시피가 그렇게 작동한다. 뒤꿈치와 발끝의 관계, 미묘한 비틀림처럼 기존 애니메이션이 품고 있는 뉘앙스를 지키고 싶기 때문이다.

공룡을 피하라

발 잠금을 과하게 적용할 때 저자가 자주 보는 마지막 문제는, 다리가 펴질 여유를 만들고 과신전을 피하려고 캐릭터의 골반을 아래로 끌어내리는 것이다. 그러면 무릎이 굽은 채로 걷게 되어 티라노사우루스 같은 모습이 나온다. 실제 애니메이션 데이터를 뜯어 보면 다리는 완전히 펴지기 직전까지 가는 일이 잦다. 발 잠금을 그토록 어렵게 만드는 요인이 여기에 있다. 골반을 내리면 과신전이야 피하겠지만, 그 대가로 애니메이션이 망가진다. 저자는 이럴 때 차라리 발이 미끄러지게 두는 편이 거의 언제나 낫다고 하며, 그래서 자신의 레시피에서는 신전의 한계를 입력 애니메이션이 가진 만큼으로 묶어 둔다.

저자의 마지막 조언은 이렇다. 기계적으로 올바르게 만들겠다고 원본 애니메이션을 망가뜨리느니 조금 미끄러지는 편이 훨씬 낫다. 아예 미끄러짐이라는 말로 생각하기를 그만두고, 입력 모션의 속도를 보존하는 문제로 바꿔 생각하라는 것이다.

가장 흥미로운 지점

내게 가장 뜻밖이었던 대목은, 저자가 마찰이라는 비유 자체를 문제의 근원으로 지목한 곳이다. 발은 바닥에 붙어 있어야 한다는 직관은 물리에서 온다. 그런데 그 직관을 그대로 따라가면 발 높이로 접촉을 찾게 되고, 뒤꿈치를 잠그게 되고, 과신전을 피하려 골반을 내리게 된다. 저자가 늘어놓은 세 가지 실수는 뿌리가 같다. 마찰이라는 비유 하나다.

저자가 대신 내미는 것은 속도 보존이라는 언어다. 발이 바닥에 붙어 있는지를 묻는 대신, 소스 데이터가 가진 발의 속도를 런타임이 얼마나 지켜 냈는지를 묻는다. 문제를 어떤 말로 정의하느냐가 해법의 모습까지 바꾼다. 이 대목이 그 점을 코드보다 선명하게 보여 준다.

출처

Daniel Holden(theorangeduck), 2026년 7월 30일 게시. 원문: https://theorangeduck.com/page/inverse-kinematics-foot-locking 소스 코드: https://github.com/orangeduck/GenoView-InverseKinematics

본문의 그림은 원문에서 인용했다. 원문에는 각 단계의 동작을 보여 주는 영상 열한 편이 함께 실려 있는데, 이 글에는 옮기지 않았다.