참고
https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows
https://www.gamedev.net/forums/topic/659535-cubemap-texture-as-depth-buffer-shadowmapping/
https://gamedev.net/forums/topic/674787-cubemap-depth-sample/5271471/
지난 번에 만들었던 Shadow Mapping 기술의 그림자는 directional lights에 적합한 기술이었다. 왜냐하면 그림자가 오직 light가 진행하는 방향에서만 발생하기 때문이었다. 그 이유는 빛의 시점을 이용해서 그림자를 그렸기 때문이다.
Point Shadows
해당 기술은 지난번 만들었던 것과 대부분이 비슷하다. 빛의 원근 투영 시점으로 depth map을 만들고 depth map과 빛 시점에서 물체의 깊이 값을 비교해서 그림자가 생기는지 안생기는지 파악한다.
가장 큰 차이는 depth map이다. 해당 그림자를 그리기 위해서는 모든 방향에 대한 deptp map이 필요로 하다. 해당 기술은 2D형태의 depth map에서 작동하지 않는다. 따라서, cubemap
의 형태로 만들어야 한다. 왜냐하면 cubemap은 육면체의 형태로 모든 방향을 포함하고 있기 때문이다.
그렇다면, depth cubemap 어떻게 만들어야 할까
depth cubemap을 만들기 위해서는 말 그대로, 6번 scene을 렌더링해야한다. 하지만 이 방법은 비용이 많이 들기 때문에 단한번의 render pass로 cube map을 만들 수 있는 geometry shader를 이용하게 된다.
depth cubemap을 만들기 위해서 Teuxture2D
buffer, DSV
, SRV
의 Description을 수정해줄 필요가 있다. 왜냐하면, 일반적인 2D화면이 아닌 큐브맵 형태의 2D 이미지를 완성해야하고 이 이미지를 완성하기 위해서 geometry shader를 이용하기 때문이다.
해당 설정들은 참고에 올린 주소들을 통해 작성하였다.
- Texture2D에서 기존과의 특이점은
MiscFlags : TEXTURECUBE
,ArraySize = 6
가 있다. 기본적으로 큐브맵의 형태를 가지고 있으므로 array 6개 필요하다. - DSV에서 기존과 다른 점으로 큐브맵 형태의 Texture2D를 DSV를 통해 연결하기 위해서
Texture2DArray
를 통해서 진행하였다. 이렇게 하면 하나의 DSV를 통해서 큐브맵 TextureCube에 바인딩이 가능하다. - SRV의 경우, 비교적 간단하다.
ViewDimension
을 TextureCube로 선언해주면 된다.
desc.Format = DXGI_FORMAT_R32_TYPELESS;
desc.BindFlags = D3D11_BIND_DEPTH_STENCIL | D3D11_BIND_SHADER_RESOURCE;
desc.SampleDesc.Count = 1;
desc.SampleDesc.Quality = 0;
desc.Width = m_shadowWidth;
desc.Height = m_shadowHeight
desc.MiscFlags = D3D11_RESOURCE_MISC_TEXTURECUBE;
desc.ArraySize = 6;
ThrowIfFailed(m_device->CreateTexture2D(
&desc, NULL, m_shadowCubeBuffers.GetAddressOf()));
dsvDesc.Format = DXGI_FORMAT_D32_FLOAT;
dsvDesc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2DARRAY;
dsvDesc.Texture2DArray.ArraySize = 6;
dsvDesc.Texture2DArray.FirstArraySlice = 0;
dsvDesc.Texture2DArray.MipSlice = 0;
dsvDesc.Flags = 0;
ThrowIfFailed(m_device->CreateDepthStencilView(
m_shadowCubeBuffers.Get(), &dsvDesc, m_shadowCubeDSVs.GetAddressOf()));
srvDesc.Format = DXGI_FORMAT_R32_FLOAT;
srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURECUBE;
srvDesc.TextureCube.MostDetailedMip = 0;
srvDesc.TextureCube.MipLevels = 1;
ThrowIfFailed(m_device->CreateShaderResourceView(
m_shadowCubeBuffers.Get(), &srvDesc, m_shadowCubeSRVs.GetAddressOf()));
이렇게 설정들을 바꿔준 후, GS로 보내어서 큐브맵 형태의 텍스쳐를 만드는 것이다.
```cpp
#include "Common.hlsli"
cbuffer MeshConstants : register(b0)
{
matrix world;
matrix worldIT;
int useHeightMap;
float heightScale;
float2 dummy;
};
cbuffer ShadowLightTransform : register(b3)
{
matrix shadowViewProj[6];
}
struct VSToGS
{
float4 posWorld : SV_Position;
float2 texcoord : TEXCOORD0;
};
struct GSToPS
{
float4 posProj : SV_POSITION;
float3 fragPos : POSITION0;
float2 texcoord : TEXCOORD0;
uint layer : SV_RenderTargetArrayIndex;
};
[maxvertexcount(18)]
void main(
triangle VSToGS input[3],
inout TriangleStream<GSToPS> triStream
)
{
for (int face = 0; face < 6; ++face)
{
GSToPS output;
output.layer = face;
for (int i = 0; i < 3; i++)
{
output.fragPos = input[i].posWorld;
output.posProj = mul(input[i].posWorld, shadowViewProj[face]);
output.texcoord = input[i].texcoord;
triStream.Append(output);
}
triStream.RestartStrip();
}
}
그림자 그리기
큐브맵을 만들어 준 뒤에 그림자를 그리는 과정은 매우 유사하다. TextureCube를 PS에 바인딩 한 후 깊이 값을 읽어서 현재 깊이 값과 비교해 그림자가 생기는지 생기지 않는지 파악하는 것이다.
float3 fragToLight = posWorld - light.position;
float currentDepth = CalcDepthInShadow(fragToLight, 0.5, 1.0);
float curDepth = length(fragToLight);
fragToLight = normalize(fragToLight);
float closestDepth = shadowCubeMap.Sample(shadowPointSampler, fragToLight).r;
closestDepth *= 4.0f;
float bias = max(0.005 * (1.0 - dot(normalize(posWorld - light.position), fragToLight)), 0.005);
shadow = curDepth - bias > closestDepth ? 1.0 : 0.0;
현재 그리고 있는 물체와 빛의 위치를 통해 빛에서 물체의 거리 벡터를 도출 한 후, 해당 벡터의 길이를 구해 깊이 값을 찾는다. 해당 깊이 값은 범위는 [0, 1]이기 때문에 실제 깊이 값과 비교를 위해서 *= farPlane
을 해준다.
발생한 문제점들
1. 나의 경우에는 여기서 문제점이 발생하였는데, 그림자의 ViewFrustum을 만들 때, [1.0, Far] 이런 식이 아니라 [0.1, Far] 이런 식으로 작성하였고 Far값이 커질 수록 DSV 자체에서 [0, 1]로 정규화하는 과정에서 생기는 오차가 커진다. 이로 인해 생각대로 그림자가 그려지지 않는 현상이 발생하였다.
2. [1.0, FAR]로 설정해주고 FAR값을 그리 크게 설정하지 않으면 문제는 어느정도 해결 된다. 아쉬운 점은 ViewFrustum의 near값이 커지면서 조명과 물체가 가까워졌을 때 물체의 앞면을 제대로 인식하지 못해 그림자가 이상해지는 현상이 발생한다는 점이다.
완성 시 아래 처럼 그림자가 출력되는 것을 확인할 수 있었다.
'Graphics' 카테고리의 다른 글
6. Deferred Lighting (0) | 2024.07.01 |
---|---|
5. Normal Mapping (0) | 2024.06.27 |
3. Directional Shadow Mapping (0) | 2024.06.21 |
2. Depth Buffer와 안개 효과 (1) | 2024.06.14 |
1. Blinn Phong 모델 (0) | 2024.05.14 |