Quaternion에서 회전 행렬

 

Result

더보기

FORCEINLINE explicit Quaternion(const Rotator & InRotator){

float cy = cosf(Math::Deg2Rad(InRotator.Yaw * 0.5f)), sy = sinf(Math::Deg2Rad(InRotator.Yaw * 0.5f));

float cp = cosf(Math::Deg2Rad(InRotator.Pitch * 0.5f)), sp = sinf(Math::Deg2Rad(InRotator.Pitch * 0.5f));

float cr = cosf(Math::Deg2Rad(InRotator.Roll * 0.5f)), sr = sinf(Math::Deg2Rad(InRotator.Roll * 0.5f));

 

W = cy * cp * cr + sy * sp * sr;

X = cy * sp * cr + sy * cp * sr;

Y = sy * cp * cr - cy * sp * sr;

Z = cy * cp * sr - sy * sp * cr;

}

 

 

오일러 각 정보를 받아 Quaternion으로 변환

 

 

Result

더보기

FORCEINLINE Matrix4x4 Quaternion::RotationMatrix() const {

Matrix4x4 result; float xs = X * X, ys = Y * Y, zs = Z * Z;

 

float wx = W * X, wy = W * Y, wz = W * Z;

float xy = X * Y, xz = X * Z;

float yz = Y * Z;

 

result.Cols[0] = Vector4(1.f - 2.f * (ys + zs), 2.f * (xy + wz), 2.f * (xz - wy), 0.f);

result.Cols[1] = Vector4(2.f * (xy - wz), 1.f - 2.f * (xs + zs), 2.f * (wx + yz), 0.f);

result.Cols[2] = Vector4(2.f * (wy + xz), 2.f * (yz - wx), 1.f - 2.f * (xs + ys), 0.f);

result.Cols[3] = Vector4(0.f, 0.f, 0.f, 1.f); return result; }

 

 

Quaterion에서 오일러 각 정보로 변환

 

Roll

 

Pitch

 

Yaw

 

Result

 

더보기

FORCEINLINE Rotator Quaternion::ToRotator() const {

Rotator Rot;

float pitchHalfRad = W * X - Y * Z;

float yawY = 2.f * (W * Y + X * Z);

float yawX = 1.f - 2.f * (X * X + Y * Y);

Rot.Yaw = Math::Rad2Deg(atan2f(yawY, yawX));

float singularity = 0.499999f;

 

if (pitchHalfRad > singularity) {

Rot.Pitch = 90.f;

Rot.Roll = Rot.Yaw - Math::Rad2Deg(2.f * atan2f(Y, W));

}

else if (pitchHalfRad < -singularity) {

Rot.Pitch = -90.f;

Rot.Roll = -Rot.Yaw + Math::Rad2Deg(2.f * atan2f(Y, W));

}

else

{

Rot.Pitch = Math::Rad2Deg(asinf(2.f * pitchHalfRad));

Rot.Roll = Math::Rad2Deg(atan2f(2.f * (W * Z + X * Y), 1.f - 2.f * (Z * Z + X * X))); }

return Rot; }

 

 

Quaternion의 Slerp를 구현을 위한 공식

 

Lerp

Slerp

 

Result

더보기

Quaternion Quaternion::Lerp(const Quaternion& q1, const Quaternion& q2, const float& t) {

Quaternion result;

result = q1 * (1.f - t) + q2 * t;

result.Normalize(); return result;

}

 

Quaternion Quaternion::Slerp(const Quaternion& q1, const Quaternion& q2, const float& t) {

 

Quaternion result;

 

float cosTheta = Math::Clamp(q1.W * q2.W + q1.X * q2.X + q1.Y * q2.Y + q1.Z * q2.Z, -1.f, 1.f);

float s1, s2;

 

if (-0.99999f < cosTheta && cosTheta < 0.99999f) {

float theta = acosf(cosTheta);

float invSin = 1.f / sinf(theta);

s1 = sinf((1.f - t) * theta) * invSin;

s2 = sinf(t * theta) * invSin; }

 

else {

return Lerp(q1, q2, t);

}

 

q = q1 * s1 + q2 * s2;

return result;

}

[사원수 대수]

 

사원수는 어떻게 구성이 돼있냐면 복소수는 거의 비슷하지만 허수가 두 종류가 더 들어간다.

 

 

그래서 i, j, k라는 허수와 실수가 있는데,

