변환 파이프라인(Transformation pipeline)은 3차원 모델 좌표계의 정점들을 2차원 화면 좌표계로 변환하는 과정을 말한다.

 

변환  파이프라인은 4단계로 구성된다. 이 과정은 결과적으로 3차원 메시 표현을 2차원 컴퓨터 화면에 그릴 수 있는 2차원 정점 표현으로 투영하는 것이다.

 

1. 월드 변환(World Transformation)

2. 카메라 변환(View Transformation, Camera Transformation)

3. 투영 변환(Projection Transformation)

4. 화면 변환(Screen Transformation)

 

변환 파이프라인

 

평행이동 변환

 

평행이동 변환은 평행이동 변환을 T라고 할 때 수학적으로 다음과 같이 표현할 수 있다.

이것은 점(x, y, z)를 x축으로 △x만큼, y축으로 △y만큼, z축으로 △z만큼 평행이동시켜 새로운 위치로 옮기는 변환이다.

이 때 이동은 좌표축에 평행하도록 이루어지므로 평행이동이라고 한다.

3차원 좌표계에서 평행이동

 

모델 좌표계로 표현된 메시(Mesh)의 정점에 평행이동 변환을 적용하여 월드 좌표계로 변환(위치를 지정)하는데 사용할 수 있다. 즉 어떤 모델 좌표계의 원점을 월드 좌표계의 P(a, b, c)로 변환하는 평행이동 변환 T1이 있다면 이 변환을 모델 메시의 한 다각형을 구성하는 모든 정점에 적용하고 이 과정을 메시를 구성하는 모든 다각형에 적용한다면 메시를 월드 좌표계의 (a, b, c)에 위치를 정한 것이다.

 

평행이동 변환을 적용하면 게임 세계에서 어떤 객체의 위치 즉, 월드 좌표계의 좌표를 알고 있다면 그 객체의 메시를 월드 좌표계로 표현할 수 있다. 다음은 이 과정을 나타내는 예제이다.

PositionInWorld.x = 0;
PositionInWorld.y = 5;
PositionInWorld.z = 0;
for (each Polygon in Mesh)
{
    for(each Vertex in Polygon)
    {
        Vertex.x = PositionInWorld.x;
        Vertex.y = PositionInWorld.y;
        Vertex.z = PositinoInWorld.z;
    }
}

평행이동 변환의 개념을 사용하여 각 객체는 다음과 같은 자룍조로 표현할 수 있다. (PositionX, PositionY, PositionZ)는 객체의 월드 좌표계의 좌표 또는 위치를 나타낸다. 이런 방법으로 객체를 게임 세계에 배치(위치를 결정)할 수 있다.

이 표현에서 메시는 따로 존재하고 객체는 이 메시에 대한 포인터를 가진다. 이러한 표현은 하나의 메시가 여러 객체를 통해 공유될 수 있도록 하는 방법을 제공한다. 이렇게 하나의 메시가 여러 객체에 공유되는 것을 인스턴싱(Instancing)이라 한다.

class Object
{
public:
    CMesh *m_pMesh;
    float PositionX;
    float PositionY;
    float PositionZ;
}

 

객체 인스턴싱

객체를 위 그림과 같이 표현하는 경우 객체를 그리는 과정은 다음과 같은 코드로 설명할 수 있다.

Class CObject
{
public:
    CMesh*pMesh;
    float PositionX;
    float PositionY;
    float PositionZ;
}
Class CVertex
{
    float x;
    float y;
    float z;
}
Class CPolygon
{
    UNIT nVerticles;
    Vertex *pVertexList;
}
Class CMesh
{
    UNIT nFaces;
    Polygon *pFaceList;
}

 

 

int gnObjects;	// 게임 객체의 개수
class CObject
{
public:
	CMesh *pMesh;
	float PositionX;
	float PositionY;
	float PositionZ;
}
class CVertex
{
	float x;
	float y;
	float z;
}
class CPolygon
{
	UNIT nVerticles;
	Vertex *pVertexList;
}
class CMesh
{
	UNIT nFaces;
	Polygon *pFaceList;
}

int gnObjects;		// 게임 객체의 개수
CObject *gpObjects;	// 게임 객체 배열
CCamera *gpCamera;	// 게임 카메라

