센로그

13. Character Animation 본문

게임/게임 그래픽 프로그래밍

13. Character Animation

seeyoun 2023. 5. 31. 18:03

◆ Skeleton

bone들이 개별적으로 움직이는 걸 관절체(articulated body)라고 함.

관절체를 중심으로 움직이는 구조를 skeleton이라고 함. 

 

캐릭터에 애니메이션을 적용하기 위해서, 보통 skeleton 구조를 사용한다. (skeletal animation)

mesh가 skeleton 뼈대를 기준으로, 뼈대와 '함께' 움직이도록 구현하는 것임.

 

■ Rigging

(a)의 경우 default pose의 mesh이고,

(b)는 3ds Max에서 제공하는, biped라고 하는 기본 skeleton이다.

이 둘이 안 맞으니까, 잘 조정해서 skeleton과 mesh를 맞춰줘야 할 것이다! 이 과정을 리깅(Rigging)이라고 함.

(c) skeleton이 너무 복잡하면 수정을 통해 조정해준다.

(d) mesh에다 skeleton을 끼워넣어준다.(embed)

(e) scale이나, pose 등을 맞춰준다.

(e) 최종적으로 default pose에 맞도록 만듦!

 

그 다음에 이제 polygon mesh의 vertex들이 어느 뼈의 움직임에 영향을 받는지 지정해줘야 함.

(각각의 뼈가 독립적으로 움직일 수 있는, "관절체"이기 때문)

 


Skeleton Hierachy

Skeleton에서, bone들은 부모-자식 관계의 hierachy structure(계층 구조)로 정의됨.

보통 pelvis가 root 노드로 정의됨. (root를 기준으로 bone들이 정의됨.)

가장 끝에 있어서 더 이상 child가 없는 node들을 end-effector라고 함. (ex: 손끝, 발끝)

Spine처럼 여러개의 child를 가진 친구들을 sub root라고 부름

 

모든 노드들은 최대 1개의 parent를 가질 수 있음. 그러나 child 수는 제한 없음.

Parent의 움직임은 child의 움직임에 영향을 줌ㅇㅇ. 반대는 아님.

End effector의 움직임은 걔만 움직임(child가 없으니까)

 


 Skeletal Animation

skeletal animation이 왜 어려울까?

  • 모든 bone은 자신만의 local 움직임을 가지고있고, 보통 rotation으로 한정됨.(상식적으로 scaling, translation은 안함)
    bone마다 가동 범위의 제한이 있고, rotation만으로 이동 표현이 가능하다는 것.
    손 앞의 관절들을 조금씩 돌리는 것으로 손을 움직이는 걸 표현할 수 있다는 것임.
  • bone의 움직임은 그 bone의 모든 자손들에게 영향을 미침.

 


 Space Change between Bones

ㆍbone들은 joint 연결되어있는 형태이다.

오른쪽 그림에서, 노란 동그라미들이 다 joint. 연결된 파란막대기들이 bone.

 

만약 왼쪽 그림에서, 내가 foream(아래 팔)을 움직인다 하자. 그럼 여기 붙어있는 vf가 같이 움직여야 할 것임.

이를 구현하기 위해서, vfforearmobject space에 있다고 가정해버리면 쉬움.

 

처음에는, 모든 vertex들의 기본 spacecharacterobject space에서 적용이 됨.

이걸 Character space라 부른다.(보통 rootobject space에 해당함)

또, bone들은 개별적으로 움직이기 때문에 bone 자체의 Object space도 존재한다. 
bone
각각을 정의하기 위한 spaceBone space라고 함.

 

Skeleton은 bone별로 움직이기 때문에, Character sapce에서 정의된 모든 vertexbone space로 옮겨줘야 할 필요가 있다. 예시에서 vf는 forearm의 bone space로 옮겨져야 할 것이고, 오른쪽 그림에서 forearm의 (2,0)으로 들어온 것을 볼 수 있다.

 


 Space Change between Bones - Bone-to-Character transform

우리가 만약 Bone space => Character space로 보내는 변환을 알고 있다면,

Character space => Bone space로의 변환은 그 inverse로 쉽게 구할 수 있음!

 

따라서 우선 각 정점이 bone space에서 정의되어 있다고 가정하고, 이를 character space로 옮기는 변환을 해보겟음

(Bone space 좌표는 알고있다는 가정하에 진행함.)

 

■ forearm의 parent의 공간으로 옮기는 변환 행렬 M_f,p를 생각해보자.

forearm의 parent는 upper arm이다. forearm에서 정의된 vf를 upper arm으로 옮겨보자.

