스켈레탈 애니메이션
- 게임에서 캐릭터 애니메이션은 가상의 뼈대인 본(Bone)을 캐릭터 메시에 심은 후, 해당 본의 움직임에 맞춰 캐릭터 메시가 변형되는 방식을 사용한다.
- 이러한 방식을 스켈레탈 애니메이션(Skeletal animation)이라고 부른다.
- 스켈레탈 애니메이션을 구현하려면 기존 메시 체계를 확장해 본을 추가해야한다.
- 본은 단순히 이동만 하는 것이 아니고 회전하거나 크기도 변경되므로 본의 정보는 트랜스폼에서 관리한다.
- 본을 구성하려면 3가지 정보가 필요하다.
- 본이 움직일 때 상대적인 변화량을 파악하는데 기준으로 사용하는 바인드보즈(Bindpose)정보
- 본의 트랜스폼 정보를 저장할 공간
- 본을 식별할 수 있는 고유한 이름 정보
- 배경으로 사용되는 애니메이션이 없는 고정된 메시는 본 정보가 필요하지 않다.
- 하지만 애니메이션이 필요한 캐릭터 메시에는 본 정보가 있어야한다.
- 본이 심어진 메시를 스킨드 메시(Skinned mesh)라고 부른다.
- 스킨드 메시를 구축한다면 아래와 같이 기존 메시를 확장해 본을 심을 수 있다.
- 메시에 본 정보를 추가한 후에는 본의 움직임에 따라 메시의 정점이 함께 움직이도록 본과 정점을 연결해야 한다.
- 이 작업을 리깅(Rigging)이라 부른다.
- 리깅은 뼈에 살을 붙이는 작업이라고 할 수 있다.
- 리깅 작업을 데이터 관점에서 살펴본다면 다음과 같다.
- 하나의 정점에 대해 어떤 본들이 영향을 미치는지를 기록하는 것
- 정점에 영향을 미치는 본이 많을수록 메시는 세심하게 변화할 것
- 하지만 메시에 영향을 미치는 본이 많아질수록 정점의 최종 위치를 구하는 계산량도 그만큼 늘어나게 된다.
- 리깅을 구현하기 위해서는 메시의 구조를 조금 확장해야 한다.
- 정점마다 몇 개의 본으로부터 영향을 받는지 개수를 지정해야 함
- 영향을 주는 본들은 서로 얼마나 영향력을 주는지 그 비율에 대한 별도의 정보가 필요함
- 한 정점에 영향을 주는 본들의 연결 정보를 웨이트(Weight)라 부른다.
- 리깅을 구현하기 위해 위 그림의 구조를 확장해 정점마다 연결된 본의 수와 연결된 본의 이름, 웨이트 정보를 추가한 내용은 아래 그림과 같다.
- 스킨드 메시 설정이 완료되면 스키닝 애니메이션의 진행을 위해 시간에 따라 본을 움직여줘야 한다.
- 본의 움직임을 구현하기 위해, 게임 엔진은 시간에 따라 값의 변화를 저장한 키프레임(Keyframe) 데이터를 사용한다.
- 키프레임은 점이 찍힌 주요 지점에서 값을 지정하면 나머지 중간 값은 관련 수식을 통해 보간하여 지정한 시간 동안 끊김 없는 데이터를 제공하도록 구성되어 있다.
- 캐릭터 애니메이션은 게임 오브젝트의 모든 상태가 최종 결정된 게임 로직이 완료된 시점에 진행되어야 한다.
- 숨쉬기와 걷기 두 가지 애니메이션이 설정된 케릭터가 있고, 정지된 상태에서 숨쉬기 애니메이션이 재생되고 있고, Y축 입력을 통해 캐릭터가 앞으로 이동하고 있는 상황을 가정
- Y축 입력을 처리하기 전에 애니메이션을 처리한다면, 현재 케릭터가 멈춘 것으로 판단해 애니메이션 시스템은 숨쉬기 애니메이션을 선택할 것
- 해당 프레임에서 캐릭터는 숨쉬는 모션을 재생하면서 앞으로 전진하게 된다.
트랜스폼 계층 구조
- 일반적으로 캐릭터를 구성하는 본은 모두 유기적으로 연결되어 있으며, 부모-자식 관계라고 불리는 계층 구조가 형성되어 있다.
- 손가락이 움직이지 않아도 팔목이 움직이면 손가락의 최종 위치가 달라지듯이 게임 캐릭터도 부모-자식의 계층 구조를 가져야 실제로 사람이 움직이는 것처럼 구현할 수 있다.
- 부모-자식 계층 관계에서 부모 트랜스폼과 자식 트랜스폼이 가져야 하는 규칙을 정리하면 다음과 같다.
- 부모가 움직이면 부모의 움직임만큼 자식도 움직인다.
- 자식이 움직여도 부모는 영향을 받지 않는다.
- 하나의 부모는 여러 자식을 가질 수 있다.
- 자식은 하나의 부모만 가질 수 있다.
- 계층 구조에서 부모가 없는 최상단 트랜스폼을 루트(Root) 트랜스폼이라고 한다.
- 트랜스폼에서 부모-자식 관계가 형성되면 자식 트랜스폼 데이터는 부모로부터 상대적인 트랜스폼과 자식이 속한 공간에 대한 트랜스폼, 두 가지로 분리해 관리해야 한다.
- 부모로부터 상대적인 트랜스폼을 로컬 트랜스폼(Local transform)이라 부른다.
- 계층 구조와 상관없이 자신이 속한 공간에서의 트랜스폼을 월드 트랜스폼(World transform)이라 부른다.
- 만약 자신이 루트 트랜스폼이면, 로컬 트랜스폼과 월드 트랜스폼의 값은 같다.
- 월드 트랜스폼은 절대 트랜스폼(Absolute transform), 로컬 트랜스폼은 상대 트랜스폼(Relative transform)이라는 용여로도 사용된다.
- 트랜스폼을 두 가지로 분리하는 이유
- 계층 구조가 변동되는 다양한 상황에 효과적으로 대처할 수 있기 때문
- 부모로부터 상대적인 정보를 기록한 로컬 트랜스폼은 계층 구조를 다룰 때 유용하게 사용된다.
- 로컬 트랜스폼의 정보를 보관하지 않고 월드 트랜스폼 정보만 보관한다면, 트랜스폼의 변동이 있을 때 마다 부모와 나의 월드 트랜스폼을 비교해 로컬 트랜스폼을 매번 계산해줘야한다.
트랜스폼 계층 구조의 변환
- 월드 트랜스폼과 로컬 트랜스폼 값이 일관되게 하려면 어떤 점들이 고려햐여 하는지 부모와 자식이 가진 트랜스폼을 중심으로 사례를 정리했다.
- 월드 트랜스폼과 로컬 트랜스폼을 동시에 다루는 경우 고려해야 할 다양한 사례를 확인할 수 있다.
- 계층 구조에 관한 문제를 구현하기 위해서는 다음과 같은 두 가지 기능이 필요함을 알 수 있다.
- 부모의 월드 트랜스폼 정보와 나의 로컬 트랜스폼 정보를 사용해 나의 월드 트랜스폼을 계산하는 기능
- 부모의 월드 트랜스폼 정보와 나의 월드 트랜스폼 정보를 비교해 부모로부터 상대적인 나의 로컬 트랜스폼을 계산하는 기능
사레 1 : 부모의 월드 트랜스폼이 변경된 경우
- 부모의 월드 트랜스폼이 변경되는 경우 나의 로컬 트랜스폼은 그대로 유지되지만 나의 월드 트랜스폼은 변경된다.
- 변경된 나의 월드 트랜스폼은 부모의 변경된 월드 트랜스폼 정보와 나의 로컬 트랜스폼을 사용해 계산할 수 있다.
- 이는 나의 자식에게도 동일한 상황이 되어 나와 자식과의 로컬 트랜스폼은 그대로 유지되지만 모든 자식들의 월드 트랜스폼은 변경되어야 한다.
- 위 상황은 다음 그림에 정리되어 있다.
- 오른쪽 그림의 주황색 선은 변경된 트랜스폼을 의미하며, 트랜스폼의 변경 순서는 숫자로 나타냈다.
사례 2 : 나의 로컬 트랜스폼이 변경된 경우
- 부모는 가만히 있는데 나의 로컬 트랜스폼이 변경되는 경우, 나의 월드 트랜스폼도 변경된 로컬 트랜스폼에 맞춰서 변경해야 한다.
- 이 경우 사례 1과 동일하게 부모의 월드 트랜스폼 정보와 변경된 나의 로컬 트랜스폼을 사용해 나의 월드 트랜스폼을 다시 계산해야 한다.
- 나의 월드 트랜스폼이 변경되면 사례 1과 동일하게 자식에게도 영향을 미친다.
- 따라서 자식의 로컬 트랜스폼은 변화가 없지만, 모든 자식의 월드 트랜스폼은 변경되어야 한다.
사례 3 : 나의 월드 트랜스폼이 변경된 경우
- 사례 3은 사례 2와 유사하다.
- 부모는 가만히 있는데 나의 월드 트랜스폼이 변경되는 경우 로컬 트랜스폼도 변경된 월드 트랜스폼에 따라 변경해야 한다.
- 그런데 이때는 부모의 월드 트랜스폼과 변경된 나의 월드 트랜스폼을 비고ㅛ해 부모로부터 상대적인 로컬 트랜스폼을 계산해야 한다.
- 나의 월드 트랜스폼이 변경됐으니 사례 1과 동일하게 자식의 월드 트랜스폼을 변경한다.
- 다음 그림은 사례 2의 그림과 구성은 동일하지만 계산 순서가 다르다.
사례 4 : 기존에 설정된 부모를 버리고 새로운 부모로 편입하는 경우
- 이 경우는 두 단계로 나눠 살펴봐야 한다.
- 첫 번째 단계에서는 나를 부모로부터 독립시키고 최상단의 루트 트랜스폼으로 만들어준다.
- 내 트랜스폼이 루트 트랜스폼이 되면 월드 트랜스폼괴 로컬 트랜스폼은 같은 값을 가지므로 나의 월드 트랜스폼 데이터를 로컬 트랜스폼에 덮어쓰는 것으로 해결할 수 있다.
- 나의 월드 트랜스폼은 변경된 적이 없으므로 자식의 트랜스폼을 변경할 필요는 없다.
- 첫 번째 단계는 다음 그림으로 정리할 수 있다.
- 두 번째 단계에서는 다를 새로운 부모의 자식으로 등록한 후, 새로운 부모와 나 사이의 로컬 트랜스폼을 계산한다.
- 이때 새로운 부모의 월드 트랜스폼과 나의 월드 트랜스폼을 비교해 새로운 부모로부터 상대적인 로컬 트랜스폼을 계산해야 한다.
- 이 단계에서도 나의 월드 트랜스폼은 변한 적이 없으므로 자식의 트랜스폼을 변경할 필요는 없다.
- 두 번째 단계를 정리하면 다음 그림과 같다.
로컬 트랜스폼으로부터 월드 트랜스폼의 계산
- 나의 월드 트랜스폼의 크기는 부모의 월드 트랜스폼의 크기와 나의 로컬 트랜스폼의 크기를 곱한 값이 된다.
- 부모의 월드 크기가 20이고, 내 로컬 크기가 0.5라면 나의 월드 크기는 10이 될 것이다.
- 크기를 구성하는 세 축은 모두 직교하므로 각 축의 크기는 독립적으로 작용된다.
- 부모의 월드 크기를 벡터 s = (s_x, s_y, s_z)로 지정하고 나의 로컬 크기를 벡터 s' = (s'_x, s'_y, s'_z)로 지정한다면 나의 월드 크기 s'_world = (s_x·s'_x, s_y·s'_y, s_z·s'_z)가 된다.
두 벡터 v_1 = (x_1, y_1, z_1)과 v_2 = (x_2, y_2, z_2)의 곱셈을 다음과 같이 정의하면
v_1 * v_2 = (x_1, y_1, z_1)*(x_2, y_2, z_2) = (x_1·x_2, y_1·y_2, z_1·z_2)
나의 월드 크기는 다음 수식으로 정리할 수 있다.
s'_world = s*s'
- 부모의 월드 회전을 사원수 q가 담당하고 나의 로컬 회전을 사원수 q'가 담당한다면, 나의 월드 회전은 다음과 같이 두 사원수의 곱으로 계산된다.
q'_world = q'·q
- 부모 월드 트랜스폼의 세 로컬 축을 각각 x = (x_x, x_y, x_z), y = (y_x, y_y, y_z), z = (z_x, z_y, z_z)로 설정하고 이동할 값을 벡터 t = (t_x, t_y, t_z)로 설정하면 부모 월드 트랜스폼으로부터 모델링 행렬 M_parent는 다음과 같이 생성된다.
┌ x_x·s_x y_x·s_y z_x·s_z t_x ┐
M_parent = | x_y·s_x y_y·s_y z_y·s_z t_y |
| x_z·s_x y_z·s_y z_z·s_z t_z |
└ 0 0 0 1 ┘
- 내 로컬 트랜스폼의 세 로컬축을 각각 x', y', z'으로 설정하고 이동할 값을 벡터 t' = (t'_x, t'_y, t'_z)로 설정하면 내 로컬 트랜스폼의 모델링 행렬 M_local은 다음과 같이 얻을 수 있다.
┌ x'_x·s'_x y'_x·s'_y z'_x·s'_z t'_x ┐
M_local = | x'_y·s'_x y'_y·s'_y z'_y·s'_z t'_y |
| x'_z·s'_x y'_z·s'_y z'_z·s'_z t'_z |
└ 0 0 0 1 ┘
- 나의 월드 트랜스폼의 행렬 M_world는 다음과 같은 행렬 곱으로 계산된다.
M_world = M_parent · M_local
┌ x_x·s_x y_x·s_y z_x·s_z t_x ┐ ┌ x'_x·s'_x y'_x·s'_y z'_x·s'_z t'_x ┐
M_world = | x_y·s_x y_y·s_y z_y·s_z t_y |·| x'_y·s'_x y'_y·s'_y z'_y·s'_z t'_y |
| x_z·s_x y_z·s_y z_z·s_z t_z | | x'_z·s'_x y'_z·s'_y z'_z·s'_z t'_z |
└ 0 0 0 1 ┘ └ 0 0 0 1 ┘
- 위에서 구한 M_world의 마지막 4열 값이 월드 트랜스폼의 이동 값이 될 것이다.
- 따라서 모두 다 곱하지 않고 4열에 대한 값만 내적과 벡터의 곱셈을 통해 정리하면 이동 값은 다음 식과 같다.
t'_world = ((x_x, y_x, z_x)·(t'*s), (x_y, y_y, z_y)·(t'*s), (x_z, y_z, z_z)·(t'*s))+t
┌ x_x y_x z_x ┐ ┌ t'_xs_x ┐
= | x_y y_y z_y | | t'_ys_y | + t
└ x_z y_z z_z ┘ └ t'_zs_z ┘
= (q·(t'*s)) + t
월드 트랜스폼으로부터 로컬 트랜스폼의 계산
- 부모의 월드 트랜스폼 정보와 나의 월드 트랜스폼 정보를 비교해 나의 로컬 트랜스폼을 계산하기 위해서는 부모의 월드 트랜스폼과 나의 월드 트랜스폼의 차이를 계산해야 한다.
- 이를 위해서는 월드 공간을 카메라 중심의 뷰 공간으로 바꾼 행렬을 이용해야 한다.
- 만일 부모의 월드 트랜스폼을 중심으로 나의 월드 트랜스폼을 해석할 수 있다면 그 값이 나의 로컬 트랜스폼이 될 것이다.
- 이를 위해서는 카메라 트랜스폼에 역변환을 적용한 것 처럼 부모의 월드 트랜스폼에 역변환을 반영하면 된다.
- 크기의 역변환은 크기의 역수로 계산된다.
크기 벡터 s = (s_x, s_y, s_z)를 역변환하면
s^-1 = (1/s_x, 1/s_y, 1/s_z)
- 크기를 배제한 트랜스폼 회전을 q로 지정하면 회전의 역변환은 켤레 사원수 q^*가 된다.
- 마지막으로 이동 값 t^-1을 구하기 위해선 트랜스폼을 담당하는 모델링 행렬의 역행렬을 계산해야한다.
- 모델링 행렬의 M^-1은 각 행렬의 역행렬을 반대 순서로 곱한 값이 된다.
M^-1 = (TRS)^-1 = S^-1 R^-1 T^-1
┌ 1/s_x 0 0 0 ┐
S^-1 = | 0 1/s_y 0 0 |
| 0 0 1/s_z 0 |
└ 0 0 0 1 ┘
┌ x_x x_y x_z 0 ┐
R^-1 = | y_x y_y y_z 0 |
| z_x z_y z_z 0 |
└ 0 0 0 1 ┘
┌ 1 0 0 -t_x ┐
T^-1 = | 0 1 0 -t_y |
| 0 0 1 -t_z |
└ 0 0 0 1 ┘
┌ x_x/s_x x_y/s_x x_z/s_z (-x·t)/s_x ┐
M^-1 = | y_x/s_y y_y/s_y y_z/s_z (-y·t)/s_y |
| z_x/s_z z_y/s_y z_z/s_z (-z·t)/s_z |
└ 0 0 0 1 ┘
- 모델링 행렬의 역행렬 M^-1에서 위치에 대한 4열의 값 t^-1은 다음과 같이 계산된다.
t^-1 = (x·(-t/s_x), y·(-t/s_y), z·(-t/s_z))
= (x·(-t), y·(-t), z·(-t))*(s^-1)
┌ x_x x_y x_z ┐ ┌ -t_x ┐
= | y_x y_y y_z |·| -t_y | * (s^-1)
└ z_x z_y z_z ┘ └ -t_z ┘
= (q^*·(-t))*(s^-1)
- 역변환 기능을 사용해 부모의 월드 트랜스폼의 역변환을 구한 후에 나의 월드 트랜스폼을 곱하면 나의 로컬 트랜스폼이 만들어진다.
M_local = M^-1_parent · M_world
- 부모 트랜스폼이 역변환된 행렬도 트랜스폼의 일종이므로 로컬 트랜스폼에서 월드 트랜스폼으로 변환과 동일하게 계산하면 된다.
- 부모의 역변환된 트랜스폼을 구성하는 요소는 다음과 같다.
- 크기 S^-1 = (1/s_x, 1/s_y, 1/s_z)
- 회전 = q^*
- 이동 = t^-1
- 부모의 월드 트랜스폼을 기준으로 상대적인 나의 로컬 트랜스폼을 구하는 수식은 다음과 같다.
나의 월드 트랜스폼의 크기를 s' = (s'_x, s'_y, s'_z)이면 나의 로컬 트랜스폼의 크기는
s'_local = s^-1 * s' = (s'_x/s_x, s'_y/s_y, s'_z/s_z)
나의 로컬 트랜스폼의 회전 q'_local은
q'_local = q^* · q'
나의 로컬 트랜스폼의 위치는
t'_local = q^* · (t'*s^-1) + (t^-1)
캐릭터 메시와 애니메이션
- 트랜스폼의 계층 구조를 이루는 캐릭터를 제작하기 위해선 캐릭터 메시를 생성하고, 계층 구조로 본을 생성한 후에 리깅을 진행해야 한다.
- 캐릭터를 구성하는 본의 계층 구조는 흔히 캐릭터의 시작 위치를 나타내는 루트(Root)에서 출발해 골반(Pelvis)으로 이동한 뒤 여기에서 부터 척추(Spine), 팔(Arm), 다리(Leg), 목(Neck)으로 퍼져나가는 구조를 띈다.
- 케릭터를 구성하기 위해서는 본도 계층 구조를 지원해야 한다.
- 계층 구조로 본을 설정한 후에는 각 본과 정점을 연결하는 리깅 작업을 진행해 캐릭터 애니메이션을 진행할 스킨드 메시를 완성시킨다.
출처
https://m.yes24.com/Goods/Detail/107025224
이득우의 게임 수학 - 예스24
39가지 실시간 렌더링 게임 프로그래밍 실습 예제를 하나씩 따라 해보며 독자가 직접 체득하는 흥미로운 게임 수학의 세계! 게임 개발자와 그래픽 아티스트들이 궁금해 했던 3D 가상 세계와 메타
m.yes24.com
'게임수학' 카테고리의 다른 글
[게임수학] 사원수(Quaternion) (0) | 2024.05.06 |
---|---|
[게임수학] 극한과 오일러 공식 (1) | 2024.05.03 |
[게임수학] 복소수 (1) | 2024.05.03 |
[게임수학] 모델링과 뷰 (0) | 2024.04.28 |
[게임수학] 절두체(Frustum) (0) | 2024.04.25 |