DirectX11 Tutorial 16: 프러스텀 컬링

강좌번역/DirectX 11 2013. 6. 5. 12:40 by 빠재

원문: http://rastertek.com/dx11tut16.html




화면에 그려지는 3차원 영역의 보이는 부분을 시야 프러스텀(viewing frustum, 시야 절두체, 역자주: 절두체라는 표현도 많이 쓰기는 하지만 이 글에서는 영어 발음대로 프러스텀이라는 용어를 쓰겠습니다)이라고 합니다. 프러스텀 안에 있는 모든 것은 비디오카드에 그려집니다. 반대로 프러스텀 바깥에 있는 것은 렌더링 과정에서 판단하고 그리지 않습니다.


하지만 큰 화면을 가진 경우 비디오 카드에 의존한 채 컬링을 하는 것은 많은 연산을 요구할 수 있습니다. 예를 들어 각각 5000개의 삼각형으로 이루어진 2000개 이상의 모델이 있는 장면에서 단지 10~20개의 모델만 시야에 있다고 해 봅시다. 비디오 카드는 시야에 있는 10개를 제외한 1990개의 모델들을 그리지 않기 위해서 2000개의 모델의 모든 삼각형을 시험해야 합니다. 충분히 상상할 수 있듯이 이것은 매우 비효율적입니다.


프러스텀 컬링은 렌더링 직전에 모델들이 프러스텀 내부에 있는지 아닌지 확인하는 일을 해 줍니다. 이를 통해 모든 삼각형들을 비디오 카드로 보내는 것이 아니라 정확히 그려야 할 것만 카드로 보낼 수 있게 됩니다. 이것이 처리되는 방식은 각각의 모델들을 정육면체나 그냥 육면체 또는 구체로 감싸고 이 물체가 보이는지는 정육면체나 상자, 구체가 보이는지의 여부로 판단하는 것입니다. 이를 위해 필요한 수학은 단지 몇줄뿐이지만 수천개의 삼각형들의 테스트 연산들을 절약해 줍니다.


이것이 어떻게 동작하는지를 보이기 위해서 우선 랜덤으로 생성된 25개의 구체들이 있는 장면을 생성할 것입니다. 그리고 나서 카메라를 왼쪽/오른쪽 버튼으로 돌리면서 시야 바깥에 있는 구체들이 컬링되는지 테스트할 것입니다. 또한 더 확실히 하기 위해서 현재 보이고 보이지 않는 구체들의 개수를 화면에 표시하도록 할 것입니다. 코드는 역시 이전 튜토리얼에서 이어집니다.





프레임워크


프레임워크는 주로 이전 튜토리얼들의 클래스로 구성되어 있습니다. 새로운 클래스는 FrustumClass, PositionClass, ModelListClass입니다. FrustumClass는 이 튜토리얼에서 다룰 프러스텀 컬링 기능을 캡슐화하고 있습니다. ModelListClass는 매 실행마다 임의로 생성된 25개의 구체들의 위치와 색상 정보를 포함합니다. PositionClass는 유저가 왼쪽/오른쪽 버튼을 누를때 시야 방향을 조절할 수 있도록 하는 기능을 담당할 것입니다.








Frustumclass.h


FrustumClass의 헤더 파일은 단순합니다. 이 클래스에서는 아무런 초기화나 마무리 함수를 요구하지 않습니다. 카메라가 한번 그리고 난 뒤로 각 프레임마다 ConstructFrustum 함수가 호출됩니다. ConstructFrustum 함수는 갱신된 카메라 위치를 기초로 m_planes 변수를 이용하여 시야 프러스텀의 6개 평면을 계산하고 저장합니다. 여기서 점이나 정육면체, 육면체, 구체 각각 시야 프러스텀 안에 있는지 그렇지 않은지 체크하는 4개의 함수를 만듭니다.


////////////////////////////////////////////////////////////////////////////////
// Filename: frustumclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _FRUSTUMCLASS_H_
#define _FRUSTUMCLASS_H_


//////////////
// INCLUDES //
//////////////
#include <d3dx10math.h>


////////////////////////////////////////////////////////////////////////////////
// Class name: FrustumClass
////////////////////////////////////////////////////////////////////////////////
class FrustumClass
{
public:
	FrustumClass();
	FrustumClass(const FrustumClass&);
	~FrustumClass();

	void ConstructFrustum(float, D3DXMATRIX, D3DXMATRIX);

	bool CheckPoint(float, float, float);
	bool CheckCube(float, float, float, float);
	bool CheckSphere(float, float, float, float);
	bool CheckRectangle(float, float, float, float, float, float);

private:
	D3DXPLANE m_planes[6];
};

#endif







Frustumclass.cpp


////////////////////////////////////////////////////////////////////////////////
// Filename: frustumclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "frustumclass.h"


FrustumClass::FrustumClass()
{
}


FrustumClass::FrustumClass(const FrustumClass& other)
{
}


FrustumClass::~FrustumClass()
{
}




GraphicsClass에 의해 ConstructFrustum 함수가 매 프레임마다 호출됩니다. GraphicsClass에서는 화면의 depth와 투영 행렬, 그리고 뷰 행렬을 전달합니다. 이 입력들을 이용하여 현재 프레임의 시야 프러스텀 행렬을 만듭니다. 새로운 프러스텀 행렬로 시야 프러스텀을 구성하는 여섯 개의 평면을 계산해 냅니다.


