원문 : http://www.rastertek.com/dx11tut16.html
Tutorial 16: Frustum Culling
모든 것이 그려지는 화면상의 3차원 뷰잉 영역은 "뷰잉 프러스텀(절두체)"이라고 부른다. 프러스텀안에 있는 모든 것들은 비디오카드에 의해 화면에 렌더링될 것입니다. 프러스텀 밖에 있는 모든 것들은 비디오 카드가 조사하고 렌더링 처리시 폐기할 것입니다.
그러나 골라내기 위해 비디오 카드에 의존한 처리는 우리가 큰 씬을 가지고 있다면 비쌀 수 있습니다. 예를 들어 각각 5000개의 폴리곤으로 이루어진 모델이 2000개 이상 있지만 주어진 시간에 10~20개만 볼 수 있는 씬을 가지고 있습니다. 비디오 카드는 씬에서 1990개의 모델들을 지우기 위해 모든 2000개의 모델들의 모든 삼각형들을 조사해야 하며 그래야 모델 10개를 그릴 수 있습니다. 보시다시피 이건 매우 비효율적입니다.
프러스텀 컬링이 우리의 문제를 해결하는 방법은 렌더링 전에 모델이 프러스텀 안에 있는지 아닌지를 대신에 알 수 있습니다. 이는 모든 삼각형을 비디오 카드에 보내는 것을 막아주며 그려질 삼각형만을 보내게 해줍니다. 이것을 하는 방법은 각 모델 주위로 큐브, 직육면체나 구를 놓고 큐브, 직육면체나 구가 볼 수 있는지를 계산하는 것입니다. 이것에 필요한 수학은 코드 몇줄로 되어 있으며 몇천개의 삼각형의 계산을 없애줍니다.
어떻게 동작하는지 설명하기 위해 먼저 랜덤하게 배치된 구 25개가 있는 씬을 만들 것입니다. 우리의 뷰 밖의 구들을 컬링하기 위해 손수 방향키를 왼쪽 오른쪽 누르며 카메라를 회전시킬 것입니다. 또 카운터를 사용하고 그려지고 있는 구들과 컬링 되지 않은 구들의 개수를 나타낼 것입니다. 씬을 만들기 위해 이전 몇몇 강좌들로 부터의 코드를 사용할 것입니다.
Framework
프레임워크는 거의 이전 몇몇 강좌들의 클래스들을 가집니다. 새로운 새 클래스 3개는 FrustumClass, PositionClass, ModelListClass입니다. FrustumClass은 이번 강좌가 포커스를 맞춘 프러스텀 컬링 기술을 캡슐화할 것입니다. ModelListClass는 우리가 프로그램을 실행할 때마다 랜덤하게 생성될 25개 구의 위치와 색상 정보의 리스트를 포함할 것입니다. PositionClass은 사용자가 누르고 있는 방향키에 따라 카메라의 회전을 처리할 것입니다.
Frustumclass.h
FrustumClass의 헤더 파일은 상당히 간단합니다. 클래스는 어떤 초기화나 정리가 필요하지 않습니다. 매 프레임마다 카메라가 렌더링되고 난 뒤 ConstructFrustum 함수가 호출됩니다. ConstructFrustum 함수는 갱신된 보는 위치에 따른 뷰 프러스텀의 6면을 계산하고 저장하기 위해 private m_planes를 사용합니다. 그로부터 점, 큐브, 구, 직육면체가 뷰잉 프러스텀 안에 있는지 아닌지 보기 위해 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()
{
}
ConstructFrustum은 매 프레임마다 GraphicsClass에 의해 호출됩니다. 화면의 깊이와 투영 행렬 그리고 뷰 행렬을 취합니다. 이 파라미터 변수들을 해당 프레임에서 뷰 프러스텀의 행렬을 계산하기 위해 사용합니다. 새 프러스텀 행렬과 함께 뷰 프러스텀을 형성하는 여섯 면을 계산합니다.
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는 한 점이 뷰잉 프러스텀안에 있는지를 확인합니다. 4개의 확인 알고리즘중 가장 일반적이지만 특정 상황에서 잘 쓰면 매우 효율적입니다. 한 점을 받아서 모든 여섯 면안에 있는지를 확인합니다. 안에 있다면 true를 반환하고 그렇지 않으면 false를 반환합니다.
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는 큐브의 여덟 꼭지점이 뷰잉 프러스텀에 있는지를 확인합니다. 파라미터로 큐브의 중앙점과 반경을 요구하며 큐브의 여덟 꼭지점을 계산하는데 사용합니다. 그러고는 뷰잉 프러스텀의 모든 6면안에 한 꼭지점이라도 있는지를 확인합니다. 찾으면 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은 씬에서 모든 모델들에 관한 정보를 유지하기 위한 새 클래스입니다. 이번 강좌에는 한가지 타입의 모델만 있어서 구 모델들의 위치와 색상을 유지합니다. 이 클래스는 다른 유형의 모델들을 유지하고 모델들의 ModelClass를 색인하기 위해 확장될 수 있으나 지금은 강좌를 간단하게 유지합니다.
///////////////////////////////////////////////////////////////////////////////
// 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:
새로운 두 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"
생성자는 private 멤버 변수들을 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()
{
Shutdown 함수의 여기서 새로운 FrustumClass와 ModelListClass 객체를 해제합니다.
// 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"
생성자는 private 멤버 변수들을 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이 될때까지 부드럽게 느려질 것입니다. 프레임 비율에 상관없이 스피드를 일정하게 하기 위해 스피드를 스페임 시간에 대해 계산합니다. 각 함수는 카메라의 새 위치를 계산하기 위해 약간의 기본적인 수학을 사용합니다.
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
SystemClass는 새로운 PositionClass를 사용하기 위해 수정되었습니다.
////////////////////////////////////////////////////////////////////////////////
// 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()
{
Shutdown 함수의 이곳에서 PositionClass 객체가 해제됩니다.
// 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());
그 후 현재 키보드 상태에 따라 이동 함수가 갱신됩니다. 이동 함수는 카메라의 위치를 이번 프레임에 대한 새로운 위치로 갱신할 것입니다.
// 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. 다른 모델들을 생성하고 그 모델을에 대해 어떤 컬링 확인이 가장 좋은지 테스트해 보세요.
'프로그래밍 > directx' 카테고리의 다른 글
DirectX 11 Tutorials - 17 : 멀티텍스쳐링과 텍스쳐 배열 (1) | 2016.12.23 |
---|---|
DirectX 11 Tutorials - 15 : FPS, CPU 사용량 그리고 타이머 (1) | 2016.06.23 |
DirectX 11 Tutorials - 14 : Direct 사운드 (0) | 2016.06.20 |
DirectX 11 Tutorials - 13 : Direct 입력 (2) | 2016.06.18 |
DirectX 11 Tutorials - 12 : 폰트 엔진 (0) | 2016.06.15 |