복소수는 이제 실수와 허수 Part를 나눠서 a + bi를 (a, b)라고 한다.

 

사원수는 종류가 세 가지이기 때문에 w, x, y, z라고 흔히 얘기를 하고, 

여기서 w는 실수 나머지 x, y, z를 허수부라고 할 수 있다.

 

그런데 i, j, k는 각각 직교하는 성질을 가지고 있다.

그래서 x, y, z는 어차피 직교하기 때문에 벡터로 보게 되므로

다음과 같이 퉁쳐서 표현을 한다고 한다.

 

 

여기서

 

 

는 세 가지 요소가 들어간 벡터라고 할 수 있고,

 

따라서 다음과 같이 표현을 할 수 있다.

 

 

Pure imaginary quaternion(순허수)

 

 

실수부가 0인 것.

 

Addition operator(사원수 덧셈)

 

 

실수부끼리 더하고 각각 직교하는 허수부끼리 더해서 만드는 것.

 

그리고 여기서 x, y, z ,w w ∈ F는 체에 속한다고 할 수 있다.

 

이렇게 했을 때 Addtion operator satisfies(덧셈 연산)에 대해서는

  • Commutativity(교환법칙)
  • Associativity(결합법칙)
  • Identity(항등원) : 0
  • Inverse(역원) : -q

이렇게 존재를 하게 된다.

 

다음 부분을 알기 전에 체(Field)라는 것은 사칙연산이 수행되는 수집합 또는 +과 · 이 있다.

 

덧셈에 대한 항등원 : 0

덧셈에 대한 역원 : 어떤 수를 덧셈 계산을 했을 때 항등원이 되는 것 즉, 음수가 되는 것

 

곱센에 대한 항등원 : 1

곱셈에 대한 역원 : 어떤 수가 곱셈 계산을 했을 때 1이 되는 것

 

그리고 체는 교환 법칙과, 결합 법칙, 분배 법칙 세 가지 법칙이 성립이 된다.

 

이것을 조금 더 수학적으로 보게 되면 군(Abelian group)이라고 한다.

군은 (R, +) 교환 법칙, 결합 법칙, 항등원, 역원 이런 수체계가 존재하는 것을 ""이라고 한다.

 

실수 집합 역시 수체계가 존재하므로 군이라고 할 수 있다.

 

그리고 "군"에서 더 추가한 것을 "환(Ring)"이라고 한다.

 

여기서 추가된 것은 곱셈 연산이다.

 

군 => ( R, + )

환 => ( R, +, · )

 

환은 +, · 이 두 가지 연산에 대해서 분배 법칙이 성립하는가? 

그리고 두 번째 연산에 대해서 항등원과 역원을 가지고 있는가?

이 두 가지의 기본 조건이 존재한다.

 

그런데 여기서 한 가지 나오지 않는 것이 있다.

 

그것은 곱셈 연산에 대해서 교환 법칙이 성립하는지는 나오지 않았다.

왜냐하면 환이 되는 조건에서는 두 번째 연산자에 대해서는 교환 법칙이 필수적으로 성립이 될 필요가 없기 때문이다.

 

물론 교환 법칙이 성립이 되는 환도 존재한다.

그것은 가환환(Commutative Ring)이라고 한다.

 

그리고 여기서 교환 법칙, 분배 법칙, 항등원, 역원 이 네 가지가 성립이 되는 특수한 것이 있는데,

그것이 "체"라고 한다.

 

따라서 군, 환, 체라는 수학적 대수의 구조가 있는데,

여기서 체를 기준으로 모두가 사용을 하고 있고,

군, 환, 체로 나눈 것은 군부터 체까지 3단계로 발전된 모습이라고 이해시키기 위해서이다.

 

결론적으로 사원수의 덧셈을 다시 보게 되면 덧셈에 대해서는 항등원, 역원, 교환 법칙, 결합 법칙이 있기 때문에

군의 성질에 만족한다고 볼 수 있다.

 

Multiplication operator

 

 

여기서 실수부는 w1w2, 허수부는 x1x2, y1y2, z1z2 이렇게 되어있다.

그런데 허수부를 보면 뭔가 내적 연산과 비슷하다.

 

v1 = (x1, y1, z1), v2(x2, y2, z2)라고 했을 때

-의 내적 연산이기 때문에 -(v1 · v2)가 나오게 되고 이것은

 

 

과 같다고 볼 수 있다.

 

