DirectX 11 스터디 - 모듈화 (2), 렌더링을 책임질 Pipeline클래스 작성
앞에서 Geometry, VertexData 부터 Output Merger 의 BlendState까지 모두 모듈화 해서 DX COM객체를 캡슐화 했고, 이번엔 모듈화한 객체들을 가지고 Rendering Pipeline 을 실제 수행하는 객체를 작성해본다.
파이프라인에서 고유하게 존재하고 사용되어야 하는 모듈들과, 3D 물체마다 값/설정이 변경되어야 하는 모듈을 구분한다. 3D 물체마다 설정이 변경되어야 하는 객체들을 별도로 그룹화 해서 PipelineInfo 구조체로 묶어준다. 렌더링 파이프라인에 이 구조체를 기준으로 설정을 바꿔서 태워주면 새로운 물체를 그릴 수 있게 됨.
PipelineInfo 는 아래 구성 요소가 포함됨. 여기서 ConstantBuffer는 3D 메시마다 필요할 수도, 필요하지 않을 수도 있기 때문에 공용 객체, 즉 PipelineInfo 에 포함시키지 않는다. 마찬가지로 Texture, SamplerState 도 공용 Pipeline 갱신에는 참여하지 않도록 제거한다. 필요할 때만 별도로 호출하도록 해야. 옵셔널로 분리.
파이프라인 공용 업데이트 함수를 정리하면 아래와 같이 코드를 짤 수 있음.
void Pipeline::Update(PipelineInfo info)
{
// IA
_deviceContext->IASetInputLayout(info.inputLayout->GetComPtr().Get());
_deviceContext->IASetPrimitiveTopology(info.topology);
// VS
if (info.vertexShader)
_deviceContext->VSSetShader(info.vertexShader->GetComPtr().Get(), nullptr, 0);
// RS
if (info.rasterizerState)
_deviceContext->RSSetState(info.rasterizerState->GetComPtr().Get());
// PS
if (info.pixelShader)
_deviceContext->PSSetShader(info.pixelShader->GetComPtr().Get(), nullptr, 0);
// OM
if (info.blendState)
_deviceContext->OMSetBlendState(info.blendState->GetComPtr().Get(), info.blendState->GetBlendFactor(), info.blendState->GetSampleMask());
}
위 Update 에서 제외된 옵셔널한 셋팅을 별도 메소드로 분리해서 제공.
VertexBuffer, IndexBuffer 가 대표적.
void Pipeline::Update(PipelineInfo info)
{
// ...
}
void Pipeline::SetVertexBuffer(shared_ptr<VertexBuffer> buffer)
{
uint32 stride = buffer->GetStride();
uint32 offset = buffer->GetOffset();
_deviceContext->IASetVertexBuffers(0, 1, buffer->GetComPtr().GetAddressOf(), &stride, &offset);
}
void Pipeline::SetIndexBuffer(shared_ptr<IndexBuffer> buffer)
{
_deviceContext->IASetIndexBuffer(buffer->GetComPtr().Get(), DXGI_FORMAT_R32_UINT, 0);
}
이렇게 VertexBuffer, IndexBuffer 설정 메소드까지 완료. 이제 ConstantBuffer를 설정해주는 메소드를 template으로 구현해줌. ConstantBuffer는 이전 예제에서 TransformData 구조체를 만들어서 ID3D11Buffer에 복사하고, 렌더 Begin/End 사이에 전달해줬는데, TransformData가 아닌 다른 타입 데이터를 복사/전달할 수 있기 때문.
이 때, ConstantBuffer는 VertexShader와 PixelShader각각 전달할 수 있음. 마찬가지로, SetShaderResource(), SetSamplers도 각각 VS용, PS용 따로따로 존재함.
class Pipeline
{
//...
template<typename T>
void SetConstantBuffer(uint32 slot, uint32 scope, shared_ptr<ConstantBuffer<T>> buffer)
{
if (scope & SS_VertexShader)
_deviceContext->VSSetConstantBuffers(slot, 1, buffer->GetComPtr().GetAddressOf());
if (scope & SS_PixelShader)
_deviceContext->PSSetConstantBuffers(slot, 1, buffer->GetComPtr().GetAddressOf());
}
}
void Pipeline::SetTexture(uint32 slot, uint32 scope, shared_ptr<Texture> texture)
{
if (scope & SS_VertexShader)
_deviceContext->VSSetShaderResources(slot, 1, texture->GetComPtr().GetAddressOf());
if (scope & SS_PixelShader)
_deviceContext->PSSetShaderResources(slot, 1, texture->GetComPtr().GetAddressOf());
}
void Pipeline::SetSamplerState(uint32 slot, uint32 scope, shared_ptr<SamplerState> samplerState)
{
if (scope & SS_VertexShader)
_deviceContext->VSSetSamplers(slot, 1, samplerState->GetComPtr().GetAddressOf());
if (scope & SS_PixelShader)
_deviceContext->PSSetSamplers(slot, 1, samplerState->GetComPtr().GetAddressOf());
}
여기까지. Pipeline 클래스 선언부만 요약하면 아래와 같음.
class Pipeline
{
// Pseudo code.
void Update(PipelineInfo info);
void SetVertexBuffer(shared_ptr<VertexBuffer> buffer);
void SetIndexBuffer(shared_ptr<IndexBuffer> buffer);
template<typename T>
void SetConstantBuffer(uint32 slot, uint32 scope, shared_ptr<ConstantBuffer<T>> buffer)
void SetTexture(uint32 slot, uint32 scope, shared_ptr<Texture> texture);
void SetSamplerState(uint32 slot, uint32 scope, shared_ptr<SamplerState> samplerState);
void Draw(uint32 vertexCount, uint32 startVertexLocation);
void DrawIndexed(uint32 indexCount, uint32 startIndexLocation, uint32 baseIndexLocation);
};
이제 위에서 만든 Pipeline 객체를 기존에 작성했던 Render() 함수에서 Pipeline::Update() 를 호출하도록 수정해보면 아래와 같이 됨.
먼저 기존 코드.
Render()
{
uint32 stride = sizeof(VertexTextureData);
uint32 offset = 0;
auto _deviceContext = _RHI->GetDeviceContext();
// IA
_deviceContext->IASetVertexBuffers(0, 1, _vertexBuffer->GetComPtr().GetAddressOf(), &stride, &offset);
_deviceContext->IASetIndexBuffer(_indexBuffer->GetComPtr().Get(), DXGI_FORMAT_R32_UINT, 0);
_deviceContext->IASetInputLayout(_inputLayout->GetComPtr().Get());
_deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
// VS
_deviceContext->VSSetShader(_vertexShader->GetComPtr().Get(), nullptr, 0);
_deviceContext->VSSetConstantBuffers(0, 1, _constantBuffer->GetComPtr().GetAddressOf());
// RS
_deviceContext->RSSetState(_rasterizerState->GetComPtr().Get());
// PS
_deviceContext->PSSetShader(_pixelShader->GetComPtr().Get(), nullptr, 0);
_deviceContext->PSSetConstantBuffers(0, 1, _constantBuffer->GetComPtr().GetAddressOf());
_deviceContext->PSSetShaderResources(0, 1, _texture1->GetComPtr().GetAddressOf());
_deviceContext->PSSetSamplers(0, 1, _samplerState->GetComPtr().GetAddressOf());
// OM
_deviceContext->OMSetBlendState(_blendState->GetComPtr().Get(), _blendState->GetBlendFactor(), _blendState->GetSampleMask());
_deviceContext->DrawIndexed(_geometry->GetIndexCount(), 0, 0);
}
아래는 Pipeline::Update() 로 교체한 코드. 2 단계로 구분해서 정리한다.
PipelineInfo info;
info.inputLayout = _inputLayout;
info.vertexShader = _vertexShader;
info.pixelShader = _pixelShader;
info.rasterizerState = _rasterizerState;
info.blendState = _blendState;
_pipeline->Update(info);
auto _deviceContext = _RHI->GetDeviceContext();
uint32 stride = sizeof(VertexTextureData);
uint32 offset = 0;
_deviceContext->IASetVertexBuffers(0, 1, _vertexBuffer->GetComPtr().GetAddressOf(), &stride, &offset);
_deviceContext->IASetIndexBuffer(_indexBuffer->GetComPtr().Get(), DXGI_FORMAT_R32_UINT, 0);
_deviceContext->VSSetConstantBuffers(0, 1, _constantBuffer->GetComPtr().GetAddressOf());
_deviceContext->PSSetShaderResources(0, 1, _texture1->GetComPtr().GetAddressOf());
_deviceContext->PSSetSamplers(0, 1, _samplerState->GetComPtr().GetAddressOf());
_deviceContext->DrawIndexed(_geometry->GetIndexCount(), 0, 0);
나머지 VertexBuffer, IndexBuffer, ConstantBuffer, ShaderResources 까지 모두 교체해서 마무리.
PipelineInfo info;
info.inputLayout = _inputLayout;
info.vertexShader = _vertexShader;
info.pixelShader = _pixelShader;
info.rasterizerState = _rasterizerState;
info.blendState = _blendState;
_pipeline->Update(info);
_pipeline->SetVertexBuffer(_vertexBuffer);
_pipeline->SetIndexBuffer(_indexBuffer);
_pipeline->SetConstantBuffer(0, SS_VertexShader, _constantBuffer);
_pipeline->SetTexture(0, SS_PixelShader, _texture1);
_pipeline->SetSamplerState(0, SS_PixelShader, _samplerState);
_pipeline->DrawIndexed(_geometry->GetIndexCount(), 0, 0);
여기까지.
렌더링을 책임져줄 Pipeline 클래스 작성과정 끝.
.
<리얼-타임 렌더링(REAL-TIME RENDERING) 4/e> 구입 링크
https://link.coupang.com/a/8VWas
<DirectX 12를 이용한 3D 게임 프로그래밍 입문> 구입 링크
https://link.coupang.com/a/8V4Wq
<이득우의 게임 수학:39가지 예제로 배운다! 메타버스를 구성하는 게임 수학의 모든 것> 구입 링크
https://link.coupang.com/a/9BqLd
유니티 에셋 스토어 링크
https://assetstore.unity.com?aid=1011lvz7h
(링크를 통해 도서/에셋 구입시 일정액의 수수료를 지급받습니다.)