void FrustumClass::ConstructFrustum(float screenDepth, D3DXMATRIX projectionMatrix, D3DXMATRIX viewMatrix)
{
	float zMinimum, r;
	D3DXMATRIX matrix;

	
	// Calculate the minimum Z distance in the frustum.
	zMinimum = -projectionMatrix._43 / projectionMatrix._33;
	r = screenDepth / (screenDepth - zMinimum);
	projectionMatrix._33 = r;
	projectionMatrix._43 = -r * zMinimum;

	// Create the frustum matrix from the view matrix and updated projection matrix.
	D3DXMatrixMultiply(&matrix, &viewMatrix, &projectionMatrix);

	// Calculate near plane of frustum.
	m_planes[0].a = matrix._14 + matrix._13;
	m_planes[0].b = matrix._24 + matrix._23;
	m_planes[0].c = matrix._34 + matrix._33;
	m_planes[0].d = matrix._44 + matrix._43;
	D3DXPlaneNormalize(&m_planes[0], &m_planes[0]);

	// Calculate far plane of frustum.
	m_planes[1].a = matrix._14 - matrix._13; 
	m_planes[1].b = matrix._24 - matrix._23;
	m_planes[1].c = matrix._34 - matrix._33;
	m_planes[1].d = matrix._44 - matrix._43;
	D3DXPlaneNormalize(&m_planes[1], &m_planes[1]);

	// Calculate left plane of frustum.
	m_planes[2].a = matrix._14 + matrix._11; 
	m_planes[2].b = matrix._24 + matrix._21;
	m_planes[2].c = matrix._34 + matrix._31;
	m_planes[2].d = matrix._44 + matrix._41;
	D3DXPlaneNormalize(&m_planes[2], &m_planes[2]);

	// Calculate right plane of frustum.
	m_planes[3].a = matrix._14 - matrix._11; 
	m_planes[3].b = matrix._24 - matrix._21;
	m_planes[3].c = matrix._34 - matrix._31;
	m_planes[3].d = matrix._44 - matrix._41;
	D3DXPlaneNormalize(&m_planes[3], &m_planes[3]);

	// Calculate top plane of frustum.
	m_planes[4].a = matrix._14 - matrix._12; 
	m_planes[4].b = matrix._24 - matrix._22;
	m_planes[4].c = matrix._34 - matrix._32;
	m_planes[4].d = matrix._44 - matrix._42;
	D3DXPlaneNormalize(&m_planes[4], &m_planes[4]);

	// Calculate bottom plane of frustum.
	m_planes[5].a = matrix._14 + matrix._12;
	m_planes[5].b = matrix._24 + matrix._22;
	m_planes[5].c = matrix._34 + matrix._32;
	m_planes[5].d = matrix._44 + matrix._42;
	D3DXPlaneNormalize(&m_planes[5], &m_planes[5]);

	return;
}




CheckPoint 함수는 하나의 점이 시야 프러스텀 내부에 있는지 확인합니다. 소개할 네 개의 체크 알고리즘들 중에서 가장 일반적이지만 올바른 환경에서 사용되면 또한 가장 효율적이기도 합니다. 이 함수는 점의 좌표를 입력으로 받고 모든 여섯 개의 평면 안에 점이 있는지를 확인합니다. 리턴값은 포함 여부입니다.


bool FrustumClass::CheckPoint(float x, float y, float z)
{
	int i;


	// Check if the point is inside all six planes of the view frustum.
	for(i=0; i<6; i++) 
	{
		if(D3DXPlaneDotCoord(&m_planes[i], &D3DXVECTOR3(x, y, z)) < 0.0f)
		{
			return false;
		}
	}

	return true;
}





CheckCube 함수는 정육면체의 8개의 꼭지점 중 어느 것이라도 시야 프러스텀 내에 있는지 확인합니다. 입력으로는 육면체의 중점과 반경만 있으면 되며 이를 이용하여 여덟개의 꼭지점 위치를 계산할 수 있습니다. 만약 꼭지점 하나라도 프러스텀 내부에 있다면 true를 리턴하고, 그렇지 않으면 false를 리턴합니다.


bool FrustumClass::CheckCube(float xCenter, float yCenter, float zCenter, float radius)
{
	int i;


	// Check if any one point of the cube is in the view frustum.
	for(i=0; i<6; i++) 
	{
		if(D3DXPlaneDotCoord(&m_planes[i], &D3DXVECTOR3((xCenter - radius), (yCenter - radius), (zCenter - radius))) >= 0.0f)
		{
			continue;
		}
		
		if(D3DXPlaneDotCoord(&m_planes[i], &D3DXVECTOR3((xCenter + radius), (yCenter - radius), (zCenter - radius))) >= 0.0f)
		{
			continue;
		}

		if(D3DXPlaneDotCoord(&m_planes[i], &D3DXVECTOR3((xCenter - radius), (yCenter + radius), (zCenter - radius))) >= 0.0f)
		{
			continue;
		}

		if(D3DXPlaneDotCoord(&m_planes[i], &D3DXVECTOR3((xCenter + radius), (yCenter + radius), (zCenter - radius))) >= 0.0f)
		{
			continue;
		}

		if(D3DXPlaneDotCoord(&m_planes[i], &D3DXVECTOR3((xCenter - radius), (yCenter - radius), (zCenter + radius))) >= 0.0f)
		{
			continue;
		}

		if(D3DXPlaneDotCoord(&m_planes[i], &D3DXVECTOR3((xCenter + radius), (yCenter - radius), (zCenter + radius))) >= 0.0f)
		{
			continue;
		}

		if(D3DXPlaneDotCoord(&m_planes[i], &D3DXVECTOR3((xCenter - radius), (yCenter + radius), (zCenter + radius))) >= 0.0f)
		{
			continue;
		}
		
		if(D3DXPlaneDotCoord(&m_planes[i], &D3DXVECTOR3((xCenter + radius), (yCenter + radius), (zCenter + radius))) >= 0.0f)
		{
			continue;
		}

		return false;
	}

	return true;
}