그런데 여기서 다음과 같이 하나만 -가 되있는 것을 볼 수 있는데,

 

 

순서가 -z1y2, -x1z2, -y1x2 이런 식으로 되어있다.

 

만약에 q2 · q1 을 하게 되면 어떻게 될까? 

결과는 다음과 같다.

 

 

Multiplication operator satisfies

 

 

그렇다면 사원수는 왜 체가 성립되지 않는가?

 

왜냐하면 곱셈은 동시성을 만족시키지 못하기 때문이다.

 

 

 

위의 식을 기반으로 사원수가 예외적으로 체의 성질을 가지는 조건은 무엇인가?

 

위의 식에서 볼 수 있듯이 서로 다른 부분이 벡터 부분이다.

그래서 다른 부분의 체가 0이 되려면 같아야 한다.

벡터 부분의 결과가 0이어야 한다는 것은

즉, 체의 성질을 가지려면 두 벡터가 평행해야 한다는 것이다.

 

 

[4차원 공간에서의 사원수 회전]

오일러 공식을 이용해 4차원 공간에서 회전을 수행하는 사원수는 어떤 형태인가?

 

 

계산을 하게 되면

 

 

이 나오게 되면서, i를 대신해서 r로 대체할 수 있게 되고, 이것은 다음과 같이 나오게 된다.

 

 

여기서 빨간 네모 부분의 형태는 4차원 공간에서의 회전이라고 할 수 있다.

 

3차원 공간 위의 벡터를 회전시키기 위해 위의 사원수를 그냥 사용하면 안 되는 이유는?

 

왜냐하면 우리는 4D 공간의 3D 부분을 사용해야 하기 때문이다.
그러기 위해서는 하나의 요소를 일정한 값으로 설정해야 한다.
그래서 사원수를 쉽게 계산을 해야하기 때문에 순허수를 사용하는 것이다.

 

 

그러나 보다시피 회전 후 실제 부분은 더 이상 0이 아니다.

즉, 4D 공간의 동일한 3D 부분에 있지 않다는 것을 의미한다.
그래서 사원수를 3D 벡터를 회전하는데 사용해서는 안 된다.

 

[3차원 공간에서의 사원수 회전]

 

쿼터니언의 핵심은 w를 0으로 만들 수 있는 방법을 찾는 것이다.

 

 

임의 축을 만들고 4차원에서의 벡터지만 3차원에서 허수부만을 가지고 있는 상태이다.

이것의 기준 벡터가 (a, b, c)라고 했을 때 이것은 r이 되고,

r의 성질은

 

 

이 된다.

 

이런 임의의 회전 축에서 3차원 공간에서 v가 있고,

v의 실수부는 0이고, 허수부는 v라고 표시를 한다.

 

그리고 v로부터 반시계 방향으로 돌리게 되면 다음과 같이 실수부가 0이고 v가 v'가 되는 형태로 나오게 된다.

 

 

그러면 이제 원점에서부터 v라는 벡터를 분해를 하는 것이다.

여기서 회전축의 수직인 부분을 v⊥라고 하고

수평인 부분을 v||라고 해보자.

 

그럼 v = v⊥ + v||이 된다.

 

이 상태에서 사원수는 분배 법칙이 적용이 되는가?

 

분배 법칙은 바꾸는 것은 되지 않지만, 순서만 맞으면 적용이 된다. 

 

그러면 이제 여기서 r을 돌렸을 때 어떻게 결과가 나오는지 생각해보자.

 

r을 기준으로 θ만큼 회전한 4차원의 형태는 (cosθ(실수부), rsinθ(허수부)) 가 나오게 된다.

그런데 다음과 같이 v||, v, v⊥는 허수부는 있지만 실수부는 없다.

 

 

 

그러면 (cosθ, rsinθ) => (0, v, v||) 이렇게 표현을 할 수 있고,

여기서 v가 q1이라고 하면 w는 0이지만

v가 q2라고 하면 w는 0이 아니게 된다.

 

이 상태에서 하나만 순허수라고 했을 때

 

 

여기서 q1이 회전이라 하고 q2가 벡터라고 가정해보면 다음과 같이 나오게 된다.

 

 

만약 둘 다 0이라면 이 상태에서 내적과 외적만 남게 된다.

 

이것은 다음과 같이 앞서서 진행했던 부분과 같은 것이고,

 

 

이것을 내적과 외적의 형태로 표현한 것뿐이다.

 

