KoreanFoodie's Study

1. 벡터의 개념과 DirectXMath 라이브러리의 벡터 본문

Game Dev/DirectX

1. 벡터의 개념과 DirectXMath 라이브러리의 벡터

GoldGiver 2021. 11. 9. 21:04

DirectX 12 3D 게임 프로그래밍의 바이블

 

'DirectX 12를 이용한 3D 게임 프로그래밍 입문'을 읽으며 내용을 정리하고 중요한 부분을 기록하는 글입니다.


1. 벡터의 개념과 DirectXMath 라이브러리의 벡터

 

알아 두어야 할 개념들 : 

- 직교 투영 : projection이라고 하는 이 개념은, 그냥 기본이다. proj(w)v라고 하면 v벡터 중 w벡터 방향의 요소를 추출할 수 있다.

projection의 간단한 수식표현

 

- 그람 슈미트 직교화 (gram schmidt orthogonalization)

그람 슈미트 직교화는 선형대수학을 들으면 모를 수가 없을 정도로 중요한 정리이다. 간단히 말해서, N차원 공간의 벡터를 각 차원을 대표하는 벡터들로 분해(decomposition)하는 것이다. 구체적인 방법이나 증명은 아래 사진을 참고하면 된다.

u1, u2 ... 녀석들은 linearly independent한 벡터들이다. 왜냐? 이전 벡터들의 projection을 빼니까 dependent한 부분이 다 사라지게 되는 거지

 

- 2차원 유사 외적 : 사실 외적이라 함은 두 3차원 벡터에 직교인 벡터를 구할 수 있는데, 2차원 유사 외적은 외적을 취했을 때 결과값이 0이 나오는 벡터를 의미한다.

예를 들어, u = (a, b)라고 했을 때, v = (-b, a)이면, u º v = -ab + ba = 0 이다. (직교임)

 

- 외적을 이용한 직교화 :

그람 슈미트 직교화 공정을 이용해서 벡터 집합을 직교화할 수도 있지만, 3차원의 경우 정규직교에 가깝지만 수치상 조금 오차가 있는 간단한 직교화 방법이 있다.

먼저, w0를 정의한다.
w2를 외적을 이용해 구한다.
3차원 공간에서 w1과 w2,w0는 수직이므로, 외적하면 w1을 구할 수 있다!

 

DirectXMath 라이브러리의 벡터

1. 벡터를 효율적으로 다루기 위해 DirectXMath 라이브러리의 XMVECTOR 형식을 사용한다. 이 형식은 SIMD 연산들을 이용해서 벡터를 효율적으로 처리한다.

XMVECTOR는 SIMD 하드웨어 레지스터에 대응된다(128비트 형식, 32비트 FLOAT 4개). 

typedef __m128 XMVECTOR;

XMVECTOR는 16바이트 경계에 정합(alignment)되어야 함.

 

2. 클래스 자료 멤버에는 XMFLOAT2, XMFLOAT3, XMFLOAT4 클래스를 사용하되, 필요에 따라 적재 함수들을 이용해서 XMVECTOR로 변환하고 저장 함수들을 이용해서 다시 XMFLOATn으로 변환한다. 초기화 구문을 이용해서 상수 벡터를 정의할 때에는 XMVECTORF32형식을 사용해야 한다.

// XMFLOAT4를 XVECTOR에 적재
XMVECTOR XM_CALLCONV XMLoadFloat4(const XMFLOAT4 *pSource);

// XMVECTOR를 XMFLOAT3에 저장
void XM_CALLCONV XMStoreFloat3(XMFLOAT3 *pDestination, FXMVECTOR V);

 

3. XMVECTOR 인스턴스를 인수로 해서 함수를 호출할 때, 효율성을 위해서는 XMVECTOR값이 스택이 아니라 SSE/SSE2 레지스터를 통해서 함수에 전달되게 해야 한다. 이를 플랫폼 독립적인 방식으로 처리하기 위해, 함수의 XMVECTOR 매개변수에 FXMVECTOR, GXMVECTOR, HXMVECTOR 형식을 지정한다. 

 

4. 함수의 처음 세 XMVECTOR 매개변수에는 FXMVECTOR 형식을, 넷째 XMVECTOR에는 GXMVECTOR 형식을, 다섯째와 여섯째 XMVECTOR에는 HXMVECTOR를, 그 이상의 XMVECTOR 매개변수들에는 반드시 CXMVECTOR 형식을 지정해야 한다. 이때, 중간에 다른 변수들이 끼어드는 것은 XMVECTOR끼리의 순서에 영향을 끼치지 않는다.

 

5. DirectXMath 라이브러리는 XMVECTOR를 이용한 벡터 덧셈, 뺄셈, 스칼라 곱셈을 위해 중복적재된 연산자들을 제공한다. 이외에도 내적, 외적, 정규화 등의 함수들이 있다.

// 예시
// XM_CALLCONV는 CALLING CONVENTION인데, __fastcall을 할 것인지 __vectorcall을 할 것인지
// __fastcall은 처음 세 XMVECTOR 인수를 레지스터를 통해 전달하고, 나머지는 스택을 사용한다.
// __vectorcall은 처음 여섯 XMVECTOR 인수를 레지스터를 통해 전달하고, 나머지는 스택을 사용한다.
// 생성자를 제외한 함수에는 XM_CALLCONV를 붙여준다고 생각하면 편하다.
XMMATRIX XM_CALLCONV XMMatrixLookAtLH(FXMVECTOR EyePosition, FXMVECTOR FocusPosition, FXMVECTOR UpDirection);

