사전 스켈레탈 애니메이션
알고리즘

스켈레탈 애니메이션

gabury1고친 사람 github-actions[bot]

스켈레탈 애니메이션은 캐릭터의 겉모습 대신 그 안에 숨긴 뼈대를 움직입니다. 뼈 몇십 개의 각도만 바꾸면 뼈대를 감싼 겉면이 따라 움직입니다. 3차원 게임과 애니메이션에서 사람과 동물을 움직일 때 흔히 쓰는 방법입니다.

쉽고 빠른 이해

스켈레탈 애니메이션은 캐릭터 안에 보이지 않는 뼈대를 넣고 그 뼈대를 움직여 캐릭터를 움직입니다. 걷는 동작이라면 허벅지·정강이·발 뼈의 각도가 시간에 따라 바뀝니다. 다리를 덮은 겉면이 그 뼈를 따라갑니다.

이게 없으면 장면마다 겉면의 점 수천 개를 하나하나 옮겨 저장해야 합니다. 뼈대를 쓰면 뼈 몇십 개의 각도만 저장합니다. 뼈대가 같은 캐릭터끼리는 걷는 동작 하나를 함께 쓸 수도 있습니다.

어떻게 도나:

  1. 동작을 몇몇 순간의 뼈 각도로 저장해 둡니다. 그 사이 시각의 각도는 비율대로 채웁니다
  2. 위팔에서 아래팔, 아래팔에서 손으로 내려가며 부모 뼈의 움직임을 자식 뼈에 더합니다
  3. 이렇게 정해진 뼈대 자세에 맞춰 겉면의 점들을 옮겨 그립니다

관절로 꺾이는 몸, 곧 사람이나 동물을 움직일 때 씁니다. 얼굴 표정이나 펄럭이는 옷처럼 마디로 나눌 수 없는 움직임에는 다른 방식을 씁니다.

대가는 뼈대를 짜고 겉면을 붙이는 사람의 작업이 먼저 필요하다는 것입니다. 관절을 크게 굽히면 그 둘레 겉면이 쪼그라드는 일도 생깁니다.

상세

스켈레탈 애니메이션은 뼈대의 자세를 시간에 따라 바꿔 3차원 모형을 움직이는 계산입니다. 넣는 것은 뼈대 구조와 동작 데이터, 그리고 지금 시각입니다. 나오는 것은 그 시각에 뼈마다 놓일 위치와 방향입니다. 이 자세에 맞춰 겉면을 휘게 하면 한 장면이 나옵니다.

이 절은 먼저 모형이 겉면과 뼈대 두 겹으로 되어 있다는 것과 뼈대를 따로 두는 까닭을 봅니다. 그다음 한 장면을 만드는 순서를 그림 하나로 잡고, 동작 데이터에서 자세를 꺼내는 계산과 부모부터 내려가는 자세 계산을 숫자 예로 따라갑니다. 끝으로 비용과, 이 방식이 맞지 않는 움직임을 봅니다.

줄 인형

나무로 만든 줄 인형을 떠올려 보면 됩니다. 사람은 인형의 옷을 하나하나 움직이지 않습니다. 줄을 당겨 안쪽의 나무 팔다리를 움직이면 그 위에 입힌 옷이 저절로 따라 움직입니다.

겉면과 뼈대

3차원 캐릭터는 두 겹으로 만듭니다. 바깥 겹은 눈에 보이는 겉면입니다. 작은 삼각형 수천에서 수만 개를 이어 붙인 것입니다. 이 삼각형 묶음이 메시입니다. 삼각형의 모서리가 만나는 점은 꼭짓점입니다. 그래픽스에서는 정점이라고도 부릅니다.

안쪽 겹은 화면에 그려지지 않는 뼈대입니다. 뼈대는 뼈 여러 개로 되어 있습니다. 뼈는 본(bone) 또는 조인트(joint)라고도 부릅니다. 뼈 하나는 그 뼈가 도는 관절 하나로 정해지기 때문에 관절이라는 이름을 뼈에도 씁니다.

애니메이터도 게임 코드도 뼈대만 움직입니다. 겉면의 꼭짓점은 뼈대에 맞춰 계산으로 옮깁니다.

꼭짓점을 직접 움직이지 않는 까닭