임의의 벡터를 두 부분으로 쪼개보았을 때 수평인 부분은

 

 

수직인 부분은

 

 

이 나오게 된다.

 

정리를 하자면

수평인 부분은 교환법칙이 성립하고,

수직인 부분은 내적이 0이기 때문에 실수부가 날라가고 허수부만 남게 된다.

 

결과적으로 수직인 부분은 교환을 하게 되면 -회전이 된다고 볼 수 있다.

 

 

지금까지 한 것을 정리해보면 다음과 같다.

 

 

내적은 영향을 받지 않는다. 하지만 외적을 하게 되면 다음과 같이 외적의 기호가 바뀌게 된다.

즉 회전축 방향이 -로 바뀐다는 뜻이다.

 

 

방정식의 첫 번째 순서가 시계 방향 회전이라면 두 번째 순서는 시계 반대 방향 회전이다.

따라서 다음과 같이 방정식을 구할 수가 있다.

 

 

이것을 증명해보면 다음과 같다.

 

 

 

  • Frustum Culling Review

평면의 방정식을 유도하고 d 의미를 정리.

 

평면이란 3차원 공간 안에 있는 무한한 면이라고 할 수 있다.

 

평면의 특성을 찾기 위해서는 보통 처음에 노멀 벡터를 하나 정의를 한 상태로 시작을 한다.

그런데 여기서 노멀 벡터만 있게 되면 유일한 평면이 되지 않기 때문에 임의의 점을 추가로 지정해 준다.

평면의 방정식이라는 것은 이 상태에서 임의의 점을 지정했을 때,

이 점이 x, y, z축을 기준으로 해서 평면에 있을 조건을 명시한 것이 평면의 방정식이다.

평면의 방정식을 구하는 방법 중에 가장 무난한 것은 이 기하적인 특성에서 어떤 성질을 유도하는 것인데,

먼저 어떤 임의의 점이 있을 때 다음과 같이 벡터를 하나 만들어주고 이 벡터를 r 이라고 지정을 해준다.

그러면 노멀 벡터랑 평면 위에 두 점이 있을 때 두 점을 빼가지고 만든 벡터는 항상 직교하게 되므로

r = (x - x0, y - y0, z - z0) · N(a, b, c) = 0이 된다.

그렇게 r · N = 0이라는 성질을 가지고 P(x, y, z)가 평면에 있을 조건을 정의할 수 있고,

 

이것을 풀어쓰게 되면

a(x - x0) + b(y - y0) + c(z - z0) = 0이 나오게 된다.

 

여기서 x, y, z로만 구성을 하게 되면 x0, y0, z0는 상수라고 할 수 있고,

a(x - x0) + b(y - y0) + c(z - z0)는 상수면서 이미 알고 있는 값이기 때문에 d라고 할 수 있게 된다.

 

그렇게 ax + by + cz + d = 0이 나오게 되면서 평면의 방정식을 만들 수 있는데,

만약 여기서 노멀 벡터가 없다고 가정하면 어떻게 해야 될까?

 

점이 세 개가 있으면 가능하다.

노멀 벡터가 있지 않은 상태에서

새로 추가한 두 점과 외적을 통해 노멀 벡터를 만들 수가 있다.

따라서 점이 세 개가 주어지거나 노멀 벡터와 점 하나가 주어지면 평면을 구할 수 있게 된다.

그런데 점 세 개가 주어졌을 때는 외적의 순서에 따라서 방향이 바뀌기 때문에

어떤 점이 첫 번째 점인지 정확하게 명시를 해줘야 면의 방향이 뒤집히지 않게 설정을 할 수 있게 된다.

 

그럼 d의 의미는 무엇일까?

 

먼저 d의 값을 구해보자.

벡터 P가 있으면  N과 벡터 P의 내적인데

중요한 건 여기서 -가 붙어있는 것이다. 그래서 다음과 서로 같다고 할 수 있고,

d의 값은 다음과 같이 나오게 된다.

d의 의미를 알기 전에 먼저 한 가지를 짚고 넘어가 보자.

 

흔히 내적 얘기를 하는 중에 제일 많이 나오는 것 중 하나는 다음과 같은 투영 공식 하나가 있을 때

벡터의 투영 공식 중에는 두 가지만 생각하면 된다.

투영한 벡터를 구하는 것과 a라는 벡터와 b라는 벡터가 각 θ를 이루고 있을 때,

