Direct3D : 월드행렬 (World Matrix) 2 - 회전행렬 Direct 3D


지난번 글에 이어서 이번에는 회전행렬에 대해서 소개해 보겠습니다.
회전행렬은 3D 그래픽스 행렬 체계의 핵심으로, _11 ~ _13, _21 ~ _23, _31 ~ _33 
인자가 담당하며, 기본적으로는 오일러 각(Euler Angle) 회전 이론을 적용시켜 둔 형태입니다.

먼저 회전행렬이 무엇인지 살펴봅시다.




이것이 고교수학에 등장하는 회전행렬입니다.

수리영역에도 회전행렬을 응용하는 문제가 많이 나오죠^^.

2차원 좌표계의 회전을 나타내며, (3차원이라 치면 Z축 회전으로 생각해 주십시오)
기존 좌표를 x,y 에 넣어주는 것으로 특정 각도만큼 변화된 좌표를 얻을 수 있습니다.



X' = (Cosθ*X)  + (-Sinθ*Y)

Y' = (Sinθ *X)  + (Cosθ*Y)


위의 행렬식을 풀어서 쓰면 이런 식입니다. 삼각함수를 통해서 특정 좌표
를 각도만큼 회전시켰을때의 좌표를 구해줍니다.
이러한 과정은 3차원 좌표계에 와서도 마찬가지입니다. 결국은 기존의
점을 회전시키는데, 변화하는 좌표의 달라질 뿐입니다.





▲ X축을 기준으로 회전하는 Y,Z 좌표를 구하는 회전행렬

이 행렬의 결과는 이렇습니다.

X' = X //X값은 변하지 않는다.

Y' = (Cosθ*Y) + (-Sinθ*Z)

Z' = (Sinθ *Y) + (Cosθ*Z)



X축을 기준으로 하여 회전한다고 생각해보면, 어디까지나 X좌표는 변하지
않은 채, Y,Z 좌표값만 변하게 될 것입니다. 

그래서 X 좌표를 산출해야 하는 행은, 단지 기존 X 좌표를 그대로 적용하기 위해서
X좌표와의 곱을 산출하는 원소에 1을 값으로 가지도록 하는 것입니다.
Y,Z 와의 곱에 사용되는 원소에는 0이 들어가 있죠. 이 연산의 결과는 기존
X 값이 그대로 산출되도록 되어있습니다. 

그리고 앞서 살펴보았던 2차원의
회전행렬을 단지 Y,Z 좌표에 대해서 적용시켜 놓은 것이 전부입니다.




조금 더 쉽게 접근할 수 있도록 구를 그리고, 내부에 Y와 Z 좌표의 각도에
따른 변화와 회전에 대한 개념을 그림으로 표현해 두었습니다.

중요한 점은, 특정 축을 기준으로 회전할 경우, 해당 기준 축에 대한
좌표만 그대로 산출되고, 나머지는 삼각비에 따른 변화된 좌표를 반환
한다는 점입니다.

더불어, 변화의 대상이 되는 좌표는 어디까지나 2개의 좌표만 변화되는 식이므로,
2차원의 회전행렬 개념을 그대로 적용해서 연산을 수행하게 되는 것입니다.

물론 연산결과 여러 축을 기준으로 회전을 수행한다면, 추가적인 변동이 필요하게
되지요. 이러한 연산 수행은 행렬의 곱으로 결과를 누적시켜 반영할 수 있습니다.



▲ Y축을 기준으로 회전하는 X,Z 좌표를 구하는 회전행렬

이 행렬의 결과는 이렇습니다.

X' = (Cosθ*X) + (-Sinθ*Z)

Y' = Y //y값은 변하지 않는다.

Z' = (Sinθ *X) + (Cosθ*Z)



Y축을 기준으로 하여 회전한다고 생각해보면,
Y 좌표를 고정으로, X,Z 좌표만 변화하게 됩니다.

맥락은 X축 회전행렬과 동일하므로, 생략하도록 하겠습니다.