뼈대 없이 캐릭터를 움직이는 가장 단순한 방법은 장면마다 모든 꼭짓점의 위치를 적어 두는 것입니다. 이 방식을 정점 애니메이션이라고 부릅니다. 꼭짓점이 만 개인 모형이 걷는 동작을 서른 장면으로 보여 주면 위치 삼십만 개가 필요합니다.

뼈대를 쓰면 장면마다 뼈 몇십 개의 자세만 적습니다. 저장량이 꼭짓점 수가 아니라 뼈 수를 따라갑니다. 꼭짓점이 만 개든 십만 개든 같은 뼈대라면 동작 데이터의 크기는 같습니다.

뼈대를 두면 동작을 다루기도 쉬워집니다. 뼈 구성이 같은 캐릭터끼리는 걷는 동작 하나를 함께 쓸 수 있습니다. 걷기와 뛰기를 반씩 섞거나, 발 뼈만 골라 땅 높이에 맞추는 일도 뼈 각도를 고치는 것으로 끝납니다.

부모와 자식으로 이어진 뼈대

뼈대는 뼈들이 부모와 자식으로 이어진 나무 모양입니다. 맨 위의 뼈를 뿌리 뼈(루트 본, root bone)라고 부릅니다. 사람 모형에서는 대개 골반이 뿌리가 됩니다. 골반 아래에 척추와 양쪽 허벅지가 매달립니다. 척추 아래에는 목과 양쪽 위팔이 매달립니다.

flowchart TD
    R["골반 · 뿌리 뼈"] --> SP["척추"]
    R --> LT["왼쪽 허벅지"]
    R --> RT["오른쪽 허벅지"]
    SP --> NK["목"]
    SP --> LS["왼쪽 위팔"]
    SP --> RS["오른쪽 위팔"]
    NK --> HD["머리"]
    LS --> LF["왼쪽 아래팔"] --> LH["왼쪽 손"]
    RS --> RF["오른쪽 아래팔"] --> RH["오른쪽 손"]
    LT --> LC["왼쪽 정강이"] --> LP["왼쪽 발"]
    RT --> RC["오른쪽 정강이"] --> RP["오른쪽 발"]

그림의 화살표는 「부모 → 자식」입니다. 부모 뼈가 돌면 그 아래 자식 뼈가 모두 따라 돕니다. 위팔만 돌려도 아래팔과 손이 함께 움직입니다. 골반을 옮기면 몸 전체가 옮겨집니다.

이렇게 되는 까닭은 뼈마다 자세를 부모 뼈 기준으로 적기 때문입니다. 아래팔 뼈에는 「위팔 끝에서 얼마나 굽었나」만 적습니다. 어떤 기준점과 축을 잡고 그 위에서 잰 위치를 그 기준의 로컬 좌표라고 부릅니다. 뼈마다 부모 뼈를 기준점으로 삼는 셈입니다.

뼈 하나가 갖는 값

뼈 하나의 자세는 세 가지로 적습니다. 부모에서 얼마나 떨어졌나(이동), 어느 방향을 향하나(회전), 얼마나 키웠나(스케일)입니다. 셋을 숫자 한 덩이로 합친 것이 변환 행렬입니다. 변환 행렬을 점에 곱하면 세 변환이 한 번에 적용됩니다.

회전은 흔히 쿼터니언으로 적습니다. 쿼터니언은 3차원 회전을 수 네 개로 나타내는 방법입니다. 축과 각도를 그대로 적는 방식보다 두 회전 사이를 매끄럽게 섞기 쉬워서 동작 데이터에 많이 씁니다.

모든 뼈의 자세를 한데 모은 것을 자세(pose)라고 부릅니다. 한 장면의 뼈대는 자세 하나로 정해집니다.

한 장면을 만드는 순서

장면 하나를 그릴 때마다 아래 순서가 한 번 돕니다. 동작 데이터에서 지금 시각의 뼈 자세를 꺼내고, 그 자세를 몸 전체 기준으로 바꾼 뒤, 겉면을 옮겨 그립니다.

동작 데이터는 몇몇 순간의 자세만 담습니다. 이 순간을 키프레임이라고 부릅니다. 그 사이 시각의 자세는 앞뒤 키프레임을 섞어 채웁니다. 아래 소절들은 이 그림을 위에서부터 따라갑니다.