궁극적으로 구하는 것은 b라는 벡터에 투영했을 때 나오는 b´을 어떻게 구할 것인가를 계산 방법이라고 할 수가 있는데

방법은 두 가지가 존재한다.

첫 번째는 b´의 길이를 구하는 것이고

두 번째는 b의 벡터를 구하는 것이다.

 

그래서 길이가 얼마인지는 모르겠지만,

b´의 길이가 있다고 했을 때 이것은 어떻게 표현을 할 수가 있을까?

 

b´의 길이를 알고 있는 상태에서 b 벡터를 정규화한 것을 곱해주면 다음과 같이 벡터 b가 나오게 된다.

그러면 이제 b의 크기를 구해줘야 하는데, 여기서 삼각함수를 사용하게 되면

가 나오게 되고,

 

여기서 cosθ는 다음과 같이 내적으로 표현을 할 수가 있다.

그러면 이제 다음과 같이 acosθ를 구할 수 있고, 그것은 b´의 크기가 된다.

b´의 크기를 구한다는 것은

와 동일하다고 할 수 있는데 이것은 다음과 같이 다시 쓸 수가 있고,

이것은 벡터 b의 |b´|로 치환이 되면서 

a와 b의 내적 한 결과가 스칼라가 나오게 되는데 이 스칼라에다가 벡터를 곱하게 되면

내적의 공식은 다음과 같이 나오게 된다.

정리를 하자면 내적이라고 하는 것은 어떤 벡터를 다른 벡터에다가 투영을 시키고,

그 투영한 벡터의 크기와 원래 있던 벡터의 크기를 서로 곱한 것이라고 정의할 수 있다.

 

앞에서 설명한 평면의 방정식으로 다시 가보자.

 

원점에서부터 벡터 P를 지나가는 평면이 있다고 가정하고,

노멀 벡터를 원점으로 옮겼을 때 

벡터 P를 노멀 방향으로 투영을 시켰다는 얘기인데

노멀 벡터를 원점으로 옮겼을 때 투영한 벡터가 나오게 된다.

여기서 투영한 벡터를 N´이라고 하고, P0에서 원점을 뺀 벡터를 P라고 정한다.

앞에서 d라는 것은

이다.

 

그런데 이것은 투영 공식에서

에다가 투영한 것과 내적이 동일하다고 했었다.

 

이 투영 공식을 시각적으로 보게 되면 다음과 같다.

그런데 여기서 노멀 벡터를 1로 만들었다면 

| N | 의 값은 1이기 때문에 무의미하게 되므로, 다음과 같이 내가 투영한 벡터의 크기만 남게 된다.

그러면 내적 한다는 것은 노멀 벡터가 크기가 1이라는 것을 제공한다면

결국에는 노멀 벡터에 투영한 벡터의 크기라고 알 수 있는데,

 

N' 크기는 무엇을 의미하는 것일까?

원점에서부터 평면 사이의 최단거리를 의미하게 되는데

즉, 이것은 원점에서부터 평면까지의 거리가 되는 것이다.

 

그런데 부호가 "-"이다.

부호가 "-"인 이유는 평면을 기준으로 해서 노멀의 반대 방향으로 -|N'|만큼 본다고 했을 때

-|N'|은 d라고 할 수 있게 된다.

 

평면의 방정식 ax + by + cz + d = 0에서 d는 원점에서 평면까지의 거리인데,

앞에 -가 붙어서 정의를 하게 된다고 할 수 있다.

 

  • Euler angle Review

1. 짐벌락 현상에 대해 설명

먼저 x, y, z 세 개의 축이 있는데,

z축은 화면 쪽으로 나와 있어서 x, y축 처럼 표시가 따로 되어 있지 않다고 생각하면 된다.

 

이 상태에서 z축을 45도 방향으로 회전 시키고, y축을 90도 회전을 시키면 다음과 같이 나온다.

 

그런데 여기서 얼굴을 x축으로 회전 시키면 시계방향 또는 반시계 방향으로만 움직이게 되는데,

이것은 처음에 z축이 화면 쪽 방향일 때 z축을 회전 시킨 거랑 다름이 없다.

 

예를 들어서 다음과 같이 얼굴을 회전 시키고 싶은데,

이런 경우에는 z축을 바라보는 상태에서 x축을 돌리거나 그 반대로 하면 되는데, 아무래도 정해진 순서가 있기 때문에 

