컴퓨터 그래픽스에서의 Skeletal Animation / Rigging 을 간단히 알아보자.
우리가 신체를 움직일 때 뼈와 관절이 주가 되듯이, Skeletal animation에서 사용되는 skeleton 또한 또한 여러 개의 bone과 joint로 구성된 hierarchy를 갖는다.
상하 관계가 존재하는 hierarchy 형태를 갖기 때문에 상위의 컴포넌트가 회전하면 그 하위의 컴포넌트들 또한 회전한다.
이 같은 종속 관계의 적용은 회전 이외에도 이동, 확대 등의 움직임에도 이론적으로는 동일하게 적용될 수 있지만, 이는 자주 사용되지는 않는다.
애니메이션이란, 모델의 움직임을 아주 짧은 시간 단위마다 포즈(pose)로 쪼개 이를 연속적으로 이어붙인 것이다. 어떤 하나의 포즈와 다른 포즈 사이를 interpolate하는 것으로 더 부드러운 움직임을 만들 수도 있지만, 이는 본 포스트에서는 다루지 않는다.
Skeletal Animation이란, 어떤 모델에 이러한 애니메이션을 입히기 위해 사용하는 기법 중 하나로, 모델의 폴리곤들을 Skeleton의 움직임에 맞게 따라 움직이게 함으로써 생물체의 실제 움직임을 비슷하게 흉내낸다.
폴리곤과 Skeleton을 어떻게 연동시킬 수 있을까?
자주 사용되는 방법 중 하나는 바로 Weight painting이다.
Weight painting에서는 모델의 모든 정점에 weight를 부여한다. weight는 어떤 bone이 해당 vertex에 얼마만큼의 영향력을 가지고 있는지를 나타낸다. (일반적으로 한 정점에 영향을 주는 bone의 갯수는 최대 4개이다)
위 사진에서는 hips bone의 weight를 heatmap 형식으로 나타내었다. hips bone인 만큼 골반 주변의 vertex들의 weight가 높게 설정되어있음을 볼 수 있다.
읽기 전에 꼭 이 포스트의 내용을 숙지하자.
skeletal animation 구현의 수학적 원리를 대략적으로 살펴보기 전에, 몇 가지 용어를 짚고 넘어가자.
skeleton의 모든 뼈들은 벡터로 나타낼 수 있는데, (이때 벡터의 길이는 중요하지 않다, normal vector로 표현한다)
이러한 벡터들의 시작점(=위치)들은 전부 이 모델의 기준점(model's origin)에 대해 상대적인 transformation(translation, rotation, scailing)으로 나타낼 수 있으며 이를 model matrix라고 부른다. (defines transformation of a bone relative to model's origin)
Inverse model matrix는 이를 invert한 행렬을 말한다.
벡터들의 시작점들을 모델의 기준점 말고 부모 bone에 대해 상대적인 transformation으로 나타낼 수 있으며, 이는 local matrix라고 부른다.
이 local matrix를 이용해 model matrix를 구할 수 있는데, 그 방법은 부모의 local matrix를 root에 도달할 때까지 반복해서 재귀적으로 곱하는 것이다. 위 사진에서 Shoulder L의 Model Matrix는 Root의 local matrix * Hips의 local matrix * ... Shoulder L의 local matrix인 것을 알 수 있다.
우리가 애니메이션을 나타내려면 결국 어떤 bone이 그 이전 상태에서 얼마나 어떻게 움직였는지를 표현할 방법이 있어야 한다. 즉 노란 화살표(행렬)를 나타낼 방법이 있어야 한다.
노란 화살표는 두 개의 다른 행렬(빨강, 파랑 화살표)을 이용해 구할 수 있다.
파랑 화살표는 애니메이션 처리 후의 오른손의 Model matrix이고,
빨강 화살표는 애니메이션 처리 전의 오른손의 Inverted model matrix 이다.
애니메이션 처리 후의 오른손의 Model matrix(파랑 화살표)는
애니메이션 처리 이후의 오른손의 parent의 Model matrix (=AnimM parent)
x 오른손의 Local matrix (=L hand)
x 오른손의 Animation matrix (애니메이션의 매 프레임마다 변하는 값, 구체적으로 어떻게 도출되는지는 잘 모르겠다. 본 포스트에서는 blender 등의 외부 소프트웨어에서 바로 이 행렬값을 제공해주기 때문에 이 값을 가져와 사용한다고 가정한다.)
를 통해 구한다.
이걸 구하고 나면 노란 화살표(=Final)를 구할 수 있고, 이 값을 셰이더로 보내 애니메이션을 처리할 수 있다.
그렇다면 이러한 정보들을 메모리 상에 어떤 형태로 저장할까?
Bone의 경우 인덱스를 매긴 후, Bone 각각 개별적으로 자신의 부모 인덱스와 Local matrix, inverse model matrix를 저장한다.
Vertex들의 경우 해당 vertex에 영향을 주는 4개의 bone의 index들과 각각의 weights들을 저장한다. 이 때 주의해야 할 점은 bone weights들의 합이 1이어야 한다는 점이다.
또 한 vertex에 영향을 주는 bone의 갯수는 꼭 4개일 필요는 없으나, 통상적으로 4개 가량 사용한다.
코드와 함께 살펴보자.
inMatrices는 매 프레임 변하는 Animation matrix이며, outMatrices는 최종적으로 우리가 셰이더에 전달할 matrix를 의미한다.
모든 bones들에 대해 local, model transfrom들을 저장하는 vector 두개를 만든다.
그 후 for loop을 돌며 animation matrix를 곱해 각 bone들별로 애니메이션 처리 이후의 local matrix들을 localTransform에 저장한다.
그 후 modelTransform[0] = localTransform[0]으로 지정하는데, 이는 모델의 루트는 그 위에 부모가 없으므로 local이 곧 model이기 때문이다.
그 후 for loop을 돌며 각 bone들의 modelTransform에 그 부모의 localTransform을 곱해준다. 이 과정이 앞서 말한 재귀적으로 곱하는 과정인데, 여기서 보면 부모 인덱스는 항상 자식 인덱스보다 앞에 있으므로 부모의 localTransform은 언제나 이미 초기화된 상태이므로 굳이 재귀를 사용해 모든 local matrix들을 곱해주지 않아도 바로 localTransform[부모인덱스]를 곱해주기만 해도 되는 것을 볼 수 있다. (Dynamic Programming)
그 후 앞서 말한 공식대로 modelTransform[i] (=i번 bone의 model matrix)과 invModelMatrix를 곱해주어 outMatrices를 반환한다.
bone들의 움직임에 의해 vertex들의 변한 위치를 정하는 과정을 skinning이라고 하는데,
이 과정을 코드로 나타내면 위와 같다.
3x4 행렬 m을 정의한 후 이 m에 총 4개의, vertex의 위치에 영향을 주는 bone들에 대해서
(bone의 위치) * (weight)를 더해주고,
이렇게 구한 행렬 m을 vertex의 위치(P)와 normal (N)에 곱해주어 vertex를 이동 및 회전시킬 수 있다.
참고:
'computer graphics > graphics theory' 카테고리의 다른 글
[그래픽스] Frustum Culling (0) | 2022.04.03 |
---|