flowchart TD
    A["동작 데이터 · 지금 시각"] --> B["뼈마다 앞뒤 키프레임을 비율대로 섞는다"]
    B --> C["뼈마다 부모 기준 자세"]
    C --> D["뿌리부터 부모 자세를 곱해 몸 전체 기준 자세"]
    D --> E["겉면 꼭짓점을 뼈대에 맞춰 옮긴다"]
    E --> F["화면에 그린다"]

키프레임과 보간

동작은 장면마다 다 적지 않고 몇몇 순간만 적습니다. 「0초에 팔꿈치 0도, 1초에 90도」처럼 시각과 자세를 짝지은 것이 키프레임입니다. 걷기나 손 흔들기처럼 이름 붙은 동작 하나를 클립이라고 합니다. 클립은 뼈마다 키프레임 목록을 갖습니다.

화면은 키프레임 사이 시각에도 그려야 합니다. 그 시각의 자세는 앞뒤 키프레임을 시간 비율대로 섞어 채웁니다. 이렇게 사이 값을 채우는 일을 보간이라고 부릅니다.

아래 코드는 팔꿈치 각도 키프레임 두 개에서 사이 시각의 각도를 구합니다. keys 는 (시각 초, 각도) 짝의 목록입니다. angle_at 은 주어진 시각 앞뒤의 키프레임을 찾아 시간 비율대로 섞습니다.

Python
keys = [(0.0, 0), (1.0, 90)]
angle_at(keys, 0.0)     # 0
angle_at(keys, 0.25)    # 22.5
angle_at(keys, 0.5)     # 45.0

0.25초는 0초와 1초 사이의 4분의 1 지점이라 각도도 90도의 4분의 1인 22.5도가 됩니다. 이렇게 시간에 비례해 섞는 방식이 선형 보간입니다. 위치와 크기도 이렇게 섞습니다.

팔꿈치처럼 축 하나로만 굽는 각도는 선형 보간으로 섞어도 됩니다. 축이 정해지지 않은 3차원 회전은 다릅니다. 회전은 쿼터니언끼리 섞습니다. 그런데 쿼터니언의 수 네 개를 선형 보간으로 섞으면 중간에서 회전 빠르기가 고르지 않습니다. 그래서 두 방향 사이를 일정한 빠르기로 돌아가게 섞는 구면 선형 보간을 따로 씁니다.

부모부터 내려가는 자세 계산

보간으로 얻은 자세는 뼈마다 부모 기준입니다. 겉면을 옮기려면 뼈마다 몸 전체 기준의 위치와 방향이 필요합니다.

이 값은 뿌리 뼈에서 출발해 차례로 구합니다. 먼저 부모의 몸 전체 기준 자세를 구합니다. 거기에 자식이 가진 부모 기준 자세를 곱하면 자식의 몸 전체 기준 자세가 나옵니다. 이 계산을 순운동학이라고 합니다.

변환을 이렇게 부모에서 자식으로 겹겹이 쌓는 방식은 계층 변환이라고 부릅니다. 이 방식에서는 자식을 계산하려면 부모의 결과가 먼저 있어야 합니다. 그래서 뼈 목록을 부모가 앞에 오도록 늘어놓고 앞에서부터 한 번 훑습니다. 뼈마다 곱셈 한 번이면 끝납니다.

아래는 평면 위의 팔 하나입니다. 어깨 관절이 원점에 있습니다. 위팔과 아래팔의 길이는 1 입니다.

s·e·h 는 위팔·아래팔·손 뼈의 자세입니다. 각 자세의 원점은 그 뼈가 시작하는 관절, 곧 어깨·팔꿈치·손목입니다.

rot(90) 은 90도 회전, move(1, 0) 은 뼈 방향으로 1 이동입니다. @ 는 행렬 곱입니다. where 는 자세의 원점이 놓인 위치를 꺼냅니다.

Python
s = rot(90)
e = s @ move(1, 0)
h = e @ move(1, 0)
where(e)            # (0, 1) 팔꿈치
where(h)            # (0, 2) 손목

어깨만 90도 돌렸는데 팔꿈치와 손목이 함께 위로 섰습니다. e 와 h 에는 「부모에서 1 떨어짐」만 적혀 있습니다. 회전은 부모에게서 물려받았습니다.

