DirectX 11 스터디 노트 - 도형 만들어보기 (Create Geometry) + 인덱스 버퍼 + 버텍스 셰이더, 픽셀 셰이더
Vertex 구조체를 만들어 Vertex Data 를 구성하고, ID3D11Buffer 를 생성해서 ID3D11Device 의 CreateBuffer 함수로 VertexBuffer 를 만들어준다.
void Game::CreateGeometry()
{
// VertexData
{
_vertices.resize(3);
_vertices[0].position = Vec3(-0.5f, -0.5f, 0.f);
_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(0.f, 1.f, 0.f, 1.f);
_vertices[2].position = Vec3(0.5f, -0.5f, 0.f);
_vertices[2].color = Color(0.f, 0.f, 1.f, 1.f);
}
// VertexBuffer
{
D3D11_BUFFER_DESC desc;
ZeroMemory(&desc, sizeof(desc));
desc.Usage = D3D11_USAGE_IMMUTABLE;
desc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
desc.ByteWidth = (uint32)(sizeof(Vertex) * _vertices.size());
D3D11_SUBRESOURCE_DATA data;
ZeroMemory(&data, sizeof(data));
data.pSysMem = _vertices.data();
_device->CreateBuffer(&desc, &data, _vertexBuffer.GetAddressOf());
}
}
참조
D3D11_USAGE_IMMUTABLE
변경할 수 없고 GPU 가 읽기만 가능.
https://learn.microsoft.com/ko-kr/windows/win32/api/d3d11/ne-d3d11-d3d11_usage
ID3D11Device::CreateBuffer 메서드
https://learn.microsoft.com/ko-kr/windows/win32/api/d3d11/nf-d3d11-id3d11device-createbuffer
Input Layout 생성
Device 에 어떤 데이터가 Input 되는지 묘사를 생성해서 전달해야 함. 이 때 Vertex Shader 를 생성해서 데이터를 넘겨줘야 함. Input Layout 을 생성할 때 필요한 Com 포인터들 먼저 생성.
ComPtr<ID3D11InputLayout> _inputLayout;
ComPtr<ID3D11VertexShader> _vertexShader;
ComPtr<ID3DBlob> _vsBlob;
ComPtr<ID3D11PixelShader> _pixelShader;
ComPtr<ID3DBlob> _psBlob;
위처럼 버텍스셰이더 포인터도 만들어주고 실제 버텍스셰이더 코드를 생성해준다.
셰이더는 2가지 방식으로 컴파일.
미리 컴파일 해서 cso file(Compiled Shader Object) 형태를 로드해서 사용하거나, 프로그램을 실행할 때 셰이더를 컴파일하거나. cso file 을 빌드해야 컴파일 타임에서 셰이더 문법 에러를 찾아낼 수 있음. 런타임 옵션으로 하면 실행할 때 셰이더 에러를 검출하기 때문에 작업 파이프라인상으로는 비효율적.
Default.hlsl 파일을 추가하면 프로젝트 속성 페이지 하단에 HLSL 컴파일러 속성 그룹이 추가됨. 중요한 부분은, 진입점 이름. main 함수로 돼있는 것을 적절하게 코딩할 함수 이름으로 수정해주고. 셰이더 모델도 적절한 버전으로 올려준다.
Default.hlsl 을 생성하면 초기 모습. 셰이더 파일 하나에 VS와 PS 를 모두 작성해서 넣는다. (구분할 수도 있음)
float4 main( float4 pos : POSITION ) : SV_POSITION
{
return pos;
}
이런 기본 함수를 지우고, 아래처럼 VS, PS 를 간단하게 작성.
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);
}
셰이더 코드를 짰으니, 로드하는 함수가 필요.
D3DCompileFromFile() 함수로 Default.hlsl 셰이더를 컴파일해서 로드할 수 있음.
https://learn.microsoft.com/ko-kr/windows/win32/api/d3dcompiler/nf-d3dcompiler-d3dcompilefromfile
경로, 엔트리포인트 함수 이름, 버전을 넘기고 ID3DBlob 으로 받음. Blobl 은 컴파일된 데이터를 담을 뿐이고, 실제 셰이더 COM 인터페이스는 _vertexShader.
ID3D11Device::CreateVertexShader 함수로 VS 를 생성해서 _vertexShader 포인터로 받음.
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::LoadShaderFromFile(const wstring& path, const string& name, const string& version, ComPtr<ID3DBlob>& blob)
{
const uint32 compileFlag = D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;
HRESULT hr = ::D3DCompileFromFile(
path.c_str(),
nullptr,
D3D_COMPILE_STANDARD_FILE_INCLUDE,
name.c_str(),
version.c_str(),
compileFlag,
0,
blob.GetAddressOf(),
nullptr);
CHECK(hr);
}
PS도 비슷하게 만들어주고. Blob 은 동일하게 쓰면 되고 _pixelShader 로 받으면 됨.
최종적으로 초기화 순서는 아래와 같음.
void Game::Init(HWND hwnd)
{
_hwnd = hwnd;
_width = GWinSizeX;
_height = GWinSizeY;
// TODO
CreateDeviceAndSwapChain();
CreateRenderTargetView();
SetViewport();
CreateGeometry();
CreateVS();
CreateInputLayout();
CreatePS();
}
다음은 Render Begin/End 에 적절한 DeviceContext 메소드를 호출해서 렌더링을 처리해주면 됨.
void Game::Render()
{
RenderBegin();
// IA - VS - RS - PS - OM
{
// Input Assembler
uint32 stride = sizeof(Vertex);
uint32 offset = 0;
_deviceContext->IASetVertexBuffers(0, 1, _vertexBuffer.GetAddressOf(), &stride, &offset);
_deviceContext->IASetInputLayout(_inputLayout.Get());
_deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
// Vertex Shader
_deviceContext->VSSetShader(_vertexShader.Get(), nullptr, 0);
// Rasterizer Stage
// Pixel Shader
_deviceContext->PSSetShader(_pixelShader.Get(), nullptr, 0);
// Output Merger
_deviceContext->Draw(_vertices.size(), 0);
}
RenderEnd();
}
여기까지. 필수요소들을 설정해서 삼각형 지오메트리를 하나 렌더링 해봄. 모든 과정들을 명확하게 기억하고 인지해야.
마무리로, Pixel Shader 에 Ceilling 함수를 살짝 넣어서 계단식 컬러로 바꿔봄.
다음은 IndexBuffer 를 사용해보기.
vector<uint32> _indices;
ComPtr<ID3D11Buffer> _indexBuffer;
uin32 로 인덱스를 담을 벡터와 ID3D11Buffer 를 하나 더 만들어서 indexBuffer 로 생성할 것.
_indices 벡터에는 시계방향으로 버텍스를 적절히 담아주고,
_indexBuffer 는 _vertexBuffer 를 만들었던 것과 동일하게. 단, BindFlag 만 BIND_INDEX_BUFFER 로 수정.
그리고 렌더링할 때 아래 함수를 사용해서 IndexBuffer 를 렌더 파이프라인에 알려주면 됨.
_deviceContext->IASetIndexBuffer(_indexBuffer.Get(), DXGI_FORMAT_R32_UINT, 0);
그리는 함수도 _deviceContext->Draw 대신 _deviceContext->DrawIndexed 로 호출하면 끝.
<리얼-타임 렌더링(REAL-TIME RENDERING) 4/e> 구입 링크
https://link.coupang.com/a/8VWas
<DirectX 12를 이용한 3D 게임 프로그래밍 입문> 구입 링크
https://link.coupang.com/a/8V4Wq
(링크를 통해 도서 구입시 일정액의 수수료를 지급받습니다.)