Forward rendering은 모든 빛에 대하여 연산을 하게 되는데 범위가 닿지 않는 Point Light 에 대해서도 연산을 하기 때문에 불필요한 연산이 발생하고 느려질 수 있다. 하지만 Deferred Rendering은 반대로 빛을 연산할 때 물체를 찾아 연산을 한다. 빛의 범위를 Volume Mesh라고 하고 그 안에 물체가 있을 경우에만 빛 연산을 한다. Directional Light일 경우 범위가 따로 없기 때문에 화면 안에 보이는 영역이 Rectangle 형태의 Volume Mesh가 된다.
// [Directional Light]
// g_int_0 : Light index
// g_tex_0 : Position RT
// g_tex_1 : Normal RT
// Mesh : Rectangle
VS_OUT VS_DirLight(VS_IN input)
{
VS_OUT output = (VS_OUT)0;
output.pos = float4(input.pos * 2.f, 1.f);
output.uv = input.uv;
return output;
}
Directional Light의 pos는 input.pos에 2배를 해주는데 우리가 만들어준 Rectangle은 좌표가 -0.5 ~ 0.5이다. 그런데 화면의 좌표는 -1 ~ 1로 표현되므로 2배를 해준 것이다.
PS_OUT PS_DirLight(VS_OUT input)
{
PS_OUT output = (PS_OUT)0;
float3 viewPos = g_tex_0.Sample(g_sam_0, input.uv).xyz;
if (viewPos.z <= 0.f)
clip(-1);
float3 viewNormal = g_tex_1.Sample(g_sam_0, input.uv).xyz;
LightColor color = CalculateLightColor(g_int_0, viewNormal, viewPos);
output.diffuse = color.diffuse + color.ambient;
output.specular = color.specular;
return output;
}
viewPos.z가 0이하일 경우 물체가 없다고 판단하는데 왜냐하면 카메라보다 뒤쪽에 있는 것을 의미하기 때문이다.
그래서 g_tex_0 은 빛의 위치를 갖고 있으므로 꺼내서 카메라 뒤에 있으면 계산하지 않는다. 카메라 앞에 빛이 있다면 g_tex_1의 빛의 normal 벡터 정보를 꺼내 LightColor을 계산하고 저장한다.
struct VS_OUT
{
float4 pos : SV_Position;
float2 uv : TEXCOORD;
};
// [Point Light]
// g_int_0 : Light index
// g_tex_0 : Position RT
// g_tex_1 : Normal RT
// g_vec2_0 : RenderTarget Resolution
// Mesh : Sphere
VS_OUT VS_PointLight(VS_IN input)
{
VS_OUT output = (VS_OUT)0;
output.pos = mul(float4(input.pos, 1.f), g_matWVP);
output.uv = input.uv;
return output;
}
PS_OUT PS_PointLight(VS_OUT input)
{
PS_OUT output = (PS_OUT)0;
// input.pos = SV_Position = Screen 좌표
float2 uv = float2(input.pos.x / g_vec2_0.x, input.pos.y / g_vec2_0.y);
float3 viewPos = g_tex_0.Sample(g_sam_0, uv).xyz;
if (viewPos.z <= 0.f)
clip(-1);
int lightIndex = g_int_0;
float3 viewLightPos = mul(float4(g_light[lightIndex].position.xyz, 1.f), g_matView).xyz;
float distance = length(viewPos - viewLightPos);
if (distance > g_light[lightIndex].range)
clip(-1);
float3 viewNormal = g_tex_1.Sample(g_sam_0, uv).xyz;
LightColor color = CalculateLightColor(g_int_0, viewNormal, viewPos);
output.diffuse = color.diffuse + color.ambient;
output.specular = color.specular;
return output;
}
Point Light일 때 PS_PointLight의 input값은 VS_PointLight의 output.pos가 아니다. VS_OUT이 SV_POSITION이기 때문에 결과적으로 Screen 좌표를 input.pos로 받는다. 그래서 다시 uv좌표(0~1인 좌표계)로 변환하기 위해 g_vec2_0 (화면 해상도)의 x 와 y로 나누어준다. 그걸 이용해서 DirLight과 마찬가지로 물체가 있는지 체크한다. 지금은 uv좌표계로 2차원 상에서 체크하고 있기 때문에 빛과 물체 사이 거리를 계산해 3차원 상에서 물체가 영역안에 있는지 한번 더 체크한다.
// [Final]
// g_tex_0 : Diffuse Color Target
// g_tex_1 : Diffuse Light Target
// g_tex_2 : Specular Light Target
// Mesh : Rectangle
VS_OUT VS_Final(VS_IN input)
{
VS_OUT output = (VS_OUT)0;
output.pos = float4(input.pos * 2.f, 1.f);
output.uv = input.uv;
return output;
}
float4 PS_Final(VS_OUT input) : SV_Target
{
float4 output = (float4)0;
float4 lightPower = g_tex_1.Sample(g_sam_0, input.uv);
if (lightPower.x == 0.f && lightPower.y == 0.f && lightPower.z == 0.f)
clip(-1);
float4 color = g_tex_0.Sample(g_sam_0, input.uv);
float4 specular = g_tex_2.Sample(g_sam_0, input.uv);
output = (color * lightPower) + specular;
return output;
}
Final에서 둘을 합쳐 최종적으로 계산한다. 빛의 영향을 받는다면 (lightPower.x!=0) 빛 연산을 한다.
enum class SHADER_TYPE : uint8
{
DEFERRED,
FORWARD,
LIGHTING,
};
enum class DEPTH_STENCIL_TYPE : uint8
{
LESS,
LESS_EQUAL,
GREATER,
GREATER_EQUAL,
NO_DEPTH_TEST, // 깊이 테스트(X) + 깊이 기록(O)
NO_DEPTH_TEST_NO_WRITE, // 깊이 테스트(X) + 깊이 기록(X)
LESS_NO_WRITE, // 깊이 테스트(O) + 깊이 기록(X)
};
enum class BLEND_TYPE : uint8
{
DEFAULT,
ALPHA_BLEND,
ONE_TO_ONE_BLEND,
END,
};
C++으로 가서 Shader 헤더파일에 위와 같이 Shader Type과 Depth Stencil Type, BlendType을 추가했다.
//Shader::Init
D3D12_RENDER_TARGET_BLEND_DESC& rt = _pipelineDesc.BlendState.RenderTarget[0];
// SrcBlend = Pixel Shader
// DestBlend = Render Target
switch (info.blendType)
{
case BLEND_TYPE::DEFAULT:
rt.BlendEnable = FALSE;
rt.LogicOpEnable = FALSE;
rt.SrcBlend = D3D12_BLEND_ONE;
rt.DestBlend = D3D12_BLEND_ZERO;
break;
case BLEND_TYPE::ALPHA_BLEND:
rt.BlendEnable = TRUE;
rt.LogicOpEnable = FALSE;
rt.SrcBlend = D3D12_BLEND_SRC_ALPHA;
rt.DestBlend = D3D12_BLEND_INV_SRC_ALPHA;
break;
case BLEND_TYPE::ONE_TO_ONE_BLEND:
rt.BlendEnable = TRUE;
rt.LogicOpEnable = FALSE;
rt.SrcBlend = D3D12_BLEND_ONE;
rt.DestBlend = D3D12_BLEND_ONE;
break;
}
새로 추가된 Blend Type 설정 코드이다. 맨 처음 BlendState는 independentEnable을 true로 설정하면 여러개의 RenderTarget을 사용가능하고 false로 설정하면 RenderTarget[0], 첫번째만 사용할 수 있다. Blend는 Pixel Shader과 RenderTarget을 어느 정도 섞을지 설정하는 것인데 타입마다 비율이 다르다.
// Engine::CreateRenderTargetGroup
// Lighting Group
{
vector<RenderTarget> rtVec(RENDER_TARGET_LIGHTING_GROUP_MEMBER_COUNT);
rtVec[0].target = GET_SINGLE(Resources)->CreateTexture(L"DiffuseLightTarget",
DXGI_FORMAT_R8G8B8A8_UNORM, _window.width, _window.height,
CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
D3D12_HEAP_FLAG_NONE, D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET);
rtVec[1].target = GET_SINGLE(Resources)->CreateTexture(L"SpecularLightTarget",
DXGI_FORMAT_R8G8B8A8_UNORM, _window.width, _window.height,
CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
D3D12_HEAP_FLAG_NONE, D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET);
_rtGroups[static_cast<uint8>(RENDER_TARGET_GROUP_TYPE::LIGHTING)] = make_shared<RenderTargetGroup>();
_rtGroups[static_cast<uint8>(RENDER_TARGET_GROUP_TYPE::LIGHTING)]->Create(RENDER_TARGET_GROUP_TYPE::LIGHTING, rtVec, dsTexture);
}
Engine에서는 RenderTargetGroup을 만들 때 새로 추가된 Shader Type 인 Lighting을 추가해준다.
//Resources::CreateDefaultShader
// Texture (Forward)
{
ShaderInfo info =
{
SHADER_TYPE::FORWARD,
RASTERIZER_TYPE::CULL_NONE,
DEPTH_STENCIL_TYPE::NO_DEPTH_TEST_NO_WRITE
};
shared_ptr<Shader> shader = make_shared<Shader>();
shader->Init(L"..\\Resources\\Shader\\forward.fx", info, "VS_Tex", "PS_Tex");
Add<Shader>(L"Texture", shader);
}
// DirLight
{
ShaderInfo info =
{
SHADER_TYPE::LIGHTING,
RASTERIZER_TYPE::CULL_NONE,
DEPTH_STENCIL_TYPE::NO_DEPTH_TEST_NO_WRITE,
BLEND_TYPE::ONE_TO_ONE_BLEND
};
shared_ptr<Shader> shader = make_shared<Shader>();
shader->Init(L"..\\Resources\\Shader\\lighting.fx", info, "VS_DirLight", "PS_DirLight");
Add<Shader>(L"DirLight", shader);
}
// PointLight
{
ShaderInfo info =
{
SHADER_TYPE::LIGHTING,
RASTERIZER_TYPE::CULL_NONE,
DEPTH_STENCIL_TYPE::NO_DEPTH_TEST_NO_WRITE,
BLEND_TYPE::ONE_TO_ONE_BLEND
};
shared_ptr<Shader> shader = make_shared<Shader>();
shader->Init(L"..\\Resources\\Shader\\lighting.fx", info, "VS_PointLight", "PS_PointLight");
Add<Shader>(L"PointLight", shader);
}
// Final
{
ShaderInfo info =
{
SHADER_TYPE::LIGHTING,
RASTERIZER_TYPE::CULL_BACK,
DEPTH_STENCIL_TYPE::NO_DEPTH_TEST_NO_WRITE,
};
shared_ptr<Shader> shader = make_shared<Shader>();
shader->Init(L"..\\Resources\\Shader\\lighting.fx", info, "VS_Final", "PS_Final");
Add<Shader>(L"Final", shader);
}
Resources에 Texture, DirLight, PointLight, Final Shader들을 추가한다. shader->init에서 각 쉐이더의 VS, PS 함수 이름을 매개변수로 받는다.
//Resources::CreateDefaultMaterial
// DirLight
{
shared_ptr<Shader> shader = GET_SINGLE(Resources)->Get<Shader>(L"DirLight");
shared_ptr<Material> material = make_shared<Material>();
material->SetShader(shader);
material->SetTexture(0, GET_SINGLE(Resources)->Get<Texture>(L"PositionTarget"));
material->SetTexture(1, GET_SINGLE(Resources)->Get<Texture>(L"NormalTarget"));
Add<Material>(L"DirLight", material);
}
// PointLight
{
const WindowInfo& window = GEngine->GetWindow();
Vec2 resolution = { static_cast<float>(window.width), static_cast<float>(window.height) };
shared_ptr<Shader> shader = GET_SINGLE(Resources)->Get<Shader>(L"PointLight");
shared_ptr<Material> material = make_shared<Material>();
material->SetShader(shader);
material->SetTexture(0, GET_SINGLE(Resources)->Get<Texture>(L"PositionTarget"));
material->SetTexture(1, GET_SINGLE(Resources)->Get<Texture>(L"NormalTarget"));
material->SetVec2(0, resolution);
Add<Material>(L"PointLight", material);
}
// Final
{
shared_ptr<Shader> shader = GET_SINGLE(Resources)->Get<Shader>(L"Final");
shared_ptr<Material> material = make_shared<Material>();
material->SetShader(shader);
material->SetTexture(0, GET_SINGLE(Resources)->Get<Texture>(L"DiffuseTarget"));
material->SetTexture(1, GET_SINGLE(Resources)->Get<Texture>(L"DiffuseLightTarget"));
material->SetTexture(2, GET_SINGLE(Resources)->Get<Texture>(L"SpecularLightTarget"));
Add<Material>(L"Final", material);
}
Material도 만들어주는데 각 material에 적용되는 texture들은 RenderTargetGroup에서 만들어 준 것이다.
void Scene::AddGameObject(shared_ptr<GameObject> gameObject)
{
if (gameObject->GetCamera() != nullptr)
{
_cameras.push_back(gameObject->GetCamera());
}
else if (gameObject->GetLight() != nullptr)
{
_lights.push_back(gameObject->GetLight());
}
_gameObjects.push_back(gameObject);
}
그리고 Scene에서는 gameObject마다 camera나 light를 가질 경우 _cameras, _lights 벡터에 추가한다.
void Scene::Render()
{
PushLightData();
// SwapChain Group 초기화
int8 backIndex = GEngine->GetSwapChain()->GetBackBufferIndex();
GEngine->GetRTGroup(RENDER_TARGET_GROUP_TYPE::SWAP_CHAIN)->ClearRenderTargetView(backIndex);
// Deferred Group 초기화
GEngine->GetRTGroup(RENDER_TARGET_GROUP_TYPE::G_BUFFER)->ClearRenderTargetView();
// Lighting Group 초기화
GEngine->GetRTGroup(RENDER_TARGET_GROUP_TYPE::LIGHTING)->ClearRenderTargetView();
// Deferred OMSet
GEngine->GetRTGroup(RENDER_TARGET_GROUP_TYPE::G_BUFFER)->OMSetRenderTargets();
shared_ptr<Camera> mainCamera = _cameras[0];
mainCamera->SortGameObject();
mainCamera->Render_Deferred();
GEngine->GetRTGroup(RENDER_TARGET_GROUP_TYPE::G_BUFFER)->WaitTargetToResource();
RenderLights();
GEngine->GetRTGroup(RENDER_TARGET_GROUP_TYPE::LIGHTING)->WaitTargetToResource();
RenderFinal();
mainCamera->Render_Forward();
for (auto& camera : _cameras)
{
if (camera == mainCamera)
continue;
camera->SortGameObject();
camera->Render_Forward();
}
}
Scene::Render에서는 Lighting Group에 대한 초기화를 추가해주고 가장 먼저 추가된 카메라를 메인 카메라로 간주한 후 메인 카메라에 대해 Deferred Rendering을 한다. 그렇게 RenderLights, RenderFinal로 세상을 그려주고 메인 카메라에 대하여 Forward Rendering을 하면 나머지 카메라에 대해서는 Render_Forward를 진행한다.
void Scene::RenderLights()
{
GEngine->GetRTGroup(RENDER_TARGET_GROUP_TYPE::LIGHTING)->OMSetRenderTargets();
// 광원을 그린다.
for (auto& light : _lights)
{
light->Render();
}
}
void Scene::RenderFinal()
{
// Swapchain OMSet
int8 backIndex = GEngine->GetSwapChain()->GetBackBufferIndex();
GEngine->GetRTGroup(RENDER_TARGET_GROUP_TYPE::SWAP_CHAIN)->OMSetRenderTargets(1, backIndex);
GET_SINGLE(Resources)->Get<Material>(L"Final")->PushData();
GET_SINGLE(Resources)->Get<Mesh>(L"Rectangle")->Render();
}
RenderLights는 빛이 물체를 찾아 연산한다고 했듯이 lights를 돌며 light->Render을 하고 RenderFinal에서 SwapChain의 백버퍼에 그릴 준비를 한 뒤 최종적으로 Render을 하게 된다. 이제 light->Render을 만들어야 한다.
void Light::SetLightType(LIGHT_TYPE type)
{
_lightInfo.lightType = static_cast<int32>(type);
switch (type)
{
case LIGHT_TYPE::DIRECTIONAL_LIGHT:
_volumeMesh = GET_SINGLE(Resources)->Get<Mesh>(L"Rectangle");
_lightMaterial = GET_SINGLE(Resources)->Get<Material>(L"DirLight");
break;
case LIGHT_TYPE::POINT_LIGHT:
_volumeMesh = GET_SINGLE(Resources)->Get<Mesh>(L"Sphere");
_lightMaterial = GET_SINGLE(Resources)->Get<Material>(L"PointLight");
break;
case LIGHT_TYPE::SPOT_LIGHT:
_volumeMesh = GET_SINGLE(Resources)->Get<Mesh>(L"Sphere");
_lightMaterial = GET_SINGLE(Resources)->Get<Material>(L"PointLight");
break;
}
}
먼저 SetLightType으로 빛 종류 별로 Resource에서 찾아서 volumeMesh와 material을 세팅한다.
void Light::Render()
{
assert(_lightIndex >= 0);
GetTransform()->PushData();
_lightMaterial->SetInt(0, _lightIndex);
_lightMaterial->PushData();
switch (static_cast<LIGHT_TYPE>(_lightInfo.lightType))
{
case LIGHT_TYPE::POINT_LIGHT:
case LIGHT_TYPE::SPOT_LIGHT:
float scale = 2 * _lightInfo.range;
GetTransform()->SetLocalScale(Vec3(scale, scale, scale));
break;
}
_volumeMesh->Render();
}
scale 이 range의 2배를 곱한 이유는 range가 반지름이므로 구에 해당하는 범위를 나타내기 위해이고, SetLocalScale을 곱한 것은 PS로 넘어갈 때 matWVP를 곱하게 되는데 그에 맞는 범위를 나타내기 위해 그 전에 LocalScale을 곱한 것이다.
private:
D3D12_RESOURCE_BARRIER _targetToResource[8];
D3D12_RESOURCE_BARRIER _resourceToTarget[8];
이대로 실행하면 지지직거리는 문제가 발생하는데 이유는 Texture을 Render Target 용도와 Resource 용도로 동시에 사용하고 있어서 그런 것이다. 이를 방지하기 위해서는 Barrier을 만들어야 한다. RenderTargetGroup 에 위 배리어를 만들고 targetToResource는 RenderTarget에서 Resource로 resourceToTarget은 그 반대를 막는 배리어이다.
//RenderTargetGroup::Init
for (int i = 0; i < _rtCount; ++i)
{
_targetToResource[i] = CD3DX12_RESOURCE_BARRIER::Transition(_rtVec[i].target->GetTex2D().Get(),
D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_COMMON);
_resourceToTarget[i] = CD3DX12_RESOURCE_BARRIER::Transition(_rtVec[i].target->GetTex2D().Get(),
D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_RENDER_TARGET);
}
RenderTargetGroup::Init에 Transition 함수로 Render Target에서 Common으로 혹은 그 반대로 배리어를 만든다.
void RenderTargetGroup::ClearRenderTargetView()
{
WaitResourceToTarget();
for (uint32 i = 0; i < _rtCount; i++)
{
D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle = CD3DX12_CPU_DESCRIPTOR_HANDLE(_rtvHeapBegin, i * _rtvHeapSize);
CMD_LIST->ClearRenderTargetView(rtvHandle, _rtVec[i].clearColor, 0, nullptr);
}
CMD_LIST->ClearDepthStencilView(_dsvHeapBegin, D3D12_CLEAR_FLAG_DEPTH, 1.f, 0, 0, nullptr);
}
ClearRenderTarget하기 전에 WaitResourceToTarget함수로 RenderTarget이 되기까지 기다리고 실행한다.
void Scene::Render()
{
PushLightData();
// SwapChain Group 초기화
int8 backIndex = GEngine->GetSwapChain()->GetBackBufferIndex();
GEngine->GetRTGroup(RENDER_TARGET_GROUP_TYPE::SWAP_CHAIN)->ClearRenderTargetView(backIndex);
// Deferred Group 초기화
GEngine->GetRTGroup(RENDER_TARGET_GROUP_TYPE::G_BUFFER)->ClearRenderTargetView();
// Lighting Group 초기화
GEngine->GetRTGroup(RENDER_TARGET_GROUP_TYPE::LIGHTING)->ClearRenderTargetView();
// Deferred OMSet
GEngine->GetRTGroup(RENDER_TARGET_GROUP_TYPE::G_BUFFER)->OMSetRenderTargets();
shared_ptr<Camera> mainCamera = _cameras[0];
mainCamera->SortGameObject();
mainCamera->Render_Deferred();
GEngine->GetRTGroup(RENDER_TARGET_GROUP_TYPE::G_BUFFER)->WaitTargetToResource();
RenderLights();
GEngine->GetRTGroup(RENDER_TARGET_GROUP_TYPE::LIGHTING)->WaitTargetToResource();
RenderFinal();
mainCamera->Render_Forward();
for (auto& camera : _cameras)
{
if (camera == mainCamera)
continue;
camera->SortGameObject();
camera->Render_Forward();
}
}
Scene::Render에서도 ClearRenderTargetView로 Resource->Target을 기다려주고 Wait TargetToResource로 Render Target -> Resource를 기다려준다. 이제 배리어를 통해 상태가 전환되기 때문에 Resource State를 RenderTarget에서
Common으로 바꿔줘야 한다.
'그래픽스' 카테고리의 다른 글
| [DX12] Particle System (0) | 2022.07.29 |
|---|---|
| [DX12] Compute Shader (0) | 2022.07.29 |
| [DX12] Render Target (0) | 2022.07.28 |
| [DX12] Orthographic Projection (0) | 2022.07.28 |
| [DX12] Quaternion #1 (0) | 2022.07.27 |