Shadow Map Filtering 에서 PCFVSM 에 대하여 간단히 알아보았다. 이번 글에서 설명할 것은 PCF 를 활용한 PCSS 다.

PCSSSoft Shadow 를 구현하는 기법 중 하나로써 2005년에 발표되어 여태까지도 꽤나 알려진 기법이다. 우선 Soft Shadow 가 무엇인지 알아보자.


출처 : assignmentpoint.com : Real Time Soft Shadow Rendering


위와 같이 빛을 가린 물체와 거리가 멀어지면 멀어질수록 밝아지는 그림자를 Soft Shadow 라고 한다. 완전한 Hard Shadow 는 어색하기 때문에 보통 PCF 를 사용하여 끝부분을 부드럽게 처리했으나, 태양광 처럼 길게 그림자를 만드는 경우가 있으면 끝 부분이 가면 갈수록 부드러워져야 한다.


출처 : Youtube


태양 빛에의해 만들어진 나무의 그림자다. 짧은 길이의 그림자는 적당히 PCF 로 대략 표현이 가능하나 이런 길은 그림자를 고정된 사이즈의 PCF 로 표현하기엔 무리가 있다. 그래서 나온것이 PCSS 다.

PCSS 를 보기전에, 우리가 알아야할 용어들이 있다. 바로 UmbraPenumbra 다.


출처 : Wikipedia : Umbra, penumbra and antumbra


Soft Shadow 가 표현하는 부드러운 부분의 그림자는 위 그림에서도 보이듯이 Penumbra 라고 한다. PCSS 에서는 부드러운 부분의 그림자를 Penumbra 라고 한다. PCSS 에서는 Penumbra 의 크기를 사용하여 PCF 의 샘플링 범위를 정해준다. 우선 PCSSPenumbra 를 계산하는 방법을 보자.


출처 : Siggraph 2005 : Percentage-Closer Soft Shadows


맨위의 노란색으로 표시된 부분은 광원을 뜻하며, 일정한 범위로 빛을 비추는 Area Light 로 가정한 후 계산한다. W - light 는 Area Light 의 범위를 뜻한다. 중간에 있는 Blocker 는 빛을 가리는 물체를 뜻하며, d blocker 는 가리는 물체와 빛과의 거리, d receiver 는 그림자가 비추는 물체와 광원 사이의 거리를 뜻한다. 그림자를 받는 부분과 빛을 가리는 물체와 광원을 서로 평행하다고 가정해서 계산한다. 2차원의 그림을 3차원으로 바꿔보자.


출처 : NVidia : Percentage-Closer Soft Shadows


보통 Pixel Shader 에서 그림자의 비춘 정도를 계산하기 때문에 Receiver 의 작은 부분을 기준으로 그림이 그려져 있다. 작은 부분을 기준으로 Area Light 와의 frustumBlocker 가 얼마나 충돌되는지 체크한다. 우리는 Shadow Map 을 사용하기 때문에 아래 그림이 조금 더 실제 계산과 비슷하다.


출처 : NVidia : Percentage-Closer Soft Shadows


그래서 Shadow Map 의 빨간색으로 하이라이트 된 부분을 샘플링해 얼마나 가리고 있는지를 확인한다. 그러면 빛을 받는 정도를 알 수 있게 되는것이다. 해당 부분을 적당히 샘플링한 다음 평균을 구해서 PCF 로 샘플링하는 범위를 계산한다. 계산하여 PCF 에서 범위를 사용해 계산한다. 코드를 보면서 이해해보자.


float PCSS_Shadow(float2 uv, float z, float2 dz_duv, float zEye)
{
	// ------------------------
	// STEP 1: blocker search
	// ------------------------
	float accumBlockerDepth = 0;
	float numBlockers = 0;
	float2 searchRegionRadiusUV = SearchRegionRadiusUV(zEye);
	FindBlocker(accumBlockerDepth, numBlockers, g_shadowMap, uv, z, dz_duv, searchRegionRadiusUV);

	// Early out if not in the penumbra
	if (numBlockers == 0)
		return 1.0;
	else if (numBlockers == BLOCKER_SEARCH_COUNT)
		return 0.0;

	// ------------------------
	// STEP 2: penumbra size
	// ------------------------
	float avgBlockerDepth = accumBlockerDepth / numBlockers;
	float avgBlockerDepthWorld = ZClipToZEye(avgBlockerDepth);
	float2 penumbraRadiusUV = PenumbraRadiusUV(zEye, avgBlockerDepthWorld);
	float2 filterRadiusUV = ProjectToLightUV(penumbraRadiusUV, zEye);

	// ------------------------
	// STEP 3: filtering
	// ------------------------
	return PCF_Filter(uv, z, dz_duv, filterRadiusUV);
}
출처 : Github NVIDIAGameWorks : D3DSamples


해당 픽셀이 어두워지는 정도를 반환하는 PCSS 계산 함수다. 코드의 주석에서는 계산을 세단계로 나눈다. 첫번째로는 Shadow Map 을 샘플링해서 얼마나 빛이 얼마나 가려지는지 계산한다. 이를 STEP 1: blocker search 라고 표기해놓았고, 두번째는 PCF 에서 샘플링할 범위를 결정하는 넓이를 계산한다. 이를 STEP 2: penumbra size 라고 한다. 세번째로는 PCF 를 계산해서 가려지는 정도를 반환한다. 자세한 코드는 출처에서 SoftShadows 항목을 들어가면 볼 수 있다.

PCSS 의 장점은 아무래도 확실한 Soft Shadow 를 구현했다는 점이다. 비록 대략적으로 가정한 부분이 많지만 장면별로 잘 맞춰주기만 한다면 괜찮은 결과가 나올것 같다. 하지만 샘플링 횟수가 꽤나 된다. PCF 만 하더라도 가볍지는 않은 편인데, Blocker 를 계산하느라 더 많이 샘플링을 한다. 하지만 잘 만들어진 게임과 요즘의 GPU 에서는 아주 큰 오버헤드는 없는걸로 보인다. (Redit : Nvidia HFTS (The Division))

참조