[DirectX12]

게임수학 4. Scale, Rotation, Translation 변환행렬

럭키🍀 2022. 8. 3. 15:20

지난시간에 행렬의 기초에서 다룬것처럼 Scale, Rotation, Translation 기본 변화를 행렬로 나타낼 수 있다.

또 월드에서 위치를 표현하기 위해 벡터를 쓰기도 하는데 x,y,z 세개의 좌표를 들고 있기 때문에 벡터로 위치를 표현할 수 있기 때문이다. 그리고 이 벡터를 x,y,z값을 가진 행렬로도 만들 수도 있다.

x,y,z외에도 뒤에 1이 붙어서 4차원으로 표현하기도 하는데 이유와 좌표에 어떤 행렬의 어떤 연산을 해야 S,R,T결과가 나오는지 알아보자.

 

 결국 위치벡터에 어떤 행렬을 연산하여 벡터의 결과값이 처음보다 크기가 변화하거나(Scale), 회전하거나(Rotation), 이동하거나(Translation)을 나타나기 위해 연산을 한다.

그리고 결과값도 다시 위치벡터여야하므로 1*3의 위치벡터를 의미하는 행렬에 3*3의 행렬을 곱한다.

 

Translation. 평행이동행렬

평행이동을 한다는 것은 원래 x,y,z의 위치에 잇었다가 각각 a,b,c의 값을 더한 값만큼 이동한다는 것이다. 즉, 각 성분이 x+a, y+b, z+c가 되어야한다.

즉, 이동에 해당하는 Translation을 구하기 위해서는 곱한 값이 아니라 각 성분에 스칼라 값을 더한 결과값이 필요하다. 각 성분에 (a,b,c)를 더한다고 하면 곱셈만으로는 모든 성분이 x,y,z값에 영향을 받기 때문에 성분 1을 더 추가한 1*4행렬을 이용해 이동연산을 구할 수 있다. 이렇게 마지막에 일부러 1을 더 넣은 좌표계?행렬?을 동차좌표계라고한다.

(x, y, z, 1) 이런 행렬을 동차 좌표이라고 한다. 실제 인게임에서는 3차원의 정보만 사용하지만 연산을 위해서만 사용하는 것이다.

 

x,y,z 좌표를 성분으로 가지고 있는 행렬에서 결과가 x+a, y+b,z+c을 가지고 있는 행렬을 결과로 얻기 위해선 어떤 행렬을 곱해줘야할까.

그 행렬은,

(1 0 0 0

 0 1 0 0

 0 0 1 0

 a b c 1) 이어야한다.

위처럼 동차좌표에 4*4행렬을 곱해서 나오는 결과값을 보면 처음 동차행렬의 마지막 성분이 1이므로 m41,m42, m43에 더해주고자 하는 값 a,b,c를 넣고 m11, m22, m33, m44의 값에 1을 넣으면 결과로 (x+a ,y+b ,z+c ,1)이 나오게 된다.

참고로 벡터끼리의 합보다 행렬의 곱이 계산이 훨씬 빨라서 CPU제조사들도 행렬곱으로 연산을 많이 하도록 되어있다.!

 

Scale. 스케일

스케일링의 공식은 굉장히 단순한데, 3차원 좌표위의 큐프가 x축으로 a배, y축으로 b배, z축으로 c배 확대어야한다고 가정하자.. 각 성분에 a,b,c를 곱해주면 된다.

이번에는 각 성분이 ax,by,cz인 행렬을 구하는것이 목표이다.! 어떤 행렬을 곱해줘야할까를 구해보자.

 

그럼 (x, y, z, 1) 이 (xa, yb, zc, 1)이 되어야하는데 아래 결과식에서 xm11, ym22, zm33 빼고는 다 0이여야하므로 m11,m22,m33빼고는 모두 0으로 만들고 각각 a,b,c면 된다. 마지막으로 마지막 성분이 1이므로 m44는 1이된다.

 

스케일을 할 때 주의할 점!

 

 

그런데 기준점을 어디로 잡느냐에 따라 이동이 생길수도 있다. 보통 발끝을 기준으로 삼는데 만약 배꼽을 기준으로 스케일을 변환하면 슈퍼마리오가 버섯먹고 그랬다면 땅을 뚫게된다. 그래서 통상적으로 발 밑을 기준으로 만들게 된다.

 

 

 

Rotation. 회전

캐릭터는 어떤 축을 기준으로 다른 축은 고정시키고 Z축을 기준으로 회전한다고 행각해보자.

