Shadow mapping

ㅋㅋ·2022년 7월 29일
0

DirectX12강의

목록 보기
34/39

광원 기준으로 깊이값을 텍스쳐로 저장

렌더 시에 카메라 기준 좌표계를 광원 기준 좌표계로 변환 후

변환된 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);

	...
}

렌더 타겟을 설정할 때 텍스쳐에 따라 크기를 바꾸도록 수정

0개의 댓글