vf는 foream에서는 (2, 0)이지만, upper arm 기준으로는 (6, 0)이다. (그림 참조)

이제 부모인 upper arm으로 가는 to-parent transform을 만들어보자!

 

이전에 space change를 해왔던 것처럼, upper arm 공간의 기저를 foream 공간 기저로 일치시키는 변환을 만들어야 함.

위 예시에서는 방향은 이미 같으니까 그냥 translation 해주면 됨. 아래와 같이 (4, 0)만큼 옮겨주자!

(Open GL 기준. 벡터의 왼쪽에 Matrix 곱한것ㅇㅇ)

 

 

 이번에는 또다른 예시로, hand 위에 있는 vh를 부모인 foream space로 옮겨보자.

같은 방식으로 옮겨주면, (3, 0)만큼 translation 해주면 된다.

 

이렇게 forearm space까지 온 vh`을 다시 upper arm으로 보낼 수도 있음. 이때 아까 구해놓은 M_f,p를 그대로 사용하면 됨.

짠~ 잘 나옴.

 


Character Space to Bone Space

정점을 i번째 boneparent space로 옮기는 MatrixM_i,p라 하자.

1번째 bone인 pelvis에서 character space(default pose)로 가는 M_1,d는 root에서의 변환을 의미하므로 I라 할 수 있음.

2번째 bone인 spine에서 가려면, 일단 부모 bone으로 보내보자. 부모 bone인 pelvis는 character space로 어떻게 가는지 이미 알고있음! 따라서 spine에서 character space로 가는 M_2,d = (M_1,d)(M_2,p)라고 할 수 있음.

...

이런식으로 계속 to-parent matrix곱해주다보면, 결국 Character space올라올 것!

 

default pose가 정해지면, 이렇게 부모로 가는 변환은 자동으로 딱 정의가 가능한 것임. 

character space에서의 좌표는 다 나오니깐.

 


 Space Change between Bones - Character-to-Bone transform

그러면 Character to Bone 변환은? 앞에서 구한 Bone to Character 변환의 Inverse를 하면 됨!

 

아까는 Bone space로부터 Character space를 구했었다. 

 

이제 이거의 역행렬을 구해보자.

이런식으로 구하면 각자의 Bone space로 옮길 수 있다~ 라는 결론!

 


◆ Forward Kinematics

지금까지는 default pose에서만 얘기했었다.

그러면 이제, bone이 움직이는 경우를 생각해보자.

 

i번째 bone이 animate되면, 그에 해당하는 vertex들도 따라 움직일 것이다.

 

bone space에서 봤을 땐 같은 위치이겠지만, world space에서는 아닐 것.

 

근데, 이게 실제로 움직인 걸 보여주려면 렌더링 파이프라인을 거쳐야 한다.

따라서, 얘네들이 각각의 bone space에 가서 animation 적용한 후에, 다시 character space (object space)로 옮겨와야한다. 그래야 world transform 같은걸 수행해줄 수 있음!

 

그림에서 5번 공간의 vertex인 v5를 animation 시킨 다음에, 다시 character space로 보내는 변환을 M_5,a라 하자.

M_5,a는 어떻게 구성되어야 할지를 구해보겠음.

 

우선 그림에서 forearm은 자신의 local joint인 elbow를 기준으로만 움직였다. 이 변환을 M_5,l이라 하겠다. (l은 local joint를 의미). M_5,l은 90도 돌린 것이니 다음과 같이 표현한다.

 

이 변환을 forearm 공간으로 변환되어있는 v5에도 적용해야 한다! 

해보자.

 

forearm 기준 (2, 0)이었던 v5가 회전되고 나서 (0, 2)가 된 것을 볼 수 있음.

이제 회전된 후의 v5`를 다시 character space로 보내줘야 함!

 

이때 이 점 (0, 2)는, 회전되기 전의 forearm 공간에서 정의된 점이었다.

이말은 즉슨, 회전되기 전의 forearm과 upper arm(forearm의 부모)간의 변환 방법을 알면, (0, 2)에 그대로 그 변환을 적용해주면 된다는 뜻이다. 따라서, (0, 2)에 아까전에 했던 forearm의 to-parent transform인 M_5,p를 적용해주면 된다.

실제로 그림상에서 보면 upper arm 기준 (4, 2) 위치에 있는 걸 확인할 수 있음~ 

 

 

이 결과가 v5를 animate하고 to parent 변환하도록 계산한 것과도 같은것 확인~

 

이제 우리는 animated된 v5의 upper arm space에서의 좌표를 얻은 것이다.

 