void DrawObjects() {
	for (int i = 0; i < gnObjects; i++)
	{
		Draw(&gpObjects[i]);
	}

}

void Draw(CObject *pObject) 
{
	static CVertex vtxPrevious;
	CMesh *pMesh = pObject->pMesh;
	for (int i = 0; i < pMesh->nFaces; i++)
	{
		CPolygon *pPolygon = &pMesh->pFaceList[i];
		for (int j = 0; j < pPolygon->nVertices; j++)
		{
			CVertex v = Transform(pPolygon->pVertexList[i], pObject);
			if (j != 0)
			{
				Draw2DLine(vtxPrevious.x, vtxprevious.y, v.x, v.y);
				vtxPrevious = v;
			}
		}
	}
}
CVertex Transform(CVertex vtxModel, CObject *pObject)
{
	CVertex vtxWorld = WorldTransform(vtxModel, pObject);
	CVertex vtxCamera = CameraTransform(vtxWorld, gpCamera);
	CVertex vtxProject = ProjectionTransform(vtxCamera);
	CVertex vtxScreen = ScreenTransform(vtxProject);
	return (vtxScreen);
}

CVertex WorldTransform(CVertex vtxModel, CObject *pObject)
{
	CVertex vtxWorld;
	vtxWorld.x = vtxModel.x + pObject->PositionX;
	vtxWorld.y = vtxModel.y + pObject->PositionY;
	vtxWorld.z = vtxModel.z + pObject->PositionZ;
	return (vtxWorld);
}

void Draw2DLine(int x1, int y1, int x2, int y2)
{
	HDC hDC = GetDC(ghWnd);
	MoveToEx(hDC, x1, y1, NULL);
	LineTo(hDC, x2, y2);
	ReleaseDC(ghWnd, hDC);
}

 

Draw2DLine() 함수는 2차원 화면의 두 점을 선분으로 그리는 함수라고 가정하자

Transform()함수는 3차원 정점을 2차원 화면 좌표계의 점으로 변환하는 함수이다.

WorldTransform()함수는 모델 좌표계의 정점을 월드 좌표계의 정점으로 변환하는 함수이다.  (평행이동만 포함하고 있다.)

 

 

회전 변환

 

x축을 중심으로 θ 만큼 회전하는 회전 변환을 Rx라고 할 때 이 변환은 수학적으로 다음과 같이 표현할 수 있다.

 

y축을 중심으로 θ 만큼 회전하는 회전 변환을 Ry라고 할 때 이 변환은 수학적으로 다음과 같이 표현할 수 있다.

 

z축을 중심으로 θ 만큼 회전하는 회전 변환을 Rz라고 할 때 이 변환은 수학적으로 다음과 같이 표현할 수 있다.

z 축을 중심으로 θ만큼 회전

어떤 점을 주어진 축을 중심으로 회전할 때 회전축에 해당 좌표는 변하지 않는다. 예를 들어, x축을 중심으로 회전할 때는 x 좌표는 변하지 않고 회전의 결과로 y 좌표와 z 좌표만 변하게 된다.

 

회전 변환은 항상 좌표계의 원점에 대한 회전을 표현하므로 회전 변환과 평행이동 변환이 모두 적용되어야 하는 경우 변환의 순서에 주의해야 한다.

 

 

월드 변환

게임 세상에서 객체의 위치와 방향을 나타내려면 객체의 표현에는 평행이동과 회전 정보가 포함되어야 한다. 

평행이동 변환과 회전 변환의 개념을 모두 사용하여 게임의 각 객체를 다음과 같은 자료구조로 표현할 수 있다.

class CObject
{
public:
	CMesh *pMesh;	 // 모양
	float PositionX; // 위치
	float PositionY;
	float PositionZ;
	float RotationX; // 방향
	float RotationY;
	float RotationZ;
}

앞의 자료구조를 사용하여 표현된 객체들의 정점을 변환하는 함수 Transform()은 다음과 같다. 이 함수는 다각형의 각 정점에 대하여 회전 변환을 적용한 다음 평행이동 변환을 수행하고 있다. 이러한 변환의 결과로 각 객체는 월드 좌표계에서 위치와 방향을 가지게 된다.

 

