https://github.com/kevinmoran/BeginnerDirect3D11/tree/master/02.%20Drawing%20a%20Triangle
이전과 변경된 점 위주로 작성한다.
// Create Vertex Shader
ID3DBlob* vsBlob;
ID3D11VertexShader* vertexShader;
{
ID3DBlob* shaderCompileErrorsBlob;
HRESULT hResult = D3DCompileFromFile(L"shaders.hlsl", nullptr, nullptr, "vs_main", "vs_5_0", 0, 0, &vsBlob, &shaderCompileErrorsBlob);
if(FAILED(hResult))
{
const char* errorString = NULL;
if(hResult == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND))
errorString = "Could not compile shader; file not found";
else if(shaderCompileErrorsBlob){
errorString = (const char*)shaderCompileErrorsBlob->GetBufferPointer();
shaderCompileErrorsBlob->Release();
}
MessageBoxA(0, errorString, "Shader Compiler Error", MB_ICONERROR | MB_OK);
return 1;
}
hResult = d3d11Device->CreateVertexShader(vsBlob->GetBufferPointer(), vsBlob->GetBufferSize(), nullptr, &vertexShader);
assert(SUCCEEDED(hResult));
}
// Create Pixel Shader
ID3D11PixelShader* pixelShader;
{
ID3DBlob* psBlob;
ID3DBlob* shaderCompileErrorsBlob;
HRESULT hResult = D3DCompileFromFile(L"shaders.hlsl", nullptr, nullptr, "ps_main", "ps_5_0", 0, 0, &psBlob, &shaderCompileErrorsBlob);
if(FAILED(hResult))
{
const char* errorString = NULL;
if(hResult == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND))
errorString = "Could not compile shader; file not found";
else if(shaderCompileErrorsBlob){
errorString = (const char*)shaderCompileErrorsBlob->GetBufferPointer();
shaderCompileErrorsBlob->Release();
}
MessageBoxA(0, errorString, "Shader Compiler Error", MB_ICONERROR | MB_OK);
return 1;
}
hResult = d3d11Device->CreatePixelShader(psBlob->GetBufferPointer(), psBlob->GetBufferSize(), nullptr, &pixelShader);
assert(SUCCEEDED(hResult));
psBlob->Release();
}
// Create Input Layout
ID3D11InputLayout* inputLayout;
{
D3D11_INPUT_ELEMENT_DESC inputElementDesc[] =
{
{ "POS", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "COL", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 }
};
HRESULT hResult = d3d11Device->CreateInputLayout(inputElementDesc, ARRAYSIZE(inputElementDesc), vsBlob->GetBufferPointer(), vsBlob->GetBufferSize(), &inputLayout);
assert(SUCCEEDED(hResult));
vsBlob->Release();
}
// Create Vertex Buffer
ID3D11Buffer* vertexBuffer;
UINT numVerts;
UINT stride;
UINT offset;
{
float vertexData[] = { // x, y, r, g, b, a
0.0f, 0.5f, 0.f, 1.f, 0.f, 1.f,
0.5f, -0.5f, 1.f, 0.f, 0.f, 1.f,
-0.5f, -0.5f, 0.f, 0.f, 1.f, 1.f
};
stride = 6 * sizeof(float);
numVerts = sizeof(vertexData) / stride;
offset = 0;
D3D11_BUFFER_DESC vertexBufferDesc = {};
vertexBufferDesc.ByteWidth = sizeof(vertexData);
vertexBufferDesc.Usage = D3D11_USAGE_IMMUTABLE;
vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
D3D11_SUBRESOURCE_DATA vertexSubresourceData = { vertexData };
HRESULT hResult = d3d11Device->CreateBuffer(&vertexBufferDesc, &vertexSubresourceData, &vertexBuffer);
assert(SUCCEEDED(hResult));
}
// Main Loop
bool isRunning = true;
while(isRunning)
{
MSG msg = {};
while(PeekMessageW(&msg, 0, 0, 0, PM_REMOVE))
{
if(msg.message == WM_QUIT)
isRunning = false;
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
if(global_windowDidResize)
{
d3d11DeviceContext->OMSetRenderTargets(0, 0, 0);
d3d11FrameBufferView->Release();
HRESULT res = d3d11SwapChain->ResizeBuffers(0, 0, 0, DXGI_FORMAT_UNKNOWN, 0);
assert(SUCCEEDED(res));
ID3D11Texture2D* d3d11FrameBuffer;
res = d3d11SwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (void**)&d3d11FrameBuffer);
assert(SUCCEEDED(res));
res = d3d11Device->CreateRenderTargetView(d3d11FrameBuffer, NULL,
&d3d11FrameBufferView);
assert(SUCCEEDED(res));
d3d11FrameBuffer->Release();
global_windowDidResize = false;
}
FLOAT backgroundColor[4] = { 0.1f, 0.2f, 0.6f, 1.0f };
d3d11DeviceContext->ClearRenderTargetView(d3d11FrameBufferView, backgroundColor);
RECT winRect;
GetClientRect(hwnd, &winRect);
D3D11_VIEWPORT viewport = { 0.0f, 0.0f, (FLOAT)(winRect.right - winRect.left), (FLOAT)(winRect.bottom - winRect.top), 0.0f, 1.0f };
d3d11DeviceContext->RSSetViewports(1, &viewport);
d3d11DeviceContext->OMSetRenderTargets(1, &d3d11FrameBufferView, nullptr);
d3d11DeviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
d3d11DeviceContext->IASetInputLayout(inputLayout);
d3d11DeviceContext->VSSetShader(vertexShader, nullptr, 0);
d3d11DeviceContext->PSSetShader(pixelShader, nullptr, 0);
d3d11DeviceContext->IASetVertexBuffers(0, 1, &vertexBuffer, &stride, &offset);
d3d11DeviceContext->Draw(numVerts, 0);
d3d11SwapChain->Present(1, 0);
}
// shaders.hlsl
struct VS_Input
{
float2 pos : POS;
float4 color : COL;
};
struct VS_Output
{
float4 position : SV_POSITION;
float4 color : COL;
};
VS_Output vs_main(VS_Input input)
{
VS_Output output;
output.position = float4(input.pos, 0.0f, 1.0f);
output.color = input.color;
return output;
}
float4 ps_main(VS_Output input) : SV_TARGET
{
return input.color;
}
// Create Vertex Shader
ID3DBlob* vsBlob;
ID3D11VertexShader* vertexShader;
{
ID3DBlob* shaderCompileErrorsBlob;
HRESULT hResult = D3DCompileFromFile(L"shaders.hlsl", nullptr, nullptr, "vs_main", "vs_5_0", 0, 0, &vsBlob, &shaderCompileErrorsBlob);
if(FAILED(hResult))
{
const char* errorString = NULL;
if(hResult == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND))
errorString = "Could not compile shader; file not found";
else if(shaderCompileErrorsBlob){
errorString = (const char*)shaderCompileErrorsBlob->GetBufferPointer();
shaderCompileErrorsBlob->Release();
}
MessageBoxA(0, errorString, "Shader Compiler Error", MB_ICONERROR | MB_OK);
return 1;
}
hResult = d3d11Device->CreateVertexShader(vsBlob->GetBufferPointer(), vsBlob->GetBufferSize(), nullptr, &vertexShader);
assert(SUCCEEDED(hResult));
}
Blob의 약자는 Binary large object이다. 즉, binary로 이루어진 객체를 얘기하며, shader(hlsl)을 컴파일 한 이후에 나오는 binary 데이터를 의미한다.
HRESULT D3DCompileFromFile(
[in] LPCWSTR pFileName,
[in, optional] const D3D_SHADER_MACRO *pDefines,
[in, optional] ID3DInclude *pInclude,
[in] LPCSTR pEntrypoint,
[in] LPCSTR pTarget,
[in] UINT Flags1,
[in] UINT Flags2,
[out] ID3DBlob **ppCode,
[out, optional] ID3DBlob **ppErrorMsgs
);
https://learn.microsoft.com/en-us/windows/win32/api/d3dcompiler/nf-d3dcompiler-d3dcompilefromfile
D3DCompileFromFile을 통해서 shaders.hlsl 파일을 읽어들이며, vertex shader의 entry function으로 vs_main 함수를 이용한다.
버전은 vs_5_0으로 vertex shader 5.0버전으로 컴파일하고, 컴파일 결과인 binary 데이터를 vsBlob instance에다가 저장한다.
오류 핸들링 작업을 진행한 후, vsBlob의 클래스는 ID3DBlob으로 binary 데이터만 있다. 이를 vertex shader로 확실하게 만들어주기 위해서 Device에서 CreateVertexShader를 통해서 초기화시켜준다.
HRESULT CreateVertexShader(
[in] const void *pShaderBytecode,
[in] SIZE_T BytecodeLength,
[in, optional] ID3D11ClassLinkage *pClassLinkage,
[out, optional] ID3D11VertexShader **ppVertexShader
);
https://learn.microsoft.com/ko-kr/windows/win32/api/d3d11/nf-d3d11-id3d11device-createvertexshader
// Create Pixel Shader
ID3D11PixelShader* pixelShader;
{
ID3DBlob* psBlob;
ID3DBlob* shaderCompileErrorsBlob;
HRESULT hResult = D3DCompileFromFile(L"shaders.hlsl", nullptr, nullptr, "ps_main", "ps_5_0", 0, 0, &psBlob, &shaderCompileErrorsBlob);
if(FAILED(hResult))
{
const char* errorString = NULL;
if(hResult == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND))
errorString = "Could not compile shader; file not found";
else if(shaderCompileErrorsBlob){
errorString = (const char*)shaderCompileErrorsBlob->GetBufferPointer();
shaderCompileErrorsBlob->Release();
}
MessageBoxA(0, errorString, "Shader Compiler Error", MB_ICONERROR | MB_OK);
return 1;
}
hResult = d3d11Device->CreatePixelShader(psBlob->GetBufferPointer(), psBlob->GetBufferSize(), nullptr, &pixelShader);
assert(SUCCEEDED(hResult));
psBlob->Release();
}
마찬가지로 PixelShader도 만들어준다.
// Create Input Layout
ID3D11InputLayout* inputLayout;
{
D3D11_INPUT_ELEMENT_DESC inputElementDesc[] =
{
{ "POS", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "COL", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 }
};
HRESULT hResult = d3d11Device->CreateInputLayout(inputElementDesc, ARRAYSIZE(inputElementDesc),
vsBlob->GetBufferPointer(), vsBlob->GetBufferSize(), &inputLayout);
assert(SUCCEEDED(hResult));
vsBlob->Release();
}
input layer는 vertex shader의 입력 값으로 들어갈 자료형을 정의하는 사용된다.
struct VS_Input
{
float2 pos : POS;
float4 color : COL;
};
struct VS_Output
{
float4 position : SV_POSITION;
float4 color : COL;
};
VS_Output vs_main(VS_Input input)
{
VS_Output output;
output.position = float4(input.pos, 0.0f, 1.0f);
output.color = input.color;
return output;
}
float4 ps_main(VS_Output input) : SV_TARGET
{
return input.color;
}
shaders.hlsl 파일을 보면 vs_main의 인자로 VS_Input 자료형을 받는데, VS_Input structure는 pos와 color를 받는다.
이때 ': POS'와 ': COL'은 입력 값에 대한 semantic을 의미한다.
https://learn.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-semantics
위 사이트에서 볼 수 있다시피 각 shader 별로 변수의 의미를 지정해줘야 내부적으로 rasterizer 같은데서 각 역할에 맞게 연산을 해준다.
따라서 선택이 아닌 필수로 설정해줘야 한다.
// Create Vertex Buffer
ID3D11Buffer* vertexBuffer;
UINT numVerts;
UINT stride;
UINT offset;
{
float vertexData[] = { // x, y, r, g, b, a
0.0f, 0.5f, 0.f, 1.f, 0.f, 1.f,
0.5f, -0.5f, 1.f, 0.f, 0.f, 1.f,
-0.5f, -0.5f, 0.f, 0.f, 1.f, 1.f
};
stride = 6 * sizeof(float);
numVerts = sizeof(vertexData) / stride;
offset = 0;
D3D11_BUFFER_DESC vertexBufferDesc = {};
vertexBufferDesc.ByteWidth = sizeof(vertexData);
vertexBufferDesc.Usage = D3D11_USAGE_IMMUTABLE;
vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
D3D11_SUBRESOURCE_DATA vertexSubresourceData = { vertexData };
HRESULT hResult = d3d11Device->CreateBuffer(&vertexBufferDesc, &vertexSubresourceData, &vertexBuffer);
assert(SUCCEEDED(hResult));
}
이제 Input Layer가 어떻게 넣을지에 대한 틀을 만들었다면, 틀에 넣어줄 데이터를 만들어줘야 한다.
IDID11Buffer를 통해서 넘겨주며, InputLayer에서 작성한 데이터 순대로 데이터를 만들어준다.
Buffer를 생성해주기 위해서는 Buffer에 대한 설명을 작성해줘야 한다.
HRESULT CreateBuffer(
[in] const D3D11_BUFFER_DESC *pDesc,
[in, optional] const D3D11_SUBRESOURCE_DATA *pInitialData,
[out, optional] ID3D11Buffer **ppBuffer
);
https://learn.microsoft.com/ko-kr/windows/win32/api/d3d11/nf-d3d11-id3d11device-createbuffer
typedef struct D3D11_BUFFER_DESC {
UINT ByteWidth;
D3D11_USAGE Usage;
UINT BindFlags;
UINT CPUAccessFlags;
UINT MiscFlags;
UINT StructureByteStride;
} D3D11_BUFFER_DESC;
https://learn.microsoft.com/ko-kr/windows/win32/api/d3d11/ns-d3d11-d3d11_buffer_desc
ByteWidth는 버퍼의 크기를 의미하고, Usage는 GPU에서 버퍼를 어떻게 관리할 것인지(R/W)에 대한 설정. BindFlags는 버퍼의 역할을 의미한다. 여기서는 vertex에 대한 의미가 된다.
if(global_windowDidResize)
{
d3d11DeviceContext->OMSetRenderTargets(0, 0, 0);
d3d11FrameBufferView->Release();
HRESULT res = d3d11SwapChain->ResizeBuffers(0, 0, 0, DXGI_FORMAT_UNKNOWN, 0);
assert(SUCCEEDED(res));
ID3D11Texture2D* d3d11FrameBuffer;
res = d3d11SwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (void**)&d3d11FrameBuffer);
assert(SUCCEEDED(res));
res = d3d11Device->CreateRenderTargetView(d3d11FrameBuffer, NULL,
&d3d11FrameBufferView);
assert(SUCCEEDED(res));
d3d11FrameBuffer->Release();
global_windowDidResize = false;
}
FLOAT backgroundColor[4] = { 0.1f, 0.2f, 0.6f, 1.0f };
d3d11DeviceContext->ClearRenderTargetView(d3d11FrameBufferView, backgroundColor);
RECT winRect;
GetClientRect(hwnd, &winRect);
D3D11_VIEWPORT viewport = { 0.0f, 0.0f, (FLOAT)(winRect.right - winRect.left), (FLOAT)(winRect.bottom - winRect.top), 0.0f, 1.0f };
d3d11DeviceContext->RSSetViewports(1, &viewport);
d3d11DeviceContext->OMSetRenderTargets(1, &d3d11FrameBufferView, nullptr);
d3d11DeviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
d3d11DeviceContext->IASetInputLayout(inputLayout);
d3d11DeviceContext->VSSetShader(vertexShader, nullptr, 0);
d3d11DeviceContext->PSSetShader(pixelShader, nullptr, 0);
d3d11DeviceContext->IASetVertexBuffers(0, 1, &vertexBuffer, &stride, &offset);
d3d11DeviceContext->Draw(numVerts, 0);
d3d11SwapChain->Present(1, 0);
global_windowDidResize는 윈도우 메시지에서 윈도우 크기가 변경될 때 SwapChain의 buffer 또한 변경되어야 하기 때문에 이에 따라 Resize 후에 RenderTargetView를 재설정해준다.
후에 DeviceContext를 통해서 GPU에게 명령을 내려준다.
RS는 Resterizer를 의미하고(여기서는 viewport 설정),
OM은 Output Merger Stage로 픽셀 셰이더에서 출력된 픽셀값을 렌더 타겟 뷰에 쓰는 스테이지를 의미한다.
IA는 Input Assembler로 어떤 primitive를 그릴지와, input Layer를 설정해준다.
VS, PS는 각가 vertex shader와 pixel shader로 이전에 컴파일하여 만든 shader를 설정한다.
IASetVertexBuffer는 Input Layer에 맞는 vertex 데이터를 넣어주는 것을 의미하며,
이전에 설정한 값을 기반으로 Back buffer에 draw해준다.
그리고 Present를 통해서 back buffer와 front buffer를 swap 해준다.