CheckSphere 함수는 구의 중심이 시야 프러스텀의 여섯 평면에 반경 이내만큼 존재하는지를 확인합니다. 어느 것이라도 바깥에 있으면 구체는 보이지 않는 것이고 함수는 false를 리턴할 것입니다. 모든 평면의 안쪽에 있다면 구체는 보이는 것이므로 true를 리턴합니다.


bool FrustumClass::CheckSphere(float xCenter, float yCenter, float zCenter, float radius)
{
	int i;


	// Check if the radius of the sphere is inside the view frustum.
	for(i=0; i<6; i++) 
	{
		if(D3DXPlaneDotCoord(&m_planes[i], &D3DXVECTOR3(xCenter, yCenter, zCenter)) < -radius)
		{
			return false;
		}
	}

	return true;
}





CheckRectangle 함수는 축마다 크기가 달라서 x반경, y반경, z반경을 각각 받는다는 것만 제외하면CheckCube함수와 작동방식이 동일합니다. 어쨌든 꼭지점 계산은 되므로 이 좌표를 구해 CheckCube와 동일하게 확인합니다.


bool FrustumClass::CheckRectangle(float xCenter, float yCenter, float zCenter, float xSize, float ySize, float zSize)
{
	int i;


	// Check if any of the 6 planes of the rectangle are inside the view frustum.
	for(i=0; i<6; i++)
	{
		if(D3DXPlaneDotCoord(&m_planes[i], &D3DXVECTOR3((xCenter - xSize), (yCenter - ySize), (zCenter - zSize))) >= 0.0f)
		{
			continue;
		}

		if(D3DXPlaneDotCoord(&m_planes[i], &D3DXVECTOR3((xCenter + xSize), (yCenter - ySize), (zCenter - zSize))) >= 0.0f)
		{
			continue;
		}

		if(D3DXPlaneDotCoord(&m_planes[i], &D3DXVECTOR3((xCenter - xSize), (yCenter + ySize), (zCenter - zSize))) >= 0.0f)
		{
			continue;
		}

		if(D3DXPlaneDotCoord(&m_planes[i], &D3DXVECTOR3((xCenter - xSize), (yCenter - ySize), (zCenter + zSize))) >= 0.0f)
		{
			continue;
		}

		if(D3DXPlaneDotCoord(&m_planes[i], &D3DXVECTOR3((xCenter + xSize), (yCenter + ySize), (zCenter - zSize))) >= 0.0f)
		{
			continue;
		}

		if(D3DXPlaneDotCoord(&m_planes[i], &D3DXVECTOR3((xCenter + xSize), (yCenter - ySize), (zCenter + zSize))) >= 0.0f)
		{
			continue;
		}

		if(D3DXPlaneDotCoord(&m_planes[i], &D3DXVECTOR3((xCenter - xSize), (yCenter + ySize), (zCenter + zSize))) >= 0.0f)
		{
			continue;
		}

		if(D3DXPlaneDotCoord(&m_planes[i], &D3DXVECTOR3((xCenter + xSize), (yCenter + ySize), (zCenter + zSize))) >= 0.0f)
		{
			continue;
		}

		return false;
	}

	return true;
}






Modellistclass.h


ModelListClass 클래스는 장면의 모든 모델들에 대한 정보를 유지/관리하는 클래스입니다. 이 튜토리얼에서는 단지 한 가지 모델만 있으므로 지금은 구체 모델의 크기와 색상만을 유지합니다. 이 클래스는 한 장면 내의 다른 타입들의 모델들을 모두 관리할 수 있게 확장할 수 있으나 일단 지금은 단순하게 만들겠습니다.


///////////////////////////////////////////////////////////////////////////////
// Filename: modellistclass.h
///////////////////////////////////////////////////////////////////////////////
#ifndef _MODELLISTCLASS_H_
#define _MODELLISTCLASS_H_


//////////////
// INCLUDES //
//////////////
#include <d3dx10math.h>
#include <stdlib.h>
#include <time.h>


///////////////////////////////////////////////////////////////////////////////
// Class name: ModelListClass
///////////////////////////////////////////////////////////////////////////////
class ModelListClass
{
private:
	struct ModelInfoType
	{
		D3DXVECTOR4 color;
		float positionX, positionY, positionZ;
	};

public:
	ModelListClass();
	ModelListClass(const ModelListClass&);
	~ModelListClass();

	bool Initialize(int);
	void Shutdown();