우리 목표는 M_5,a를 구하는 것 이었다는 걸 상기하고 가자.

 

근데 이제 upper arm도 animate 될 수 있을 것.

M_4,a (4번 공간의 점을 animation 시킨다음에 character space로 보내는 것)를 알 수 있다면, upper arm 공간으로 옮긴 v5를 character space로 보낼 수도 있을 것이다. 즉, 다음과 같이 표현할 수 있다는 것.

이식을 자세히 보자. M_5,l은 animation 정보가 주어지면 당연히 알 수 있는 것.

M_5,p는 부모로의 transform인데 구할 수 있다는 걸 증명했음.

이제 M_4,a만 알면 된다. M_4,a를 알려면, M_3,a를 알아야하고, ... , (root에서 charcter로의 변환인) M_1,a를 알면 된다.

그런데 M_1,a는 I라고 두면 됨. 따로 정의할 필요 없이, 결국 animated된 애들의 character space로 모은 다음에, world transform 시켜주면 되는 것임. 결론은, 거슬러 올라가면 결국 M_i,a를 알 수 있다는 것이다.

 

따라서 다음과 같이 일반화해서 쓸 수 있다. 

굿.

이렇게 모든 animated된 vertex들을 character space로 모을 수 있다는 걸 보인 것임.

 

 

ex)

