원문 : http://www.rastertek.com/dx11tut04.html
Tutorial 4: Buffers, Shaders, and HLSL
이번 강좌는 DirectX 11에서 버텍스 쉐이더와 픽셀 쉐이더를 작성해 볼 것입니다. 또 DirectX 11에서 버텍스 버퍼와 인덱스 버퍼도 사용해 볼 것입니다. 방금 얘기한 것들은 여러분이 3D 그래픽을 렌더링하는 것을 이해하고 활용하는데 매우 기본적인 개념들 입니다.
Vertex Buffers
이해해야 할 첫번째 개념은 바로 버텍스 버퍼입니다. 이 개념을 설명하기 위해 구의 3D 모델을 예시로 살펴 보도록 하겠습니다.
이 3D 구는 실제로는 수백개의 삼각형으로 구성되어 있습니다.
이 구 모델의 각 삼각형들은 삼각형을 이루는 3개의 점을 가지며 우리는 이것을 "정점"이라고 부릅니다. 그래서 우리가 구 모델을 렌더링하기 위해 구를 이루는 모든 정점들을 "버텍스 버퍼"라고 부르는 특별한 배열에 넣어야 합니다. 구 모델의 모든 점들을 버텍스 버퍼에 넣으면 GPU에 버텍스 버퍼를 전달하여 모델을 렌더링 할 수 있습니다.
Index Buffers
인덱스 버퍼는 버텍스 버퍼와 연관이 있습니다. 인덱스 버퍼는 버텍스 버퍼에 있는 각 정점의 위치를 기록하기 위함입니다. 그러면 GPU는 버텍스 버퍼의 특정 정점들을 빠르게 찾는데에 인덱스 버퍼를 사용합니다. 인덱스 버퍼의 개념은 책에서 "색인"(혹은 찾아보기)을 사용하는 개념과 비슷해서 여러분이 찾고 있는 주제를 더 빠르게 찾도록 돕습니다. DirectX SDK 공식문서에서 말하기를 인덱스 버퍼를 사용하는 것은 비디오 메모리에서 정점 데이터의 캐싱의 가능성을 높인다고
합니다. 그래서 인덱스 버퍼의 사용은 성능 향상의 측면에서도 매우 권고됩니다.
Vertex Shaders
버텍스 쉐이더는 주로 버텍스 버퍼의 정점들을 3D 공간으로 변형시키도록 작성된 작은 프로그램입니다. 각 정점에 대한 법선을 계산하는 것과 같은 다른 연산들도 들어갑니다. 처리해야할 각 정점에 대해 GPU가 버텍스 쉐이더를 호출 할 것입니다. 예를들어 5,000개의 폴리곤으로 이루어진 모델 하나를 그리기 위해 매 프레임마다 15,000번의 버텍스 쉐이더 프로그램이 실행 될 것입니다. 또 만약 여러분의 그래픽 프로그램을 60 fps로 고정한다면 5,000개의 삼각형을 그리기 위해 버텍스 쉐이더를 초당 900,000번 호출 할 것입니다. 여러분도 알듯이 효율적인 버텍스 쉐이더를 작성하는 것은 매우 중요합니다.
Pixel Shaders
픽셀 쉐이더는 우리가 그리는 폴리곤들의 색상을 입히는 것이 작성되는 작은 프로그램입니다. 화면에 그려질 모든 볼 수 있는 픽셀들에 대해 GPU에서 동작합니다. 색상 입히기, 텍스쳐 입히기, 빛 처리 그리고 대부분의 여러분의 폴리곤의 면에 보여 지게 될 모든 효과들은 픽셀 쉐이더 프로그램에 의해 처리됩니다. 픽셀 쉐이더는 GPU에 의해 호출되는 횟수 때문에 반드시 효율적으로 작성되어야 합니다.
HLSL
HLSL은 우리가 DirectX 11에서 버텍스 쉐이더, 픽셀 쉐이더를 작성하기위해 사용하는 언어입니다. 문법은 미리 정의된 타입들과 함께 C 언어와 거의 같습니다. HLSL 프로그램은 전역 변수, 타입 정의, 버텍스 쉐이더, 픽셀 쉐이더 그리고 지오메트리 쉐이더로 구성되어 있습니다. 이번 강좌는 HLSL은 처음이기 때문에 DirectX 11을 이용하여 매우 간단한 HLSL프로그램을 만들어 볼 것입니다.
Updated Framework
이번 강좌를 위해 프레임워크를 업데이트 하였습니다. GraphicsClass 밑에 CameraClass, ModelClass 그리고 ColorShaderClass 3개를 추가하였습니다. CameraClass는 전에 얘기했던 뷰 행렬을 다룰 것입니다. 월드 좌표상의 카메라 위치를 처리하고 그래픽을 그릴때 쉐이더에 넘겨서 우리가 씬을 어디서 보고있는지 알아 낼 것입니다. ModelClass는 우리의 3D 모델의 기하학적인 것을 다룰 것 인데 이번 강좌에서 3D 모델은 간단하게 삼각형 하나가 다 입니다. 그리고 마지막으로 ColorShaderClass는 HLSL을 호출하여 모델을 화면상에 렌더링하는데 사용 될 것입니다.
먼저 HLSL 쉐이더 프로그램을 보며 이번 강좌 코드 리뷰를 시작하겠습니다.
Color.vs
Color.vs는 우리의 첫 쉐이더 프로그램이 될 것입니다. 쉐ㅣ더는 모델들의 실제적인 렌더링을 하는 작은 프로그램입니다. 이 쉐이더는 HLSL로 작성되며 소스파일 color.vs와 color.ps에 저장됩니다. 저는 이 파일을 지금은 엔진의 .h와 .cpp과 함께 두었습니다. 이번 쉐이더의 목적은 이번 첫 HLSL 강좌에서 가능한 간단하게 하기 위해 원색의 삼각형을 그리는 것이 다 입니다. 먼저 버텍스 쉐이더 코드입니다.
////////////////////////////////////////////////////////////////////////////////
// Filename: color.vs
////////////////////////////////////////////////////////////////////////////////
먼저 전역 변수로 시작합니다. 이 전역변수들은 여러분의 C++코드에서 수정될 수 있습니다. 여러분은 int, float과 같은 많은 변수형을 사용할 수 있고 쉐이더 프로그램이 사용하도록 외부에서 설정할 수 있습니다. 일반적으로 cbuffer라 부르는 버퍼 오브젝트 형에 대부분의 전역변수들을 넣을 것이며 한개일 때도 마찬가지입니다. 논리적으로 이러한 버퍼들을 만드는 거슨 쉐이더의 효출적인 실행뿐만 아니라 그래픽 카드가 버퍼들을 어떻게 저장할지에 대해서도 중요합니다. 이번 예제에서는 같은 버퍼에 3개의 행렬 변수를 넣었습니다. 매 프레임마다 동시에 업데이트 되기 때문입니다.
/////////////
// GLOBALS //
/////////////
cbuffer MatrixBuffer
{
matrix worldMatrix;
matrix viewMatrix;
matrix projectionMatrix;
};
C언어와 같이 우리가 원하는 자료형을 정의할 수 있습니다. 우리는 float4와 같이 쉐이더 작성을 쉽고 가독성이 좋게하고 HLSL에 사용가능한 다른 자료형들을 사용할 것입니다. 이번 예제에서는 x, y, z, w 값을 가지는 position 벡터와 빛의 삼원색(RGB)과 알파값을 가지는 color로 이루어진 자료형을 만들 것입니다. POSITION, COLOR 그리고 SV_POSITION 은 GPU에게 변수의 사용을 전달하는 의미입니다. 두 구조체가 동일하지만 버텍스, 픽셀 쉐이더에게 의미가 다르기 때문에 구조체를 두개로 만들어야 합니다. COLOR 는 두 쉐이더에 대해 동일하게 동작하지만 POSITION 은 버텍스 쉐이더에 대해 동작하고 SV_POSITION 은 픽셀 쉐이더에 대해 동작합니다. 만야 같은 자료형을 더 추가하고 싶으면 COLOR0, COLOR1 등등과 같이 뒤에 숫자를 추가하면 됩니다.
//////////////
// TYPEDEFS //
//////////////
struct VertexInputType
{
float4 position : POSITION;
float4 color : COLOR;
};
struct PixelInputType
{
float4 position : SV_POSITION;
float4 color : COLOR;
};
버텍스 쉐이더는 쉐이더 안으로 보내진 버텍스 버퍼의 데이터를 처리할 때 GPU에서 호출됩니다. ColorVertexShader 라고 이름 붙인 버텍스 쉐이더는 버텍스 버퍼안의 모든 정점 하나하나에 대해 호출 될 것입니다. 버텍스 쉐이더로의 입력은 반드시 버텍스 버퍼 내부 데이터의 형식과 쉐이더 소스 파일 이번 경우 VertexInputType 내에 자료형 정의와도 일치해야 합니다. 버텍스 쉐이더의 출력은 픽셀 쉐이더로 보내져 픽셀 쉐이더의 입력이 됩니다. 이번 경우에는 반환 형식은 위에서 정의된 PixelInputType 입니다.
방금 말했던 것처럼 버텍스 쉐이더가 PixelInputType 형 변수를 만드는 것이 보이실 것입니다. 그리곤 입력 정점의 위치를 취하고 월드, 뷰, 투영 행렬에 차례로 곱해집니다. 정점은 3D 공간에서 뷰에 따라 렌더링하여 2D 화면 위 맞는 위치로 바뀔 것입니다. output 변수가 input의 color의 카피를 취하고 픽셀 쉐이더에 input으로 사용 될 output을 반환합니다. 또 input.position의 W값을 1.0으로 설정한 것을 주의하세요. 그렇지 않으면 위치를 계산하느데 XYZ만 읽기 때문에 W값이 정의되지 않습니다.
//////////////////////////////////////////////////////////////////////////////// // Vertex Shader //////////////////////////////////////////////////////////////////////////////// PixelInputType ColorVertexShader(VertexInputType input) { PixelInputType output; // 적절한 행렬 연산을 위해 벡터의 4번째 요소를 추가(? 원래 있었지만) 합니다. input.position.w = 1.0f; // 정점의 위치를 월드, 뷰, 투영 행렬에 대해 계산합니다. output.position = mul(input.position, worldMatrix); output.position = mul(output.position, viewMatrix); output.position = mul(output.position, projectionMatrix); // 입력 색상을 픽셀 쉐이더가 사용하도록 저장합니다. output.color = input.color; return output; }
Color.ps
픽셀 쉐이더는 화면상에 렌더링 될 폴리곤의 각 픽셀들을 그립니다. 픽셀 쉐이더에서 PixelInputType을 입력으로 사용하고 최종 픽샐 색상으로 표현 될 출력으로 'float4' 변수 하나를 반환합니다. 이 픽셀 쉐이더 프로그램은 입력으로 들어온 색상 값을 그대로 반환합니다. 픽셀 쉐이더의 입력은 버텍스 쉐이더의 출력에서 얻는 것을 주시하시기 바랍니다.
////////////////////////////////////////////////////////////////////////////////
// Filename: color.ps
////////////////////////////////////////////////////////////////////////////////
//////////////
// TYPEDEFS //
//////////////
struct PixelInputType
{
float4 position : SV_POSITION;
float4 color : COLOR;
};
////////////////////////////////////////////////////////////////////////////////
// Pixel Shader
////////////////////////////////////////////////////////////////////////////////
float4 ColorPixelShader(PixelInputType input) : SV_TARGET
{
return input.color;
}
Modelclass.h
앞에서 언급했듯이 ModelClass은 3D 모델의 기하학적 부분을 캡슐화 해야 합니다. 이번 강좌에서는 직접 초록색 삼각형 하나의 데이터를 설정하도록 하겠습니다. 또 삼각형이 렌더링될 수 있도록 인덱스, 버텍스 버퍼를 만들도록 하겠습니다.
////////////////////////////////////////////////////////////////////////////////
// Filename: modelclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _MODELCLASS_H_
#define _MODELCLASS_H_
//////////////
// INCLUDES //
//////////////
#include <d3d11.h>
#include <d3dx10math.h>
////////////////////////////////////////////////////////////////////////////////
// Class name: ModelClass
////////////////////////////////////////////////////////////////////////////////
class ModelClass
{
private:
ModelClass에서 버텍스 버퍼와 같이 사용 될 정점 타입의 정의가 있습니다. 여기서 typedef는 나중 강좌에서 살펴 볼 ColorShaderClass 안의 형식과 일치해야 합니다.
struct VertexType
{
D3DXVECTOR3 position;
D3DXVECTOR4 color;
};
public:
ModelClass();
ModelClass(const ModelClass&);
~ModelClass();
이 함수들은 모델의 버텍스, 인덱스 버퍼의 초기화와 정리를 처리합니다. Render() 함수는 쉐이더로 그리는 것을 준비하기 위해 비디오 카드에 모델 기하 정보를 넣습니다.
bool Initialize(ID3D11Device*);
void Shutdown();
void Render(ID3D11DeviceContext*);
int GetIndexCount();
private:
bool InitializeBuffers(ID3D11Device*);
void ShutdownBuffers();
void RenderBuffers(ID3D11DeviceContext*);
ModelClass의 private 변수는 버텍스, 인덱스 버퍼와 각 버퍼 사이즈의 길이를 유지하는 두 정수 변수가 있습니다. 모든 DirectX 11 버퍼는 보통 ID3D11Buffer 형을 사용하며 만들어질때 버퍼 description으로 구별됩니다.
private:
ID3D11Buffer *m_vertexBuffer, *m_indexBuffer;
int m_vertexCount, m_indexCount;
};
#endif
Modelclass.cpp
////////////////////////////////////////////////////////////////////////////////
// Filename: modelclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "modelclass.h"
클래스 생성자는 버텍스, 인덱스 버퍼의 포인터를 null로 초기화합니다.
ModelClass::ModelClass()
{
m_vertexBuffer = 0;
m_indexBuffer = 0;
}
ModelClass::ModelClass(const ModelClass& other)
{
}
ModelClass::~ModelClass()
{
}
Initialize() 함수는 버텍스, 인덱스 버퍼를 초기화 하는 함수를 호출합니다.
bool ModelClass::Initialize(ID3D11Device* device) { bool result; // 삼각형의 기하 정보를 가지고 있는 버텍스, 인덱스 버퍼를 초기화 한다. result = InitializeBuffers(device); if(!result) { return false; } return true; }
Shutdown() 함수는 버텍스, 인덱스 버퍼를 정리하는 함수를 호출합니다.
void ModelClass::Shutdown() { // 버텍스, 인덱스 버퍼 해제. ShutdownBuffers(); return; }
Render() 함수는 GraphicsClass::Render() 함수에서 호출 됩니다. 이 함수가 버텍스, 인덱스 버퍼를 그래픽 파이프라인에 넣는 RenderBuffers() 함수를 호출하여 color 쉐이더가 렌더링 할 수 있도록 합니다.
void ModelClass::Render(ID3D11DeviceContext* deviceContext) { // 버텍스, 인덱스 버퍼를 그려질 수 있도록 그래픽 파이프라인에 넣습니다. RenderBuffers(deviceContext); return; }
GetIndexCount() 함수는 모델의 인덱스의 수를 리턴합니다. color 쉐이더는 해당 모델을 그리는데 이 정보가 필요할 것입니다.
int ModelClass::GetIndexCount()
{
return m_indexCount;
}
InitializeBuffers() 함수는 버텍스, 인덱스 버퍼를 생성하는 것을 처리하는 곳입니다. 주로 모델에서 읽어서 그 데이터 파일로 버퍼를 생성했을 것입니다. 그러나 이번 강좌에서는 삼각형 하나라서 직접 버텍스, 인덱스 버퍼에 점을 설정할 것입니다.
bool ModelClass::InitializeBuffers(ID3D11Device* device)
{
VertexType* vertices;
unsigned long* indices;
D3D11_BUFFER_DESC vertexBufferDesc, indexBufferDesc;
D3D11_SUBRESOURCE_DATA vertexData, indexData;
HRESULT result;
먼저 나중에 최종버퍼에 들어갈 버텍스, 인덱스 데이터를 가지는 임시 배열 두개를 만듭니다.
// 버텍스 배열의 정점 갯수를 설정. m_vertexCount = 3; // 인덱스 배열의 인덱스 수를 설정. m_indexCount = 3; // 버텍스 배열 생성. vertices = new VertexType[m_vertexCount]; if(!vertices) { return false; } // 인덱스 배열 생성. indices = new unsigned long[m_indexCount]; if(!indices) { return false; }
이제 버텍스, 인덱스 버퍼를 삼각형의 세 점과 그 각각의 점에 대한 인덱스로 채웁니다. 여기서 주의할 점은 삼각형의 점들을 시계방향으로 넣은 것입니다. 만약 시계 반대방향으로 넣을 경우 삼각형의 앞면이 원래 그리려던 반대 방향으로 보게 되어 back face culling(뒷면을 그리지 않음)때문에 그리지 않습니다. GPU에게 정점들을 보낼 때 이것을 기억하는게 매우 중요합니다. 이 부분도 버텍스 description의 일부이기 때문에 색상도 여기서 지정됩니다. 색상은 회색으로 설정하였습니다.
// 버텍스 배열에 데이터를 넣습니다. vertices[0].position = D3DXVECTOR3(-1.0f, -1.0f, 0.0f); // 왼쪽 아래 vertices[0].color = D3DXVECTOR4(0.0f, 1.0f, 0.0f, 1.0f); vertices[1].position = D3DXVECTOR3(0.0f, 1.0f, 0.0f); // 가운데 위 vertices[1].color = D3DXVECTOR4(0.0f, 1.0f, 0.0f, 1.0f); vertices[2].position = D3DXVECTOR3(1.0f, -1.0f, 0.0f); // 오른쪽 아래 vertices[2].color = D3DXVECTOR4(0.0f, 1.0f, 0.0f, 1.0f); // 인덱스 배열에 데이터를 넣습니다. indices[0] = 0; // 왼쪽 아래 indices[1] = 1; // 가운데 위 indices[2] = 2; // 오른쪽 아래
버텍스, 인덱스 버퍼를 채움으로 이제 버텍스, 인덱스 버프를 생성하는데 사용할 수 있게 되었습니다. 같은 방식으로 두 버퍼를 만듭니다. 먼저 버퍼의 description을 채웁니다. description에서 ByteWidth (버퍼의 바이트 크기)와 BindFlags (버퍼의 타입)은 정확히 입력되었는지 확실히 해야하는 것들입니다. description을 다 채우고 전에 만든 버텍스, 인덱스 배열을 가리키는 subresource 포인터도 채워야 합니다. description과 subresource 포인터가 있으면 D3D 디바이스를 이용하여 CreateBuffer() 함수를 호출합니다. 그리고 여러분의 새 버퍼에 포인터를 리턴할 것입니다.
// 정적 정점 버퍼의 description을 작성합니다. vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT; vertexBufferDesc.ByteWidth = sizeof(VertexType) * m_vertexCount; vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER; vertexBufferDesc.CPUAccessFlags = 0; vertexBufferDesc.MiscFlags = 0; vertexBufferDesc.StructureByteStride = 0; // 버텍스 데이터(버텍스 배열)의 포인터를 subresource 구조체에 넣습니다. vertexData.pSysMem = vertices; vertexData.SysMemPitch = 0; vertexData.SysMemSlicePitch = 0; // 버텍스 버퍼를 생성합니다. result = device->CreateBuffer(&vertexBufferDesc, &vertexData, &m_vertexBuffer); if(FAILED(result)) { return false; } // 정적 인덱스 버퍼의 description을 작성합니다. indexBufferDesc.Usage = D3D11_USAGE_DEFAULT; indexBufferDesc.ByteWidth = sizeof(unsigned long) * m_indexCount; indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER; indexBufferDesc.CPUAccessFlags = 0; indexBufferDesc.MiscFlags = 0; indexBufferDesc.StructureByteStride = 0; // 인덱스 데이터의 포인터를 subresource 구조체에 넣습니다. indexData.pSysMem = indices; indexData.SysMemPitch = 0; indexData.SysMemSlicePitch = 0; // 인덱스 버퍼 생성. result = device->CreateBuffer(&indexBufferDesc, &indexData, &m_indexBuffer); if(FAILED(result)) { return false; }
버텍스, 인덱스 버퍼를 생성된 이후 데이터가 버퍼로 복사되었으므로 더 이상 필요가 없어진 버텍스, 인덱스 배열을 지웁니다.
// 버텍스, 인덱스 버퍼가 생성되었으므로 배열을 해제합니다. delete [] vertices; vertices = 0; delete [] indices; indices = 0; return true; }
ShutdownBuffers() 함수는 InitializeBuffers() 함수에서 만들어진 버텍스, 인덱스 버퍼를 해제합니다.
void ModelClass::ShutdownBuffers() { // 인덱스 버퍼 해제. if(m_indexBuffer) { m_indexBuffer->Release(); m_indexBuffer = 0; } // 버텍스 버퍼 해제. if(m_vertexBuffer) { m_vertexBuffer->Release(); m_vertexBuffer = 0; } return; }
RenderBuffers() 함수는 Render() 함수에서 호출됩니다. 이 함수의 목적은 버텍스, 인덱스 버퍼를 GPU의 어셈블러 입력에 활성화 시키는 것입니다. GPU가 활성화된 버텍스 버퍼를 가지면 쉐이더를 사용할 수 있습니다. 이 함수는 또 버퍼가 어떻게 그려질지 삼각형인지 선인지 부채꼴 등등인지를 정의합니다. 이번 강좌에서 어셈블러 입력에 버텍스, 인덱스 버퍼를 활성화시키고 IASetPrimitiveTopology() DirectX함수를 사용하여 GPU에게 해당 버퍼는 삼각형으로 그려질 거라고 알려줍니다.
void ModelClass::RenderBuffers(ID3D11DeviceContext* deviceContext) { unsigned int stride; unsigned int offset; // 버텍스 버퍼 걸음(단위)와 오프셋을 설정합니다. stride = sizeof(VertexType); offset = 0; // 어셈블러 입력에 버텍스 버퍼를 활성화 시켜 렌더링 될 수 있습니다. deviceContext->IASetVertexBuffers(0, 1, &m_vertexBuffer, &stride, &offset); // 어셈블러 입력에 인덱스 버퍼를 활성화 시켜 렌더링 될 수 있습니다. deviceContext->IASetIndexBuffer(m_indexBuffer, DXGI_FORMAT_R32_UINT, 0); // 이 버텍스 버퍼로부터 그릴 기본 자료형을 설정합니다. 이 경우 삼각형입니다. deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); return; }
Colorshaderclass.h
ColorShaderClass은 GPU위 3D 모델을 그리는 것에 대해 HLSL 쉐이더를 호출할 것입니다.
////////////////////////////////////////////////////////////////////////////////
// Filename: colorshaderclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _COLORSHADERCLASS_H_
#define _COLORSHADERCLASS_H_
//////////////
// INCLUDES //
//////////////
#include <d3d11.h>
#include <d3dx10math.h>
#include <d3dx11async.h>
#include <fstream>
using namespace std;
////////////////////////////////////////////////////////////////////////////////
// Class name: ColorShaderClass
////////////////////////////////////////////////////////////////////////////////
class ColorShaderClass
{
private:
버텍스 쉐이더에서도 사용되는 cBuffer 타입의 정의입니다. 이 typedef는 올바른 렌더링을 위해 modelclass에서 VertexType이 일치하였던 것처럼 쉐이더의 cBuffer와 일치해야 합니다.
struct MatrixBufferType
{
D3DXMATRIX world;
D3DXMATRIX view;
D3DXMATRIX projection;
};
public:
ColorShaderClass();
ColorShaderClass(const ColorShaderClass&);
~ColorShaderClass();
여기 함수들은 쉐이더의 초기화와 정리를 담당합니다. Render() 함수는 쉐이더의 매개변수를 설정하고 준비된 모델 정점들을 쉐이더를 이용하여 그립니다.
bool Initialize(ID3D11Device*, HWND);
void Shutdown();
bool Render(ID3D11DeviceContext*, int, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX);
private:
bool InitializeShader(ID3D11Device*, HWND, WCHAR*, WCHAR*);
void ShutdownShader();
void OutputShaderErrorMessage(ID3D10Blob*, HWND, WCHAR*);
bool SetShaderParameters(ID3D11DeviceContext*, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX);
void RenderShader(ID3D11DeviceContext*, int);
private:
ID3D11VertexShader* m_vertexShader;
ID3D11PixelShader* m_pixelShader;
ID3D11InputLayout* m_layout;
ID3D11Buffer* m_matrixBuffer;
};
#endif
Colorshaderclass.cpp
////////////////////////////////////////////////////////////////////////////////
// Filename: colorshaderclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "colorshaderclass.h"
늘 그렇듯이 생성자는 private 포인터들을 null로 초기화 합니다.
ColorShaderClass::ColorShaderClass()
{
m_vertexShader = 0;
m_pixelShader = 0;
m_layout = 0;
m_matrixBuffer = 0;
}
ColorShaderClass::ColorShaderClass(const ColorShaderClass& other)
{
}
ColorShaderClass::~ColorShaderClass()
{
}
Initialize() 함수는 쉐이더를 위한 초기화 함수를 호출합니다. HLSL 쉐이더 파일의 이름을 넘기는데 이번 강좌에서 color.vs와 color.ps파일 입니다.
bool ColorShaderClass::Initialize(ID3D11Device* device, HWND hwnd) { bool result; // 버텍스, 픽셀 쉐이더를 초기화합니다. result = InitializeShader(device, hwnd, L"../Engine/color.vs", L"../Engine/color.ps"); if(!result) { return false; } return true; }
Shutdown() 함수는 쉐이더의 정리 함수를 호출합니다.
void ColorShaderClass::Shutdown() { // 버텍스, 픽셀 쉐이더와 관련 오브젝트들을 정리합니다. ShutdownShader(); return; }
Render() 함수는 먼저 SetShaderParameters() 함수를 이용하여 쉐이더 내부로 파라미터들을 설정합니다. 파라미터가 한번 설정되고 나면 RenderShader() 함수를 호출하여 HLSL 쉐이더를 이용하여 녹색 삼각형을 그립니다.
bool ColorShaderClass::Render(ID3D11DeviceContext* deviceContext, int indexCount, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix) { bool result; // 렌더링하는데 사용할 쉐이더 파라미터들을 설정합니다. result = SetShaderParameters(deviceContext, worldMatrix, viewMatrix, projectionMatrix); if(!result) { return false; } // 이제 준비된 버퍼를 쉐이더로 렌더링합니다. RenderShader(deviceContext, indexCount); return true; }
이번 강좌에서 매우 중요한 함수중 하나인 InitializeShader() 함수를 살펴 볼 것입니다. 이 함수는 쉐이더 파일들이 실제 로로드되고 DirectX와 GPU가 사용할 수 있게끔 합니다. 레이아웃의 설정과 버텍스 버퍼 데이터가 어떻게 GPU안의 그래픽 파이프라인을 확인할지(사용될지?) 볼 것입니다. 레이아웃은 modelclass.h 파일 안의 VertexType과 color.vs 파일 안에 정의된 것과 일치를 필요로 합니다.
bool ColorShaderClass::InitializeShader(ID3D11Device* device, HWND hwnd, WCHAR* vsFilename, WCHAR* psFilename) { HRESULT result; ID3D10Blob* errorMessage; ID3D10Blob* vertexShaderBuffer; ID3D10Blob* pixelShaderBuffer; D3D11_INPUT_ELEMENT_DESC polygonLayout[2]; unsigned int numElements; D3D11_BUFFER_DESC matrixBufferDesc; // 이 함수에서 사용할 포인터를 null로 초기화 합니다. errorMessage = 0; vertexShaderBuffer = 0; pixelShaderBuffer = 0;
여기는 버퍼에 쉐이더 프로그램을 컴파일 하는 곳입니다. 쉐이더 파일 이름, 쉐이더의 이름, 쉐이더 버전(DirectX 11 에서는 5.0), 쉐이더가 컴파일될 버퍼를 파라미터로 넘깁니다. 만약 쉐이더 컴파일을 실패할 경우 다른 함수에서 출력하도록 보낼 errorMessage라는 문자열에 에러메시지를 담습니다. 여전히 실패에 에러메시지도 없을 경우 쉐이더 파일을 찾을 수 없다는 것을 의미하여 이 경우 메시지박스 하나를 띄워 "찾을 수 없다"고 출력해줍니다.
// 버텍스 쉐이더 코드를 컴파일 합니다. result = D3DX11CompileFromFile(vsFilename, NULL, NULL, "ColorVertexShader", "vs_5_0",
D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL, &vertexShaderBuffer, &errorMessage, NULL);
if(FAILED(result)) { // 만약 쉐이더 컴파일을 실패할 경우 에러메시지에 뭔가가 쓰여져 있습니다. if(errorMessage) { OutputShaderErrorMessage(errorMessage, hwnd, vsFilename); } // 에러메시지가 없을 경우 간단하게 직접 쉐이파일 찾을 수 없다고 메시지박스로 출력합니다. else { MessageBox(hwnd, vsFilename, L"Missing Shader File", MB_OK); } return false; } // 필셀 쉐이더 코드를 컴파일 합니다. result = D3DX11CompileFromFile(psFilename, NULL, NULL, "ColorPixelShader", "ps_5_0",
D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL, &pixelShaderBuffer, &errorMessage, NULL);
if(FAILED(result)) { // 만약 쉐이더 컴파일을 실패할 경우 에러메시지에 뭔가가 쓰여져 있습니다. if(errorMessage) { OutputShaderErrorMessage(errorMessage, hwnd, psFilename); } // 에러메시지가 없을 경우 간단하게 직접 쉐이파일 찾을 수 없다고 메시지박스로 출력합니다. else { MessageBox(hwnd, psFilename, L"Missing Shader File", MB_OK); } return false; }
버텍스 쉐이더와 픽셀 쉐이더가 성공적으로 버퍼 안으로 컴파일 되면 해당 버퍼를 쉐이더 오브젝트를 만드는데 사용합니다. 이제부터 앞으로 이 포인터를 버텍스, 픽셀 쉐이더에 대한 인터페이스로 사용할 것입니다.
// 버퍼로부터 버텍스 쉐이더 생성. result = device->CreateVertexShader(vertexShaderBuffer->GetBufferPointer(),
vertexShaderBuffer->GetBufferSize(), NULL, &m_vertexShader); if(FAILED(result)) { return false; } // 버퍼로부터 픽셀 쉐이더 생성.Create the pixel shader from the buffer. result = device->CreatePixelShader(pixelShaderBuffer->GetBufferPointer(),
pixelShaderBuffer->GetBufferSize(), NULL, &m_pixelShader); if(FAILED(result)) { return false; }
다음은 쉐이더가 처리할 정점 데이터의 레이아웃을 만들어야 합니다. 이 쉐이더가 위치, 색상 벡터를 사용하기 때문에 둘의 사이즈를 명시하는 레이아웃 2개를 만들어야 합니다. semantic name 은 레이아웃에서 첫번째로 채우는데 레이아웃 요소의 용법을 쉐이더가 결정하게 해줍니다. 두 개의 다른 요소를 가지는데 첫번째로 POSITION을 사용하고 두번째로 COLOR를 사용합니다. 다음으로 레이아웃의 중요한 부분은 Format입니다. 위치벡터를 위해 DXGI_FORMAT_R32G32B32_FLOAT을 사용하고 색상을 위해 DXGI_FORMAT_R32G32B32A32_FLOAT을 사용합니다. 마지막으로 주의해야 하는 것은 버퍼에 데이터가 어떻게 되는지를 나타내는 AlignedByteOffset 입니다. 레이아웃에에게 position은 12 바이트 이며 color는 16바이트 이라고 알려줘야하는데 AlignedByteOffset는 각 요소의 시작을 나타냅니다. AlignedByteOffset에 특정 값을 넣는 대신에 D3D11_APPEND_ALIGNED_ELEMENT을 사용하여 위치를 알 수 있습니다. 나머지 다른 설정은 현재 이번 강좌에서 필요치 않아 기본설저으로 하였습니다.
// 쉐이더로 전달할 데이터의 레이아웃을 설정합닌다. // 이 설정은 쉐이더와 ModelClass에 있는 VertexType 구조체의 일치가 필요합니다. polygonLayout[0].SemanticName = "POSITION"; polygonLayout[0].SemanticIndex = 0; polygonLayout[0].Format = DXGI_FORMAT_R32G32B32_FLOAT; polygonLayout[0].InputSlot = 0; polygonLayout[0].AlignedByteOffset = 0; polygonLayout[0].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; polygonLayout[0].InstanceDataStepRate = 0; polygonLayout[1].SemanticName = "COLOR"; polygonLayout[1].SemanticIndex = 0; polygonLayout[1].Format = DXGI_FORMAT_R32G32B32A32_FLOAT; polygonLayout[1].InputSlot = 0; polygonLayout[1].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT; polygonLayout[1].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; polygonLayout[1].InstanceDataStepRate = 0;
한번 레이아웃 description이 설정되고 나면 사이즈를 얻을 수 있고 D3D 장치를 이용하여 입력 레이아웃을 생성할 수 있습니다. 레이아웃이 만들어지면 버텍스, 픽셀 쉐이더 버퍼는 더 이상 필요 없기 때문에 해제합니다.
// 레이아웃 요소 갯수를 얻습니다. (여기서는 2개) numElements = sizeof(polygonLayout) / sizeof(polygonLayout[0]); // 버텍스 입력 레이아웃 생성. result = device->CreateInputLayout(polygonLayout, numElements, vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), &m_layout); if(FAILED(result)) { return false; } // 더 이상 필요 없는 버텍스, 픽셀 쉐이더 버퍼를 해제합니다. vertexShaderBuffer->Release(); vertexShaderBuffer = 0; pixelShaderBuffer->Release(); pixelShaderBuffer = 0;
마지막으로 쉐이더를 사용하는데 필요한 설정은 상수 버퍼 입니다. 버텍스 쉐이더에서 봤듯이 우리는 상수버퍼를 하나만 가지며 여기서 하나만 설정하여 쉐이더에 대한 인터페이스로 사용합니다. 상수 버퍼는 매 프레임마다 업데이트 됨으로(행렬값이다.) 버퍼 사용법은 동적(dynamic)으로 설정합니다. bind flags 는 현재 이 버퍼가 상수 버퍼가 될 것이라는 것을 나타냅니다. cpu access flags 는 위 용법(usage)와 맞춰야하기 때문에 D3D11_CPU_ACCESS_WRITE로 설정합니다. description이 작성되면 상수버퍼 인터페이스를 만들 수 있고 SetShaderParameters() 함수를 이용하여 쉐이더 내부 변수에 접근 할 수 있습니다.
// 버텍스 쉐이더에 있는 동적 행렬 상수 버퍼의 description을 작성합니다. matrixBufferDesc.Usage = D3D11_USAGE_DYNAMIC; matrixBufferDesc.ByteWidth = sizeof(MatrixBufferType); matrixBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; matrixBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; matrixBufferDesc.MiscFlags = 0; matrixBufferDesc.StructureByteStride = 0; // 상수 버퍼 포인터를 만듭니다. 이제 이 클래스에서 버텍스 쉐이더 상수 버퍼에 접근할 수 있습니다. result = device->CreateBuffer(&matrixBufferDesc, NULL, &m_matrixBuffer); if(FAILED(result)) { return false; } return true; }
ShutdownShader() 함수는 InitializeShader() 함수에서 설정했던 4개의 인터페이스를 해제합니다.
void ColorShaderClass::ShutdownShader() { // 행렬 상수 버퍼 해제. if(m_matrixBuffer) { m_matrixBuffer->Release(); m_matrixBuffer = 0; } // 레이아웃 해제. if(m_layout) { m_layout->Release(); m_layout = 0; } // 픽셀 쉐이더 해제. if(m_pixelShader) { m_pixelShader->Release(); m_pixelShader = 0; } // 버텍스 쉐이더 해제. if(m_vertexShader) { m_vertexShader->Release(); m_vertexShader = 0; } return; }
OutputShaderErrorMessage() 함수는 버텍스 혹은 픽셀 쉐이더를 컴파일 할때 발생하는 에러메시지를 기록합니다.
void ColorShaderClass::OutputShaderErrorMessage(ID3D10Blob* errorMessage, HWND hwnd, WCHAR* shaderFilename) { char* compileErrors; unsigned long bufferSize, i; ofstream fout; // 에러 메시지 텍스트 버퍼의 포인터를 얻습니다. compileErrors = (char*)(errorMessage->GetBufferPointer()); // 메시지의 길이를 얻습니다. bufferSize = errorMessage->GetBufferSize(); // 에러메시지를 기록할 파일을 엽니다. fout.open("shader-error.txt"); // 에러 메시지를 기록합니다. for(i=0; i<bufferSize; i++) { fout << compileErrors[i]; } // 파일을 닫습니다. fout.close(); // 에러메시지 해제. errorMessage->Release(); errorMessage = 0; // 컴파일 에러 텍스트 파일을 유저가 확인하도록 메시지 팝업창을 띄웁니다. MessageBox(hwnd, L"Error compiling shader. Check shader-error.txt for message.", shaderFilename, MB_OK); return; }
SetShaderVariables() 함수는 쉐이더의 전역 변수에 더 쉽게 설정하기 위해 존재합니다. 이 함수에 사용된 행렬들은 GraphicsClass안에서 만들어 졌으며 이 함수가 종료된 이후 Render() 함수가 호출되는 동안 버텍스 쉐이더로 보내집니다.
bool ColorShaderClass::SetShaderParameters(ID3D11DeviceContext* deviceContext, D3DXMATRIX worldMatrix,
D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix)
{
HRESULT result;
D3D11_MAPPED_SUBRESOURCE mappedResource;
MatrixBufferType* dataPtr;
unsigned int bufferNumber;
행렬들이 쉐이더로 보내지기 전 전치행렬로 바꾸는데 DirectX(행우선)와 쉐이더(열우선)가 행렬을 다루는 방식이 다르기 때문에 맞추어주기 위해 전체행렬을 취한다.
// 쉐이더가 사용할 수 있게 전치행렬로 바꿉니다. D3DXMatrixTranspose(&worldMatrix, &worldMatrix); D3DXMatrixTranspose(&viewMatrix, &viewMatrix); D3DXMatrixTranspose(&projectionMatrix, &projectionMatrix);
m_matrixBuffer 잠그고 안에다가 새 행렬을 설정하고 다시 풉니다.
// 상수 버퍼를 잠급니다. 이제 수정될 수 있습니다. result = deviceContext->Map(m_matrixBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource); if(FAILED(result)) { return false; } // 상수버퍼 내부 데이터에 대한 포인터를 얻습니다. dataPtr = (MatrixBufferType*)mappedResource.pData; // 상수버퍼 안으로 행렬들을 복사합니다. dataPtr->world = worldMatrix; dataPtr->view = viewMatrix; dataPtr->projection = projectionMatrix; // 상수버퍼에 대한 잠금을 풉니다. deviceContext->Unmap(m_matrixBuffer, 0);
이제 HLSL 버텍스 쉐이더에 업데이트된 행렬 버퍼를 설정합니다.
// 버텍스 쉐이더의 상수 버퍼 위치 설정. bufferNumber = 0; // 마지막으로 업데이트된 값과 함께 버텍스 쉐이더의 상수 버퍼를 설정합니다. deviceContext->VSSetConstantBuffers(bufferNumber, 1, &m_matrixBuffer); return true; }
RenderShader() 함수는 Render() 함수에서 두번째로 호출되는 함수입니다. SetShaderParameters() 함수는 쉐이더 파라미터가 제대로 설정되었지는 확인하기 전에 호출됩니다.
이 함수에서 처음으로 할 것은 우리의 입력 레이아웃을 입력 어셈블러에 활성화 시키는 것입니다. 이는 GPU가 버텍스 버퍼의 데이터의 포맷을 알게 합니다. 두번째는 이 버텍스 버퍼를 렌더링하는데 사용할 버텍스, 픽셀 쉐이더를 설정하는 것입니다. 쉐이더가 설정되면 D3D device context를 이용하여 DrawIndexed DirectX 11 함수를 호출하여 삼각형을 렌더링합니다. 이 함수가 호출되고 나면 초록 삼각형이 렌더링 될 것입니다.
void ColorShaderClass::RenderShader(ID3D11DeviceContext* deviceContext, int indexCount) { // 버텍스 입력 레이아웃을 설정. deviceContext->IASetInputLayout(m_layout); // 삼각형 렌더링에 사용할 버텍스, 픽셀 쉐이더 설정. deviceContext->VSSetShader(m_vertexShader, NULL, 0); deviceContext->PSSetShader(m_pixelShader, NULL, 0); // 삼각형 렌더링. deviceContext->DrawIndexed(indexCount, 0, 0); return; }
Cameraclass.h
지금까지 우리는 어떻게 HLSL 쉐이더를 작성하며 버텍스, 인덱스 버퍼를 설정하고 ColorShaderClass를 이용하여 버퍼를 호출하기 위해 HLSL 쉐이더를 어떻게 호출하는지에 대해 살펴보았습니다. 그러나 우리가 놓치고 있는 하나는 그래픽이 그려질 뷰포인트 입니다. 이것을 위해 우리는 DirectX 11이 우리가 씬을 어디서 어떻게 보고있는지를 알도록 camera 클래스가 필요합니다. camera 클래스는 카메라 위치와 회전상태를 기록할 것입니다. 렌더링을 위해 HLSL 쉐이더에 넘길 뷰 행렬을 만드는데 필요한 위치, 회전 정보를 사용할 것입니다.
////////////////////////////////////////////////////////////////////////////////
// Filename: cameraclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _CAMERACLASS_H_
#define _CAMERACLASS_H_
//////////////
// INCLUDES //
//////////////
#include <d3dx10math.h>
////////////////////////////////////////////////////////////////////////////////
// Class name: CameraClass
////////////////////////////////////////////////////////////////////////////////
class CameraClass
{
public:
CameraClass();
CameraClass(const CameraClass&);
~CameraClass();
void SetPosition(float, float, float);
void SetRotation(float, float, float);
D3DXVECTOR3 GetPosition();
D3DXVECTOR3 GetRotation();
void Render();
void GetViewMatrix(D3DXMATRIX&);
private:
float m_positionX, m_positionY, m_positionZ;
float m_rotationX, m_rotationY, m_rotationZ;
D3DXMATRIX m_viewMatrix;
};
#endif
CameraClass 헤더는 사용할 4개의 함수와 함께 상당히 간단합니다. SetPosition(), SetRotation() 함수는 카메라 오브젝트의 위치와 회전을 설정하는데 사용할 것입니다. Render() 함수는 카메라 위치와 회전을 기초로한 뷰 행렬을 생성하는데 사용할 것입니다. 그리고 마지막으로 GetViewMatrix() 함수는 쉐이더가 렌더링시 사용할 수 있도록 카메라 오브젝트로부터 뷰 행렬을 얻는데 사용할 것입니다.
Cameraclass.cpp
////////////////////////////////////////////////////////////////////////////////
// Filename: cameraclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "cameraclass.h"
클래스 생성자는 카메라가 원점에 위치하도록 위치와 회전을 초기화합니다.
CameraClass::CameraClass()
{
m_positionX = 0.0f;
m_positionY = 0.0f;
m_positionZ = 0.0f;
m_rotationX = 0.0f;
m_rotationY = 0.0f;
m_rotationZ = 0.0f;
}
CameraClass::CameraClass(const CameraClass& other)
{
}
CameraClass::~CameraClass()
{
}
SetPosition(), SetRotation() 는 카메라의 위치와 회전을 설정하는데 사용됩니다.
void CameraClass::SetPosition(float x, float y, float z)
{
m_positionX = x;
m_positionY = y;
m_positionZ = z;
return;
}
void CameraClass::SetRotation(float x, float y, float z)
{
m_rotationX = x;
m_rotationY = y;
m_rotationZ = z;
return;
}
GetPosition(), GetRotation() 은 호출한 함수에게 카메라의 위치와 회전값을 반환합니다.
D3DXVECTOR3 CameraClass::GetPosition()
{
return D3DXVECTOR3(m_positionX, m_positionY, m_positionZ);
}
D3DXVECTOR3 CameraClass::GetRotation()
{
return D3DXVECTOR3(m_rotationX, m_rotationY, m_rotationZ);
}
Render() 함수는 뷰 행렬을 만들고 업데이트 하기위해 카메라의 위치와 회전값을 사용합니다. 먼저 up 벡터, 위치, 회전 등등의 변수를 설정합니다. 그리곤 씬의 원위치(원점에서 +z 방향. 참고로 DirectX 수학과 달리 z축 방향이 다르다.) 에서 카메라를 카메라의 x, y, z 회전 값에 따라 회전시킵니다. 회전후에 카메라의 위치를 3D 공간으로 옮깁니다. 맞게 설정된 position, lookAt, up 값으로 D3DXMatrixLookAtLH() 함수를 호출하여 카메라 회전과 변환을 표현하는 뷰 행렬을 만들 수 있습니다.
void CameraClass::Render() { D3DXVECTOR3 up, position, lookAt; float yaw, pitch, roll; D3DXMATRIX rotationMatrix; // up 벡터를 설정합니다. up.x = 0.0f; up.y = 1.0f; up.z = 0.0f; // 월드좌표에서 카메라 위치를 설정합니다. position.x = m_positionX; position.y = m_positionY; position.z = m_positionZ; // 카메라가 보는 곳을 기본으로 설정합니다. lookAt.x = 0.0f; lookAt.y = 0.0f; lookAt.z = 1.0f; // yaw (Y축), pitch (X축), roll (Z축) 회전 값을 라디안값으로 설정합니다. pitch = m_rotationX * 0.0174532925f; yaw = m_rotationY * 0.0174532925f; roll = m_rotationZ * 0.0174532925f; // yaw, pitch, roll 값으로 회전 행렬을 만듭니다. D3DXMatrixRotationYawPitchRoll(&rotationMatrix, yaw, pitch, roll); // lookAt, up 벡터를 회전 행렬로 회전 변환합니다. 이제 뷰가 원점에서 회전되었습니다. D3DXVec3TransformCoord(&lookAt, &lookAt, &rotationMatrix); D3DXVec3TransformCoord(&up, &up, &rotationMatrix); // 회전된 카메라 위치가 적용된 lookAt을 얻습니다. lookAt = position + lookAt; // 마지막으로 세개의 설정된 벡터로 뷰 행렬을 생성합니다. D3DXMatrixLookAtLH(&m_viewMatrix, &position, &lookAt, &up); return; }
Render() 함수가 뷰 행렬을 생성하기 위해 호출 된 후 GetViewMatrix() 함수를 이용하여 호출하는 함수에게 업데이트된 뷰 행렬을 제공할 수 있습니다. 뷰 행렬은 HLSL 버텍스 쉐이더에서 세개의 주요 행렬중 하나가 될 것입니다.
void CameraClass::GetViewMatrix(D3DXMATRIX& viewMatrix)
{
viewMatrix = m_viewMatrix;
return;
}
Graphicsclass.h
이제 GraphicsClass는 새로운 클래스 3개가 추가되었습니다. CameraClass, ModelClass, 그리고 ColorShaderClass 의 헤더파일이 여기에 인클루드되었고 private속성에 각 객체 포인터도 추가되었습니다. GraphicsClass 는 이 프로젝트를 위해 필요한 모든 클래스 객체를 호출하여 씬을 렌더링하는데 사용되는 메인 클래스인 것을 꼭 기억하시기 바랍니다.
////////////////////////////////////////////////////////////////////////////////
// Filename: graphicsclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _GRAPHICSCLASS_H_ #define _GRAPHICSCLASS_H_ /////////////////////// // MY CLASS INCLUDES // /////////////////////// #include "d3dclass.h" #include "cameraclass.h" #include "modelclass.h" #include "colorshaderclass.h" ///////////// // GLOBALS // ///////////// const bool FULL_SCREEN = true; const bool VSYNC_ENABLED = true; const float SCREEN_DEPTH = 1000.0f; const float SCREEN_NEAR = 0.1f; //////////////////////////////////////////////////////////////////////////////// // Class name: GraphicsClass //////////////////////////////////////////////////////////////////////////////// class GraphicsClass { public: GraphicsClass(); GraphicsClass(const GraphicsClass&); ~GraphicsClass(); bool Initialize(int, int, HWND); void Shutdown(); bool Frame(); private: bool Render(); private: D3DClass* m_D3D; CameraClass* m_Camera; ModelClass* m_Model; ColorShaderClass* m_ColorShader; }; #endif
Graphicsclass.cpp
////////////////////////////////////////////////////////////////////////////////
// Filename: graphicsclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "graphicsclass.h"
GraphicsClass의 첫번째 변화는 클래스 생성자에서 camera, model, color shader 객체포인터를 null로 초기화하는 것입니다.
GraphicsClass::GraphicsClass() { m_D3D = 0; m_Camera = 0; m_Model = 0; m_ColorShader = 0; }
Initialize() 함수는 새로운 객체 3개를 생성하고 초기화하는 코드가 업데이트 되었습니다.
bool GraphicsClass::Initialize(int screenWidth, int screenHeight, HWND hwnd) { bool result; // Direct3D 객체 생성. m_D3D = new D3DClass; if(!m_D3D) { return false; } // Direct3D 객체 초기화. 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; } // 카메라 객체 생성. m_Camera = new CameraClass; if(!m_Camera) { return false; } // 카메라 기본 위치 설정. m_Camera->SetPosition(0.0f, 0.0f, -10.0f); // 모델 객체 생성. m_Model = new ModelClass; if(!m_Model) { return false; } // 모델 객체 초기화. result = m_Model->Initialize(m_D3D->GetDevice()); if(!result) { MessageBox(hwnd, L"Could not initialize the model object.", L"Error", MB_OK); return false; } // color shader 객체 생성. m_ColorShader = new ColorShaderClass; if(!m_ColorShader) { return false; } // color shader 객체 초기화. result = m_ColorShader->Initialize(m_D3D->GetDevice(), hwnd); if(!result) { MessageBox(hwnd, L"Could not initialize the color shader object.", L"Error", MB_OK); return false; } return true; }
Shutdown() 함수도 역시 새 객체 3개를 정리하고 해제하는 코드가 업데이트 되었습니다.
void GraphicsClass::Shutdown() { // color shader 객체 해제. if(m_ColorShader) { m_ColorShader->Shutdown(); delete m_ColorShader; m_ColorShader = 0; } // 모델 객체 해제. if(m_Model) { m_Model->Shutdown(); delete m_Model; m_Model = 0; } // 카메라 객체 해제. if(m_Camera) { delete m_Camera; m_Camera = 0; } // Direct3D 객체 해제. if(m_D3D) { m_D3D->Shutdown(); delete m_D3D; m_D3D = 0; } return; }
Frame() 함수는 이전 강좌와 동일하게 그대로입니다.
bool GraphicsClass::Frame()
{
bool result;
// 그래픽 씬을 렌더링 한다.
result = Render();
if(!result)
{
return false;
}
return true;
}
여러분이 예상하다시피 Render() 함수는 매우 많이 바뀌었습니다. 그래도 여전히 씬을 검정색으로 지우는 것으로 시작합니다. 그 후에 Initialize() 함수에서 설정한 카메라 위치를 기반으로 한 뷰 행렬 생성하기 위해 카메라 객체의 Render() 함수를 호출합니다. 뷰 행렬이 생성된 이후 카메라 클래스로부터 뷰 행렬을 복사합니다. 또 D3DClass 객체로부터 월드 행렬과 투영 행렬도 복사합니다. 그래픽파이프라인에 초록색 삼각형 모델을 기하 정보를 넣기 위해 ModelClass::Render() 함수를 호출합니다. 준비된 정점들로 각 정점들을 맞게 위치시키는 행렬 3개와 모델 정보를 이용하여 정점들을 그리는 color 쉐이더를 호출합니다. 이제 초록색 삼각형이 후면 버퍼에 그려집니다. 이것으로 씬을 완료하고 EndScene() 함수를 호출하여 화면상에 출력합니다.
bool GraphicsClass::Render() { D3DXMATRIX viewMatrix, projectionMatrix, worldMatrix; bool result; // 씬을 시작하기 위해 버퍼를 비웁니다. m_D3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f); // 카메라 위치해 기반한 뷰 행렬을 생성합니다. m_Camera->Render(); // 월드, 뷰, 투영 행렬을 카메라, d3d 객체로부터 얻습니다. m_Camera->GetViewMatrix(viewMatrix); m_D3D->GetWorldMatrix(worldMatrix); m_D3D->GetProjectionMatrix(projectionMatrix); // 모델 정점과 인덱스 버퍼를 그리기위해 그래픽 파이프라인에 넣습니다. m_Model->Render(m_D3D->GetDeviceContext()); // color 쉐이더를 이용하여 모델을 렌더링 합니다. result = m_ColorShader->Render(m_D3D->GetDeviceContext(), m_Model->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix); if(!result) { return false; } // 렌더링된 씬을 화면으로 출력합니다. m_D3D->EndScene(); return true; }
요약
요약하면 여러분은 버텍스, 인덱스 버퍼가 어떻게 동작하는지에 대한 기본을 익히셨을 것입니다. 또 버텍스, 픽셀 쉐이더의 기본과 어떻게 HLSL을 이용하여 작성하지에 대해서도 익히셨을 것입니다. 그리고 마지막으로 화면상에 추력된 초록색 삼각형을 생성하기 위해 어떻게 새로운 개념들을 우리의 프레임워크에 녹여냈는지를 이해하셨을 것입니다. 제가 또 말하고 싶은 것은 단순히 삼각형 하나를 그리는데 비해 코드가 상당히 길고 아마도 main() 함수에 모든 코드를 다 넣어 버릴 수 있을 겁니다. 그러나 제가 이런 방식의 프레임워크를 취한 것은 앞으로의 강좌에서는 훨씬 더 복잡한 그래픽을 위한 코드변화가 크기 않기 때문에 이런 방식으로 코드를 작성하였습니다.
연습하기
1. 컴파일 후 실행해 보세요. 초록색 삼각형이 그려지는지 확인하고 esc키를 누러 종료시키세요.
2. 삼각형 색상을 빨간색으로 바꿔보세요.
3. 삼각형을 사각형으로 바꿔보세요.
4. 카메라를 뒤로 10칸정도 이동해 보세요.
5. 밝기가 반이 되도록 픽셀 쉐이더를 수정하세요.
(결정적 힌트! ColorPixelShader의 뭔가에 0.5f를 곱하세요.)
'프로그래밍 > directx' 카테고리의 다른 글
DirectX 11 Tutorials - 7 : 3D 모델 렌더링 (0) | 2016.05.05 |
---|---|
DirectX 11 Tutorials - 6 : 분산 조명 (0) | 2016.05.02 |
DirectX 11 Tutorials - 5 : 텍스쳐링 (0) | 2016.04.30 |
DirectX 11 Tutorials - 3 : DirectX 11 초기화하기 (0) | 2016.04.11 |
DirectX 11 Tutorials - 2 : 윈도우와 프레임워크 만들기 (1) | 2016.04.05 |