	int GetModelCount();
	void GetData(int, float&, float&, float&, D3DXVECTOR4&);

private:
	int m_modelCount;
	ModelInfoType* m_ModelInfoList;
};

#endif







Modellistclass.cpp


///////////////////////////////////////////////////////////////////////////////
// Filename: modellistclass.cpp
///////////////////////////////////////////////////////////////////////////////
#include "modellistclass.h"



생성자에서는 모델 정보 리스트를 null로 초기화합니다.


ModelListClass::ModelListClass()
{
	m_ModelInfoList = 0;
}


ModelListClass::ModelListClass(const ModelListClass& other)
{
}


ModelListClass::~ModelListClass()
{
}


bool ModelListClass::Initialize(int numModels)
{
	int i;
	float red, green, blue;




우선 모델들의 숫자를 저장하고 ModelInfoType 타입의 배열을 만듭니다.


	// Store the number of models.
	m_modelCount = numModels;

	// Create a list array of the model information.
	m_ModelInfoList = new ModelInfoType[m_modelCount];
	if(!m_ModelInfoList)
	{
		return false;
	}




랜덤 숫자 생성기의 seed를 현재 시간으로 주고 임의로 색상과 위치를 정하여 모델 배열에 저장합니다.


	// Seed the random generator with the current time.
	srand((unsigned int)time(NULL));

	// Go through all the models and randomly generate the model color and position.
	for(i=0; i<m_modelCount; i++)
	{
		// Generate a random color for the model.
		red = (float)rand() / RAND_MAX;
		green = (float)rand() / RAND_MAX;
		blue = (float)rand() / RAND_MAX;

		m_ModelInfoList[i].color = D3DXVECTOR4(red, green, blue, 1.0f);

		// Generate a random position in front of the viewer for the mode.
		m_ModelInfoList[i].positionX = (((float)rand()-(float)rand())/RAND_MAX) * 10.0f;
		m_ModelInfoList[i].positionY = (((float)rand()-(float)rand())/RAND_MAX) * 10.0f;
		m_ModelInfoList[i].positionZ = ((((float)rand()-(float)rand())/RAND_MAX) * 10.0f) + 5.0f;
	}

	return true;
}




Shutdown 함수는 모델 정보가 담긴 배열을 해제합니다.


void ModelListClass::Shutdown()
{
	// Release the model information list.
	if(m_ModelInfoList)
	{
		delete [] m_ModelInfoList;
		m_ModelInfoList = 0;
	}

	return;
}




GetModelCount 함수는 현재 관리하고 있는 모델들의 숫자를 알려줍니다.


int ModelListClass::GetModelCount()
{
	return m_modelCount;
}




GetData 함수는 주어진 인덱스로 구체의 위치와 색상을 가져옵니다.


void ModelListClass::GetData(int index, float& positionX, float& positionY, float& positionZ, D3DXVECTOR4& color)
{
	positionX = m_ModelInfoList[index].positionX;
	positionY = m_ModelInfoList[index].positionY;
	positionZ = m_ModelInfoList[index].positionZ;

	color = m_ModelInfoList[index].color;

	return;
}










Graphicsclass.h


////////////////////////////////////////////////////////////////////////////////
// Filename: graphicsclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _GRAPHICSCLASS_H_
#define _GRAPHICSCLASS_H_


/////////////
// GLOBALS //
/////////////
const bool FULL_SCREEN = true;
const bool VSYNC_ENABLED = true;
const float SCREEN_DEPTH = 1000.0f;
const float SCREEN_NEAR = 0.1f;





이번 튜토리얼의 GraphicsClass 는 이전 튜토리얼의 많은 클래스들을 포함합니다. 또한 새로 frustumclass.h와 modellistclass.h 헤더 파일도 포함시킵니다.


///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "d3dclass.h"
#include "cameraclass.h"
#include "textclass.h"
#include "modelclass.h"
#include "lightshaderclass.h"
#include "lightclass.h"
#include "modellistclass.h"
#include "frustumclass.h"


////////////////////////////////////////////////////////////////////////////////
// Class name: GraphicsClass
////////////////////////////////////////////////////////////////////////////////
class GraphicsClass
{
public:
	GraphicsClass();
	GraphicsClass(const GraphicsClass&);
	~GraphicsClass();

	bool Initialize(int, int, HWND);
	void Shutdown();
	bool Frame(float);
	bool Render();

private:




두 새로운 전용 변수는 m_Frustum과 m_ModelList입니다.


	D3DClass* m_D3D;
	CameraClass* m_Camera;
	TextClass* m_Text;
	ModelClass* m_Model;
	LightShaderClass* m_LightShader;
	LightClass* m_Light;
	ModelListClass* m_ModelList;
	FrustumClass* m_Frustum;
};

#endif








Graphicsclass.cpp


이전 튜토리얼에서 달라진 것을 위주로 다루겠습니다.


////////////////////////////////////////////////////////////////////////////////
// Filename: graphicsclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "graphicsclass.h"




생성자에서는 모든 전용 변수들을 null로 초기화합니다.


GraphicsClass::GraphicsClass()
{
	m_D3D = 0;
	m_Camera = 0;
	m_Text = 0;
	m_Model = 0;
	m_LightShader = 0;
	m_Light = 0;
	m_ModelList = 0;
	m_Frustum = 0;
}