이번에는 팔꿈치도 90도 굽혀 봅니다. 아래팔 자세 e 의 줄 끝에 회전 하나를 더 곱합니다.

Python
e = s @ move(1, 0) @ rot(90)
h = e @ move(1, 0)
where(e)            # (0, 1) 팔꿈치
where(h)            # (-1, 1) 손목

팔꿈치 위치는 그대로 (0, 1) 입니다. 손목만 팔꿈치를 축으로 돌아 (-1, 1) 로 갔습니다. 아래팔 뼈 하나의 값만 바꿨습니다. 위팔 쪽은 손대지 않았습니다.

뼈대 자세를 겉면에 옮기기

한 장면 순서의 마지막 단계는 겉면의 꼭짓점을 뼈대 자세에 맞춰 옮기는 일입니다. 이 단계가 스키닝입니다. 꼭짓점마다 따라갈 뼈와 얼마나 따라갈지를 가중치로 미리 적어 둡니다.

꼭짓점 위치는 겉면을 뼈대에 처음 붙일 때의 값으로 저장돼 있습니다. 그때의 뼈대 자세를 바인드 포즈라고 합니다. 팔을 옆으로 벌린 T 자 모양을 흔히 씁니다.

그래서 장면마다 각 뼈가 바인드 포즈에서 얼마나 움직였는지를 구합니다. 꼭짓점은 자기 뼈들의 움직임을 가중치대로 섞어 새 위치로 갑니다. 팔꿈치 둘레 꼭짓점이 위팔과 아래팔에 반씩 끌려 중간쯤으로 접히는 것이 이 계산의 결과입니다.

이 계산은 꼭짓점마다 따로 돌아서 GPU(Graphics Processing Unit, 그래픽 처리 장치)에 맡기기 좋습니다. GPU 위에서 꼭짓점마다 한 번씩 도는 작은 프로그램이 정점 셰이더입니다. 스키닝은 대개 정점 셰이더 안에서 돕니다.

계산량

이 소절은 캐릭터 하나를 한 장면 그릴 때 드는 비용을 셉니다. 뼈 수를 B, 꼭짓점 수를 V, 꼭짓점 하나가 따라가는 뼈 수를 k 라고 두겠습니다.

단계 비례하는 값
키프레임 보간 B
부모부터 내려가는 자세 계산 B
스키닝 V × k

합치면 장면 하나에 O(B + V·k) 입니다. 보간과 자세 계산은 뼈 수만 따라가서 꼭짓점이 늘어도 그대로입니다. 꼭짓점이 뼈보다 훨씬 많으므로 비용은 대개 스키닝 쪽이 정합니다.

캐릭터가 많아지면 그 수만큼 곱해집니다. 화면에 백 명이 걸어 다니면 이 계산이 장면마다 백 번 돕니다. 멀리 있는 캐릭터를 뼈와 꼭짓점이 적은 모형으로 바꿔 그리는 레벨 오브 디테일이 이 비용을 줄이는 흔한 방법입니다.

자세를 얻는 여러 방법

키프레임 재생만이 뼈대 자세를 정하는 방법은 아닙니다. 뼈대가 자세를 숫자로 받기 때문에, 자세를 어디서 얻느냐를 바꿀 수 있습니다. 흔히 쓰는 것을 한데 모으면 이렇습니다.

방법 자세를 어디서 얻나
키프레임 재생 사람이 만들거나 녹화한 클립을 시각에 맞춰 보간한다
애니메이션 블렌딩 클립 둘 이상의 자세를 비율대로 섞는다. 걷기에서 뛰기로 넘어가는 동안 쓴다
역운동학 손이나 발이 닿을 목표 위치를 먼저 정하고, 거기 닿도록 위쪽 뼈들의 각도를 거꾸로 구한다
래그돌 뼈마다 물리 몸체를 달아 충돌과 중력으로 자세를 정한다. 쓰러지는 캐릭터에 쓴다
모션 캡처 사람 몸에 단 표지를 카메라로 따라가 그 움직임을 키프레임으로 옮긴다

이 방법들은 한 장면 안에서 겹쳐 쓰입니다. 걷기 클립을 재생하면서 발만 역운동학으로 계단 높이에 맞추는 식입니다. 어느 쪽이든 결과는 뼈마다 자세 하나입니다. 그다음 순서는 같습니다.

