장치초기화의 코드 흐름이다.
- Device와 Swapchain 생성
- SwapChain에서 BackBuffer를 통해 RenderTargetView생성 후 OM(Output Merge)에 설정
- 뷰포트 설정
- RenderTarget Clear
- 그릴게 없으니 생략, Present로 전면 후면버퍼 교환
여기서 이제 그릴게 생겼으니 추가적으로 해주어야할게 생기게 되는데
- Input Assembler에 정점 정보 등록
- Vertex Shader 단계 추가
- Pixel Shader 단계 추가
- Draw Call
정점에 대한 정보를 등록한다는 말은 CPU메모리에서 GPU메모리로 복사를 수행한다는 말이며, 이 과정은 VertexBuffer를 생성하여 IASetVertexBuffers를 호출하는 과정으로 이루어진다.
HRESULT hr;
_vertexCount = static_cast<uint32>(vec.size());
uint32 bufferSize = _vertexCount * sizeof(Vertex);
D3D11_BUFFER_DESC bd = {};
bd.Usage = D3D11_USAGE_DEFAULT;
bd.ByteWidth = bufferSize;
bd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
bd.CPUAccessFlags = 0;
D3D11_SUBRESOURCE_DATA InitData = {};
InitData.pSysMem = &vec[0];
hr = DEVICE->CreateBuffer(&bd, &InitData, &_vertexBuffer);
CHECK_FAIL(hr, L"Failed Create Vertex Buffer");
// Set vertex buffer
uint32 stride = sizeof(Vertex);
uint32 offset = 0;
DEVICECTX->IASetVertexBuffers(0, 1, &_vertexBuffer, &stride, &offset);
// Set primitive topology
DEVICECTX->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
CHECK_FAIL은 그냥 임의로 만든 매크로 함수이니 무시해도 상관없다. D3D11_BUFFER_DESC 구조체를 통해 몇 가지 정보를 입력하고 BindFlags를 D3D11_BIND_VERTEX_BUFFER로 등록하여 VertexBuffer로 사용할 것이라고 명시해준다. D3D11_SUBRESOURCE_DATA 구조체는 버퍼를 만들 때 초기 데이터가 존재할 경우 사용하는 구조체이며 없다면 CreateBuffer함수에서 해당 위치를 nullptr로 지정해주면 된다. (대신 다른 방법으로 버퍼를 채워주긴 해야한다.) 각 인자의 자세한 내용은 MSDN을 참고하길 바란다. IASetVertexBuffer를 통해 버퍼를 등록하고, IASetPrimitiveTopology함수는 각 정점을 어떤식으로 연결할 것인지 알려주는 함수이고 TRIANGLELIST로 지정하면 삼각형으로 연결하겠다는 것이다. 이렇게 하면 정점 정보를 등록하는 것은 끝났다. 하지만 정점의 위치 정보만 넘겨줬을 뿐 아직 어떤 형식인지 어떻게 쓰일 것인지는 넘겨주지 않은 상태이다. 그래서 D3D11_INPUT_ELEMENT_DESC구조체를 통해 이를 알려주어야 한다. 이것은 Vertex Shader 단계에서 사용된다.
HRESULT hr;
CreateVertexShader(path, "VS_Main", "vs_5_0");
CreatePixelShader(path, "PS_Main", "ps_5_0");
// Define the input layout
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 },
};
uint32 numElements = ARRAYSIZE(layout);
// Create the input layout
hr = DEVICE->CreateInputLayout(layout, numElements, _vsBlob->GetBufferPointer(),
_vsBlob->GetBufferSize(), &_vertexLayout);
_vsBlob->Release();
CHECK_FAIL(hr, L"Failed Create Input Layout");
// Set the input layout
DEVICECTX->IASetInputLayout(_vertexLayout);
CreateVertexShader, CreatePixelShader 함수는 DX sample sdk와 MSDN을 참고하면서 만들었다. 찾아보면 셰이더 파일을 컴파일하고 객체로 만드는 방법은 여러가지가 있으니 참고하길 바란다. 딱히 중요한 내용은 아니니 넘어가고, 셰이더 코드에는 VS_IN 구조체에 pos와 color 객체에 Input layout에서 지정한 POSITION과 COLOR가 있는 것을 볼 수 있는데 이것을 위해 Input layout을 작성했다고 보면되고 Input layout에서의 이름과 셰이더 코드의 이름이 정확히 일치하지 않으면 오류가 나니 주의해야 한다.
struct VS_IN
{
float3 pos : POSITION;
float4 color : COLOR;
};
struct VS_OUT
{
float4 pos : SV_Position;
float4 color : COLOR;
};
VS_OUT VS_Main(VS_IN input)
{
VS_OUT output = (VS_OUT)0;
output.pos = float4(input.pos, 1.f);
output.color = input.color;
return output;
}
float4 PS_Main(VS_OUT input) : SV_Target
{
return input.color;
}
셰이더 문법 자체가 C언어와 유사해서 어느정도 해석이 가능할 수 있는데 쉽게 보면 VS_Main은 Vertex Shader단계 PS_Main은 Pixel Shader 단계라고 보면되고, 둘다 그냥 입력받은 값을 거의 그대로 출력하는 것을 확인할 수 있다. 각 셰이더를 등록할때는 DeviceContext를 통해 VSSetShader, PSSetShader함수로 지정할 수 있고, 예상할 수 있듯이 앞으로 사용할 헐셰이더, 지오메트리 셰이더도 HS, GS 와 같은 이름으로 존재한다. 이렇게까지하면 마지막으로 Draw Call만 남았다. 이 부분은 이제 Render Target에 말 그대로 그려달라는 요청을 하는 함수이고 여기서는 Draw함수를 썻지만 DrawIndexed, DrawIndexedInstanced 등 다양한 Draw함수들이 있는데 상황에 맞춰 사용하면 된다.

'C++ > DirectX' 카테고리의 다른 글
6. 색인 버퍼(Index Buffer) (0) | 2023.05.15 |
---|---|
5. 상수 버퍼(Constant Buffer) (0) | 2023.05.15 |
3. 장치 초기화 (1) | 2023.05.14 |
2. DX11의 렌더링 파이프라인 (0) | 2023.05.14 |
1. WinAPI 기초 (0) | 2023.05.14 |