이전 글 : Unity에서의 짐벌 락
지난 시간에 짐벌락에 대해 다뤘었다.
유니티에서는 회전을 계산할 때
오일러 각이 아닌 사원수 (Quaternion) 를 사용하게 된다.
사원수를 왜 쓰는지,
그리고 이것으로 물체의 회전을 어떻게 계산하는지 알아보고자 한다.
행렬을 쓰지 않는 이유
행렬로도 회전변환이 가능하다.
위키피디아에서 보면
u=(x,y,z)를 축으로 반시계 방향으로 θ만큼 회전하는 행렬은 다음과 같다.
그러나 회전 행렬 변환은 다음과 같은 문제를 가지고 있다.
- 점유 메모리 영역이 너무 크고 계산 부하가 높다.
- 매끄러운 회전을 위한 보간(Lerp)을 표현하기가 힘들다.
따라서 유니티에서는 사원수(Quaternion)을 사용하고 있다.
복소수에 대해
고등학교 때 한 번쯤은 배우게 되는 복소수.
왜 복소수 이야기가 갑자기 나오냐면,
사원수는 복소수의 확장으로 정의되는 수 체계이기 때문이다.
일단, 가장 먼저 짚고 넘어가야 하는 것.
복소수는 벡터이다.
복소평면 상에서 나타내어지는 벡터라는 것이다.
복소수에서 곱하기의 기하학적 의미
복소수를 배울 때,
i2=−1 이라는 것 하나로, i의 거듭제곱을 계산하는 문제가 자주 나왔다.
이 i를 곱한다는 것.
기하학적으로는
복소평면 상에서의 90도 회전을 의미한다.
즉, 정리하자면 이렇다.
실수의 곱셈은 복소평면 벡터의 크기를 변화시키고, (Scaling)
허수의 곱셈은 복소평면 벡터를 회전시킨다! (Rotation)
90도가 아닌 다른 형식 역시
복소수의 곱셈만으로 나타낼수 있고,
오일러 형 (Euler’s Form)으로 더욱 쉽게 나타낼 수 있다.
잘 감이 오지 않는다면,
유튜브의 다음의 재생목록을 참고하자.
필자는 푸리에 변환을 배울 때 너무나도 큰 도움이 되었던 영상이다.
3차원 복소공간을 가지는 복소수?
처음에는 이렇게 생각할 수 있다.
허수부를 한 개 더 추가한, 일종의 삼원수를 사용하면 될 것 같은데?
아쉽지만 아니다.
애초에 삼원수는 존재하지 않는다.
실수부 한개, 허수부 두개가 존재한다고 가정하고
곱셈을 통해 회전을 시켜보면,
허수부 둘 중 하나는 실수라는 모순이 나오기 때문이다.
우리는 저번 게시글에서
3차원 공간의 회전 데이터를 한 번에 표현하려면,
4차원의 정보가 필요하다는 것을 알았다.
즉, 실수부 1개 + 허수부 3개를 가진 4차원 공간은 가능한가?라는 질문이 자연스레 이어지고,
그 답은 이번 글에서 다루고 있는 사원수가 되겠다.
사원수에서의 회전
사원수는 다음과 같이 표현한다.
위 공식에서 벡터 V는 허수부 (i,j,k)를 한 번에 벡터로 나타낸 것이다.
그래서 사원수를 이야기할 때 실수부 a를 스칼라부, 허수부 V를 벡터부라고 부르기도 한다.
사원수의 작동방식은 다음과 같은 방정식에서 시작한다.
아까 복소수처럼 곱셈을 기하적으로 표현해보자.
첫번째로, i를 곱하는 것은 i 축을 중심으로 회전을 하겠다는 소리이다.
Q=j일 때 즉, (0,(0,1,0))일 때 i를 계속해서 곱해주면 다음과 같다.
???
복소수일 때랑은 살짝 다르다.
복소수에서는 곱셈 그 자체가 회전이지만,
사원수는 말그대로 회전을 나타내는 것은 아니고,
사원수로 3차원 공간에서의 회전을 나타내기 위해
여러 가지 제약조건을 걸어서 연산에 활용하게 된다.
어떻게 활용하냐면,
3차원의 회전을 사원수로 계산할 때는
사원수의 허수부만 이용한다.
즉, 3차원의 오일러각을 담은 벡터를
사원수로 변환할 때
허수부의 벡터로 변환한다는 말이다.
이 때 회전변환을 하는 사원수를 q라고 하면,
단순히 q를 곱해주는 것은 벡터를 회전시킨다고 볼 수 없다.
q를 곱해주는 순간
3차원 공간 상에 있던 벡터가
사원수의 4차원 복소공간으로 넘어가버린다!
엄밀히 말하자면,
우리는 3차원의 벡터를 실수부가 0인 사원수로 변환했기에,
이를 4차원 복소공간에서 관찰하면
스칼라부 = 0
인 3차원 투영공간에 존재하는 벡터로 볼 수 있는 것이다.
우리가 얻고자 하는 값은 역시 회전 변환이 이루어진 벡터.
즉, 결과값도 스칼라부 = 0
인 3차원 투영공간에 존재해야 한다!
그러려면 어떻게 해야하는가?
간단하다.
한 번의 사원수 곱셈으로 스칼라부 ≠ 0
인 복소공간으로 튀어나와버린 벡터를
스칼라부 = 0
인 공간으로 다시 집어넣는 작업을 해주면 되는 것이다.
곱셈이 두 번 필요하다!라고 이해하면 되는 것이다.
공식유도를 살짝 해보자.
공식유도
참고로 사원수의 곱셈을 (스칼라부, 벡터부)
로 표현했을 때는
다음과 같이 표현할 수 있다.
이 때 3차원 벡터를 사원수로 나타낸 값 p=(0,v)에 대해
회전변환을 마친 벡터 p′=(0,v′)을 구하기 위해
사원수 q1=(α1+β1w1)와 q2=(α2+β2w2)를 각각 곱해준다고 해보자.
이 때 v, w1, w2는 길이가 1인 단위벡터이다.
즉, 위 공식대로면
실수부 −α2β1v⋅w1−α1β2v⋅w2−β1β2(w1×v)⋅w2=0이기만 하면 된다.
이 때 α1=α2, β1=β2, w1=−w2라는 constraints를 추가해주면,
실수부가 0이 된다!
해당 constraints는 q1과 q2를 켤레 관계로 만드는 제약 조건이다.
이 때 α=α1=α2, β=β1=β2, w1=−w2로 놓고 벡터부를 계산하자.
지금까지의 계산은 다음과 같다.
3차원 벡터를 사원수로 나타낸 값 p=(0,v)와
사원수 q=(α+βw)에 대해 (단, w는 길이가 1인 단위벡터)
이제 다 왔다.
위에서 정리된 식은,
로드리게스 회전 공식과 똑같은 꼴을 가지고 있다!
로드리게스 회전 공식에 의하면
3차원 공간의 벡터 v를
u=(x,y,z)를 축으로
반시계 방향으로 θ만큼 회전한 벡터 vrot은 다음과 같다.
즉, 사원수 곱셈 결과에 따르면 이렇게 쓸 수 있다.
위 식을 모두 만족하는 α와 β가 있다면 끝이다.
고등학교 때 삼각함수의 반각공식과 배각공식을 들어보지 않았는가?
그렇다. 위 세 식을 만족하는 값은 α=cos2θ, β=sin2θ 이다.
이제 결론이다!
로드리게스 회전 공식에 의하여
3차원 공간의 벡터 v를 사원수로 나타낸 값 p=(0,v)에 대하여
u=(x,y,z)를 축으로
반시계 방향으로 θ만큼 회전한 벡터 v′의 사원수 값 p′=(0,v′)는 다음과 같이 구할 수 있다.
사원수 (Quaternion) 회전의 장점
회전을 세 번에 걸쳐 나누어 계산하는 것이 아니라
임의의 축에 대해 회전을 계산하기 때문에
짐벌락 현상이 아예 없다.
무슨 말인가 싶을텐데, 회전 변환 행렬의 경우 계산과정에 삼각함수가 굉장히 자주 들어간다.
삼각함수 자체도 다른 연산에 비해 무겁지만, 결과가 -1f~1f
로 매우 좁기 때문에
부동소수점으로 인한 오차가 계속해서 누적된다!
- 연산이 빠르며 행렬에 비해 메모리를 적게 사용한다.
계산이 이루어질 때,
행렬 곱이 아닌 벡터 연산처럼 이루어지기 때문에
행렬에 비해 훨씬 빠르고,
메모리 공간 역시 적게 차지한다.
90도 회전 변환을 천천히 한다면
이를 1.5도짜리 회전 변환 60번을 하는 사원수로 바꿔
이 사원수를 앞뒤로 계속 곱해주기만 하면 되는 것이다.
행렬의 경우 이것이 힘든 이유가,
매 회전 변환을 할 때마다 행렬이 새로 계산되기 때문이다.
지금까지 사원수(Quaternion)를 이용한 회전 연산에 대해 알아봤다.
사실 기본적으로 오일러각을 사원수로 변환한다던지, 보간 등의 기능을
유니티에서 전부 지원하기 때문에
해당 내용을 반드시 숙지하고 있어야할 이유는 없지만,
짐벌락 발생이 우려된다거나,
사원수를 직접 사용할 일이 있을 때
해당 내용에 대해 직관을 가지고 있는 것이 큰 도움이 되리라 생각한다.
Uploaded by N2T