맞지 않는 움직임

스켈레탈 애니메이션은 관절로 꺾이는 움직임에 맞습니다. 사람·동물·로봇처럼 단단한 마디가 이어진 몸이 그렇습니다. 꼬리나 머리 다발처럼 마디를 길게 이어 붙일 수 있는 것도 뼈를 사슬로 넣어 움직입니다.

마디로 나눌 수 없는 움직임에는 다른 방식을 씁니다.

움직임 쓰는 방식
얼굴 표정 표정마다 겉면 모양을 여러 벌 만들어 두고 섞는 모프 타깃
펄럭이는 옷자락 물리 계산으로 꼭짓점을 직접 옮기는 옷감 시뮬레이션
문짝·자동차처럼 휘지 않는 물체 물체 전체에 변환 하나를 거는 것으로 충분하다

캐릭터 하나에 여러 방식을 같이 쓰기도 합니다. 몸은 뼈대로, 얼굴은 모프 타깃으로, 망토는 옷감 시뮬레이션으로 움직이는 식입니다.

대가

뼈대는 저절로 생기지 않습니다. 모형에 뼈대를 넣고 꼭짓점마다 따라갈 뼈를 정하는 작업을 리깅이라고 부릅니다. 이 작업은 사람이 손으로 합니다. 가중치를 붓으로 칠하듯 그려 넣는 웨이트 페인팅도 이 작업에 들어갑니다.

겉면이 틀어지는 일도 생깁니다. 꼭짓점을 뼈 여럿의 위치 사이에서 선형으로 섞기 때문에, 관절을 크게 굽히면 그 둘레가 안쪽으로 끌려 들어갑니다. 팔꿈치나 무릎이 쪼그라들어 보이는 부피 손실이 대표적입니다. 줄이는 방법은 스키닝 항목이 다룹니다.

관련 항목

스켈레탈 애니메이션이 속하는 상위 분류

컴퓨터 애니메이션 · 캐릭터 애니메이션 · 3D 그래픽스 · 그래픽스 · 게임 개발 · 실시간 렌더링

스켈레탈 애니메이션을 이루는 데이터

스켈레톤 · 조인트 · 뿌리 뼈 · 포즈 · 바인드 포즈 · 애니메이션 클립 · 키프레임 · 메시 · 정점 · 스키닝 가중치

한 장면의 자세가 거치는 처리 단계

보간 · 순운동학 · 계층 변환 · 스키닝 · 정점 셰이더 · 그래픽스 파이프라인

뼈의 자세를 적고 섞는 수학 도구

변환 행렬 · 행렬 곱 · 역행렬 · 동차 좌표 · 쿼터니언 · 선형 보간 · 구면 선형 보간 · 로컬 좌표계 · 좌표 변환

뼈대 자세를 정하는 기법

애니메이션 블렌딩 · 블렌드 트리 · 애니메이션 상태 머신 · 역운동학 · 래그돌 · 모션 캡처 · 루트 모션

뼈대 대신 모형을 움직이는 다른 방식

정점 애니메이션 · 정점 애니메이션 텍스처 · 모프 타깃 · 옷감 시뮬레이션 · 스프라이트 애니메이션

뼈대를 짜고 동작을 다듬는 작업

리깅 · 웨이트 페인팅 · 애니메이션 리타기팅 · 애니메이션 압축

뼈대를 따라 휜 겉면에서 나는 변형 결함과 보정

부피 손실 · 캔디 래퍼 현상 · 듀얼 쿼터니언 스키닝 · 트위스트 본 · 보정 셰이프

스켈레탈 애니메이션 비용을 줄이는 기법

레벨 오브 디테일 · GPU 스키닝 · 인스턴싱 · 컬링

뼈대와 동작을 담아 나르는 파일 포맷

glTF · FBX · COLLADA · USD · BVH

스켈레탈 애니메이션을 만들고 돌리는 도구와 엔진

Blender · Maya · Unity · Unreal Engine · 스키닝 메시 렌더러

다른 이름: skeletal animation · Skeletal Animation · 골격 애니메이션 · 뼈대 애니메이션 · 본 애니메이션 · bone animation