bool GraphicsClass::Initialize(int screenWidth, int screenHeight, HWND hwnd)
{
	bool result;
	D3DXMATRIX baseViewMatrix;

		
	// Create the Direct3D object.
	m_D3D = new D3DClass;
	if(!m_D3D)
	{
		return false;
	}

	// Initialize the Direct3D object.
	result = m_D3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NEAR);
	if(!result)
	{
		MessageBox(hwnd, L"Could not initialize Direct3D.", L"Error", MB_OK);
		return false;
	}

	// Create the camera object.
	m_Camera = new CameraClass;
	if(!m_Camera)
	{
		return false;
	}

	// Initialize a base view matrix with the camera for 2D user interface rendering.
	m_Camera->SetPosition(0.0f, 0.0f, -1.0f);
	m_Camera->Render();
	m_Camera->GetViewMatrix(baseViewMatrix);

	// Create the text object.
	m_Text = new TextClass;
	if(!m_Text)
	{
		return false;
	}

	// Initialize the text object.
	result = m_Text->Initialize(m_D3D->GetDevice(), m_D3D->GetDeviceContext(), hwnd, screenWidth, screenHeight, baseViewMatrix);
	if(!result)
	{
		MessageBox(hwnd, L"Could not initialize the text object.", L"Error", MB_OK);
		return false;
	}

	// Create the model object.
	m_Model = new ModelClass;
	if(!m_Model)
	{
		return false;
	}




이 튜토리얼에서는 육면체 모델 대신 구체 모델을 로드합니다.


	// Initialize the model object.
	result = m_Model->Initialize(m_D3D->GetDevice(), L"../Engine/data/seafloor.dds", "../Engine/data/sphere.txt");
	if(!result)
	{
		MessageBox(hwnd, L"Could not initialize the model object.", L"Error", MB_OK);
		return false;
	}

	// Create the light shader object.
	m_LightShader = new LightShaderClass;
	if(!m_LightShader)
	{
		return false;
	}

	// Initialize the light shader object.
	result = m_LightShader->Initialize(m_D3D->GetDevice(), hwnd);
	if(!result)
	{
		MessageBox(hwnd, L"Could not initialize the light shader object.", L"Error", MB_OK);
		return false;
	}

	// Create the light object.
	m_Light = new LightClass;
	if(!m_Light)
	{
		return false;
	}

	// Initialize the light object.
	m_Light->SetDirection(0.0f, 0.0f, 1.0f);




여기에 ModelListClass 객체를 생성하고 25개의 랜덤한 위치에 랜덤한 색상을 가진 구체 모델들을 만들도록 합니다.


	// Create the model list object.
	m_ModelList = new ModelListClass;
	if(!m_ModelList)
	{
		return false;
	}

	// Initialize the model list object.
	result = m_ModelList->Initialize(25);
	if(!result)
	{
		MessageBox(hwnd, L"Could not initialize the model list object.", L"Error", MB_OK);
		return false;
	}




여기에 FrustumClass 객체를 생성합니다. 매 프레임마다 ConstructFrustum 함수를 호출할 것이기 때문에 어떤 초기화 함수도 필요하지 않습니다.


	// Create the frustum object.
	m_Frustum = new FrustumClass;
	if(!m_Frustum)
	{
		return false;
	}

	return true;
}


void GraphicsClass::Shutdown()
{




FrustumClass와 ModelListClass를 Shutdown 함수에서 해제합니다.


	// Release the frustum object.
	if(m_Frustum)
	{
		delete m_Frustum;
		m_Frustum = 0;
	}

	// Release the model list object.
	if(m_ModelList)
	{
		m_ModelList->Shutdown();
		delete m_ModelList;
		m_ModelList = 0;
	}

	// Release the light object.
	if(m_Light)
	{
		delete m_Light;
		m_Light = 0;
	}

	// Release the light shader object.
	if(m_LightShader)
	{
		m_LightShader->Shutdown();
		delete m_LightShader;
		m_LightShader = 0;
	}

	// Release the model object.
	if(m_Model)
	{
		m_Model->Shutdown();
		delete m_Model;
		m_Model = 0;
	}

	// Release the text object.
	if(m_Text)
	{
		m_Text->Shutdown();
		delete m_Text;
		m_Text = 0;
	}

	// Release the camera object.
	if(m_Camera)
	{
		delete m_Camera;
		m_Camera = 0;
	}

	// Release the Direct3D object.
	if(m_D3D)
	{
		m_D3D->Shutdown();
		delete m_D3D;
		m_D3D = 0;
	}

	return;
}




Frame 함수는 SystemClass로부터 호출될 때 카메라의 회전값을 인자로 받습니다. Render 함수에서 뷰 행렬이 제대로 갱신될 수 있도록 카메라의 위치와 회전 정도를 세팅합니다.


bool GraphicsClass::Frame(float rotationY)
{
	// Set the position of the camera.
	m_Camera->SetPosition(0.0f, 0.0f, -10.0f);

	// Set the rotation of the camera.
	m_Camera->SetRotation(0.0f, rotationY, 0.0f);

	return true;
}