이렇게 회전 변환과 평행이동 변환을 통하여 모델 좌표계로 표현된 객체의 메시가 월드 좌표계로 변환되는 과정을 월드 좌표 변환(World Transformation) 또는 월드 변환이라고 한다.

 

CVertex WorldTransform(CVertex vtxModel, CObject *pObject) {
	float fPitch = pObject->RotationX,
		  fYaw   = pObject->RotationY,
		  fRoll  = pObject->RotationZ;
	CVertex vtxWorld = vtxModel, vtxRotated;
	if (fPitch) { // x axis
		vtxRotated.y = vtxWorld.y * cos(fPitch) - vtxWorld.z * sin(fPitch);
		vtxRotated.z = vtxWorld.z * sin(fPitch) + vtxWorld.z * cos(fPitch);
		vtxWorld = vtxRotated;
	}
	if (fYaw) { // y axis
		vtxRotated.x = vtxWorld.x * cos(fYaw) + vtxWorld.z * sin(fYaw);
		vtxRotated.z = -vtxWorld.x * sin(fYaw) + vtxWorld.z * cos(fYaw);
		vtxWorld = vtxRotated;
	}
	if (fRoll) { // z axis
		vtxRotated.x = vtxWorld.x * cos(fRoll) + vtxWorld.y * sin(fRoll);
		vtxRotated.y = vtxWorld.x * sin(fRoll) + vtxWorld.y * cos(fRoll);
		vtxWorld = vtxRotated;
	}

	vtxWorld.x += pObject->PositionX;
	vtxWorld.y += pObject->PositionY;
	vtxWorld.z += pObject->PositionZ;
	return (vtxWorld);
}

 

yaw, pitch, roll

 