A의 좌표(x1, y1)에서 B의 위치(x2, y2)로 가려면 그 좌표를 어떻게 구할것인가를 알아봐야한다. 

원점에서 A까지이루는 선분과 원점에서 B까지 이루는 선분이 이루는 각을 θ, 원점에서 선분 OB사이의 각을 α라고 해보자.

A의 좌표는 (dcosα, dsinα), B의 좌표는 (dcos(α+θ), dsin(α+θ))이다. 

B의 좌표를 코사인의 덧셈정리(cos(α + β) = cosαcosβ - sinαsinβ),

( sin(α + β) = sinαcosβ + cosαsinβ)를 이용하면

(dcosαcosθ - dsinαsinθ, dsinαcosθ + dcosαsinθ)이다. 

dcosα, dsinα가 각각 x1,y1이였으므로

 B좌표는 (x1cosθ - y1sinθ, ycosθ + x1sinθ)이 된다.

*코사인의 덧셈정리는 코코사사 에서 부호가 +였으면 -로 바꾸는것이였고 사인의 덧셈정리는 사코코사에 부호 그대로였다.

 

z축 회전을 위한 행렬 구하기

 

이를 유도하기 위해 우선 w자리에 1이 오려면 위처럼 m14, m24, m34에 0, m44에 1이 있어야한다. 또 m41, m42, m43에도

값이 있을 필요가 없다. 

그리고 z축의 값은 같아야하므로 m31,m32도 지우고 m33에는 1이 들어가야한다.

x2,y2는 z 랑 관련이 없으므로 m13, m23,m31,m32도 지우고 결과와 비교해보면 m11이 cosθ ,m12가 -sinθ, m21이 sinθ, m22가 cosθ여야하는 것이다.위는 z축 회전 행렬의 예시이며 나머지 축 회전을 구할 행렬은 아래 같이 계산하여 알 수 있다.

왼손좌표계 기준?

만약 x,y,z축 회전이 모두 일어난다면 세 행렬을 합해서 그 행렬을 원래 위치를 나타내는 행렬에 곱해서 회전된 결과를 구할 수 있다. x,y,z축 순서는 상관없이 모두 같은행,같은열의 수를 가지고있어서 곱하는데 순서는 상관 없다.

 

그리고 회전도 스케일처럼 어디를 중심으로 하느냐에 따라 달라지는 문제가 있다. 

원점을 중심으로 회전하면 왼쪽처럼 한 자리에 있는 자전일수도 있고 오른쪽처럼 공전일수도 있다. 주로 게임에서는 자전을 기준으로 만들어주는 경우가 많다. 

 

SRT 행렬 연산시 주의할 점

어쨌든 이렇게 변환 행렬에 대해 살펴봤는데 행렬의 특징중에 교환법칙은 성립이 되지 않으나 결합법칙은 성립되는게 있었다.

별모양을 위치벡터 S,R,T를 각각 변환 행렬이라고하자.

그래서 보통 변환행렬을 미리 결합하여 연산하기는 하는데 순서를 Scale, Rotation, Translation으로 하는것은 바뀌면 안된다. 왜냐면 scale, rotation은 기준이 원점이냐 아니야에 영향을 많이 받기 때문이다. 만약 rotation이 translation다음에 이루어진다면 평행이동 이후에 회전이 이루어지는 것이므로 결과에 큰 차이가 일어나게된다. 반대로 rotation이 scale에 앞선다고 생각해보자. 만약 삼각형을 z축 회전 후 x만 2배로 바꾼다고 생각해보면 다른 곳에 있을 것이다. 

 

 

그래서 항상 이 왼쪽의 '스자이공부'의 순서를 지켜서 곱한 행렬을 대상으로 연산해야하는데 각각

스:스케일

자:자전

이:이동

공:공전

부:부모행렬

을 뜻한다. 이 중 공전은 잘 사용하진 않지만 부모행렬이란 오브젝트가 다른 부모오브젝트에게 상속되어있어 같이 변화가 일어나는 경우가 있어 마지막에 부모의 스자이공부결과행렬을 같이 연산해줘야한다. 이 SRT연산을 위한 행렬은 주로 쉐이더에서 상수버퍼에서 사용하는 TransformData이름으로 Transform Matrix를 만들어서 사용한다.

 

메쉬에 S,R,T를 적용시켜 월드에 배치시키는데 사용할수 있다. 캐릭터의 이동이랄지, 둘러본다던지, 보스몬스터는 크기가 커진다든지..! 그리고 이런게 가끔 면접에 질문으로 나오기도 하므로 익혀두는게 좋겟다.