bool GraphicsClass::Render()
{
	D3DXMATRIX worldMatrix, viewMatrix, projectionMatrix, orthoMatrix;
	int modelCount, renderCount, index;
	float positionX, positionY, positionZ, radius;
	D3DXVECTOR4 color;
	bool renderModel, result;


	// Clear the buffers to begin the scene.
	m_D3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f);

	// Generate the view matrix based on the camera's position.
	m_Camera->Render();

	// Get the world, view, projection, and ortho matrices from the camera and d3d objects.
	m_D3D->GetWorldMatrix(worldMatrix);
	m_Camera->GetViewMatrix(viewMatrix);
	m_D3D->GetProjectionMatrix(projectionMatrix);
	m_D3D->GetOrthoMatrix(orthoMatrix);




Render 함수의 가장 큰 변화는 매 프레임마다 뷰 행렬에 근거하여 시야 프러스텀을 구축한다는 것입니다. 이 구축 과정은 뷰 행렬이 바뀌거나 우리의 프러스텀 확인이 맞지 않을 때 일어납니다.


	// Construct the frustum.
	m_Frustum->ConstructFrustum(SCREEN_DEPTH, projectionMatrix, viewMatrix);

	// Get the number of models that will be rendered.
	modelCount = m_ModelList->GetModelCount();

	// Initialize the count of models that have been rendered.
	renderCount = 0;




ModelListClass 객체의 모든 모델들을 돌아봅니다.


	// Go through all the models and render them only if they can be seen by the camera view.
	for(index=0; index<modelCount; index++)
	{
		// Get the position and color of the sphere model at this index.
		m_ModelList->GetData(index, positionX, positionY, positionZ, color);

		// Set the radius of the sphere to 1.0 since this is already known.
		radius = 1.0f;




여기서 FrustumClass 객체를 사용합니다. 구체가 현재 시야 프러스텀에 보이는지를 체크하는 것이지요. 만약 그렇다면 보이는 것이므로 그것을 그리고, 보이지 않는다면 그리지 않고 다음 것으로 넘어갑니다. 이 프러스텀 컬링을 통해 속도 향상을 얻을 수 있습니다.


		// Check if the sphere model is in the view frustum.
		renderModel = m_Frustum->CheckSphere(positionX, positionY, positionZ, radius);

		// If it can be seen then render it, if not skip this model and check the next sphere.
		if(renderModel)
		{
			// Move the model to the location it should be rendered at.
			D3DXMatrixTranslation(&worldMatrix, positionX, positionY, positionZ); 

			// Put the model vertex and index buffers on the graphics pipeline to prepare them for drawing.
			m_Model->Render(m_D3D->GetDeviceContext());

			// Render the model using the light shader.
			m_LightShader->Render(m_D3D->GetDeviceContext(), m_Model->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix, 
					      m_Model->GetTexture(), m_Light->GetDirection(), color);

			// Reset to the original world matrix.
			m_D3D->GetWorldMatrix(worldMatrix);

			// Since this model was rendered then increase the count for this frame.
			renderCount++;
		}
	}




실제로 몇 개의 구체들이 그려지는지 화면에 보여주기 위해서 TextClass를 약간 바꿉니다. FrustumClass 객체를 사용해서 그 숫자를 가져올 수 있는데, 그려지는 구체 수 대신 컬링되어 보이지 않게 된 숫자도 표시하게 할 수 있습니다.


	// Set the number of models that was actually rendered this frame.
	result = m_Text->SetRenderCount(renderCount, m_D3D->GetDeviceContext());
	if(!result)
	{
		return false;
	}

	// Turn off the Z buffer to begin all 2D rendering.
	m_D3D->TurnZBufferOff();

	// Turn on the alpha blending before rendering the text.
	m_D3D->TurnOnAlphaBlending();

	// Render the text string of the render count.
	m_Text->Render(m_D3D->GetDeviceContext(), worldMatrix, orthoMatrix);
	if(!result)
	{
		return false;
	}

	// Turn off alpha blending after rendering the text.
	m_D3D->TurnOffAlphaBlending();

	// Turn the Z buffer back on now that all 2D rendering has completed.
	m_D3D->TurnZBufferOn();

	// Present the rendered scene to the screen.
	m_D3D->EndScene();

	return true;
}








Positionclass.h


이 튜토리얼에서 카메라를 왼쪽/오른쪽 화살표 키로 조작하기 위해 카메라의 위치를 계산하고 유지하는 일을 하는 클래스를 만듭니다. 지금 당장 이 클래스는 좌우로 회전하는 것밖에 할 수 없지만 나중에 다른 움직임을 넣도록 확장할 수도 있습니다. 구현하는 움직임 중에는 부드러운 카메라 이동 효과를 위한 가속이나 감속도 포함됩니다.


////////////////////////////////////////////////////////////////////////////////
// Filename: positionclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _POSITIONCLASS_H_
#define _POSITIONCLASS_H_


//////////////
// INCLUDES //
//////////////
#include <math.h>


////////////////////////////////////////////////////////////////////////////////
// Class name: PositionClass
////////////////////////////////////////////////////////////////////////////////
class PositionClass
{
public:
	PositionClass();
	PositionClass(const PositionClass&);
	~PositionClass();

	void SetFrameTime(float);
	void GetRotation(float&);

	void TurnLeft(bool);
	void TurnRight(bool);

private:
	float m_frameTime;
	float m_rotationY;
	float m_leftTurnSpeed, m_rightTurnSpeed;
};

#endif