다음과 같이 등장했을 때는 항상 x축과 z축은 똑같은 결과만을 양산하게 된다.

이와 같은 상황을 짐벌락 현상이라고 한다.

 

2. 오일러 각 표현의 장점과 단점

오일러 각 표현의 장점 오일러 각 표현의 단점
직관적으로 회전 설계가 가능 짐벌락 현상으로 인해서 원하는 방향으로 회전이 되지 않음.
데이터 사용량이 적음

 

 

 

3. 오일러 각 표현으로 저장된 두 데이터는 한 축만 사용할 때에는 덧셈이 성립되나,

두 축 이상을 사용할 때에는 덧셈이 성립되지 않는다. 이유를 수학식으로 정리

 

다음과 같이 세 가지 오일러 각을 표현한다고 했을 때

E1 + E2 = E3가 된다는 것은 명확하게 인식할 수가 있는데,

 

여기서 pitch와 roll은 값이 0이다.

이런 경우 행렬로 표현을 하려면 어떻게 해야 할까?

 

세 가지 각을 행렬로 계산을 하면 회전 값이 0일 때는 전부 항등 행렬이 된다.

정리를 하면 다음과 같은 결과가 나오게 되고

여기서 I끼리 곱하면 똑같기 때문에 I를 빼고 계산을 하게 되면 다음과 같이 유도할 수가 있게 된다.

사실 한 축 회전만 잡고 오일러 공식을 사용하는 것에는 보간하는 부분에 대해서 아무런 문제가 없다.

그런데 문제는 다음과 같이 한 축이 아닌 두 축 이상을 사용했을 때이다.

이런 경우에 E1 + E2를 했을 때 값이 E3와 같은가?

 

일반적으로 계산을 해보게 되면

이렇게 나오게 된다.

이때 방향이 서로 같냐가 문제인데, 당연히 같을 수가 없다.

왜냐하면 다음과 같이 서로 p와 y가 서로 바뀌었다고 가정하면 같다고 할 수 있다.

 

그런데 문제는 이것이 서로 방향이 다르기 때문에 같지 않다고 할 수 있고 유도해서 구할 수는 있지만

한 축이 아닌 두 축 이상을 표현할 때에는 보간이 적용되지 않는다고 할 수 있다. 

 

따라서 보간은 임의의 축에 대해서는 사용을 할 수가 없다고 정리할 수가 있다.

 

  • Rodrigues Rotation Review

로드리게스 회전 공식의 유도 과정

어떤 점 P가 있고

축에 대한 노멀 값을 n,

원점에서부터 점 P에 대한 벡터를 r로 지정을 한다.

여기서 P라는 벡터가 있을 때 이것이 θ만큼 이동한 P´를 어떻게 구할지를 생각해보는 것인데,

벡터들이 이렇게 있을 때 이 벡터로 투영된 두 값은 어떻게 되는 것일까?

 

여기 있는 벡터들은 모두 같은 평면 위에 있기 때문에 원점으로부터 거리가 같다.

 

여기서 θ는 어떻게 구하는 것인가?

 

O´, Q, P에 대해서 구해보면 다음과 같다.

이 상태에서 θ만큼 이동한 것이 P´가되는데, 

P´는 결과적으로 어떤 지점이 있다고 했을 때 O´ 에서 P´으로 가는 길과 O´ 에서 P로 가는 길이 서로 같다.

 

삼각함수로 계산을 하면

가 나오게 되고 최종적으로 다음과 같은 식이 나오게 된다.

 

 

Backface Culling

 

백페이스 컬링이란 간단하게 카메라가 볼 수 없는 면을 그리지 않는 것이다.

 

위의 그림의 경우 안쪽 면까지 다 보이는 상태인데 여기서 백페이스 컬링을 적용하게 되면 다음과 같이 보인다.

 

 

게임을 하면서 관심 있는 부분은 우리 눈에 보이는 부분이다.

 

보이는 부분만 그리는 것과 보이지 않는 부분까지 그리는 것의 성능 차이가 상당하고,

게임 속 캐릭터나 배경 오브젝트 같은 경우는 겉 모습만 보이기 때문에 안쪽 부분까지 그릴 필요가 없다.

 

그러므로 3D 엔진에서는 다각형을 그리기 전에

다각형을 구성하는 각 면이 보이지 않는 면인가를 빠르게 판단해야 한다.

 

각 면이 보이지 않는 면인가를 판단하려면, 

