1. 개요
우리가 지난번 만들었던 그림자에는 한가지 문제점이 있습니다.
https://yeoul0714.tistory.com/37
[DX11] Point Light Shadow - Peter Panning, Shadow Acne
1. 개요게임 세상에서 그림자를 띄울 수 있다면 보다 더 실감나는 게임이 될것입니다. 오늘 알아볼내용은 바로 그림자를 구현하는 방법입니다. 우선 그림자를 구현하기 위해서는 Depth값이 필요
yeoul0714.tistory.com
그것은 바로 테두리가 울퉁불퉁 하다는 것입니다. (Aliasing)
이런식으로 그림자가 생긴곳을 가까이서 보도록 합시다.
이렇듯 그림자의 테두리가 울퉁불퉁하고 날카로운것을 볼 수 있습니다.
이러한 문제가 생기는 이유는 스태틱 메시의 depth를 담고있는 Texture2D의 해상도가 1024x1024로 제한되어
depth값을 세밀하게 표현하는데 한계가 있기 때문입니다.
또한 depth map을 sampling하는 방식도 영향을 줍니다.
결론적으로 오늘은 이 테두리를 부드럽게 만드는 기술에 대해서 공부하고 적용하도록 하겠습니다.
2. PCF (Percentage Closer Filtering)
이러한 현상을 해결하는 다양한 기법이 있지만 PCF가 가장 기본적인 방식입니다.
PCF를 이용해서 Aliasing문제를 해결하는 과정을 설명하도록 하겠습니다.
쉐이더 코드에서 조금만 손을 봐준다면 PCF를 구현할 수 있습니다.
쉽게 원리를 설명하자면 한 픽셀의 주변 픽셀들끼리의 평균을 구해서 부드럽게 만드는 방식입니다.
float CalculatePointLightShadow(float3 worldPos,float3 worldNormal, FPointLight PointLight,int mapIndex)
{
float4 WoldPos4 = float4(worldPos, 1.0f);
float3 LightDirection = normalize(worldPos - PointLight.Position);
float3 LightDirectionAbs = abs(LightDirection); // 절대값 각 축 크기 구함.
int face;
if (LightDirectionAbs.x >= LightDirectionAbs.y && LightDirectionAbs.x >= LightDirectionAbs.z)
face = LightDirection.x > 0 ? 0 : 1; // +X, -X
else if (LightDirectionAbs.y >= LightDirectionAbs.z)
face = LightDirection.y > 0 ? 2 : 3; // +Y, -Y
else
face = LightDirection.z > 0 ? 4 : 5; // +Z, -Z
float4 LightViewPos = mul(WoldPos4, PointLight.View[face]); // 월드 → 라이트(큐브 face) 공간
float4 clipPos = mul(LightViewPos, PointLight.Proj); // 라이트 공간 -> Clip space
//FIXME : bias 적용
// NDC 깊이 (0~1) 추출
//float refDepth = clipPos.z / clipPos.w - bias;
float bias = max(0.001 * (1.0 - dot(worldNormal, LightDirection)), 0.0001);
float refDepth = clipPos.z / clipPos.w - bias;
// PCF 파라미터 설정
float shadowSum = 0.0;
float numSamples = 0.0;
float pcfRadius = 0.001; // PCF 필터 반경 (조정 가능)
// 샘플링 패턴 (5x5 필터링)
const int filterSize = 6; // 중앙을 포함한 반경 (-2, -1, 0, 1, 2) -> 5x5
for (int x = -filterSize; x <= filterSize; x++)
{
for (int y = -filterSize; y <= filterSize; y++)
{
// 샘플링 오프셋 벡터 계산
float3 offset = float3(0, 0, 0);
if (face == 0 || face == 1) // X축 면
offset = float3(0, x * pcfRadius, y * pcfRadius);
else if (face == 2 || face == 3) // Y축 면
offset = float3(x * pcfRadius, 0, y * pcfRadius);
else // Z축 면
offset = float3(x * pcfRadius, y * pcfRadius, 0);
// 오프셋을 적용한 샘플링 방향
float3 sampleDirection = normalize(LightDirection + offset);
// 샘플링
float shadowSample = PointLightShadowMap[mapIndex].SampleCmp(
CompareSampler,
sampleDirection,
refDepth
);
shadowSum += shadowSample;
numSamples += 1.0;
}
}
return shadowSum / numSamples;
}
이곳에서 shadow를 구하는 기본적인 방식은 이전에 작성한 글과 같습니다.
중심적으로 봐야하는 곳을 설명해드리겠습니다.
우선 중요한 변수들이 있습니다.
float pcfRadius = 0.001; // PCF 필터 반경 (조정 가능)
const int filterSize = 6;
바로 이부분인데 각각 설명하겠습니다.
filterSize
클수록 더 부드러운 그림자를 얻을 수 있지만 계산 비용이 증가합니다. (반복문 도는 횟수)
pcfRadius
샘플 포인트 간의 간격을 결정하며, 이 값이 작을수록 세밀한 필터링이 가능하지만 블러 효과는 감소합니다.
(얼마나 가까운 픽셀을 샘플링 할것인지)
성능과 품질사이의 적당한 간격을 찾아서 이 값들을 설정해 주어야 합니다.
이곳이 바로 주변의 여러개의 픽셀을 Sampling하는 부분입니다.
for (int x = -filterSize; x <= filterSize; x++)
{
for (int y = -filterSize; y <= filterSize; y++)
{
// 샘플링 오프셋 벡터 계산
float3 offset = float3(0, 0, 0);
if (face == 0 || face == 1) // X축 면
offset = float3(0, x * pcfRadius, y * pcfRadius);
else if (face == 2 || face == 3) // Y축 면
offset = float3(x * pcfRadius, 0, y * pcfRadius);
else // Z축 면
offset = float3(x * pcfRadius, y * pcfRadius, 0);
// 오프셋을 적용한 샘플링 방향
float3 sampleDirection = normalize(LightDirection + offset);
// 샘플링
float shadowSample = PointLightShadowMap[mapIndex].SampleCmp(
CompareSampler,
sampleDirection,
refDepth
);
shadowSum += shadowSample;
numSamples += 1.0;
}
}
return shadowSum / numSamples;
pcfRadius라는 작은 값만큼 offset을 만들어서 LightDirection에 더해줍니다. (각 면에 따라서 offset이 조금씩 다릅니다.)
그렇게 나온 값을 shadowSum에 전부 더해줍니다.
그리고 최종적으론 더해준 픽셀의 갯수로 나눠서 평균을 구한뒤 해당 값으로 그림자를 처리합니다.
이러한 느낌으로 생각하시면 됩니다.
offset은 매우 작은 벡터로 나오기 때문에 (pcRadius가 매우 작습니다.)
계산하려는 픽셀과 아주 가까운 거리의 주변 픽셀들을 함께 샘플링하는 의미가 됩니다.
이렇게 평균적인 값으로 그림자를 구했을 때 어떻게 달라지는지 비교해보도록 하겠습니다.
이전과는 다르게 아주 부드러운 그림자 테두리가 만들어졌습니다.
'DirectX11' 카테고리의 다른 글
[DirectX11] FBX SDK적용, 컴파일 옵션 /Md /Mt (0) | 2025.05.07 |
---|---|
[DX11] Lua 스크립트 연동 + SOL2 (0) | 2025.04.27 |
[DX11] Renderdoc 사용법 CubeMap 디버깅 (0) | 2025.04.23 |
[DX11] Point Light Shadow - Peter Panning, Shadow Acne (2) | 2025.04.23 |
[DX11] Normal Map (0) | 2025.04.16 |