본문 바로가기

그래픽스

[DX12] Shadow Mapping

Shadow Mapping은 빛의 위치에 카메라를 두고 그 화면 기준 depth를 저장한다. 그러기 위해 물체들을 대상으로 WVPmatrix를 곱하면 ClipPos가 나오는데 w로 나누면 Projection 좌표계를 구할 수 있고 그 z값이 깊이 값이다. 

 

그리고 장면을 찍는 카메라 기준으로 계산해야 하는데 viewPos에 viewInverseMatrix를 곱해 WorldPos를 구한다. 

거기다 빛의 위치 카메라 기준 ViewProjMatrix를 곱하면 또 ClipPos가 나오고 w로 나눠주면  Projection 좌표가 나온다.

범위는 -1~1이므로 0~1인 uv좌표계로 바꿔준다. 

 

 

첫번째는 Projection 좌표 기준 우리가 찍은 좌표에 대한 빛 위치의 카메라 기준 depth값이고 두번째 uv좌표계는 빛 위치의 카메라 기준 가장 가까이에 있는 물체의 depth 값이다. 1번의 깊이값이 2번의 깊이값보다 크다면 1번 좌표에 그림자를 그려줘야 한다. 

 

정리하면 처음에 빛 위치 카메라 기준 depth를 RenderTarget에 저장하고 장면 카메라로 물체를 그릴 때 (라이팅 연산할 때)

uv좌표와 depth를 비교해서 그림자를 그린다. 그리고 Shadow map은 screen 크기와 같게하면 부자연스럽게 표현되기 때문에 screen 크기보다 더 크게 해야 한다. 

 

struct VS_IN
{
    float3 pos : POSITION;
};

struct VS_OUT
{
    float4 pos : SV_Position;
    float4 clipPos : POSITION;
};

VS_OUT VS_Main(VS_IN input)
{
    VS_OUT output = (VS_OUT)0.f;

    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);
}

 

shadow.hlsl의 전체코드인데 pos에는 matWVP를 곱해주고 clipPos를 구한다. 그리고 clipPos.z / clipPos.w 로 깊이 값을 구해 SV_Target으로  넘긴다. 

 

 //lighting.hlsl
    if (length(color.diffuse) != 0)
    {
        matrix shadowCameraVP = g_mat_0;

        float4 worldPos = mul(float4(viewPos.xyz, 1.f), g_matViewInv);
        float4 shadowClipPos = mul(worldPos, shadowCameraVP);
        float depth = shadowClipPos.z / shadowClipPos.w;

        // x [-1 ~ 1] -> u [0 ~ 1]
        // y [1 ~ -1] -> v [0 ~ 1]
        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;
            }
        }
    }

 

lighting.hlsl에서는 directional light일 경우에만 그림자를 그리기로 한다. matViewInv를 곱해 WorldPos로 변환하고 빛위치 카메라 기준으로 변환하기 위해 shadowCameraVP를 곱해 shadowClipPos를 구한다. depth는 shadowClipPos.z / shadowClipPos.w 로 그리려는 해당 지점의 depth이다. 그 지점에 그림자가 있어야 할지 계산하기 위해 uv좌표를 구한다. 

 

 

ClipPos에서 uv좌표로 변환하는 것은 위 그림과 같다. 변환하면 빛위치카메라 영역 안이라는 것이므로 shadow.hlsl의 PS_Main에서 저장했던 RenderTarget에 uv 좌표를 이용해 빛위치 카메라 기준 가장 먼저 부딪히는 깊이 shadowDepth를 구해주고 그것을 현재 깊이 depth와 비교해 depth가 더 크다면 그림자를 그려줘야 한다. \

 

 

	shared_ptr<GameObject> _shadowCamera;
void Light::RenderShadow()
{
	_shadowCamera->GetCamera()->SortShadowObject();
	_shadowCamera->GetCamera()->Render_Shadow();
}

 

C++코드로가서 Light는 Shadow를 그리기 위한 Shadow Camera를 내부적으로 갖는다. 그리고 RenderShadow가 생겼다.

 

void Camera::SortShadowObject()
{
	shared_ptr<Scene> scene = GET_SINGLE(SceneManager)->GetActiveScene();
	const vector<shared_ptr<GameObject>>& gameObjects = scene->GetGameObjects();

	_vecShadow.clear();

	for (auto& gameObject : gameObjects)
	{
		if (gameObject->GetMeshRenderer() == nullptr)
			continue;

		if (gameObject->IsStatic())
			continue;

		if (IsCulled(gameObject->GetLayerIndex()))
			continue;

		if (gameObject->GetCheckFrustum())
		{
			if (_frustum.ContainsSphere(
				gameObject->GetTransform()->GetWorldPosition(),
				gameObject->GetTransform()->GetBoundingSphereRadius()) == false)
			{
				continue;
			}
		}

		_vecShadow.push_back(gameObject);
	}
}

 

그림자를 그리기전 그림자를 그릴 물체를 sort하는데 static이 아닌 물체만 그림자를 그려준다 정적인 물체들은 그림자가 정해져있기 때문에 빠른 연산을 위해 shadowmapping을 하지 않는다. 

 

void MeshRenderer::RenderShadow()
{
	GetTransform()->PushData();
	GET_SINGLE(Resources)->Get<Material>(L"Shadow")->PushGraphicsData();
	_mesh->Render();
}

 

Camera::RenderShadow를 거쳐 MeshRender::RenerShadow를 하는데 shadow를 위한 공용 material을 사용한다.

 

	// Shadow Group
	{
		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);
	}

 

Shadow 타입 RenderTargetGroup을 만든다. 화면 크기보다 더 커야하므로 4096으로 맞춰져 있고 depthstencilTexture도 그 크기에 맞춰야 하기 때문에 기존에 쓰던 것을 쓰는 것이 아닌 새로운 Texture을 만들어 사용한다.

 

//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);
	}

 

Light::Render에서 만든 Shadow의 Texture을 이용해 렌더링한다. 

 

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);

 

Shadow의 Texture 크기가 다르므로 SetRenderTarget할 때 Texture의 크기가 달라질 수 있다. 그래서 RenderTargetGroup마다 자신이 세팅할 RenderTarget 크기에 맞게 Viewport, Rect를 설정한다.

 

전체적인 순서는 light에 shadow camera를 배치해 light::finalupdate 때 shadow camera가 light와 똑같은 방향을 바라보도록 배치하고 Render Shadow를 먼저 한다. 끝나면 Shadow Map RenderTarget에 그려질 것이고 RenderLights를 할 때 RenderTarget으로 Shadow도 함께 넘겨서 그림자에 대하여 계산한다.

'그래픽스' 카테고리의 다른 글

[DX12] Terrain  (0) 2022.07.31
[DX12] Tessellation  (0) 2022.07.31
[DX12] Instancing  (0) 2022.07.30
[DX12] Particle System  (0) 2022.07.29
[DX12] Compute Shader  (0) 2022.07.29