삼각형을 보았을 때

0 => 1과 0 => 2로 가는 벡터를 만들어주고,

외적을 시키게 되면은 해당 형태에서 노말 값이 나오게 되는데,

 

Backface Culling Cross Product(백페이스 컬링의 외적 공식)은 다음과 같다.

 

 

여기서 카메라의 정면(-Z 방향)과 노말 값을 내적 해줬을 때 같은 방향이 나오게 되면 면을 그리지 않도록 해주면 된다.

 

Result

 

Perspective Projection

 

먼저 FOV가 60도로 정해져있다고 가정해보면 시야 각은 절반인 30도가 되고, 

절반이라는 부분이 있을 때 여기서 투영할 전체 3D 공간에서 거리를 정하는 것이다.

 

여기서 초점거리는 어떻게 정해지는 것인가?

 

예를 들어서 3D 공간에 여러 가지 물체가 있다고 했을 때 임의의 평면을 하나 만들어서 부착을 시켜주게 되면

Perspective 모드에 맞춰서 3차원에 있는 물체를 2차원에 매핑을 시키는 작업이 필요하게 되는 상황이 발생하게 되는데,

 

이 상태에서 내가 투영할 스크린을 1로 정규화 시키는 것이다.

1로 정규화를 시키게 되면은 초점거리에 대해서는 다음과 같은 공식을 통해서 구할 수가 있다.

이 상태를 가정하고 다음과 같이 평면 밖에 있는 어떤 점을 투영을 시키게 되면 Pndc의 좌표가 나오게 되는데

여기서 Pndc의 좌표는 절반의 크기가 1이기 때문에 1이 넘어갈 수가 없기 때문에

0과 1 사이에 들어온다는 결론이 나게 되는데, 결과적으로는 다음과 같이 비례 삼각형을 하나 더 그리게 되면

뷰 좌표계로 본다고 했을 때 카메라 앞 방향의 길이는 -Zview가 되고,

 

d : -Zview의 비율은

가 나오게 된다.

 

이렇게 해서 Yndc의 값을 구할 수 있게 됐고,

Xndc도 같은 방식으로 구할 수 있는데 여기서 화면 비율이 정사각형이면 아무런 문제가 없는데

 

항상 종횡비(4:3, 16:9)를 가지고 있기 때문에 정사각형과 똑같이 1이라고 가정하고

원을 그렸을 때 정사각형 화면은 제대로 나오지만 종횡 비율 화면은 다음과 같이 찌그러진 형태가 나오게 된다.

 

이렇게 되면 종횡 비율 화면에서 지저분하게 보이기 때문에 이것을 보정하기 위해서는

종횡비를 미리 계산해놓고 이것을 나눠주게 되면은 1의 사이즈가 줄어들게 되는데,

 

이렇게 세로를 기준으로 하고 가로로 똑같은 크기로 나올 수 있게 보정해 주는 작업이 필요해서

a는

가 되고, 최종적으로 다음과 같이 보정이 된 상태로 나오게 된다.

 

이렇게 투영된 X, Y좌표를 구하게 되었고,

다음은 Yndc와 Xndc에 대한 행렬식이다.

y 값을 구할 때 x는 영향을 미치지 않기 때문에 나머지를 0으로 놓고 x에 대해서만 계산을 하면 된다.

그러면 다음과 같이 행렬식의 두 개에 대한 부분에 대해서 채울 수가 있다.

그러면 여기서 x와 y의 공통점은 무엇일까?

분모가 둘 다 -Zview인 것이다.

-Zview가 z랑 연관이 있을 수도 있기 때문에 분모를 뺀 상태로 다음과 같이 넣어본다.

이렇게 되면 뭔지는 모르겠지만 w'가 -Zview가 나오도록 해보는 것이다.

 

그러면 위에 두 식은 다르지만 w' 값을 하나 구할 수 있게 되었고,

x, y 값은 나중에 1/-Zview로 나눠주면 되기 때문에 행렬을 이런 식으로 해놓은 것이다.

 

그 상태에서 이제 마지막으로 z'의 값을 구해야 되는데,

평면에는 x, y 값만 있으면 되기 때문에 z'값은 필요가 없어서 다음과 같은 식으로 해놓아도 상관이 없다.

그러면 무엇 때문에 z'값을 구해야 될까?

 

z 값을 구해야 되는 이유는 카메라 안에서 평면에는 똑같이 그려지지만

