RayTracing에서 그림자를 구현할 때는 우리가 보고 있는 시점으로부터 위치로부터 광원까지 ShadowRay를 쏴서
그 사이 물체가 있으면 그림자가 생기게 하는 방식으로 구현했다. 하지만 Rasterization에서는 어떤 지점을 렌더링할 때
삼각형 단위로 그리기때문에 다른 곳에 물체가 있는지를 알 수 없다.
그래서 사용하는 방법은 우리가 시점으로부터 물체의 Depth Map을 만들었듯이 광원으로부터 물체의 Depth Map을 만드는 것이다. 이것을 Shadow Map이라고 부른다.
// 그림자맵 만들기
AppBase::SetShadowViewport(); // 그림자맵 해상도
AppBase::SetPipelineState(Graphics::depthOnlyPSO);
for (int i = 0; i < MAX_LIGHTS; i++) {
if (m_globalConstsCPU.lights[i].type & LIGHT_SHADOW) {
// RTS 생략 가능
m_context->OMSetRenderTargets(0, NULL, m_shadowDSVs[i].Get());
m_context->ClearDepthStencilView(m_shadowDSVs[i].Get(),
D3D11_CLEAR_DEPTH, 1.0f, 0);
AppBase::SetGlobalConsts(m_shadowGlobalConstsGPU[i]);
for (auto &i : m_basicList)
if (i->m_castShadow && i->m_isVisible)
i->Render(m_context);
m_skybox->Render(m_context);
m_mirror->Render(m_context);
}
}
시점을 조명으로 바꾸어 ShadowMap을 만들려면 Global Constants로 광원에서 물체를 바라보는 방향으로 교체해줘야 한다. DSV를 shadowDSV로 GlobalConstants도 shadowGlobalConsts로 바꿔주고 렌더링을 하면 된다. 그리고 광원을 여러개 사용할 수 있게 조명 개수만큼 루프를 돌리도록 했다.
void AppBase::SetShadowViewport() {
// Set the viewport
D3D11_VIEWPORT shadowViewport;
ZeroMemory(&shadowViewport, sizeof(D3D11_VIEWPORT));
shadowViewport.TopLeftX = 0;
shadowViewport.TopLeftY = 0;
shadowViewport.Width = float(m_shadowWidth);
shadowViewport.Height = float(m_shadowHeight);
shadowViewport.MinDepth = 0.0f;
shadowViewport.MaxDepth = 1.0f;
m_context->RSSetViewports(1, &shadowViewport);
}
shadow 해상도는 화면 해상도와 같을 필요 없으므로 width,height 1280,1280 1:1로 해주고 그에 맞추어 viewport도
설정해줘야 한다. 그림자는 화면과 해상도가 다르기 때문이다.
// 그림자 Buffers (Depth 전용)
desc.Width = m_shadowWidth;
desc.Height = m_shadowHeight;
for (int i = 0; i < MAX_LIGHTS; i++) {
ThrowIfFailed(m_device->CreateTexture2D(
&desc, NULL, m_shadowBuffers[i].GetAddressOf()));
}
// 그림자 DSVs
for (int i = 0; i < MAX_LIGHTS; i++) {
ThrowIfFailed(
m_device->CreateDepthStencilView(m_shadowBuffers[i].Get(), &dsvDesc,
m_shadowDSVs[i].GetAddressOf()));
}
// 그림자 SRVs
for (int i = 0; i < MAX_LIGHTS; i++) {
ThrowIfFailed(m_device->CreateShaderResourceView(
m_shadowBuffers[i].Get(), &srvDesc,
m_shadowSRVs[i].GetAddressOf()));
}
해상도에 따라 바뀐 shadowBuffers, shadowDSV, shadowSRV를 조명개수만큼 만들어준다.
// 그림자맵들도 공용 텍스춰들 이후에 추가
// 주의: 마지막 shadowDSV를 RenderTarget에서 해제한 후 설정
vector<ID3D11ShaderResourceView *> shadowSRVs;
for (int i = 0; i < MAX_LIGHTS; i++) {
shadowSRVs.push_back(m_shadowSRVs[i].Get());
}
m_context->PSSetShaderResources(15, UINT(shadowSRVs.size()),
shadowSRVs.data());
그리고 생성한 shadowSRV를 PS에 Set한다.

이렇게 만든 ShadowMap을 통해 어떻게 그림자를 만들까? Shading하려는 Point의 월드 좌표를 광원시점의 nearPlane에
투영을 시키면 위와 같은 그림이 된다. ShadowMap에 저장되어 있는 Depth 값보다 Shading하려는 Point의 Depth값이
작다면 물체가 그 Point를 가리고 있는 것이다. 이게 그림자를 표현하는 원리다.
radiance = LightRadiance(lights[i], input.posWorld, normalWorld, shadowMaps[i]);
BasicPS에서 DirectLighting을 계산할때 사용하는 함수 LightRadiance의 마지막 인자에 shadowMaps가 들어간다.
index i는 현재 DirectX버전에서는 마음대로 조절할 수 없기 때문에 임시로 해결하기 위해 for문 루프 위에 [unroll]을 붙인다.
// Shadow map
float shadowFactor = 1.0;
if (light.type & LIGHT_SHADOW)
{
const float nearZ = 0.01; // 카메라 설정과 동일
// 1. Project posWorld to light screen
float4 lightScreen = mul(float4(posWorld, 1.0), light.viewProj);
lightScreen.xyz /= lightScreen.w;
// 2. 카메라(광원)에서 볼 때의 텍스춰 좌표 계산
float2 lightTexcoord = float2(lightScreen.x, -lightScreen.y);
lightTexcoord += 1.0;
lightTexcoord *= 0.5;
// 3. 쉐도우맵에서 값 가져오기
float depth = shadowMap.Sample(shadowPointSampler, lightTexcoord).r;
// 4. 가려져 있다면 그림자로 표시
if (depth + 0.001 < lightScreen.z)
shadowFactor = 0.0;
}
float3 radiance = light.radiance * spotFator * att * shadowFactor;
return radiance;
PS에서 Shadow map을 이용해 그림자를 그리는 코드이다. shadow factor가 1일때 그리지 않고 0일때 그림자를 그린다.
먼저 shadow map을 만들 때 이용했던 light.viewproj 를 이용해 shading할 월드 포인트를 NDC로 빛의 nearPlane으로
투영한다. viewProj 곱했으니 w나누기도 해줘야 한다. 두번째로 NDC [-1,1]에서 광원에서의 텍스쳐 좌표계 [0,1]로 변환한다. 세번째로 shadow map에서 값을 샘플링해오면 depth값을 가져올 수 있다. 월드 포인트의 depth값 (lightScreen.z) 가
depth보다 크다면 물체에 의해 가려진 것이다. 가려졌으면 shadowFactor을 0으로 설정한다. 0.001 적절한 bias 값을 넣어줘야 노이즈(shadow acne)가 생기지 않는다.
'그래픽스' 카테고리의 다른 글
| [그래픽스] 부드러운 그림자 - PCSS (0) | 2023.07.23 |
|---|---|
| [그래픽스] 부드러운 그림자 - PCF (0) | 2023.07.23 |
| [그래픽스] Depth Map (0) | 2023.07.23 |
| [그래픽스] 렌더링 엔진 구조 변경 (0) | 2023.07.23 |
| [그래픽스] HDR 파이프라인 (0) | 2023.07.22 |