Yaw, pitch, roll에 대한 설명 (https://www.serola.net/research-entry/pitch-roll-yaw/)

 

 

카메라 변환

 

 

'게임 개발 > DirectX' 카테고리의 다른 글

3D 그래픽 파이프라인  (0) 2019.10.16
Direct3D -5- 벡터  (0) 2019.10.05
Direct3D -4- 벡터  (0) 2019.10.05
Direct3D -3-  (0) 2019.10.04
Direct3D -2- (Win32 API에서 Direct3d 구동하기)  (0) 2019.10.04

3D 그래픽을 사용하는 게임들이 포함하고 있는 모듈들.

- 사용자 입력(User Input)

- 자원 관리(Resource Management)

- 그래픽 로딩과 렌더링(Loading and Rendering Graphics)

- 스크립트 해석과 실행(Interpreting and Executing Scripts)

- 음향 처리(Playing Sound Effects)

- 인공 지능(Artificial Intelligence)

 

위의 소스 코드 모듈은 집합적으로 게임 엔진(Game Engine)을 구성한다.

 

 

게임 엔진의 구성

 

객체의 표현과 렌더링

 

렌더링(Rendering)이란 3D 게임 세계의 객체들을 컴퓨터의 화면(2D)으로 그리는 것, 또는 그리는 과정이다.

 

메시(Mesh)는 게임 공간 또는 게임 세계에서 하나의 객체(Object)를 시각적으로 표현하기 위한 개념이다.

메시는 객체의 외관을 생성하기 위해 연결된 다강형들의 집합이라고 정의 할 수 있다.

가장 간단한 다각형은 삼각형이다. 일반적으로 메시는 삼각형들의 집합으로 표현된다. 모델(Model)이라고도 한다.

 

면(Face)은 메시를 구성하고 있다. 각 면은 3차원 공간에서 정의 되는 점들을 일련의 선분으로 연결함으로써 생성된다.

 

정점(Vertex)는 메시를 구성하는 점들이다.

 

텍스쳐 맵(Texture Map)이라고 하는 2차원 이미지가 메시(면)의 질감과 색상을 표현하기 위해 사용될 수 있다.

 

렌더링의 과정은 크게 변환(Transformatino)과 색칠(Coloring, Lighting)하기로 구분할 수 있다.

 

변환 과정은 다각형의 3D 좌표를 2D 화면 좌표로 바꾸는 것을 의미한다.

색칠하기 과정은 화면에 그려지는 다각형들의 점들의 색상을 결정하는 것이며 조명처리 또는 텍스처 매핑 등의 개념이 포함된다.

 

 

좌표계

 

메시를 기하학적으로 표현하려면 좌표계가 필요하다.

 

2차원 좌표계

2차원 공간을 표현하기 위한 좌표계로 직교 좌표계(Cartesian Coodinate System)를 사용한다.

2차원 직교 좌표계

 

 

화면 좌표계

모니터 디스플레이 화면을 정의하는 좌표계를 화면 좌표계 또는 스크린 좌표계(Screen Coordinate System)이라고 한다.

디스플레이 화면의 왼쪽 위는 화면 좌표계의 원점이 되고 좌표축의 방향은 원점에서 오른쪽으로 가면서 x축이 증가하고 원점에서 아래 방향으로 가면서 y축이 증가한다.

화면 좌표계

3차원 좌표계

3차원 공간을 표현하기 위해 3차원 직교 좌표계를 사용한다.

왼손 좌표계, 오른손 좌표계

왼손 좌표계(Left Hand Coordinates)와 오른손 좌표계(Right Hand Coordinates)가 있는데

Direct3D는 왼손 좌표계를 사용한다.

 

3차원 좌표계에서 점을 표현하기 위한 자료구조는 다음과 같다.

struct 3Dpoint
{
    float x;
    float y;
    float z;
};

 

3차원 좌표계에서 정육면체를 나타내면 다음과 같다.

3차원 좌표계에서 정육면체 표현

 

3차원 좌표계에서 면 또는 다각형은 3차원 점들의 순서화된 리스트로 표현할 수 있고 메시는 이렇게 표현된 다각형의 집합으로 표현할 수 있다. 이러한 개념을 구조체로 표현한다면 

 

C언어

struct Vertex
{
    float x;
    float y;
    float z;
};
struct Polygon
{
    UINT nVertices;
    Vertex *pVertexList;
};
struct Mesh
{
    UINT nFaces;
    Polygon *pFaceList;
};

 

C++언어 의 클래스로도 표현

class CVertex
{
public:
    CVertex(float fX, float fY, float fZ);
    CVertex();
    float x;
    float y;
    float z;
};

 

모델 좌표계와 월드 좌표계

모델 좌표계는 각 객체의 모델 메시의 점을 표현하기 위한 좌표계이다. 보통 메시의 중심이 모델 좌표계의 원점이 되며 메시의 각 점은 이 원점에 상대적인 좌표로 표현된다. (메시를 구성하는 점들은 정점(Vertex)이라고 하며 객체를 표현하는 메시의 객체의 모델(Model)이라고 한다.

 

모델 좌표계는 각 모델을 표현하기 위해 사용하는 지역 좌표계(Local Coordinate System)이다.

객체의 지역적 공간(Object Local Space)을 표현하는 좌표계이다.

 

월드 좌표계는 게임 공간에 존재하는 객체들의 위치와 뱡향을 표현하기 위한 좌표계이다. 게임 세계 전체를 하나의 통일된 좌표계로 표현하기 위한 좌표계이며, 게임에 나타나는 모든 객체에 적용될 수 있는 전역 좌표계(Global Coordinates System)이다.

 

모델이 화면에 렌더링되기 위해서 모델 좌표계로 표현된 메시는 월드 좌표계로 변환된다. 이때 월드 변환 행렬이 적용된다.

 

와인딩 순서(Winding Order)는 메시를 구성하는 다각형의 정점들을 나열하는 순서를 말한다. 즉, 다각형의 정점 또는 모서리(Edge)가 어떤 순서로 연결되는지를 나타낸다. 

 

와인딩 순서는 시계 방향(Clockwise) 순서와 반시계 방향(Counter Clockwise) 순서가 있다. 

시계 방향, 반시계 반향

와인딩 순서는 다각형의 면이 3D 객체의 바깥쪽을 향하게 될 때 즉, 바깥쪽 면이 될 때를 기준으로 정한다.

모델 메시의 다각형들을 정의할 때 모든 다각형의 와인딩 순서는 시계 방향이 되도록 하여야 한다. 와인딩 순서는 은면 제거(Back Face Culling)를 수행하기 위해 사용된다.

 

은면은 게임 플레이어가 볼 수 없는 숨겨진 면을 일컫는다.

은면 제거는 다각형을 바라보는 방향(카메라 방향)과 반대 방향인 다각형을 렌더링 과정에서 제거하는 것을 말한다.

Unity는 시계방향(CW)으로 와인딩 순서를 사용한다. 블렌더는 시계 반대 방향으로 와인딩 한다.

Direct3D에서는 기본적으로 시계방향으로 와인딩 순서를 사용한다. 이것은 카메라의 관점에서 정점이 나열되는 순서가 시계 방향이면 은면이 아님을 의미한다.

 

정육면체의 와인딩 순서(CW)

 

화면 렌더링

그래픽 엔진은 매 순간 3차원 게임 세계의 한 장면을 2차원 디스플레이 화면에 그리는 과정을 반복한다.

렌더링 과정은 3차원 게임의 한 장면을 구성하는 모든 메시들을 그리는 것이라고 볼 수 있다. 메시들은 다각형들의 집합이므로 결과적으로 렌더링은 메시를 구성하는 다각형을 그리는 것이라 볼 수 있다. 이때 다각형을 그린다는 것은 다각형을 구성하는 점들(Vertices)을 그리는(찍는)것이라 볼 수 있다.

 

이때 3차원 세상을 2차원 평면에 그리는 과정을 화가의 알고리즘(Painter's Algorithm)이라고 한다.

 

화면 렌더링 과정은 다음과 같은 코드로 표현될 수 있다.

void RenderScene()
{
    for(int i = 0; i < nMeshes; i++)
    {
         CMesh *pMesh = gpMeshArray[i];
         for(int k = 0; k < pMesh->nFaces; k++)
         {
             CPolygon *pPoly = &pMesh->pFaceList[k];
             Draw(pPoly->pVertexList, pPoly->nVertices);
         }
    }
}

3차원 좌표에서 화면 공간 좌표로 변환된 다각형의 정점들이 얻어지면 각 정점은 화면의  픽셀의 위치를 나타낸다.

 

'게임 개발 > DirectX' 카테고리의 다른 글

변환 파이프라인  (0) 2019.10.16
Direct3D -5- 벡터  (0) 2019.10.05
Direct3D -4- 벡터  (0) 2019.10.05
Direct3D -3-  (0) 2019.10.04
Direct3D -2- (Win32 API에서 Direct3d 구동하기)  (0) 2019.10.04

Win.cpp 파일에 

 

메시지 루프

	// Main message loop:
	while (GetMessage(&msg, nullptr, 0, 0))
	{
		if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
	}

TranslateMessage에서 키보드 입력에 관련된 문자 키에 대한 것을 번역하고

DispatchMessager를 통해서 윈도우 프로시저에 실행이 될 수 있도록 OS에 요청한다. 

 

 GetMessage는 문제가 있다. 버퍼에 있는가를 계속 조사한다. 없으면 대기 상태에 들어가있다.

위의 while루프를 게임 루프로 실행하게되면 Update Rander를 실행하지 못하게 된다. (이 소스를 그대로 사용하게 되면 마우스를 가지고 계속 흔들어서 메시지를 발생 시켜야 된다.)

따라서 이 부분을 아래와 같이 변형시킵니다.

while (true)
{
	if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) 
	{
		if (msg.message == WM_QUIT) {
			break;
		}
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	else {
		// update and rendering
	}
}

"PM_REMOVE"는 메시지가 있으면 메시지 버퍼를 제거해주는 역할을 한다 

"WM_QUIT"는 윈도우가 종료할 때 나오는 메시지이다. 

 

'게임 개발 > DirectX' 카테고리의 다른 글

3D 그래픽 파이프라인  (0) 2019.10.16
Direct3D -5- 벡터  (0) 2019.10.05
Direct3D -4- 벡터  (0) 2019.10.05
Direct3D -3-  (0) 2019.10.04
Direct3D -2- (Win32 API에서 Direct3d 구동하기)  (0) 2019.10.04

+ Recent posts