변환 파이프라인(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

+ Recent posts