다이렉트 X 에서는 삼각형을 화면에서 띄우는거 조차 처음에는 어렵게 느껴지게 된다.
그도 그럴것이 여기서부터는 랜더링 파이프라인에 대해 이해가 필요하기 때문이다.

랜더링파이프라인은 3D 모델의 정점 데이터를 변환하고 조명 및 텍스처를 적용한 후, 최종적으로 픽셀에 색상을 할당하여 화면에 렌더링하는 과정을 의미한다.
Tessellation 단계(Hull,Tessellator,Domatin)과 Geometry는 이번 삼각형 띄우기에서는 사용하지 않으므로 넘어가도록하겠다
IA 단계는 메모리에서 기하 자료를 읽어 기하학적 기본도형(삼각형,선분,점)을 조립한다.
IA단계에서 기본도형을 조립한후 해당 정점들을 Vertex Shader 단계로 넘겨준다.
정점들을 처리하여 변환, 스키닝, 모핑 및 정점별 조명과 같은 정점별 작업을 수행을한다. Vertex Shader는 항상 단일 입력 정점에서 작동하여 단일 출력 정점을 생성한다.
파이프라인이 실행되도록 Vertex Shader 단계는 항상 활성 상태여야 한다.
Rasterizer 는 투영된 3차원 삼각형으로 부터 픽셀 색상들을 계산한다고 의미를 하는데
클리핑 : 카메라 바깥의 삼각형들을 그리지않도록 잘라낸다
보간 : 삼각형 내부 픽셀들의 색상을 부드럽게 보간하여 그라데이션 효과를 만든다
Pixel Shader는 보간된 정점 특성들을 입력받아서 하나의 색상을 출력한다. 픽셀 셰이더는 그냥 고정된 상수 색상을 돌려주는 아주 간단한것부터 픽셀당 조명, 반사 , 그림자 효과등 복잡한 작업을 수행하는것에 이르기까지 다양하다
Output Merger 단계는 최종적으로 후면 버퍼(Back Buffer)에 픽셀 데이터를 결합하고 출력하는 단계이다.
private:
//Geometry
//정점(Vertex) 데이터를 저장하고 GPU에서 사용할 수 있도록 준비하는 역할을 한다.
//정점 정보 저장
vector<Vertex>_vertices;
//정점 버퍼
ComPtr<ID3D11Buffer> _vertexBuffer = nullptr;
// 정점 데이터의 형식(레이아웃)을 GPU에 전달하는 역할, 설명서라고 보면 됨
ComPtr<ID3D11InputLayout>_inputLayout = nullptr;
//VS
// GPU에서 실행될 Vertex Shader 객체
ComPtr<ID3D11VertexShader> _vertexShader = nullptr;
ComPtr<ID3DBlob> _vsBlob = nullptr;
//PS
// GPU에서 실행될 Pixel Shader 객체
ComPtr<ID3D11PixelShader> _pixelShader = nullptr;
ComPtr<ID3DBlob> _psBlob = nullptr;
ComPtr _vsBlob = nullptr;
blob은 .hlsl 파일에서 작성된 vertex shader 코드가 컴파일되면, 컴파일 결과를 바이너리 형태로 저장하는 역할을 한다
ComPtr _psBlob = nullptr;
마찬가지로 .hlsl 파일에서 작성된 Pixel shader 코드가 컴파일되면, 컴파일 결과를 바이너리 형태로 저장하는 역할을 한다
void Game::Init(HWND hwnd)
{
_hwnd = hwnd;
_width = GWinSizeX;
_height = GWinSizeY;
CreateDeviceAndSwapChain();
CreateRenderTargetView();
SetViewPort();
CreateGeometry();
CreateVS();
CreateInputLayOut();
CreatePS();
}
void Game::Render()
{
RenderBegin();
{
uint32 stride = sizeof(Vertex);
uint32 offset = 0;
//IA
_deviceContext->IASetVertexBuffers(0, 1, _vertexBuffer.GetAddressOf(), &stride, &offset);
_deviceContext->IASetInputLayout(_inputLayout.Get());
_deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
//VS
_deviceContext->VSSetShader(_vertexShader.Get(), nullptr, 0);
//RS
//PS
_deviceContext->PSSetShader(_pixelShader.Get(), nullptr, 0);
//OM
_deviceContext->Draw(_vertices.size(), 0);
}
RenderEnd();
}
void Game::CreateGeometry()
{
{
_vertices.resize(3);
_vertices[0].position = Vec3( - 0.5f, -0.5f, 0.f ); //-1~1 스크린좌표
_vertices[0].color = Color(1.f, 0.f, 0.f, 1.f);
_vertices[1].position = Vec3( 0.f,0.5f,0.f );
_vertices[1].color = Color(1.f, 0.f, 0.f, 1.f);
_vertices[2].position = Vec3( 0.5f,-0.5f,0.f );
_vertices[2].color = Color(1.f, 0.f, 0.f, 1.f);
}
{
D3D11_BUFFER_DESC desc;
ZeroMemory(&desc, sizeof(desc));
//IMMUTABLE : GPU만 읽을수있음 CPU는 접근 불가
//삼각형에 대한 기하학적인 도형은 안변함 ==고칠일없음
desc.Usage = D3D11_USAGE_IMMUTABLE;
desc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
desc.ByteWidth =static_cast<uint32> (sizeof(Vertex) * _vertices.size());
D3D11_SUBRESOURCE_DATA data;
ZeroMemory(&data, sizeof(data));
data.pSysMem = _vertices.data();
_device->CreateBuffer(&desc, &data, _vertexBuffer.GetAddressOf());
}
}
CreateGeometry
1. _vertices 배열에 삼각형을 구성하는 정점 데이터(위치, 색상)를 설정
2. D3D11_BUFFER_DESC를 이용해 정점 버퍼(Vertex Buffer) 생성 설정
3. D3D11_SUBRESOURCE_DATA를 이용해 CPU 메모리에 있던 _vertices 데이터를 GPU로 전송
4. _device->CreateBuffer()를 호출하여 GPU에서 사용할 정점 버퍼 생성
void Game::CreateInputLayOut()
{
D3D11_INPUT_ELEMENT_DESC layout[] =
{
{"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 },
};
const int32 count = sizeof(layout) / sizeof(D3D11_INPUT_ELEMENT_DESC);
_device->CreateInputLayout(layout, count, _vsBlob->GetBufferPointer(), _vsBlob->GetBufferSize(), _inputLayout.GetAddressOf());
}
void Game::CreateVS()
{
LoadShaderFromFile(L"Default.hlsl", "VS", "vs_5_0", _vsBlob);
HRESULT hr= _device->CreateVertexShader(_vsBlob->GetBufferPointer(), _vsBlob->GetBufferSize(), nullptr, _vertexShader.GetAddressOf());
CHECK(hr);
}
void Game::CreatePS()
{
LoadShaderFromFile(L"Default.hlsl", "PS", "ps_5_0", _psBlob);
HRESULT hr = _device->CreatePixelShader(_psBlob->GetBufferPointer(), _psBlob->GetBufferSize(), nullptr, _pixelShader.GetAddressOf());
CHECK(hr);
}
void Game::LoadShaderFromFile(const wstring& path, const string& name, const string& version, ComPtr<ID3DBlob>& blob)
{
const uint32 compileFlag = D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;
::D3DCompileFromFile(
path.c_str(),
nullptr,
D3D_COMPILE_STANDARD_FILE_INCLUDE,
name.c_str(),
version.c_str(),
compileFlag,
0,
blob.GetAddressOf(),
nullptr);
}
위 코드들은 HLSL 셰이더를 로드하고 Direct3D 11에서 사용할 수 있도록 준비하는 과정이다
struct VS_INPUT
{
float4 position : POSITION;
float4 color : COLOR;
};
struct VS_OUTPUT
{
float4 position : SV_POSITION;
float4 color : COLOR;
};
VS_OUTPUT VS(VS_INPUT input)
{
VS_OUTPUT output;
output.position = input.position;
output.color = input.color;
return output;
}
float4 PS(VS_OUTPUT input) : SV_Target
{
return float4(1, 0, 0, 0);
}
hlsl 파일을 보면 VS_INPUT의 POSITION과 COLOR 는 CreateInputLayout 의
D3D11_INPUT_ELEMENT_DESC layout[] =
{
{"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 },
};
와 대응된다.
hlsl과정
C++에서 정점 데이터를 INPUT_LAYOUT을 통해 GPU로 보냄
HLSL의 VS_INPUT이 C++에서 정의한 입력 레이아웃을 받아서 Vertex Shader 실행
Vertex Shader에서 변환된 데이터(SV_POSITION)가 Rasterizer를 거쳐 픽셀 단위로 변환됨 Pixel Shader(PS)에서 SV_Target을 통해 최종 색상을 출력