Positionclass.cpp


////////////////////////////////////////////////////////////////////////////////
// Filename: positionclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "positionclass.h"




생성자에서 전용 변수들을 0으로 초기화합니다.


PositionClass::PositionClass()
{
	m_frameTime = 0.0f;
	m_rotationY = 0.0f;
	m_leftTurnSpeed  = 0.0f;
	m_rightTurnSpeed = 0.0f;
}


PositionClass::PositionClass(const PositionClass& other)
{
}


PositionClass::~PositionClass()
{
}




SetFrameTime 함수는 이 클래스의 프레임 속도를 설정하기 위해 사용됩니다. PositionClass는 이 프레임 시간을 사용하여 카메라가 얼마나 빠르게 움직이고 회전하는지 계산합니다. 이 함수는 카메라가 움직이기 전에 매 프레임의 시작에 불리도록 해야 합니다.


void PositionClass::SetFrameTime(float time)
{
	m_frameTime = time;
	return;
}




GetRotation 함수는 카메라의 Y축 회전 정도를 리턴합니다. 이 튜토리얼을 위해 만든 도우미 함수일 뿐이지만 나중에 더 많은 정보를 알려주도록 확장될 수 있습니다.


void PositionClass::GetRotation(float& y)
{
	y = m_rotationY;
	return;
}




두 움직임 관련 함수는 같은 일을 합니다. 둘 다 매 프레임마다 호출됩니다. 각 함수마다 있는 keydown 인자는 각각 유저가 왼쪽이나 오른쪽 키를 눌렀는지를 나타냅니다. 만약 키가 눌려 있다면 매 프레임마다 최고 속도까지 해당 방향으로 가속됩니다. 이런 방식의 속도 증가는 자동차의 부드러운 움직임과 일맥상통합니다. 그와 비슷하게 유저가 키를 떼는 경우 keydown 변수는 false가 되고 그 때에는 매 프레임마다 속도가 일정한 비율로 부드럽게 줄어들어 마침내 0이 됩니다. 이 속도는 프레임간 시간에 기초해 있기 때문에 fps값에 상관없이 동일하게 움직입니다. 각 함수는 카메라의 새 위치를 계산하기 위해 간단한 연산을 합니다.


void PositionClass::TurnLeft(bool keydown)
{
	// If the key is pressed increase the speed at which the camera turns left.  If not slow down the turn speed.
	if(keydown)
	{
		m_leftTurnSpeed += m_frameTime * 0.01f;

		if(m_leftTurnSpeed > (m_frameTime * 0.15f))
		{
			m_leftTurnSpeed = m_frameTime * 0.15f;
		}
	}
	else
	{
		m_leftTurnSpeed -= m_frameTime* 0.005f;

		if(m_leftTurnSpeed < 0.0f)
		{
			m_leftTurnSpeed = 0.0f;
		}
	}

	// Update the rotation using the turning speed.
	m_rotationY -= m_leftTurnSpeed;
	if(m_rotationY < 0.0f)
	{
		m_rotationY += 360.0f;
	}

	return;
}


void PositionClass::TurnRight(bool keydown)
{
	// If the key is pressed increase the speed at which the camera turns right.  If not slow down the turn speed.
	if(keydown)
	{
		m_rightTurnSpeed += m_frameTime * 0.01f;

		if(m_rightTurnSpeed > (m_frameTime * 0.15f))
		{
			m_rightTurnSpeed = m_frameTime * 0.15f;
		}
	}
	else
	{
		m_rightTurnSpeed -= m_frameTime* 0.005f;

		if(m_rightTurnSpeed < 0.0f)
		{
			m_rightTurnSpeed = 0.0f;
		}
	}

	// Update the rotation using the turning speed.
	m_rotationY += m_rightTurnSpeed;
	if(m_rotationY > 360.0f)
	{
		m_rotationY -= 360.0f;
	}

	return;
}








Systemclass.h


PositionClass를 사용하기 위해 SystemClass 클래스를 수정합니다.


////////////////////////////////////////////////////////////////////////////////
// Filename: systemclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _SYSTEMCLASS_H_
#define _SYSTEMCLASS_H_


///////////////////////////////
// PRE-PROCESSING DIRECTIVES //
///////////////////////////////
#define WIN32_LEAN_AND_MEAN


//////////////
// INCLUDES //
//////////////
#include <windows.h>


///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "inputclass.h"
#include "graphicsclass.h"
#include "timerclass.h"
#include "positionclass.h"


////////////////////////////////////////////////////////////////////////////////
// Class name: SystemClass
////////////////////////////////////////////////////////////////////////////////
class SystemClass
{
public:
	SystemClass();
	SystemClass(const SystemClass&);
	~SystemClass();

	bool Initialize();
	void Shutdown();
	void Run();

	LRESULT CALLBACK MessageHandler(HWND, UINT, WPARAM, LPARAM);

private:
	bool Frame();
	void InitializeWindows(int&, int&);
	void ShutdownWindows();

private:
	LPCWSTR m_applicationName;
	HINSTANCE m_hinstance;
	HWND m_hwnd;

	InputClass* m_Input;
	GraphicsClass* m_Graphics;
	TimerClass* m_Timer;
	PositionClass* m_Position;
};


/////////////////////////
// FUNCTION PROTOTYPES //
/////////////////////////
static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);