default pose에서 정의된 vertex v의 경우, 위와 같은 변환을 통해 animated pose의 v`로 변환시킬 수 있다는 뜻.

inverse M_i,d를 통해 bone space로 보낸 후, M_i,a를 통해 animate하고 다시 character space로 갖고오는 것.

(Open GL 기준이라 벡터 왼쪽에 변환 곱함)

 


Skinning

Skin 이란?

Character animation에서, skeletal 움직임에 따라 움직이는 polygon mesh를 skin이라 부름.

 

■ Skin deformation

upper arm에 속한 vertex a와, forearm에 속한 vertex b, c를 보자.

그림과 같이 forearm이 굽었을 때, 변형되는 vertex는 b, c밖에 없음.

그런데 사실, 현실에서는 이런 변형이 일어날 때 upper arm에 속한 a도 어느정도 영향을 받을 것이다.

이걸 고려하지 않았기 때문에 오른쪽 그림처럼 딱딱하게 변형된 것임.

 

따라서 한 vertex가 자신이 속한 bone 이외에, multiple bone들에 대해 어느정도 영향을 받도록 한다면 문제가 개선될 수 있을 것이다.

vertex별 upper arm과 forearm의 weight를 정해서 계산하니, 비교적 부드럽게 보이는 걸 확인할 수 있다. 보간의 느낌.

각 vertex가 bone에 얼마나 속해있는지에 대한 비율을 참조해서 계산하는 거라고 할 수 있다.

 

간단하다. bone->character 변환시 아까처럼 변환하되, 그냥 weight를 곱해주면 됨~

간단하게 표현하기 위해 "character space의 v를 i번째 bone space로 보내서 애니메이션 적용시킨 후(M_i,a), 다시 character space로 돌려놓는(inverse M_i,d) 변환 행렬" M_i라 하겠다. 그럼 이렇게 표현 가능함.

 

이 weight를 blend weight 라 하고,

이러한 작업을 Skinning이라고 함.

 

  • 보통 한 vertex에 영향을 주는 bone의 개수를 4개 정도로 두고 계산함.
  • 보통 skinning은 vertex shader에서 처리하곤 함!
  • bone이 20개가 있다면 M_i도 20개가 있어야 할 것임.
  • 이 M_i들은 matrix palette라고 하는 table에 저장됨.
  • matrix palette index들과 blend weight들은 vertex array에 저장되어서 vertex shader에게 전달되고, 최종적으로 skinning 처리됨.

 


 Keyframe Animation

Keyframe animation에 Skeletal animation이 어떻게 통합될 수 있을지 알아볼 것임.

즉, Keyframe 사이사이의 frame들에서 skinning을 어떻게 해줄거냐? 에 관한 이야기.

이 과정에서 쿼터니언 개념과 보간의 개념이 들어감.

 

Skeletal Animation을 Keyframe Animation에 적용하기 위해서, 우리는 frame마다 M_i를 업데이트 해줘야 함.

M_i는 다음과 같이 정의했었다.

요소를 하나하나 살펴보자.

inverse M_i,d의 경우 처음에 한번 traversal 돌아서 한번만 구하면 됨.

반면 M_i,a는 매 frame마다 업데이트 해줘야 한다.

M_i-1,a는 부모꺼니까 일단 제쳐두고. M_i,p와 M_i,l을 구해야 함.

이때 M_i,l은 보통 (상식적으로) rotation 변환이고,

M_i,p는 부모로의 transform이므로 translation 변환과 rotation 변환을 포함한다.

이 둘은 rigid transform으로, [R|t] 형식으로 표현할 수 있다!!

 

이 둘을 합친 Matrix를 M_i,p,l이라 하자.

M_i,p,l은 [R|t]로 표현할 수 있다고 했었다. 그런데 사실 M_i,p,l은 보간 대상이므로 그대로 [R|t]로 저장해서는 안된다. (keyframe 사이 frame들에서의 M_i,p,l을 구하려면 양쪽 keyframe을 보간해서 얻어야 함).

따라서, R을 그대로 R로 두면 안되고, (보간을 잘하기위한) Quaternion 형식으로 바꿔야 함. (t는 그냥 선형 보간 하면 되니까 그대로 t로 두면 됨)

더보기

이전(https://grace7040.tistory.com/93)에 이런 결론을 냈었다.

 

그러면 이제 Keyframe들에 저장되어 있는 데이터는 Quaternion q와 translation vector t라 할 수 있다.

보간할 수 있는 데이터 형태가 되었다!

 

굿. 이제 frame별로 보간된 M_i,p,l을 구해보자.

양쪽 keyframe에서 q를 갖고와서 보간(slerp) 해주고, t도 갖고와서 그냥 선형 보간 해주면 됨.

Quaternion 보간한 결과값은 Quaternion이니, [R|t]형태로 쓰기 위해 다시 행렬 R로 바꿔주면 됨

보간한 t는 그냥 [R|t]의 4열에다 넣어주면 됨. 그러면 [R|t] 완성~

즉 M_i,p,l이 확정된 것임.

이제 root부터 top-down traversal하면서 쭉 내려가면, 중간 frame들에서의 M_i,a가 만들어질 것이다.

확정된 M_i,p,l에 M_i-1,a를 곱하면 M_i,a 만들어짐.

거기다가 아까 구한 inverse M_i,d 곱하면 M_i 완성~ 

 

frame마다의 M_i들을 구한 것이다.

그럼 앞서 배운 Skinning 그대로 적용하면 된다~

Skinning 공식

 


 Keyframe Animation - pseudo code

 

[pseudo code]

 

[pseudo code 한글판^^]

각 bone마다 inverse M_i,d를 로드해준다.

 

각 frame마다, top-down traversal을 수행한다.

key data (q, t)를 interpolate 해서 (q`, t`)를 만든다.

이로부터 M_i,a를 계산한다.

앞서 구한 inverse M_i,d와 M_i,a를 합쳐서 M_i를 정의한다.

M_i를 matrix palette에 넣는다. (vertex array에 들어감)

vertex shader에게 skinning해달라고 한다.

 

 


Inverse Kinematics

Forward Kinematics(FK)joint angle로부터 end effector pose계산한다

그 반대가 Inverse Kinematics(IK)이다.

즉, end effector의 목표 pose가 주어지면, 이에 따른 다른 joint angle들이 계산된다.

object상태를 정의하는 독립적인 변수의 수를 DOF라 함

object의 상태가 rotation으로만 정의될 때, 우리가 구해야 하는 joint angle 개수는 DOF가 되겟죠? 

 


Inverse Kinematics - Analytic solution

아래 그림에서 DOF는 2가 된다. 윗팔-아랫팔 사이의 각도 θ와, 쇄골-윗팔 사이의 각도 Φ를 구해야 한다.

(a) 와 같이 목표 position G와 arm의 초기 pose가 주어졌을 때, 어깨와 팔꿈치의 각도를 구해야 한다.

 

(b) 에서 코사인 법칙을 사용해 θ를 구할 수 있다.

 

(c) 에서 단위벡터 v1과 v2를 일치시켜야 하는데, 둘 사이의 각도를 알아야 함.

이를 위해 v1ㆍv2을 풀어주면, Φ를 구할 수 있다.

이제 v1×v2를 통해 v1과 v2에 공통으로 수직인 rotation axis를 구해서, 이를 기준으로 회전하면 끝~

'게임 > 게임 그래픽 프로그래밍' 카테고리의 다른 글

15. Physically Based Rendering  (0) 2023.06.10
14. Shadow Mapping  (0) 2023.06.09
12. Normal Mapping and Tessellation  (0) 2023.05.31
11. Screen-space Object Manipulation  (0) 2023.05.28
10. Euler transforms and quaternions  (0) 2023.05.28
Comments