▲ Z축을 기준으로 회전하는 X,Y 좌표를 구하는 회전행렬

이 행렬의 결과는 이렇습니다.

X' = (Cosθ*X) + (-Sinθ*Y)

Y' = (Sinθ *X) + (Cosθ*Y)

Z' = Z //z값은 변하지 않는다.



처음 소개하였던 2차원 좌표계에서의 회전행렬과 동일한 연산결과를
보여주는군요. 변화된 것은 Z 좌표가 추가되었다는 점만 달라졌습니다.

하지만 Z는 기존 값을 그대로 가져가는 꼴이며, X,Y 좌표 연산시에는
Z 좌표가 활용되지 않으므로, 회전 시의 좌표 변화에 대해서는
2차원의 회전행렬 연산과 동일하다 할 수 있겠습니다.

역시 전반적인 맥락은 위와 같습니다.



여기까지, 3차원 회전행렬에 대한 기본적인 내용을 살펴 보았습니다. 
여기서부터는 이러한 회전행렬의 개념이 적용된 3D 그래픽스의 4x4 행렬을
살펴보겠습니다.


덧붙이자면, Direct 환경에서 실제로 활용하는 행렬은 


형태가 위의 회전행렬과는 조금 다릅니다.





뭐, 맥락이 바뀌는것은 아닙니다. 어디까지나 결과적으로 동일한 연산결과를
반영하긴 합니다.

하지만 행렬의 형태가 조금 바뀌게 되죠.
쉽게 말하자면 행과 열이 바뀐... 그러니까 가로 세로가 바뀐 느낌으로 말이죠.
어찌되었건 식을 풀어서 써 보면 산출된 결과는 동일합니다.







X축 회전행렬입니다. 위에서 소개한 X축 회전행렬과 크게
다르지 않습니다. 음수인 sin세타의 위치가 변경되어 있습니다만,
이는 위에서 언급했던 회전행렬과 형태가 다르기 때문에 달라진 것입니다.

위에서 증명에 사용했던 행렬은 3 X 3 행렬과 3 X 1 행렬을 연산하여,
3 X 1 행렬의 결과로써 이동값을 표현하도록 했었습니다. 

하지만 Direct 에서 사용하는 행렬은 보통 1 X 4 행렬과 4 X 4 행렬을
곱하여 연산처리합니다. 때문에 결과도 1 X 4 행렬로 표현되며,
이렇게 풀어서 쓰는데 적합하도록 인자가 일부 수정되었습니다.
이 점은 유의하면서 봐 주시길 바랍니다.

(그렇다고 결과값이 달라진건 아닙니다. 어디까지나 행렬의 형태를 다르게
 사용했다는 것이죠)

조금 더 보충하자면, 실제로 반대로 해도 회전처리가 됩니다.
대신에 이때는 축을 중심으로 회전하는 방향이 반대로 되지요.
식의 부호를 반대로 해 둔 다음에 직접 연산해 보면, 
sin세타는 각각 Y 좌표, Z 좌표를 반영하는 위치에 있습니다.

이들의 연산의 절대값이 같고, 부호만 음수로 변경되었다면, 각각의 증감폭은
그대로 가져가면서 증가 연산은 감소 연산으로, 감소 연산은 증가 연산으로 변화됩니다.
때문에 회전의 방향이 반대로 된 것이라는 결론을 내릴 수 있는 것입니다.

(무엇보다도, 식을 풀어서 쓴 다음 직접 계산해보면 확실하게 결과가 나옵니다^^)




이같은 X축 회전행렬을 적용하여 월드행렬로 반영한 예시입니다.
Identity에 대한 소개는 이전 글에서 하였으므로, Rotation 만 소개하도록 하겠습니다.


D3DXMatrixRotationX

X축을 중심으로 회전하였을때, 회전 각도에 따른 좌표 변환을 어떻게 하면 되는가에
대한 수치를 행렬값으로 설정해 주는 Rotation 함수입니다.
이 함수는 X,Y,Z 축 모든 축에 대해서 각각 존재합니다.