XMMATRIX XM_CALLCONV XMMatrixTransformation2D(FXMVECTOR ScalingOrigin,  float ScalingOrientation, FXMVECTOR Scaling, FXMVECTOR RotationOrigin, float Rotation, GXMVECTOR Translation);

void XM_CALLCONV XMVectorSinCos(XMVECTOR* pSin, XMVECTOR* pCos, FXMVECTOR V);

XMVECTOR XM_CALLCONV XMVectorHermiteV(FXMVECTOR Position0, FXMVECTOR Tangent0, FXMVECTOR Position1, GXMVECTOR Tangent1, HXMVECTOR T);

XMMATRIX(FXMVECTOR R0, FXMVECTOR R1, FXMVECTOR R2, CXMVECTOR R3)

XMVECTOR XM_CALLCONV XMVector2Transform(FXMVECTOR V, FXMMATRIX M);

XMMATRIX XM_CALLCONV XMMatrixMultiplyTranspose(FXMMATRIX M1, CXMMATRIX M2);

XM_CALLCONV에 대한 자세한 설명은 여기로.

 

6. 부동소수점 오차 때문에, 두 벡터의 상등을 판정하는 것은 작은 값인 Episilon을 이용해서 판별한다.

 

연습문제  19번 :

다음 프로그램의 출력을 살펴보고, 각 XMVector* 함수가 하는 일을 추측하기. 그리고 DirectXMath 문서에서 해당 함수들을 찾아보기.

#include <Windows.h> // XMVerifyCPUSupport에 필요함
#include <DirectXMath.h>
#include <DirectXPackedVector.h>
#include <iostream>

using namespace std;
using namespace DirectX;
using namespace DirectX::PackedVector;

// XMVECTOR 객체를 cout으로 출력하기 위해
// "<<" 연산자를 overloading 한다.

ostream& XM_CALLCONV operator<<(ostream& os, FXMVECTOR v)
{
	XMFLOAT4 dest;
	XMStoreFloat4(&dest, v);

	os << "(" << dest.x << ", " << dest.y << ", "
		<< dest.z << ", " << dest.w << ")";
	
	return os;
}

int main()
{
	// set flag의 줄임말. 출력 형식 지정
	cout.setf(ios_base::boolalpha);

	// SSE2를 지원하는지 (Pentinum4, AMD K8 이상) 확인한다.
	if (!XMVerifyCPUSupport())
	{
		cout << "DirectXMath를 지원하지 않음" << endl;
		return 0;
	}
	
	XMVECTOR p = XMVectorSet(2.0f, 2.0f, 1.0f, 0.0f);
	XMVECTOR q = XMVectorSet(2.0f, -0.5f, 0.5f, 1.0f);
	XMVECTOR u = XMVectorSet(1.0f, 2.0f, 4.0f, 8.0f);
	XMVECTOR v = XMVectorSet(-2.0f, 1.0f, -3.0f, 2.5f);
	XMVECTOR w = XMVectorSet(0.0f, XM_PIDIV4, XM_PIDIV2, XM_PI);

	cout << "XMVECTORAbs(v)					= " << XMVectorAbs(v) << endl;
	cout << "XMVECTORCos(w)					= " << XMVectorCos(w) << endl;
	cout << "XMVECTORLog(u)					= " << XMVectorLog(u) << endl;
	cout << "XMVECTORExp(p)					= " << XMVectorExp(p) << endl;

	cout << "XMVECTORPow(u, p)				= " << XMVectorPow(u, p) << endl;
	cout << "XMVECTORSqrt(u)					= " << XMVectorSqrt(u) << endl;

	cout << "XMVectorSwizzle(u, 2, 2, 1, 3)			= " << XMVectorSwizzle(u, 2, 2, 1, 3) << endl;
	cout << "XMVectorSwizzle(u, 2, 2, 0, 3)			= " << XMVectorSwizzle(u, 2, 2, 0, 3) << endl;

	cout << "XMVectorMultiply(u, v)				= " << XMVectorMultiply(u, v) << endl;
	cout << "XMVectorSaturate(q)				= " << XMVectorSaturate(q) << endl;
	cout << "XMVectorMin(p, v)				= " << XMVectorMin(p, v) << endl;
	cout << "XMVectorMax(p, v)				= " << XMVectorMax(p, v) << endl;

}

결과값!

Log와 Exp의 경우 밑이 2인 것만 기억하면 된다. Swizzle의 경우, (벡터, index, index, index, index)로, 순서를 섞어준다.

마지막으로 Saturate는 아래와 같은 작업을 해 준다. saturate는 어떤 값을 0 ~ 1 범위의 값으로 조정해 주는 역할을 수행한다.

XMVECTOR Result;

Result.x = min(max(V1.x, 0.0f), 1.0f);
Result.y = min(max(V1.y, 0.0f), 1.0f);
Result.z = min(max(V1.z, 0.0f), 1.0f);
Result.w = min(max(V1.w, 0.0f), 1.0f);

return Result;
Comments