광원 기준으로 깊이값을 텍스쳐로 저장
렌더 시에 카메라 기준 좌표계를 광원 기준 좌표계로 변환 후
변환된 uv 좌표와 깊이 텍스쳐를 통하여 그림자 필요 유무를 판별한다.
shadow 쉐이더 파일
struct VS_OUT
{
float4 pos : SV_Position;
float4 clipPos : POSITION;
};
VS_OUT VS_Main(VS_IN input)
{
...
output.pos = mul(float4(input.pos, 1.f), g_matWVP);
output.clipPos = output.pos;
return output;
}
float4 PS_Main(VS_OUT input) : SV_Target
{
return float4(input.clipPos.z / input.clipPos.w, 0.f, 0.f, 0.f);
}
버텍스 쉐이더의 system value position은 픽셀 쉐이더로 넘어갈 시
스크린 좌표계이기 때문에 따로 저장
픽셀 쉐이더에서 투영 좌표계의 깊이 값을 반환하도록 구현
cbuffer TRANSFORM_PARAMS : register(b1)
{
...
row_major matrix g_matViewInv;
};
cbuffer MATERIAL_PARAMS : register(b2)
{
...
row_major float4x4 g_mat_0;
row_major float4x4 g_mat_1;
row_major float4x4 g_mat_2;
row_major float4x4 g_mat_3;
};
쉐이더에서 카메라 기준 좌표를 광원 기준 좌표로
변환하기 위하여 필요한 행렬 데이터들을 추가
PS_OUT PS_DirLight(VS_OUT input)
{
...
if (length(color.diffuse) != 0)
{
...
float4 worldPos = mul(float4(viewPos.xyz, 1.f), g_matViewInv);
float4 shadowClipPos = mul(worldPos, shadowCameraVP);
float depth = shadowClipPos.z / shadowClipPos.w;
float2 uv = shadowClipPos.xy / shadowClipPos.w;
uv.y = -uv.y;
uv = uv * 0.5 + 0.5;
if (0 < uv.x && uv.x < 1 && 0 < uv.y && uv.y < 1)
{
float shadowDepth = g_tex_2.Sample(g_sam_0, uv).x;
if (shadowDepth > 0 && depth > shadowDepth + 0.00001f)
{
color.diffuse *= 0.5f;
color.specular = (float4) 0.f;
}
}
}
...
}
현재 좌표의 depth와 텍스쳐에 저장되어 잇는 shadow depth를 비교하여
현재 좌표에 그림자 그려져야 하는 지를 판단
light 클래스 수정
class Light : public Component
{
public:
...
void Render();
void RenderShadow();
...
private:
...
shared_ptr<GameObject> _shadowCamera;
};
light 객체는 shadow depth 텍스쳐를 만들기 위해 카메라를 가지게 됨
void Light::RenderShadow()
{
_shadowCamera->GetCamera()->SortShadowObject();
_shadowCamera->GetCamera()->Render_Shadow();
}
렌더 전에 그림자를 먼저 계산한다.
camera 클래스 수정
void Camera::SortShadowObject()
{
...
for (auto& gameObject : gameObjects)
{
...
if (gameObject->IsStatic())
continue;
...
_vecShadow.push_back(gameObject);
}
}
광원이 가지고 있는 카메라가 씬의 모든 객체들을 가지고 그림자 필요 물체 판별
물체가 움직이지 않는 경우 미리 계산된 텍스쳐를 사용
void Camera::Render_Shadow()
{
...
for (auto& gameObject : _vecShadow)
{
gameObject->GetMeshRenderer()->RenderShadow();
}
}
그림자 렌더 시에 객체의 메쉬 렌더러를 이용하여 그림자 렌더
mesh renderer 클래스 수정
void MeshRenderer::RenderShadow()
{
GetTransform()->PushData();
GET_SINGLE(Resources)->Get<Material>(L"Shadow")->PushGraphicsData();
_mesh->Render();
}
그림자 렌더 시에는 메쉬 렌더러가 가지고 있는 메테리얼을 사용하는 것이 아닌
공통된 그림자 메테리얼를 이용
scene 클래스 수정
void Scene::Render()
{
PushLightData();
ClearRTV();
RenderShadow();
RenderDeferred();
RenderLights();
RenderFinal();
RenderForward();
}
void Scene::RenderShadow()
{
...
for (auto& light : _lights)
{
if (light->GetLightType() != LIGHT_TYPE::DIRECTIONAL_LIGHT)
continue;
light->RenderShadow();
}
...
}
일단 directional light인 경우에만 그림자를 그리도록 구현
engine 클래스 수정
void Engine::CreateRenderTargetGroups()
{
...
{
vector<RenderTarget> rtVec(RENDER_TARGET_SHADOW_GROUP_MEMBER_COUNT);
rtVec[0].target = GET_SINGLE(Resources)->CreateTexture(L"ShadowTarget",
DXGI_FORMAT_R32_FLOAT, 4096, 4096,
CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
D3D12_HEAP_FLAG_NONE, D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET);
shared_ptr<Texture> shadowDepthTexture = GET_SINGLE(Resources)->CreateTexture(L"ShadowDepthStencil",
DXGI_FORMAT_D32_FLOAT, 4096, 4096,
CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
D3D12_HEAP_FLAG_NONE, D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL);
_rtGroups[static_cast<uint8>(RENDER_TARGET_GROUP_TYPE::SHADOW)] = make_shared<RenderTargetGroup>();
_rtGroups[static_cast<uint8>(RENDER_TARGET_GROUP_TYPE::SHADOW)]->Create(RENDER_TARGET_GROUP_TYPE::SHADOW, rtVec, shadowDepthTexture);
}
...
}
render target group을 만들 때 그림자 깊이 정보를 저장할 텍스쳐 생성
그림자 텍스쳐의 경우 해상도가 낮을 시 그림자가 이상하게 그려지기 때문에
보통 해상도보다 2배 크게 그림
그래서 텍스쳐 크기를 4096 * 4096으로 만들어 둠
shader 클래스 수정
void Shader::CreateGraphicsShader(const wstring& path, ShaderInfo info, const string& vs, const string& ps, const string& gs)
{
...
switch (info.shaderType)
{
...
case SHADER_TYPE::SHADOW:
_graphicsPipelineDesc.NumRenderTargets = 1;
_graphicsPipelineDesc.RTVFormats[0] = DXGI_FORMAT_R32_FLOAT;
break;
}
...
}
engine에서 만드는 그림자 텍스쳐 포맷과 쉐이더 포맷을 일치시킨다.
transform 클래스 수정
class Transform : public Component
{
public:
...
void LookAt(const Vec3& dir);
static bool CloseEnough(const float& a, const float& b, const float& epsilon = std::numeric_limits<float>::epsilon());
static Vec3 DecomposeRotationMatrix(const Matrix& rotation);
...
};
광원의 방향을 바꾸면 카메라의 방향 또한 동일하게 바꿔주어야 함
이를 위하여 LookAt 함수 정의 및 구현
void Transform::PushData()
{
...
transformParams.matViewInv = Camera::S_MatView.Invert();
CONST_BUFFER(CONSTANT_BUFFER_TYPE::TRANSFORM)->PushGraphicsData(&transformParams, sizeof(transformParams));
}
쉐이더에 넣어줄 뷰 월드 변환 행렬 입력
light 클래스 수정 2
void Light::SetLightType(LIGHT_TYPE type)
{
...
switch (type)
{
case LIGHT_TYPE::DIRECTIONAL_LIGHT:
_volumeMesh = GET_SINGLE(Resources)->Get<Mesh>(L"Rectangle");
_lightMaterial = GET_SINGLE(Resources)->Get<Material>(L"DirLight");
_shadowCamera->GetCamera()->SetScale(1.f);
_shadowCamera->GetCamera()->SetFar(10000.f);
_shadowCamera->GetCamera()->SetWidth(4096);
_shadowCamera->GetCamera()->SetHeight(4096);
break;
...
}
}
광원의 타입이 정해질 때 광원이 사용할 카메라를 설정 해준다.
void Light::Render()
{
...
if (static_cast<LIGHT_TYPE>(_lightInfo.lightType) == LIGHT_TYPE::DIRECTIONAL_LIGHT)
{
shared_ptr<Texture> shadowTex = GET_SINGLE(Resources)->Get<Texture>(L"ShadowTarget");
_lightMaterial->SetTexture(2, shadowTex);
Matrix matVP = _shadowCamera->GetCamera()->GetViewMatrix() * _shadowCamera->GetCamera()->GetProjectionMatrix();
_lightMaterial->SetMatrix(0, matVP);
}
...
}
렌더 시 그림자 텍스쳐를 메테리얼에 입력
render target group 클래스 수정
void RenderTargetGroup::OMSetRenderTargets(uint32 count, uint32 offset)
{
D3D12_VIEWPORT vp = D3D12_VIEWPORT{ 0.f, 0.f, _rtVec[0].target->GetWidth() , _rtVec[0].target->GetHeight(), 0.f, 1.f };
D3D12_RECT rect = D3D12_RECT{ 0, 0, static_cast<LONG>(_rtVec[0].target->GetWidth()), static_cast<LONG>(_rtVec[0].target->GetHeight()) };
GRAPHICS_CMD_LIST->RSSetViewports(1, &vp);
GRAPHICS_CMD_LIST->RSSetScissorRects(1, &rect);
...
}
렌더 타겟을 설정할 때 텍스쳐에 따라 크기를 바꾸도록 수정