받는 인자는 대상이 되는 행렬과, 변화시킬 각도의 라디안 각도입니다.
물론, 우리 입장에서는 디그리(Degree) 각도가 훨씬 친숙한 것이 사실이므로,
디그리 각을 라디안 각도로 변환하여 인자로 전달해도 됩니다.

Direct 내부에서도 각도 변환을 위한 매크로가 준비되어 있습니다.
D3DXToRadian 매크로가 이 역할을 해 줍니다.


▲ 각도변환의 매크로

이러한 매크로는 각도 변환공식이 그대로 구현되어 있는것에 불과하므로,
직접 수치를 작성하여 동일한 결과를 얻을 수도 있습니다.
꽤나 자주 사용될 수 있는 수치와 변환기능이므로, Direct 환경이 아닌 곳에서는 
직접 매크로를 준비해 두고 작업을 시작하는것도 좋습니다.



당연하지만 함수의 기능을 이렇게 풀어서 직접 작성할 수도 있습니다.
X축 회전행렬은 X값은 원본값을 유지하고, 나머지 Y,Z 값만 각도 변화값에 따라서
변화하게 되므로, 이에 맞추어 각각 행렬의 원소들 역시 이에 맞추어 X 값은
식 연산을 거치더라도 원본값을 산출하도록 맞춥니다.

더불어 타 좌표를 구해낼때는 X 값을 배제하고 변환을 수행하게 되므로,
Y,Z 좌표를 연산하는 식을 구할 때는 X 항이 0이 되도록 설정하고 있습니다.
_12,_13 항은 이때문에 0을 넣고 있는 것입니다.

이같은 개념은 축이 변경되더라도 동일하게 적용됩니다.







X축으로 회전한 결과입니다.


넣은 각도만큼 회전한 결과가 반영됩니다.
지금은 X축으로 회전시키고 있으므로, 가로 중심을 방향으로 회전하는 결과를
볼 수 있습니다.






Y축 회전행렬입니다. 맥락은 X축 회전행렬과 동일하며, 이 행렬 연산은
X,Y,Z 좌표 중, Y 좌표를 변화시키지 않고, Y,Z의 변환된 좌표를 구하기 위한
식을 만드는 역할을 합니다.


Y축 회전행렬을 적용하여 월드행렬로 반영한 예시입니다.


D3DXMatrixRotationY

RotationY 함수는 RotationX 에서 축이 다르므로, 다른 행렬이 적용됩니다.
Y 좌표값은 변환 결과가 원본값이 산출되도록 구성되어 있습니다.
하지만 이외의 차이점은 없으므로, 소개는 생략하도록 하겠습니다.



마찬가지로 풀어서 직접 작성할 수도 있습니다.
RotationY 함수는 주석을 걸고, 직접 값을 연산하여 대입했습니다.
결과는 동일합니다.








Y축으로 회전한 결과입니다.






2차원에서의 회전행렬과 같은 맥락인 Z축 회전행렬 입니다.
Z 좌표는 원본값을 유지시켜 주지만, 나머지 좌표는 연산을 통해
변경된 값을 산출합니다.



함수를 통해 Z축으로 회전하는 코드를 작성한 예시입니다.

D3DXMatrixRotationZ

Z축으로 회전하므로, X,Y 좌표만 연산을 통해 변환합니다. Z좌표는 변동되지
않습니다.


풀어서 작성한 결과입니다. _33 항은 Z의 원본값을 유지하기 위해서 1을 넣습니다.








Z축으로 회전한 결과


여기까지가 단일 회전행렬들의 소개 및 결과를 출력해본 과정이었습니다.
하지만 이정도 기능만 사용해서는 원하는 만큼의 회전 결과를 만들기 힘듭니다.

여러 축에 대한 회전 결과를 반영하고 싶다면 어떻게 해야 할까요?
이때에는 각 회전행렬들의 곱 연산을 통해 회전결과를 누적시키면 됩니다.