z 값이 어떤 깊이에 있는지를 알아야 되기 때문이다.

왜냐하면 점이 같은 위치에 있더라도 서로 깊이가 다르다는 것을 z 값으로 구별할 수가 있기 때문이다.

결론적으로 깊이에 대한 값을 판단해서 앞에 물체가 있으면 바로 뒤에 있는 물체는 그리지 않는 것이다.

 

그런데 만약 깊이 정보가 없으면 어떤 문제가 발생할까?

 

이런 식으로 그리는 경우에는 그리는 순서에 영향을 받는다.

 

매쉬 순서에 의해서 앞에 있는 물체가 먼저 그려지게 되면은

뒤에 있는 물체를 나중에 그렸을 때 무조건 덮어 씌우게 된다.

 

어떤 물체를 그릴 때 삼각형을 먼저 그리게 된다.

여기서 뒤에 있는 삼각형이 앞에 있는 삼각형에 가려져야 되는데,

뒤에 있는 삼각형이 카메라 위치 또는 매쉬 순서에 의해서 가장 나중에 그려지게 되면은 덮어 씌우게 댄다.

 

그래서 깊이 정보가 없이 만들게 되면은 다음과 같이 이도 저도 아닌 물체가 나오게 된다.

이제 z 값에 대해서 어떻게 정할지 생각을 해봐야 한다.

 

위아래, 양옆에 대한 정보는 -1 ~ 1로 규정을 해서 구해놓았고,

z 값의 정보, near랑 far에 대한 정보를 어떻게 줄 것인가에 대한 문제가 있는 것인데.

 

near에 대한 부분도 x, y와 똑같이 -1과 1을 넣어주고 계산하는 것이다.

그러면 z값이 near에 위치한 경우 -1, far에 위치한 경우 1이 나오게 되고,

이것을 기반으로 해서 다음과 같이 방정식을 푸는 것이다.

뷰 좌표계에서 -n 값, 즉 카메라가 있으면 (0, 0, -1, 1)은 카메라가 n 만큼 간 위치에 해당하는 것이다.

그러면 여기서 z 축으로만 간 것이기 때문에 x, y랑은 상관이 없어서 x, y의 값은 0이 되는 것이다.

그런데 여기에 모르는 미지수 두 개가 존재한다.

여기서 모르는 미지수 두 개를 다음과 같이 곱해서 -n이 나와야 하는데 

왜 -n이 나와야 되는 것인가?

 

앞에서 w'를 -Zview로 저장한 이유는 -Zview가 x, y와 연관성이 있기 때문에 w'에 저장을 시키고

나중에 계산할 때 나누는 것이 이 과정에 함축돼있는 것이기 때문이고,

 

네 번째 행에 대해서 이렇게 계산을 할 수 있게 된 것이다.

그래서 아래와 같이 나온 이 좌표들을 -Zview로 나눠줘야 한다.

그렇게 최종적으로 Zview는

-Zview는 

가 나오게 된다.

 

이번에는 far 값을 계산해보자

여기서 far 값은 -f, 1이 되고 w 값은 f로 나와 있는데

z 값은 f이기 때문에 계산 결과가 1이 나오게 되었고,

 

n과 f 서로 k, l이 뭐가 나와야 하는지 모르겠지만

각각 n과 f가 나와야 되는 이유가 밝혀졌다.

그러면 이것을 이제 방정식으로 만들어서 똑같이 l과 f로 나눠서 각각 구해주게 되면은 다음과 같은 공식이 나오게 된다.

이렇게 k와 l에 대한 값을 구했으므로 다음과 같이 최종 투영 행렬을 완성할 수 있게 된다.

그런데 다음과 같은 식을 곱했을 때 나오는 결과는 w를 아직 나누지 않은 상태로 값이 나오게 된다.

이것은 정규화를 시키기 위한 준비 과정이라고 볼 수 있는데,

w 값을 나누지 않은 이 값을 Clip Coordinate라고 한다.

여기서 w의 값은

가 되고 이것은 뷰 좌표계에서 카메라부터의 깊이 값이라고 할 수 있다.

 

그리고 w 값을 나누게 되면 NDC 값이 만들어지게 되고,

 

Normalized Device Coordinate(NDC)

-1 ~ 1값으로 정규화가 된다.

 

지금까지 한 것을 정리해보면 다음과 같다.

 

 

+ Recent posts