/////////////
// GLOBALS //
/////////////
static SystemClass* ApplicationHandle = 0;


#endif








Systemclass.cpp


이전 튜토리얼과 달라진 점을 위주로 다루겠습니다.


////////////////////////////////////////////////////////////////////////////////
// Filename: systemclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "systemclass.h"


SystemClass::SystemClass()
{
	m_Input = 0;
	m_Graphics = 0;
	m_Timer = 0;

생성자에서 PositionClass 객체를 null로 초기화합니다.


	m_Position = 0;
}


bool SystemClass::Initialize()
{
	int screenWidth, screenHeight;
	bool result;


	// Initialize the width and height of the screen to zero before sending the variables into the function.
	screenWidth = 0;
	screenHeight = 0;

	// Initialize the windows api.
	InitializeWindows(screenWidth, screenHeight);

	// Create the input object.  This object will be used to handle reading the keyboard input from the user.
	m_Input = new InputClass;
	if(!m_Input)
	{
		return false;
	}

	// Initialize the input object.
	result = m_Input->Initialize(m_hinstance, m_hwnd, screenWidth, screenHeight);
	if(!result)
	{
		MessageBox(m_hwnd, L"Could not initialize the input object.", L"Error", MB_OK);
		return false;
	}

	// Create the graphics object.  This object will handle rendering all the graphics for this application.
	m_Graphics = new GraphicsClass;
	if(!m_Graphics)
	{
		return false;
	}

	// Initialize the graphics object.
	result = m_Graphics->Initialize(screenWidth, screenHeight, m_hwnd);
	if(!result)
	{
		return false;
	}
	
	// Create the timer object.
	m_Timer = new TimerClass;
	if(!m_Timer)
	{
		return false;
	}

	// Initialize the timer object.
	result = m_Timer->Initialize();
	if(!result)
	{
		MessageBox(m_hwnd, L"Could not initialize the Timer object.", L"Error", MB_OK);
		return false;
	}




PositionClass 객체를 생성합니다. 이 클래스는 초기화 과정이 필요없습니다.


	// Create the position object.
	m_Position = new PositionClass;
	if(!m_Position)
	{
		return false;
	}

	return true;
}


void SystemClass::Shutdown()
{




PositionClass 객체를 Shutdown 함수에서 해제합니다.


	// Release the position object.
	if(m_Position)
	{
		delete m_Position;
		m_Position = 0;
	}

	// Release the timer object.
	if(m_Timer)
	{
		delete m_Timer;
		m_Timer = 0;
	}

	// Release the graphics object.
	if(m_Graphics)
	{
		m_Graphics->Shutdown();
		delete m_Graphics;
		m_Graphics = 0;
	}

	// Release the input object.
	if(m_Input)
	{
		m_Input->Shutdown();
		delete m_Input;
		m_Input = 0;
	}

	// Shutdown the window.
	ShutdownWindows();
	
	return;
}


bool SystemClass::Frame()
{
	bool keyDown, result;
	float rotationY;


	// Update the system stats.
	m_Timer->Frame();

	// Do the input frame processing.
	result = m_Input->Frame();
	if(!result)
	{
		return false;
	}




매 프레임마다 PositionClass 객체의 프레임 시간을 갱신합니다.


	// Set the frame time for calculating the updated position.
	m_Position->SetFrameTime(m_Timer->GetTime());




프레임 시간을 갱신한 뒤 PositionClass의 현재 키보드 상태를 얻어와 움직임 관련 함수들을 호출합니다. 이 함수들은 카메라의 현재 위치를 갱신할 것입니다.


	// Check if the left or right arrow key has been pressed, if so rotate the camera accordingly.
	keyDown = m_Input->IsLeftArrowPressed();
	m_Position->TurnLeft(keyDown);

	keyDown = m_Input->IsRightArrowPressed();
	m_Position->TurnRight(keyDown);




카메라의 새 회전값을 받아 Graphics::Frame 함수에 전달하여 실제 카메라 위치를 반영합니다.


	// Get the current view point rotation.
	m_Position->GetRotation(rotationY);

	// Do the frame processing for the graphics object.
	result = m_Graphics->Frame(rotationY);
	if(!result)
	{
		return false;
	}

	// Finally render the graphics to the screen.
	result = m_Graphics->Render();
	if(!result)
	{
		return false;
	}

	return true;
}








마치면서


어떻게 오브젝트들이 컬링되는지 보았습니다. 서로 다른 모양의 오브젝트들을 빠르게 컬링하는 한 가지 트릭은 물체를 육면체나 구로 감싸거나 점 자체를 효율적으로 사용하는 것입니다.








연습 문제


1. 프로그램을 다시 컴파일하고 실행해 보십시오. 왼쪽/오른쪽 화살표 키를 이용하여 카메라를 움직이고 왼쪽 위 화면에 구체 수가 나오는지 확인하십시오.


2. CheckCube 함수를 확인하기 위해 정육면체 모델을 로드하도록 해 보십시오.


3. 몇 가지 다른 모델들을 만들고 어느 모양을 이용한 컬링 방식이 가장 좋은지 찾아 보십시오.







소스 코드


Visual Studio 2008 프로젝트: dx11tut16.zip


소스 코드: dx11src16.zip


실행 파일: dx11exe16.zip

Nav