물방울 책이라고 하는 DX11 책에 나오는 내용을 집중적으로 부분부분 알아보자.
물방울 책은 오래전엔 나올 책이기에, 그 책에 나와있는 코드를 그대로 사용할 경우 100% 에러가 날 것이다. 그렇기 때문에 스스로 수정을 해야만한다.
Direct3D의 초기화
4장 첫번째 챕터인 초기화하는 부분 코드를 분석해보자.
일단 코드 자체를 실행하면 다음과 같다.
빈 화면과 함께 프레임, ms초가 나온다.
#include "pch.h"
#include "01. InitDemo.h"
InitDemo::InitDemo(HINSTANCE hInstance)
: App(hInstance)
{
}
InitDemo::~InitDemo()
{
}
bool InitDemo::Init()
{
if (!App::Init())
return false;
return true;
}
void InitDemo::OnResize()
{
App::OnResize();
}
void InitDemo::UpdateScene(float dt)
{
}
void InitDemo::DrawScene()
{
_deviceContext->ClearRenderTargetView(_renderTargetView.Get(), reinterpret_cast<const float*>(&Colors::Blue));
_deviceContext->ClearDepthStencilView(_depthStencilView.Get(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);
HR(_swapChain->Present(0, 0));
}
이 코드가 실행되고 있는데 여기서 App이라는 코드를 상속받고 있으니 이를 먼저 분석하자.
#pragma once
#include "Utils.h"
#include "GameTimer.h"
#include <string>
class App
{
public:
App(HINSTANCE hInstance);
virtual ~App();
HINSTANCE AppInst() { return _hAppInst; }
HWND MainWnd() { return _hMainWnd; }
float AspectRatio() { return static_cast<float>(_clientWidth) / _clientHeight; }
int32 Run();
virtual bool Init();
virtual void OnResize();
virtual void UpdateScene(float dt) = 0;
virtual void DrawScene() = 0;
virtual LRESULT MsgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
virtual void OnMouseDown(WPARAM btnState, int32 x, int32 y){ }
virtual void OnMouseUp(WPARAM btnState, int32 x, int32 y) { }
virtual void OnMouseMove(WPARAM btnState, int32 x, int32 y){ }
protected:
bool InitMainWindow();
bool InitDirect3D();
void CalculateFrameStats();
private:
void CreateDeviceAndSwapChain();
void CreateRenderTargetView();
void CreateDepthStencilView();
protected:
HINSTANCE _hAppInst = 0;
HWND _hMainWnd = 0;
bool _appPaused = false;
bool _minimized = false;
bool _maximized = false;
bool _resizing = false;
uint32 _4xMsaaQuality = 0;
GameTimer _timer;
protected:
// Device & SwapChain
ComPtr<ID3D11Device> _device;
ComPtr<ID3D11DeviceContext> _deviceContext;
ComPtr<IDXGISwapChain> _swapChain;
// DSV
ComPtr<ID3D11Texture2D> _depthStencilBuffer;
ComPtr<ID3D11DepthStencilView> _depthStencilView;
// RTV
ComPtr<ID3D11RenderTargetView> _renderTargetView;
// Viewport
D3D11_VIEWPORT _viewport;
// Etc
std::wstring _mainWindowCaption = L"DX11 Application";
D3D_DRIVER_TYPE _driverType = D3D_DRIVER_TYPE_HARDWARE;
int32 _clientWidth = 800;
int32 _clientHeight = 600;
bool _enable4xMsaa = false;
};
WinAPI와 DX의 코드를 분리하지 않고 하나의 클래스로 관리한 점이 특징이다. 각 함수들이 우리가 그전에 작업했던 내용은 이를 분리해서 각각의 Manager를 두고 다루게끔 했었다.
아주 사소한 부분까지 전부 다 처리를 하고 있어 생각보다 복잡하다.
bool App::Init()
{
if (!InitMainWindow())
return false;
if (!InitDirect3D())
return false;
return true;
}
Init이 실행되면 WinApi를 초기화 하고 DX를 초기화 한다.
bool App::InitDirect3D()
{
CreateDeviceAndSwapChain();
OnResize();
return true;
}
Device와 SwapChain은 아주 익숙한 내용이다.
그럼 근본적으로 DirectX는 무엇일까?
컴퓨터에는 주요 부품으로 CPU와 RAM이 있다. 그리고 CPU에서 하기 버거운 연산, 가벼운 연산이지만 너무 많은 연산은 대신 외주를 맡겨야하는데 그게 바로 GPU이다. 하지만 세상에는 많은 GPU 제조사들이 있고 각각 다른 느낌으로 동작한다.
이것을 컨트롤하기 위해 DirectX라고 하는 로우레벨 렌더링 API를 개발한 것이다. 즉, OS와 중간다리 역할인 것이다.
Gpu랑 통신하기 위해 만들어야하는 것이 ID3D11Device, ID3D11DeviceContext 이다.
device는 생성을 담당하고(버퍼를 만들고) devicecontext는 연결을 담당한다.(어떻게 사용할 것인지)
SwapChain은 더블버퍼를 떠올려야한다. 이는 가장 중요한 내용이다.
SwapChain, 더블버퍼를 사용하지 않고 게임을 제작했을 경우
생기는 문제점은 무조건 알고 있어야한다.
이전 프레임에 그렸던 결과물과 이번 프레임에 그릴 결과물이 뒤섞여 깔끔하게 보이지 않는다. 즉 그려지는 모습이 보일 수 있기 때문에 전시용과 그리는 용도를 나눠놓은 것이 SwapChain이다.
결론적으로 다음과 같은 순서로 dx를 초기화 한다.
- ID3D11Device (디바이스 인터페이스)와 ID3D11DeviceContext(디바이스 컨텍스트 인터페이스)에 대한 포인터를 D3D11CreateDevice 함수 통해 생성.
- 4x 멀티 샘플링 어느 정도의 퀄리티로 보장되는지 ID3D11Device::CheckMultisampleQualityLevels 함수 통해 확인.
- 스왑 체인을 DXGI_SWAP_CHAIN_DESC 구조체 통해 초기화 한다.
- 사용한 디바이스를 IDXGIFactory형 인스턴스에 할당 후 스왑 체인 IDXGISwapChain 인스턴스를 생성.
- 스왑 체인 백버퍼로 렌더 타깃 뷰를 생성.
- 깊이/스텐실 버퍼와 그에 따른 뷰 생성.
- 렌더 타깃 뷰에 깊이 뷰를 바인딩.
- 뷰 포트 설정
렌더링 파이프라인
카메라의 위치와 비추는 방향을 가지고 있는 3D 화면 정보를 가지고 2D 이미지를 만드는 과정을 통틀어서 렌더링 파이프라인이라고 칭한다.
- IA 단계에서 특정 오브젝트의 각각의 정점이 어떻게 분석되고 자신의 로컬좌표의 어디에 위치에 있는지에 대한 정보를 넘겨준다.
- VS 단계에서는 넘겨받은 각 정점을 가지고 게임세상의 기준이 되는 월드좌표로, 그리고 카메라를 원점으로 하는 카메라 좌표로 변환한다. 그 후 카메라의 옵션(원근 투영, 직교 투영)에 따라 투영좌표로 변환 한 후 스크린 좌표로 변환한다.
- RS 단계에서는 카메라와 너무 멀어서 그릴 필요가 없는 정점들은 전부 날려주고, 후면을 그리는 작업도 전부 날려주게된다. 즉 후처리 작업을 하게된다. 또한 정점으로 되어있는 부분을 이제 픽셀로 그려야하기 때문에 이를 보간을 한다.
- PS 단계에서는 각 픽셀에 대한 색상을 빛과 본래 가지고 있던 색상을 조합하는 연산을 통해서 정한다.
- OM 단계에서는 이를 출력하는 역할을 한다.
그리기 연산
물방울 책의 예제 중 첫 번째인 Box를 띄우는 것부터 분석해보자. 코드의 전문은 다음과 같다.
#include "pch.h"
#include "02. BoxDemo.h"
#include "MathHelper.h"
BoxDemo::BoxDemo(HINSTANCE hInstance) : App(hInstance)
{
_mainWindowCaption = L"Box Demo";
_lastMousePos.x = 0;
_lastMousePos.y = 0;
XMMATRIX I = ::XMMatrixIdentity();
::XMStoreFloat4x4(&_world, I);
::XMStoreFloat4x4(&_view, I);
::XMStoreFloat4x4(&_proj, I);
}
BoxDemo::~BoxDemo()
{
}
bool BoxDemo::Init()
{
if (!App::Init())
return false;
BuildGeometryBuffers();
BuildFX();
BuildVertexLayout();
return true;
}
void BoxDemo::OnResize()
{
App::OnResize();
// The window resized, so update the aspect ratio and recompute the projection matrix.
XMMATRIX P = XMMatrixPerspectiveFovLH(0.25f * XM_PI, AspectRatio(), 1.0f, 1000.0f);
XMStoreFloat4x4(&_proj, P);
}
void BoxDemo::UpdateScene(float dt)
{
// Convert Spherical to Cartesian coordinates.
float x = _radius * sinf(_phi) * cosf(_theta);
float z = _radius * sinf(_phi) * sinf(_theta);
float y = _radius * cosf(_phi);
// Build the view matrix.
XMVECTOR pos = XMVectorSet(x, y, z, 1.0f);
XMVECTOR target = XMVectorZero();
XMVECTOR up = XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f);
XMMATRIX V = XMMatrixLookAtLH(pos, target, up);
XMStoreFloat4x4(&_view, V);
}
void BoxDemo::DrawScene()
{
_deviceContext->ClearRenderTargetView(_renderTargetView.Get(), reinterpret_cast<const float*>(&Colors::LightSteelBlue));
_deviceContext->ClearDepthStencilView(_depthStencilView.Get(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);
_deviceContext->IASetInputLayout(_inputLayout.Get());
_deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
uint32 stride = sizeof(Vertex);
uint32 offset = 0;
_deviceContext->IASetVertexBuffers(0, 1, _vertexBuffer.GetAddressOf(), &stride, &offset);
_deviceContext->IASetIndexBuffer(_indexBuffer.Get(), DXGI_FORMAT_R32_UINT, 0);
// Set constants
XMMATRIX world = ::XMLoadFloat4x4(&_world);
XMMATRIX view = ::XMLoadFloat4x4(&_view);
XMMATRIX proj = ::XMLoadFloat4x4(&_proj);
XMMATRIX worldViewProj = world * view * proj;
_fxWorldViewProj->SetMatrix(reinterpret_cast<float*>(&worldViewProj));
D3DX11_TECHNIQUE_DESC techDesc;
_tech->GetDesc(&techDesc);
for (uint32 p = 0; p < techDesc.Passes; ++p)
{
_tech->GetPassByIndex(p)->Apply(0, _deviceContext.Get());
// 36 indices for the box.
_deviceContext->DrawIndexed(36, 0, 0);
}
HR(_swapChain->Present(0, 0));
}
void BoxDemo::OnMouseDown(WPARAM btnState, int x, int y)
{
_lastMousePos.x = x;
_lastMousePos.y = y;
::SetCapture(_hMainWnd);
}
void BoxDemo::OnMouseUp(WPARAM btnState, int x, int y)
{
::ReleaseCapture();
}
void BoxDemo::OnMouseMove(WPARAM btnState, int x, int y)
{
if ((btnState & MK_LBUTTON) != 0)
{
// Make each pixel correspond to a quarter of a degree.
float dx = XMConvertToRadians(0.25f * static_cast<float>(x - _lastMousePos.x));
float dy = XMConvertToRadians(0.25f * static_cast<float>(y - _lastMousePos.y));
// Update angles based on input to orbit camera around box.
_theta += dx;
_phi += dy;
// Restrict the angle mPhi.
_phi = MathHelper::Clamp(_phi, 0.1f, MathHelper::Pi - 0.1f);
}
else if ((btnState & MK_RBUTTON) != 0)
{
// Make each pixel correspond to 0.005 unit in the scene.
float dx = 0.005f * static_cast<float>(x - _lastMousePos.x);
float dy = 0.005f * static_cast<float>(y - _lastMousePos.y);
// Update the camera radius based on input.
_radius += dx - dy;
// Restrict the radius.
_radius = MathHelper::Clamp(_radius, 3.0f, 15.0f);
}
_lastMousePos.x = x;
_lastMousePos.y = y;
}
void BoxDemo::BuildGeometryBuffers()
{
// Create vertex buffer
Vertex vertices[] =
{
{ XMFLOAT3(-1.0f, -1.0f, -1.0f), XMFLOAT4((const float*)&Colors::White) },
{ XMFLOAT3(-1.0f, +1.0f, -1.0f), XMFLOAT4((const float*)&Colors::Black) },
{ XMFLOAT3(+1.0f, +1.0f, -1.0f), XMFLOAT4((const float*)&Colors::Red) },
{ XMFLOAT3(+1.0f, -1.0f, -1.0f), XMFLOAT4((const float*)&Colors::Green) },
{ XMFLOAT3(-1.0f, -1.0f, +1.0f), XMFLOAT4((const float*)&Colors::Blue) },
{ XMFLOAT3(-1.0f, +1.0f, +1.0f), XMFLOAT4((const float*)&Colors::Yellow) },
{ XMFLOAT3(+1.0f, +1.0f, +1.0f), XMFLOAT4((const float*)&Colors::Cyan) },
{ XMFLOAT3(+1.0f, -1.0f, +1.0f), XMFLOAT4((const float*)&Colors::Magenta) }
};
D3D11_BUFFER_DESC vbd;
vbd.Usage = D3D11_USAGE_IMMUTABLE;
vbd.ByteWidth = sizeof(Vertex) * 8;
vbd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
vbd.CPUAccessFlags = 0;
vbd.MiscFlags = 0;
vbd.StructureByteStride = 0;
D3D11_SUBRESOURCE_DATA vinitData;
vinitData.pSysMem = vertices;
HR(_device->CreateBuffer(&vbd, &vinitData, _vertexBuffer.GetAddressOf()));
// Create the index buffer
uint32 indices[] = {
// front face
0, 1, 2,
0, 2, 3,
// back face
4, 6, 5,
4, 7, 6,
// left face
4, 5, 1,
4, 1, 0,
// right face
3, 2, 6,
3, 6, 7,
// top face
1, 5, 6,
1, 6, 2,
// bottom face
4, 0, 3,
4, 3, 7
};
D3D11_BUFFER_DESC ibd;
ibd.Usage = D3D11_USAGE_IMMUTABLE;
ibd.ByteWidth = sizeof(uint32) * 36;
ibd.BindFlags = D3D11_BIND_INDEX_BUFFER;
ibd.CPUAccessFlags = 0;
ibd.MiscFlags = 0;
ibd.StructureByteStride = 0;
D3D11_SUBRESOURCE_DATA iinitData;
iinitData.pSysMem = indices;
HR(_device->CreateBuffer(&ibd, &iinitData, _indexBuffer.GetAddressOf()));
}
void BoxDemo::BuildFX()
{
DWORD shaderFlags = 0;
#if defined( DEBUG ) || defined( _DEBUG )
shaderFlags |= D3D10_SHADER_DEBUG;
shaderFlags |= D3D10_SHADER_SKIP_OPTIMIZATION;
#endif
ComPtr<ID3D10Blob> compiledShader = 0;
ComPtr<ID3D10Blob> compilationMsgs = 0;
HRESULT hr = ::D3DCompileFromFile(L"../Shaders/02. color.fx", 0, D3D_COMPILE_STANDARD_FILE_INCLUDE, 0, "fx_5_0", shaderFlags, 0, compiledShader.GetAddressOf(), compilationMsgs.GetAddressOf());
// compilationMsgs can store errors or warnings.
if (FAILED(hr))
{
if (compilationMsgs != 0)
::MessageBoxA(0, (char*)compilationMsgs->GetBufferPointer(), 0, 0);
assert(false);
}
HR(D3DX11CreateEffectFromMemory(compiledShader->GetBufferPointer(), compiledShader->GetBufferSize(), 0, _device.Get(), _fx.GetAddressOf()));
_tech = _fx->GetTechniqueByName("T0");
_fxWorldViewProj = _fx->GetVariableByName("gWorldViewProj")->AsMatrix();
}
void BoxDemo::BuildVertexLayout()
{
// Create the vertex input layout.
D3D11_INPUT_ELEMENT_DESC vertexDesc[] =
{
{"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0},
{"COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0}
};
// Create the input layout
D3DX11_PASS_DESC passDesc;
_tech->GetPassByIndex(0)->GetDesc(&passDesc);
HR(_device->CreateInputLayout(vertexDesc, 2, passDesc.pIAInputSignature, passDesc.IAInputSignatureSize, _inputLayout.GetAddressOf()));
}
사실 여기서 중요한 부분은 Init, DrawScene, BuildGeometryBuffers, BuildFX, BuildVertexLayout 이다.
bool BoxDemo::Init()
{
if (!App::Init())
return false;
BuildGeometryBuffers();
BuildFX();
BuildVertexLayout();
return true;
}
먼저 Init에서 App을 초기화 해서 DirectX를 사용할 준비를 해준 후 순서대로 Gemotry, Fx, Layout을 실행하게된다.
void BoxDemo::BuildGeometryBuffers()
{
// Create vertex buffer
Vertex vertices[] =
{
{ XMFLOAT3(-1.0f, -1.0f, -1.0f), XMFLOAT4((const float*)&Colors::White) },
{ XMFLOAT3(-1.0f, +1.0f, -1.0f), XMFLOAT4((const float*)&Colors::Black) },
{ XMFLOAT3(+1.0f, +1.0f, -1.0f), XMFLOAT4((const float*)&Colors::Red) },
{ XMFLOAT3(+1.0f, -1.0f, -1.0f), XMFLOAT4((const float*)&Colors::Green) },
{ XMFLOAT3(-1.0f, -1.0f, +1.0f), XMFLOAT4((const float*)&Colors::Blue) },
{ XMFLOAT3(-1.0f, +1.0f, +1.0f), XMFLOAT4((const float*)&Colors::Yellow) },
{ XMFLOAT3(+1.0f, +1.0f, +1.0f), XMFLOAT4((const float*)&Colors::Cyan) },
{ XMFLOAT3(+1.0f, -1.0f, +1.0f), XMFLOAT4((const float*)&Colors::Magenta) }
};
D3D11_BUFFER_DESC vbd;
vbd.Usage = D3D11_USAGE_IMMUTABLE;
vbd.ByteWidth = sizeof(Vertex) * 8;
vbd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
vbd.CPUAccessFlags = 0;
vbd.MiscFlags = 0;
vbd.StructureByteStride = 0;
D3D11_SUBRESOURCE_DATA vinitData;
vinitData.pSysMem = vertices;
HR(_device->CreateBuffer(&vbd, &vinitData, _vertexBuffer.GetAddressOf()));
// Create the index buffer
uint32 indices[] = {
// front face
0, 1, 2,
0, 2, 3,
// back face
4, 6, 5,
4, 7, 6,
// left face
4, 5, 1,
4, 1, 0,
// right face
3, 2, 6,
3, 6, 7,
// top face
1, 5, 6,
1, 6, 2,
// bottom face
4, 0, 3,
4, 3, 7
};
D3D11_BUFFER_DESC ibd;
ibd.Usage = D3D11_USAGE_IMMUTABLE;
ibd.ByteWidth = sizeof(uint32) * 36;
ibd.BindFlags = D3D11_BIND_INDEX_BUFFER;
ibd.CPUAccessFlags = 0;
ibd.MiscFlags = 0;
ibd.StructureByteStride = 0;
D3D11_SUBRESOURCE_DATA iinitData;
iinitData.pSysMem = indices;
HR(_device->CreateBuffer(&ibd, &iinitData, _indexBuffer.GetAddressOf()));
}
Gemotry 함수에서는 우리가 그릴 박스의 정점 8개를 정해주고 또한 각 정점에서의 색상을 어떤 것으로 할지에 대한 버퍼를 만들어준다. 각 정점에 대한 구조는 이렇게 되어있다.
struct Vertex
{
XMFLOAT3 Pos;
XMFLOAT4 Color;
};
그리고 정점에 대한 정보를 만들었으면 이를 효율적으로 그리기 위해(중복되는 정점을 줄이고자) 인덱스 버퍼를 만들어준다. 이러한 역할이 BuildGemotryBuffers이다.
void BoxDemo::BuildFX()
{
DWORD shaderFlags = 0;
#if defined( DEBUG ) || defined( _DEBUG )
shaderFlags |= D3D10_SHADER_DEBUG;
shaderFlags |= D3D10_SHADER_SKIP_OPTIMIZATION;
#endif
ComPtr<ID3D10Blob> compiledShader = 0;
ComPtr<ID3D10Blob> compilationMsgs = 0;
HRESULT hr = ::D3DCompileFromFile(L"../Shaders/02. color.fx", 0, D3D_COMPILE_STANDARD_FILE_INCLUDE, 0, "fx_5_0", shaderFlags, 0, compiledShader.GetAddressOf(), compilationMsgs.GetAddressOf());
// compilationMsgs can store errors or warnings.
if (FAILED(hr))
{
if (compilationMsgs != 0)
::MessageBoxA(0, (char*)compilationMsgs->GetBufferPointer(), 0, 0);
assert(false);
}
HR(D3DX11CreateEffectFromMemory(compiledShader->GetBufferPointer(), compiledShader->GetBufferSize(), 0, _device.Get(), _fx.GetAddressOf()));
_tech = _fx->GetTechniqueByName("T0");
_fxWorldViewProj = _fx->GetVariableByName("gWorldViewProj")->AsMatrix();
}
FX 단계에서는 사용할 쉐이더를 로드하는 것이다. 쉐이더를 로드하고 쉐이더에서 사용할 VS, RS, PS등을 지정해놓은 Pass를 설정하고 cbuffer를 사용할 값을 찾아 세팅해주는 역할이다. 여기서는 gWorldViewProj이다.
이것이 쉐이더 이다.
cbuffer cbPerObject
{
float4x4 gWorldViewProj;
};
struct VertexIn
{
float3 PosL : POSITION;
float4 Color : COLOR;
};
struct VertexOut
{
float4 PosH : SV_POSITION;
float4 Color : COLOR;
};
VertexOut VS(VertexIn vin)
{
VertexOut vout;
// Transform to homogeneous clip space.
vout.PosH = mul(float4(vin.PosL, 1.0f), gWorldViewProj);
// Just pass vertex color into the pixel shader.
vout.Color = vin.Color;
return vout;
}
float4 PS(VertexOut pin) : SV_Target
{
return pin.Color;
}
technique11 T0
{
pass P0
{
SetVertexShader(CompileShader(vs_5_0, VS()));
SetGeometryShader(NULL);
SetPixelShader(CompileShader(ps_5_0, PS()));
}
}
void BoxDemo::BuildVertexLayout()
{
// Create the vertex input layout.
D3D11_INPUT_ELEMENT_DESC vertexDesc[] =
{
{"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0},
{"COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0}
};
// Create the input layout
D3DX11_PASS_DESC passDesc;
_tech->GetPassByIndex(0)->GetDesc(&passDesc);
HR(_device->CreateInputLayout(vertexDesc, 2, passDesc.pIAInputSignature, passDesc.IAInputSignatureSize, _inputLayout.GetAddressOf()));
}
Layout 단계에서는 내가 만든 vertexBuffer가 어떻게 끊어서 분석해야하는지 알려주는 역할을 하는 InputLayout을 만드는 작업을 한다. Position은 vec4이므로 12바이트를 차지하기 때문에 Color를 분석할 때는 12바이트를 띈 후부터 분석하라는 것이다.
이러한 작업을 전부 완료를 하게 되면 우리가 그릴 Box와 Shader에 대한 정보를 "세팅"하는데 까진 완료 했다.
이제 매 프레임마다 DrawScene을 호출하게 될 것이다. 즉, 이제부터 렌더링 파이프라인을 시작한다고 보면 된다.
void BoxDemo::DrawScene()
{
_deviceContext->ClearRenderTargetView(_renderTargetView.Get(), reinterpret_cast<const float*>(&Colors::LightSteelBlue));
_deviceContext->ClearDepthStencilView(_depthStencilView.Get(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);
_deviceContext->IASetInputLayout(_inputLayout.Get());
_deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
uint32 stride = sizeof(Vertex);
uint32 offset = 0;
_deviceContext->IASetVertexBuffers(0, 1, _vertexBuffer.GetAddressOf(), &stride, &offset);
_deviceContext->IASetIndexBuffer(_indexBuffer.Get(), DXGI_FORMAT_R32_UINT, 0);
// Set constants
XMMATRIX world = ::XMLoadFloat4x4(&_world);
XMMATRIX view = ::XMLoadFloat4x4(&_view);
XMMATRIX proj = ::XMLoadFloat4x4(&_proj);
XMMATRIX worldViewProj = world * view * proj;
_fxWorldViewProj->SetMatrix(reinterpret_cast<float*>(&worldViewProj));
D3DX11_TECHNIQUE_DESC techDesc;
_tech->GetDesc(&techDesc);
for (uint32 p = 0; p < techDesc.Passes; ++p)
{
_tech->GetPassByIndex(p)->Apply(0, _deviceContext.Get());
// 36 indices for the box.
_deviceContext->DrawIndexed(36, 0, 0);
}
HR(_swapChain->Present(0, 0));
}
IA 단계에서 어떤 도화지에 그릴 것인지, 깊이 값은 어디에 나타낼 것인지 등을 넘겨주는 RenderTargetView와 DepthStencilView를 지정한다. 그리고 InputLayout를 세팅하고 어떤 방식, 여기선 삼각형 방식으로 분석하겠다고 설정한다. 마지막으로 VertexBuffer와 IndexBuffer를 넘겨준다.
그리고는 이 Box의 월드 좌표, 카메라 좌표, 투영 좌표를 아까 설정해둔 cbuffer로 넘겨주게된다. 여기서 주의해야할 점은 카메라와 투영좌표는 어차피 같다는 것이고 월드 좌표만 변하게 된다.
이렇게 다 넘겨주고 난 후 VS 단계가 시작된다.
VertexOut VS(VertexIn vin)
{
VertexOut vout;
// Transform to homogeneous clip space.
vout.PosH = mul(float4(vin.PosL, 1.0f), gWorldViewProj);
// Just pass vertex color into the pixel shader.
vout.Color = vin.Color;
return vout;
}
넘겨받은 각 정점에 대한 정보 Pos, color를 이용해서 Ps단계까지 넘겨줄 구조체를 채워준다. pos는 본래 로컬좌표인데 WVP를 곱해 카메라 스페이스로 바꿔준다.
RS 과정은 Shader에 담겨있지 않지만 자동으로 각 정점이 나타내는 색상을 보간하게 된다.
그리고 그렇게 보간된 각 픽셀에 대한 값을 PS에 넘겨준다.
float4 PS(VertexOut pin) : SV_Target
{
return pin.Color;
}
PS 단계에서는 빛과 같은 연산을 하지 않고 별 다른 작업 없이 그대로 색상을 내보낸다.
HR(_swapChain->Present(0, 0));
마지막에 OM 단계에서 이를 화면에 보여주면 끝이다. swapChian의 후면 버퍼에 PS단계까지 그려진 도화지가 준비되어 있을 것이고 이를 전면버퍼와 교체 or 복사를 통해 화면에 보여주게된다.
'C++ > DirectX 11' 카테고리의 다른 글
[DirectX] 물방울 책 - Shader (1) | 2024.10.25 |
---|---|
[DirectX] 물방울 책 - 조명, 텍스처 (0) | 2024.10.24 |
[DirectX] GPU - ViewPort, Collider, Picking (0) | 2024.10.17 |
[DirectX] GPU - RawBuffer, System Value 분석, TextureBuffer, StructureBuffer (0) | 2024.10.15 |
[DirectX] 인스턴싱 - 드로우 콜 (1) | 2024.10.10 |