다음 코드에서는 각각의 회전행렬을 선언하고, 이를 각 Rotation 함수로 설정해주고 있습니다.
그 후에 이들의 곱을 월드행렬로 연산하고 있습니다.




결과는 이런 식입니다.

X 축으로 320도,
Y 축으로 10도
Z 축으로 0도

로 설정한 결과입니다.

여기까지, 월드행렬을 설정하기 위한 기본적인 회전행렬에 대해서 살펴보았습니다.
사실 Rotation 을 사용한 회전 적용은, 각 축에 대한 회전이 개별적으로 이루어지는 것입니다.
개별적으로 회전된 값을 곱으로 합산하여 결과를 반영하는 것인데, 
이를 오일러(Euler) 회전이라고 부릅니다. 

일단 곱 자체는 여러 수가 있습니다.

XYZ, XZY, YXZ, YZX, ZXY, ZYX

이렇게 6가지의 경우의 수가 있지 않겠습니까?
각 곱의 예를 들면,



▲ XYZ 순서로 곱했을때의 도출식


▲ ZYX 순서로 곱했을때의 도출식


이런 식으로 보면, 도출된 식은, 연산 순서에 따라서 꽤 많은 차이를 보이게 됩니다.
이들 식을 보다 쪼개어 분석하기 위해서는 여러 사인 코사인 법칙 등을 섞어 분석해야 합니다.
보다 세세한 분석은 추후에, 오일러 각에 대해서 보다 깊게 소개할 때 하도록 하고,

일단은 이 식이 어떤 성질을 띄는지만 나열하겠습니다.



XYZ 순으로 회전행렬을 곱했습니다.
이 경우 Z->Y->X 순서로 회전을 한다고 생각하면 됩니다.

더불어 각 축의 회전시에 남은 축들은, 현재 회전의 대상이 되는 축에 종속적으로 동작합니다.
이를테면, 지금은 Z축이 먼저 회전하게 되지요?

그러면 Z축이 회전할때, Y,X 축이 Z축 회전을 따라서 회전합니다.
축도 함께 회전을 해버린 것입니다.

다음으로 Y축이 회전을 할때에는, Z축만큼 회전된 상태인 축을 기준으로 삼게 됩니다.
Y가 회전할때는 X축이 Y 회전값만큼 회전하게 됩니다. 이때 먼저 회전했던 Z축은
변하지 않습니다.

마지막으로는 X축이 회전할 것입니다만, 이 X축은, 이미 Z,Y 축 회전으로 인해
많이 회전된 상태인 X축이 될 것입니다. 그리고 X축은 현재 종속관계상 가장 하위의 축이므로,
X축이 회전한다고 해도 다른 축에 영향을 주지 않습니다.

이 순서는 회전행렬을 곱한 순서에 따라서 달라지는 것으로써,
ZYX 순으로 곱을 수행하였다면
X->Y->Z 순서로 회전을 하게되고, 이에 따른 축의 종속관계도 여기에 맞춰 변경됩니다.



이런 회전 방식은 경우에 따라서 문제를 발생시키기도 합니다.
종속관계에 따른 축이 변동되는 방식인 탓에, 축이 겹쳐지는 일이 생길 수 있기 때문인데요,
이러한 현상은 짐벌락(Gimbal Lock) 이라고 부릅니다.
이 문제점은 쿼터니언(quaternion) 사원수 개념을 도입해 해결할 수 있습니다.
이러한 부분에 대해서는 추후에 보다 자세히 언급하도록 하겠습니다.

일단은 여기까지로, 회전행렬에 관한 기본적인 소개를 마치도록 하겠습니다.
다음 글에서는 마지막 행렬인 크기변환행렬(Scale Matrix)에 대해서 소개하겠습니다.




덧글

  • ㅇㅇ 2018/02/07 11:57 # 삭제 답글

    감사합니다 덕분에 정말 도움 많이 됐습니다 !!! ^^;
댓글 입력 영역


라운드 시계