Deferred Rendering을 이해하기 위해선 Forward Rendering에 대한 이해가 필요하다.
Forward Rendering의 Forward는 StraightForward의 줄임말로 렌더링 파이프라인을 한번만 거쳐서 GPU의 백버퍼에 그려지는 과정을 이야기한다.
지금까지처럼 물체가 Vertex Shader 단계에서 space에 대한 계산을 하고 Pixel Shader에서는 광원에 대한 계산을하여 해당 픽셀에 그려지는 것을 Forward Rendering 이라고 한다.
그렇다면 Deferred Rendering은 무엇일까??
Deffered 라는 단어는 "지연된" 이라는 뜻을 가지고 있다. 모든 물체는 한번의 파이프라인을 통과시켜 화면에 그리는것이 아니라 두번의 파이프라인을 통해 화면에 그려지게 된다.
첫 번째 파이프라인에서는 물체에 대한 정보(Position, Color, Normal, Specular)들을 나눠서 저장하고, 2번째 파이프라인에서는 이미 계산된 정보들만을 이용하여 화면에 merge하여 화면에 그리는 방식이다.

위의 그림과 같이 물체의 정보를 여러가지 텍스쳐에 나누어서 그리고 그것들을 병합하여 화면에 표시한다. 그리고 첫 번째 랜더링의 경우 MRT를 이용해 한번의 파이프라인으로 수행이 가능하다.
그렇다면 Deffered Rendering과 Forward Rendering의 차이점은 무엇이 있을까??
Forward 방식의 경우, 물체마다 한번의 파이프라인을 통과한다.
광원을 계산하는 주체가 물체가 되기 때문에 라이팅 단계에서 해당 물체마다 모든 광원에 대하여 자신이 광원에 영향을 받는지를 계산하여야 한다. 만약 광원의 개수가 많아지게되면 연산량이 엄청나게 많아지는 단점이 있다.
그리고 모든 물체를 한번씩 파이프라인에 통과시켜 그대로 그리기 때문에, 이미 그려진 픽셀위에 다른 물체가 올라온다면 이전 픽셀은 그려질 필요가 없었는데도 그리고나서 덮어쓰는 방식으로 그려지게 된다. 따라서 사실 이전에 그려진 픽셀은 픽셀셰이더에서 불필요한 계산들을 하게되는 것이다.
Deferred Rendering의 경우, First Pass에서 각 픽셀에 대한 그려야되는 정보들을 저장하게 된다. 물체의 경우 라이팅 연산을 하지않고 위치, 법선벡터, color 같은 정보만 남기고, 광원의 경우 직접 주체가 되어 자신이 화면에 주는 영향을 남기게 된다. 그리고 Merge 단계에서는 해당 픽셀마다 저장된 정보를 이용해서 한번씩만 라이팅을 계산하면 된다.
Forward Rendering의 경우 각 오브젝트마다 Pixel Shader 단계에서 라이팅을 계산하여 OutputMerge 단계에서 Depth Test를 통해 통과한 픽셀만이 화면에 그려지게 되지만, Deferred Rendering의 경우 First Pass에서 해당 픽셀에 그려질 픽셀을 한개로 선정하여 정보를 남겨놓는 방식이기 때문에, 같은 픽셀에 대해 라이팅이 여러번 계산되지 않는 다는점이 장점이다.
반대로 Deferred Rendering의 경우 해당 픽셀에 대해 선정된 한 개의 픽셀만 계산되므로 알파 블렌딩을 사용할 수 없다는 단점이 있고 많은 랜더 타겟 텍스쳐를 사용해야 하므로 더 많은 메모리를 사용해야 한다는 단점이 있다.
먼저 Deferred Rendering의 First Pass를 위해서 MRT(Multi Render Target)을 구현해야한다.
#pragma once
#include "CEntity.h"
class CMRT :
public CEntity
{
private:
Ptr<CTexture> m_arrRTTex[8];
ID3D11RenderTargetView* m_RTView[8];
Vec4 m_ClearColor[8];
UINT m_RTCount;
Ptr<CTexture> m_DSTex;
public:
void Create(Ptr<CTexture>* _pArrTex, UINT _RTCount, Ptr<CTexture> _DSTex);
void SetClearColor(Vec4* _arrClearColor, UINT _RTCount);
void OMSet();
void Clear();
void ClearRT();
void ClearDS();
CLONE_DISABLE(CMRT);
public:
CMRT();
~CMRT();
};
MRT는 여러장의 랜더타겟을 가질수 있고, 최대 한장의 뎊스 스텐실 텍스쳐를 갖도록 한다. 그리고 각각에 대하 Clear될 때의 Color와 렌더 타겟 텍스쳐를 갖고 있도록 했다.
#include "pch.h"
#include "CMRT.h"
#include "CDevice.h"
CMRT::CMRT()
: m_RTView{}
, m_ClearColor{}
{
}
CMRT::~CMRT()
{
}
void CMRT::Create(Ptr<CTexture>* _pArrTex, UINT _RTCount, Ptr<CTexture> _DSTex)
{
assert(_RTCount <= 8);
for (m_RTCount = 0; m_RTCount < _RTCount; ++m_RTCount)
{
m_arrRTTex[m_RTCount] = _pArrTex[m_RTCount];
m_RTView[m_RTCount] = m_arrRTTex[m_RTCount]->GetRTV().Get();
}
m_DSTex = _DSTex;
}
void CMRT::SetClearColor(Vec4* _arrClearColor, UINT _RTCount)
{
for (UINT i = 0; i < _RTCount; ++i)
{
m_ClearColor[i] = _arrClearColor[i];
}
}
void CMRT::OMSet()
{
CONTEXT->OMSetRenderTargets(m_RTCount, m_RTView, m_DSTex->GetDSV().Get());
}
void CMRT::Clear()
{
ClearRT();
ClearDS();
}
void CMRT::ClearRT()
{
for (UINT i = 0; i < m_RTCount; ++i)
{
CONTEXT->ClearRenderTargetView(m_RTView[i], m_ClearColor[i]);
}
}
void CMRT::ClearDS()
{
CONTEXT->ClearDepthStencilView(m_DSTex->GetDSV().Get(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.f, 0);
}
Create에서는 사용할 렌더 타겟 텍스쳐들과, 렌더 타겟의 개수, 뎊스 스텐실 텍스처를 인자로 받아 뷰를 뽑고 자신의 멤버변수에 저장해둔다.
그리고 OMSet을 통해 MRT를 파이프라인에 꽂아준다.
enum class MRT_TYPE
{
SWAPCHAIN, // RT 1, DS 1
DEFERRED, // RT 5, DS 0
LIGHT, // RT 3, DS 0
SHADOW_DEPTH, // RT 1, DS 1
END,
};
사용할 MRT들을 enum class로 정리하고
class CMRT;
class CRenderMgr :
public CSingleton<CRenderMgr>
{
SINGLE(CRenderMgr);
private:
CMRT* m_arrMRT[(UINT)MRT_TYPE::END];
vector<CCamera*> m_vecCam;
CCamera* m_EditorCam;
Ptr<CTexture> m_PostProcessTex;
CStructuredBuffer* m_Light2DBuffer;
vector<CLight2D*> m_vecLight2D;
CStructuredBuffer* m_Light3DBuffer;
vector<CLight3D*> m_vecLight3D;
list<tDebugShapeInfo> m_DbgShapeInfo;
CGameObject* m_pDebugObj;
bool m_DebugPosition;
// NoiseTexture
vector<Ptr<CTexture>> m_vecNoiseTex;
// render function pointer
typedef void(CRenderMgr::*RENDER_FUNC)(void);
RENDER_FUNC m_RenderFunc;
...
RenderMgr에서는 MRT를 포인터 배열로 들고있도록 한다.
void CRenderMgr::CreateMRT()
{
Vec2 vResolution = CDevice::GetInst()->GetRenderResolution();
// =============
// SwapChain MRT
// =============
{
Ptr<CTexture> RTTex = CAssetMgr::GetInst()->FindAsset<CTexture>(L"RenderTargetTex");
Ptr<CTexture> DSTex = CAssetMgr::GetInst()->FindAsset<CTexture>(L"DepthStencilTex");
Vec4 vClearColor = (Vec4(0.3f, 0.3f, 0.3f, 1.f));
m_arrMRT[(UINT)MRT_TYPE::SWAPCHAIN] = new CMRT;
m_arrMRT[(UINT)MRT_TYPE::SWAPCHAIN]->Create(&RTTex, 1, DSTex);
m_arrMRT[(UINT)MRT_TYPE::SWAPCHAIN]->SetClearColor(&vClearColor, 1);
}
// ============
// Deferred MRT
// ============
{
Ptr<CTexture> pRTTex[4] =
{
CAssetMgr::GetInst()->CreateTexture(L"ColorTargetTex"
, vResolution.x, vResolution.y
, DXGI_FORMAT_R8G8B8A8_UNORM
, D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE),
CAssetMgr::GetInst()->CreateTexture(L"PositionTargetTex"
, vResolution.x, vResolution.y
, DXGI_FORMAT_R32G32B32A32_FLOAT
, D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE),
CAssetMgr::GetInst()->CreateTexture(L"NormalTargetTex"
, vResolution.x, vResolution.y
, DXGI_FORMAT_R32G32B32A32_FLOAT
, D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE),
CAssetMgr::GetInst()->CreateTexture(L"DataTargetTex"
, vResolution.x, vResolution.y
, DXGI_FORMAT_R32G32B32A32_FLOAT
, D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE),
};
Vec4 arrClearColor[4] = {
Vec4(0.f, 0.f, 0.f, 1.f),
Vec4(0.f, 0.f, 0.f, 0.f),
Vec4(0.f, 0.f, 0.f, 0.f),
Vec4(0.f, 0.f, 0.f, 0.f),
};
Ptr<CTexture> DSTex = CAssetMgr::GetInst()->FindAsset<CTexture>(L"DepthStencilTex");
m_arrMRT[(UINT)MRT_TYPE::DEFERRED] = new CMRT;
m_arrMRT[(UINT)MRT_TYPE::DEFERRED]->Create(pRTTex, 4, DSTex);
m_arrMRT[(UINT)MRT_TYPE::DEFERRED]->SetClearColor(arrClearColor, 4);
}
}
그리고 Render Mgr가 Init이 될 때, 사용할 MRT들을 미리 만들어 포인터로 들고 있도록 했다.
위와 같이 Deferred Rendering을 위한 MRT를 만들어 저장한다. Color는 물체의 색이 담길 텍스쳐로 R8G8B8A8의 포맷으로 만들었지만, Position, Normal, Data의 경우 RGB에 float이 저장되어야 하므로 R32G32B32A32 포맷의 텍스쳐로 만들었다.