계단식 그림자 지도
CSM(계단식 그림자 맵)은 그림자로 가장 널리 퍼진 오류 중 하나인 원근 별칭을 방지할 수 있는 가장 좋은 방법입니다. 판독기가 섀도 매핑에 익숙하다고 가정하는 이 기술 문서에서는 CSM의 주제를 다룹니다. 특히 다음을 수행합니다.
- 는 CSM의 복잡성을 설명합니다.
- 는 CSM 알고리즘의 가능한 변형에 대한 세부 정보를 제공합니다.
- 는 가장 일반적인 두 가지 필터링 기술인 PCF(백분율 근접 필터링) 및 VSM(분산 그림자 맵)을 사용한 필터링을 설명합니다.
- 는 CSM에 필터링을 추가하는 것과 관련된 몇 가지 일반적인 문제를 식별하고 해결합니다. 및
- 는 Direct3D 11 하드웨어를 통해 CSM을 Direct3D 10에 매핑하는 방법을 보여 줍니다.
이 문서에 사용된 코드는 CascadedShadowMaps11 및 VarianceShadows11 샘플의 DirectX SDK(소프트웨어 개발 키트)에서 찾을 수 있습니다. 이 문서는 기술 문서에서 다루는 기술을 구현한 후 가장 유용한 것으로 입증됩니다. 섀도 깊이 맵을 개선하기 위한 일반적인 기술이 구현됩니다.
계단식 그림자 지도 및 원근 별칭
그림자 맵의 원근감 별칭은 극복해야 할 가장 어려운 문제 중 하나입니다. 기술 문서인 섀도 깊이 맵을 개선하기 위한 일반적인 기술, 원근감 별칭 지정이 설명되고 문제를 완화하기 위한 몇 가지 방법이 식별됩니다. 실제로 CSM은 최상의 솔루션인 경향이 있으며 일반적으로 최신 게임에 사용됩니다.
CSM의 기본 개념은 이해하기 쉽습니다. 카메라 프러스텀의 영역이 다르면 해상도가 다른 그림자 맵이 필요합니다. 눈에 가장 가까운 개체는 더 먼 개체보다 더 높은 해상도가 필요합니다. 사실, 눈이 기하 도형에 매우 가깝게 움직이면 눈에서 가장 가까운 픽셀에는 해상도가 너무 많아서 4096 × 4096 섀도 맵이 충분하지 않을 수 있습니다.
CSM의 기본 개념은 frustum을 여러 frusta로 분할하는 것입니다. 각 하위 일시 중단에 대해 그림자 맵이 렌더링됩니다. 픽셀 셰이더는 필요한 해상도와 가장 일치하는 맵의 샘플을 제공합니다(그림 2).
그림 1. 그림자 맵 검사
그림 1에서 품질은 최고에서 최저로(왼쪽에서 오른쪽으로) 표시됩니다. 보기 frustum(빨간색으로 반전된 원뿔)이 있는 그림자 맵을 나타내는 일련의 그리드는 픽셀 범위가 다른 해상도 그림자 맵에 미치는 영향을 보여 줍니다. 그림자는 섀도 맵의 텍셀에 대한 조명 공간의 1:1 비율 매핑 픽셀이 있는 경우 최고 품질(흰색 픽셀)입니다. 원근감 별칭은 너무 많은 픽셀이 동일한 그림자 텍셀에 매핑될 때 크고 블로키한 텍스처 맵(왼쪽 이미지)의 형태로 발생합니다. 그림자 맵이 너무 크면 샘플링됩니다. 이 경우 텍셀을 건너뛰고, 반짝이는 아티팩트가 도입되고, 성능이 영향을 받습니다.
그림 2. CSM 섀도 품질
그림 2는 그림 1의 각 그림자 맵에서 최고 품질 섹션의 컷아웃을 보여 줍니다. 가장 밀접하게 배치된 픽셀(정점)이 있는 그림자 맵은 눈에서 가장 가깝습니다. 기술적으로 이러한 맵은 동일한 크기의 맵이며, 흰색과 회색은 계단식 그림자 맵의 성공을 예시하는 데 사용됩니다. 흰색은 눈 공간 픽셀과 섀도 맵 텍셀에 대해 1:1 비율의 좋은 커버리지를 표시하기 때문에 이상적입니다.
CSM에는 프레임당 다음 단계가 필요합니다.
frustum을 subfrusta로 분할합니다.
각 하위 영속에 대한 직교 프로젝션을 계산합니다.
각 하위 주물에 대한 그림자 맵을 렌더링합니다.
장면을 렌더링합니다.
그림자 맵을 바인딩하고 렌더링합니다.
꼭짓점 셰이더는 다음을 수행합니다.
- 필요한 텍스처 좌표가 픽셀 셰이더에서 계산되지 않는 한 각 광원 하위 일시 중단에 대한 텍스처 좌표를 계산합니다.
- 꼭짓점 등을 변환하고 표시합니다.
픽셀 셰이더는 다음을 수행합니다.
- 적절한 그림자 맵을 결정합니다.
- 필요한 경우 텍스처 좌표를 변환합니다.
- 연계를 샘플로 표시합니다.
- 픽셀을 비추고 있습니다.
Frustum 분할
frustum 분할은 subfrusta를 만드는 작업입니다. frustum을 분할하는 한 가지 기술은 Z 방향에서 간격을 0%에서 100%로 계산하는 것입니다. 각 간격은 가까운 평면과 원거리 평면을 Z축의 백분율로 나타냅니다.
그림 3. 임의로 분할된 frustums 보기
실제로 프레임당 frustum 분할을 다시 계산하면 그림자 가장자리가 반짝입니다. 일반적으로 허용되는 방법은 시나리오당 정적 계단식 간격 집합을 사용하는 것입니다. 이 시나리오에서는 Z축을 따라 간격을 사용하여 frustum을 분할할 때 발생하는 하위 프러스텀을 설명합니다. 지정된 장면에 대한 올바른 크기 간격을 결정하는 것은 여러 요인에 따라 달라집니다.
장면 기하 도형의 방향
장면 기하 도형과 관련하여 카메라 방향은 계단식 간격 선택에 영향을 줍니다. 예를 들어 축구 경기의 지상 카메라와 같이 지면 바로 근처에 있는 카메라는 하늘의 카메라와 다른 정적 계단식 간격 집합을 가집니다.
그림 4에서는 서로 다른 카메라와 각 파티션을 보여 줍니다. 장면의 Z 범위가 매우 크면 더 많은 분할 평면이 필요합니다. 예를 들어 눈이 지상 평면 근처에 있지만 먼 물체가 여전히 표시되는 경우 여러 계단식이 필요할 수 있습니다. 더 많은 분할이 눈 근처에 있도록 (원근 별칭이 가장 빠르게 변화하는 곳) frustum을 나누는 것도 중요합니다. 대부분의 기하 도형이 뷰 frustum의 작은 섹션(예: 오버헤드 보기 또는 플라이트 시뮬레이터)에 뭉쳐 있는 경우 더 적은 수의 계단식이 필요합니다.
그림 4. 구성에 따라 서로 다른 frustum 분할이 필요합니다.
(왼쪽) 기하 도형의 동적 범위가 Z인 경우 많은 계단식이 필요합니다. (가운데) 기하 도형의 동적 범위가 Z에 낮으면 여러 frustums의 이점이 거의 없습니다. (오른쪽) 동적 범위가 중간인 경우 세 개의 파티션만 필요합니다.
조명 및 카메라의 방향
각 계단식의 프로젝션 매트릭스는 해당 하위 주물 주위에 단단히 맞습니다. 보기 카메라와 조명 방향이 직교형인 구성에서는 계단식이 거의 겹치지 않은 상태로 단단히 맞을 수 있습니다. 조명과 뷰 카메라가 병렬 맞춤으로 이동하면 겹침이 커집니다(그림 5). 조명과 뷰 카메라가 거의 병렬인 경우 "결투 frusta"라고 하며 대부분의 그림자 알고리즘에 매우 어려운 시나리오입니다. 이 시나리오가 발생하지 않도록 조명과 카메라를 제한하는 것은 드문 일이 아닙니다. 그러나 CSM은 이 시나리오의 다른 많은 알고리즘보다 훨씬 더 잘 수행됩니다.
그림 5. 조명 방향이 카메라 방향과 평행해짐에 따라 계단식 겹침이 증가합니다.
많은 CSM 구현은 고정 크기 frusta를 사용합니다. 픽셀 셰이더는 Z 깊이를 사용하여 frustum이 고정 크기 간격으로 분할된 경우 계단식 배열로 인덱싱할 수 있습니다.
View-Frustum 바인딩 계산
frustum 간격을 선택하면 장면에 적합하고 계단식에 맞추기라는 두 가지 중 하나를 사용하여 하위 프러스타가 만들어집니다.
장면에 맞추기
모든 frusta는 동일한 근거리 평면으로 만들 수 있습니다. 이렇게 하면 계단식이 강제로 겹칩니다. CascadedShadowMaps11 샘플은 이 기술을 장면에 맞게 호출합니다.
Cascade에 맞춤
또는 실제 파티션 간격을 근거리 및 먼 평면으로 사용하여 frusta를 만들 수 있습니다. 이로 인해 더 단단해지지만 결투 frusta의 경우 장면에 맞게 퇴화됩니다. CascadedShadowMaps11 샘플은 이 기술을 연계에 적합이라고 부릅니다.
이러한 두 메서드는 그림 6에 나와 있습니다. 계단식에 맞을수록 해상도가 떨어지게 됩니다. 계단식 맞춤의 문제는 직교 프로젝션이 보기 frustum의 방향에 따라 증가하고 축소된다는 것입니다. 장면에 맞는 기술은 보기 프러스텀의 최대 크기로 직교 프로젝션을 채우며 보기 카메라가 움직일 때 나타나는 아티팩트를 제거합니다. 그림자 깊이 맵을 개선하기 위한 일반적인 기술은 "텍셀 크기 증분에서 조명 이동" 섹션에서 빛이 이동할 때 나타나는 아티팩트를 해결합니다.
그림 6. 장면에 맞춤 및 계단식에 맞춤
그림자 맵 렌더링
CascadedShadowMaps11 샘플은 그림자 맵을 하나의 큰 버퍼로 렌더링합니다. 이는 텍스처 배열의 PCF가 Direct3D 10.1 기능이기 때문입니다. 모든 계단식에 대해 해당 계단식에 해당하는 깊이 버퍼의 섹션을 포함하는 뷰포트가 만들어집니다. 깊이만 필요하므로 null 픽셀 셰이더가 바인딩됩니다. 마지막으로 깊이 맵이 기본 섀도 버퍼로 한 번에 하나씩 렌더링됨에 따라 각 계단식에 대해 올바른 뷰포트 및 섀도 행렬이 설정됩니다.
장면 렌더링
이제 그림자가 포함된 버퍼가 픽셀 셰이더에 바인딩됩니다. CascadedShadowMaps11 샘플에서 구현된 캐스케이드를 선택하는 방법에는 두 가지가 있습니다. 이러한 두 메서드는 셰이더 코드로 설명됩니다.
Interval-Based Cascade 선택
그림 7. 간격 기반 계단식 선택
간격 기반 선택(그림 7)에서 꼭짓점 셰이더는 꼭짓점의 세계 공간에서 위치를 계산합니다.
Output.vDepth = mul( Input.vPosition, m_mWorldView ).z;
픽셀 셰이더는 보간된 깊이를 받습니다.
fCurrentPixelDepth = Input.vDepth;
간격 기반 계단식 선택 항목은 벡터 비교 및 점 제품을 사용하여 올바른 캐스케이드를 결정합니다. CASCADE_COUNT_FLAG 계단식 수를 지정합니다. m_fCascadeFrustumsEyeSpaceDepths_data 보기 frustum 파티션을 제한합니다. 비교 후 fComparison에는 현재 픽셀이 장벽보다 큰 값 1과 현재 계단식이 더 작은 경우 값 0이 포함됩니다. 점 제품은 이러한 값을 배열 인덱스로 합산합니다.
float4 vCurrentPixelDepth = Input.vDepth;
float4 fComparison = ( vCurrentPixelDepth > m_fCascadeFrustumsEyeSpaceDepths_data[0]);
float fIndex = dot(
float4( CASCADE_COUNT_FLAG > 0,
CASCADE_COUNT_FLAG > 1,
CASCADE_COUNT_FLAG > 2,
CASCADE_COUNT_FLAG > 3)
, fComparison );
fIndex = min( fIndex, CASCADE_COUNT_FLAG );
iCurrentCascadeIndex = (int)fIndex;
계단식이 선택되면 텍스처 좌표가 올바른 계단식으로 변환되어야 합니다.
vShadowTexCoord = mul( InterpolatedPosition, m_mShadow[iCascadeIndex] );
이 텍스처 좌표는 X 좌표와 Y 좌표를 사용하여 텍스처를 샘플링하는 데 사용됩니다. Z 좌표는 최종 깊이 비교를 수행하는 데 사용됩니다.
Map-Based Cascade 선택
지도 기반 선택(그림 8)은 계단식의 네 면에 대해 테스트하여 특정 픽셀을 커버하는 가장 엄격한 지도를 찾습니다. 꼭짓점 셰이더는 세계 공간의 위치를 계산하는 대신 모든 계단식에 대한 뷰 공간 위치를 계산합니다. 픽셀 셰이더는 현재 연계를 인덱싱할 수 있도록 텍스처 좌표의 크기를 조정하고 이동하기 위해 계단식으로 반복합니다. 그런 다음 텍스처 좌표가 텍스처 경계에 대해 테스트됩니다. 텍스처 좌표의 X 및 Y 값이 계단식으로 들어가면 텍스처를 샘플링하는 데 사용됩니다. Z 좌표는 최종 깊이 비교를 수행하는 데 사용됩니다.
그림 8. 지도 기반 계단식 선택
Interval-Based 선택 및 Map-Based 선택
계단식 선택을 직접 수행할 수 있으므로 간격 기반 선택은 지도 기반 선택보다 약간 빠릅니다. 지도 기반 선택은 텍스처 좌표를 계단식 경계와 교차해야 합니다.
섀도 맵이 완벽하게 정렬되지 않을 때 지도 기반 선택은 계단식 을 보다 효율적으로 사용합니다(그림 8 참조).
계단식 혼합
VSM(이 문서의 뒷부분에서 설명) 및 PCF와 같은 필터링 기술을 저해상도 CSM과 함께 사용하여 부드러운 그림자를 생성할 수 있습니다. 아쉽게도 해상도가 일치하지 않기 때문에 계단식 레이어 사이에 표시되는 이음새(그림 9)가 발생합니다. 솔루션은 두 계단식 모두에 대해 그림자 테스트가 수행되는 그림자 맵 사이에 밴드를 만드는 것입니다. 그런 다음 셰이더는 혼합 대역에서 픽셀의 위치에 따라 두 값 사이에 선형적으로 보간됩니다. CascadedShadowMaps11 및 VarianceShadows11 샘플은 이 흐림 밴드를 늘리고 줄이는 데 사용할 수 있는 GUI 슬라이더를 제공합니다. 셰이더는 대부분의 픽셀이 현재 계단식에서만 읽을 수 있도록 동적 분기를 수행합니다.
그림 9. 계단식 솔기
(왼쪽) 계단식이 겹치는 경우 보이는 솔기를 볼 수 있습니다. (오른쪽) 계단식이 혼합되면 이음새가 발생하지 않습니다.
그림자 맵 필터링
PCF
일반 그림자 맵을 필터링해도 부드럽고 흐릿한 그림자는 생성되지 않습니다. 필터링 하드웨어는 깊이 값을 흐리게 한 다음 흐리게 표시된 값을 광원 공간 텍셀과 비교합니다. 통과/실패 테스트로 인해 발생하는 하드 에지가 여전히 존재합니다. 그림자 맵을 흐리게 하는 것은 하드 에지를 잘못 이동하는 역할만 합니다. PCF를 사용하면 섀도 맵에서 필터링할 수 있습니다. PCF의 일반적인 개념은 총 하위 샘플 수에 대한 깊이 테스트를 통과하는 하위 샘플 수에 따라 그림자에 있는 픽셀의 백분율을 계산하는 것입니다.
Direct3D 10 및 Direct3D 11 하드웨어는 PCF를 수행할 수 있습니다. PCF 샘플러에 대한 입력은 텍스처 좌표와 비교 깊이 값으로 구성됩니다. 간단히 하기 위해 PCF는 네 번의 탭 필터로 설명됩니다. 텍스처 샘플러가 표준 필터와 유사하게 텍스처를 네 번 읽습니다. 그러나 반환된 결과는 깊이 테스트를 통과한 픽셀의 백분율입니다. 그림 10에서는 4개의 깊이 테스트 중 하나를 통과하는 픽셀이 그림자에서 25%인 방법을 보여 줍니다. 반환되는 실제 값은 부드러운 그라데이션을 생성하기 위해 텍스처 읽기의 하위 텍스트 좌표를 기반으로 하는 선형 보간입니다. 이 선형 보간이 없으면 네 번의 탭 PCF는 { 0.0, 0.25, 0.5, 0.75, 1.0 }의 다섯 값만 반환할 수 있습니다.
그림 10. 선택한 픽셀의 25%가 적용된 PCF 필터링 이미지
하드웨어 지원 없이 PCF를 수행하거나 PCF를 더 큰 커널로 확장할 수도 있습니다. 일부 기술은 가중 커널을 사용하여 샘플링하기도 합니다. 이렇게 하려면 N × N 그리드에 대한 커널(예: Gaussian)을 만듭니다. 가중치는 최대 1을 추가해야 합니다. 그런 다음 텍스처가 N2번 샘플링됩니다. 각 샘플은 커널의 해당 가중치에 따라 크기가 조정됩니다. CascadedShadowMaps11 샘플에서는 이 방법을 사용합니다.
깊이 바이어스
대형 PCF 커널을 사용할 때 깊이 바이어스가 더욱 중요해집니다. 픽셀의 광 공간 깊이를 깊이 맵에 매핑되는 픽셀과 비교하는 것만 유효합니다. 깊이 맵 텍셀의 인접 항목은 다른 위치를 나타냅니다. 이 깊이는 유사할 수 있지만 장면에 따라 매우 다를 수 있습니다. 그림 11에서는 발생하는 아티팩트가 강조 표시됩니다. 단일 깊이는 그림자 맵의 인접한 텍셀 3개와 비교됩니다. 깊이는 현재 기하 도형의 계산된 광 공간 깊이와 상관 관계가 없으므로 깊이 테스트 중 하나가 잘못 실패합니다. 이 문제에 대한 권장 해결 방법은 더 큰 오프셋을 사용하는 것입니다. 그러나 오프셋이 너무 크면 Peter Panning이 발생할 수 있습니다. 꽉 가까운 평면과 원거리 평면을 계산하면 오프셋을 사용하는 효과를 줄이는 데 도움이 됩니다.
그림 11. 잘못된 자기 그림자
잘못된 셀프 섀도는 조명 공간 깊이의 픽셀과 상관 관계가 없는 그림자 맵의 텍셀을 비교하여 발생합니다. 조명 공간의 깊이는 깊이 맵의 섀도 텍셀 2와 상관 관계가 있습니다. 텍셀 1은 광역 깊이보다 크지만 2는 같고 3은 작습니다. 텍셀 2와 3은 깊이 테스트를 통과하고 텍셀 1은 실패합니다.
대형 PCF용 DDX 및 DDY를 사용하여 Per-Texel 깊이 바이어스 계산
큰 PCF에 대해 ddx 및 ddy 를 사용하여 텍셀당 깊이 바이어스를 계산하는 것은 인접한 그림자 맵 텍셀에 대해 표면이 평면이라고 가정하여 올바른 깊이 바이어스를 계산하는 기술입니다.
이 기술은 파생 정보를 사용하여 평면에 대한 비교 깊이에 맞습니다. 이 기술은 계산적으로 복잡하기 때문에 GPU에 여분의 컴퓨팅 주기가 있는 경우에만 사용해야 합니다. 매우 큰 커널을 사용하는 경우 Peter Panning을 유발하지 않고 자체 섀도 아티팩트 제거를 위해 작동하는 유일한 기술일 수 있습니다.
그림 12는 문제를 강조 표시합니다. 조명 공간의 깊이는 비교되는 하나의 텍셀로 알려져 있습니다. 깊이 맵의 인접 텍셀에 해당하는 광 공간 깊이는 알 수 없습니다.
그림 12. 장면 및 깊이 맵
렌더링된 장면이 왼쪽에 표시되고 샘플 텍셀 블록이 있는 깊이 맵이 오른쪽에 표시됩니다. 눈 공간 텍셀은 블록 가운데에 D라는 레이블이 지정된 픽셀에 매핑됩니다. 이 비교는 정확합니다. 인접한 D가 알 수 없는 픽셀과 관련된 올바른 눈 공간 깊이입니다. 픽셀이 D와 동일한 삼각형과 관련이 있다고 가정하는 경우에만 인접한 텍셀을 다시 눈 공간에 매핑할 수 있습니다.
깊이는 광역 위치와 상관 관계가 있는 텍셀로 알려져 있습니다. 깊이 맵의 인접 텍셀에 대해 깊이를 알 수 없습니다.
높은 수준에서 이 기술은 ddx 및 ddy HLSL 연산을 사용하여 광역 위치의 파생 항목을 찾습니다. 파생 연산이 화면 공간과 관련하여 광역 깊이의 그라데이션을 반환하기 때문에 이는 매우 중요합니다. 이를 광역 공간과 관련하여 광역 깊이의 그라데이션으로 변환하려면 변환 행렬을 계산해야 합니다.
셰이더 코드에 대한 설명
나머지 알고리즘의 세부 정보는 이 작업을 수행하는 셰이더 코드에 대한 설명으로 제공됩니다. 이 코드는 CascadedShadowMaps11 샘플에서 찾을 수 있습니다. 그림 13에서는 조명 공간 텍스처 좌표가 깊이 맵에 매핑되는 방법과 X 및 Y의 파생을 사용하여 변환 행렬을 만드는 방법을 보여 줍니다.
그림 13. 광역 행렬에 대한 화면 공간
X 및 Y의 광역 위치 파생은 이 행렬을 만드는 데 사용됩니다.
첫 번째 단계는 광원 보기 공간 위치의 파생을 계산하는 것입니다.
float3 vShadowTexDDX = ddx (vShadowMapTextureCoordViewSpace);
float3 vShadowTexDDY = ddy (vShadowMapTextureCoordViewSpace);
Direct3D 11 클래스 GPU는 2개의 × 2개의 픽셀 쿼드를 병렬로 실행하고 ddx 용 X의 인접 항목과 ddy용 Y의 인접 항목에서 텍스처 좌표를 빼서 이러한 파생을 계산합니다. 이러한 두 파생은 2 × 2 행렬의 행을 구성합니다. 현재 형식에서 이 행렬을 사용하여 화면 공간 인접 픽셀을 광역 기울기로 변환할 수 있습니다. 그러나 이 행렬의 역방향이 필요합니다. 광역 인접 픽셀을 화면 공간 기울기로 변환하는 행렬이 필요합니다.
float2x2 matScreentoShadow = float2x2( vShadowTexDDX.xy, vShadowTexDDY.xy );
float fInvDeterminant = 1.0f / fDeterminant;
float2x2 matShadowToScreen = float2x2 (
matScreentoShadow._22 * fInvDeterminant,
matScreentoShadow._12 * -fInvDeterminant,
matScreentoShadow._21 * -fInvDeterminant,
matScreentoShadow._11 * fInvDeterminant );
그림 14. 화면 공간에서 화면 공간으로의 광원
그런 다음 이 행렬을 사용하여 현재 텍셀의 위와 오른쪽에 있는 두 텍셀을 변환합니다. 이러한 인접 항목은 현재 텍셀의 오프셋으로 표시됩니다.
float2 vRightShadowTexelLocation = float2( m_fTexelSize, 0.0f );
float2 vUpShadowTexelLocation = float2( 0.0f, m_fTexelSize );
float2 vRightTexelDepthRatio = mul( vRightShadowTexelLocation,
matShadowToScreen );
float2 vUpTexelDepthRatio = mul( vUpShadowTexelLocation,
matShadowToScreen );
행렬이 만드는 비율은 마지막으로 깊이 파생체를 곱하여 인접 픽셀의 깊이 오프셋을 계산합니다.
float fUpTexelDepthDelta =
vUpTexelDepthRatio.x * vShadowTexDDX.z
+ vUpTexelDepthRatio.y * vShadowTexDDY.z;
float fRightTexelDepthDelta =
vRightTexelDepthRatio.x * vShadowTexDDX.z
+ vRightTexelDepthRatio.y * vShadowTexDDY.z;
이제 PCF 루프에서 이러한 가중치를 사용하여 위치에 오프셋을 추가할 수 있습니다.
for( int x = m_iPCFBlurForLoopStart; x < m_iPCFBlurForLoopEnd; ++x )
{
for( int y = m_iPCFBlurForLoopStart; y < m_iPCFBlurForLoopEnd; ++y )
{
if ( USE_DERIVATIVES_FOR_DEPTH_OFFSET_FLAG )
{
depthcompare += fRightTexelDepthDelta * ( (float) x ) +
fUpTexelDepthDelta * ( (float) y );
}
// Compare the transformed pixel depth to the depth read
// from the map.
fPercentLit += g_txShadow.SampleCmpLevelZero( g_samShadow,
float2(
vShadowTexCoord.x + ( ( (float) x ) * m_fNativeTexelSizeInX ) ,
vShadowTexCoord.y + ( ( (float) y ) * m_fTexelSize )
),
depthcompare
);
}
}
PCF 및 CSM
PCF는 Direct3D 10의 텍스처 배열에서 작동하지 않습니다. PCF를 사용하려면 모든 계단식이 하나의 큰 텍스처 아틀라스에 저장됩니다.
Derivative-Based 오프셋
CSM에 대한 파생 기반 오프셋을 추가하면 몇 가지 문제가 발생합니다. 이는 서로 다른 흐름 제어 내의 파생 계산 때문입니다. 문제는 GPU가 작동하는 기본적인 방식으로 인해 발생합니다. Direct3D11 GPU는 2개의 × 2개의 픽셀 쿼드에서 작동합니다. 파생을 수행하기 위해 GPU는 일반적으로 동일한 변수의 인접 픽셀 복사본에서 변수의 현재 픽셀 복사본을 뺍니다. 이 작업은 GPU에서 GPU에 따라 다릅니다. 텍스처 좌표는 지도 기반 또는 간격 기반 계단식 선택에 따라 결정됩니다. 픽셀 쿼드의 일부 픽셀은 픽셀의 나머지 부분과 다른 연계를 선택합니다. 파생 기반 오프셋이 완전히 잘못되었으므로 그림자 맵 사이에 이음새가 표시됩니다. 솔루션은 조명 보기 공간 텍스처 좌표에서 파생을 수행하는 것입니다. 이러한 좌표는 모든 계단식에 대해 동일합니다.
PCF 커널용 패딩
섀도 버퍼가 패딩되지 않은 경우 계단식 파티션 외부의 PCF 커널 인덱스입니다. 해결 방법은 PCF 커널의 절반 크기로 계단식의 바깥쪽 테두리를 패딩하는 것입니다. 이는 캐스케이드를 선택하는 셰이더와 테두리가 유지될 만큼 큰 계단식 을 렌더링해야 하는 프로젝션 매트릭스에서 구현되어야 합니다.
분산 그림자 맵
VSM(자세한 내용은 Donnelly 및 Lauritzen의 분산 그림자 맵 참조)을 통해 직접 섀도 맵 필터링을 사용하도록 설정합니다. VSM을 사용하는 경우 텍스처 필터링 하드웨어의 모든 기능을 사용할 수 있습니다. 삼선형 및 이방성(그림 15) 필터링을 사용할 수 있습니다. 또한 VSM은 나선형을 통해 직접 흐리게 표시될 수 있습니다. VSM에는 몇 가지 단점이 있습니다. 깊이 데이터의 두 채널을 저장해야 합니다(깊이 및 깊이 제곱). 그림자가 겹치면 가벼운 출혈이 일반적입니다. 그러나 더 낮은 해상도로 잘 작동하며 CSM과 결합할 수 있습니다.
그림 15. 이방성 필터링
알고리즘 세부 사항
VSM은 깊이와 깊이를 2 채널 그림자 맵에 제곱하여 작동합니다. 그런 다음 이 2개 채널 섀도 맵을 일반 텍스처처럼 흐리게 표시하고 필터링할 수 있습니다. 그런 다음 알고리즘은 픽셀 셰이더에서 Chebychev의 같지 않음을 사용하여 깊이 테스트를 통과할 픽셀 영역의 비율을 추정합니다.
픽셀 셰이더는 깊이 및 깊이 제곱 값을 가져옵니다.
float fAvgZ = mapDepth.x; // Filtered z
float fAvgZ2 = mapDepth.y; // Filtered z-squared
깊이 비교가 수행됩니다.
if ( fDepth <= fAvgZ )
{
fPercentLit = 1;
}
깊이 비교에 실패하면 조명된 픽셀의 백분율이 추정됩니다. 분산은 평균 제곱에서 평균 제곱을 뺀 값으로 계산됩니다.
float variance = ( fAvgZ2 ) − ( fAvgZ * fAvgZ );
variance = min( 1.0f, max( 0.0f, variance + 0.00001f ) );
fPercentLit 값은 체비초프의 같지 않음으로 추정됩니다.
float mean = fAvgZ;
float d = fDepth - mean;
float fPercentLit = variance / ( variance + d*d );
가벼운 출혈
VSM의 가장 큰 단점은 가벼운 출혈입니다(그림 16). 빛 출혈은 여러 그림자 캐스터가 가장자리를 따라 서로를 폐색 할 때 발생합니다. VSM은 깊이 차이에 따라 그림자 가장자리를 음영 처리합니다. 그림자가 서로 겹치면 그림자가 되어야 하는 영역 중앙에 깊이 차이가 있습니다. VSM 알고리즘을 사용하는 데 문제가 있습니다.
그림 16. VSM 빛 출혈
문제의 부분적인 해결 방법은 fPercentLit를 전원으로 올리는 것입니다. 이로 인해 흐림 효과를 줄여 깊이 차이가 작은 아티팩트가 발생할 수 있습니다. 때로는 문제를 완화하는 마법의 값이 존재합니다.
fPercentLit = pow( p_max, MAGIC_NUMBER );
전원에 켜진 백분율을 높이는 대안은 그림자가 겹치는 구성을 방지하는 것입니다. 고도로 조정된 섀도 구성에도 조명, 카메라 및 기하 도형에 대한 몇 가지 제약 조건이 있습니다. 또한 고해상도 텍스처를 사용하여 가벼운 출혈을 줄입니다.
LVSM(계층화된 분산 섀도 맵)은 광원에 수직인 레이어로 frustum을 분리하는 대신 문제를 해결합니다. CSM도 사용할 때 필요한 맵 수가 매우 많습니다.
또한 VSM에 대한 논문의 공동 저자이자 LVSM에 대한 논문의 저자인 Andrew Lauritzen은 BEYOND3D 포럼에서 빛 혼합을 중화하기 위해 VSM과 지수 그림자 맵(ESM)을 결합하는 것에 대해 논의했습니다.
CSM이 있는 VSM
샘플 VarianceShadow11은 VSM과 CSM을 결합합니다. 조합은 매우 간단합니다. 샘플은 CascadedShadowMaps11 샘플과 동일한 단계를 따릅니다. PCF는 사용되지 않으므로 2패스 분리 가능한 컨볼루션에서 그림자가 흐려집니다. 또한 PCF를 사용하지 않을 경우 샘플에서 텍스처 아틀라스 대신 텍스처 배열을 사용할 수 있습니다. 텍스처 배열의 PCF는 Direct3D 10.1 기능입니다.
CSM이 있는 그라데이션
그림 17과 같이 그라데이션을 CSM과 함께 사용하면 두 계단식 사이의 테두리를 따라 이음새를 생성할 수 있습니다. 샘플 명령은 픽셀 간의 파생을 사용하여 필터에 필요한 mipmap 수준과 같은 정보를 계산합니다. 이로 인해 특히 Mipmap 선택 또는 이방성 필터링에 문제가 발생합니다. 쿼드의 픽셀이 셰이더에서 다른 분기를 사용하는 경우 GPU 하드웨어에서 계산한 파생 항목이 유효하지 않습니다. 이로 인해 그림자 맵을 따라 들쭉날쭉한 이음새가 생성됩니다.
그림 17. 서로 다른 흐름 제어를 사용하는 이방성 필터링으로 인한 계단식 테두리의 솔기
이 문제는 조명 보기 공간의 위치에 대한 파생 개체를 계산하여 해결됩니다. 조명 보기 공간 좌표는 선택한 계단식에만 해당되지 않습니다. 계산된 파생물은 프로젝션-텍스처 매트릭스의 배율 부분에 따라 올바른 Mipmap 수준으로 스케일링할 수 있습니다.
float3 vShadowTexCoordDDX = ddx( vShadowMapTextureCoordViewSpace );
vShadowTexCoordDDX *= m_vCascadeScale[iCascade].xyz;
float3 vShadowTexCoordDDY = ddy( vShadowMapTextureCoordViewSpace );
vShadowTexCoordDDY *= m_vCascadeScale[iCascade].xyz;
mapDepth += g_txShadow.SampleGrad( g_samShadow, vShadowTexCoord.xyz,
vShadowTexCoordDDX, vShadowTexCoordDDY );
VSM과 PCF의 표준 그림자 비교
VSM과 PCF는 모두 깊이 테스트를 통과하는 픽셀 영역의 비율을 근사화하려고 시도합니다. VSM은 필터링 하드웨어에서 작동하며 분리 가능한 커널로 흐리게 표시될 수 있습니다. 분리 가능한 컨볼루션 커널은 전체 커널보다 구현 비용이 상당히 저렴합니다. 또한 VSM은 조명 공간 깊이 맵의 한 값과 하나의 광 공간 깊이를 비교합니다. 즉, VSM에는 PCF와 동일한 오프셋 문제가 없습니다. 기술적으로 VSM은 더 큰 영역에 대한 깊이를 샘플링하고 통계 분석을 수행합니다. 이는 PCF보다 정확도가 낮습니다. 실제로 VSM은 혼합을 아주 잘 수행하므로 오프셋이 덜 필요합니다. 위에서 설명한 대로 VSM의 가장 큰 단점은 가벼운 출혈입니다.
VSM 및 PCF는 GPU 컴퓨팅 성능과 GPU 텍스처 대역폭 간의 절름발이를 나타냅니다. VSM은 분산을 계산하기 위해 더 많은 수학을 수행해야 합니다. PCF에는 더 많은 텍스처 메모리 대역폭이 필요합니다. 대형 PCF 커널은 텍스처 대역폭으로 인해 신속하게 병목 상태가 될 수 있습니다. GPU 대역폭보다 GPU 계산 성능이 더 빠르게 증가함에 따라 VSM은 두 알고리즘의 실용성이 높아지고 있습니다. VSM은 혼합 및 필터링으로 인해 해상도가 낮은 섀도 맵에서도 더 잘 보입니다.
요약
CSM은 큐브 뷰 별칭 문제에 대한 솔루션을 제공합니다. 타이틀에 필요한 시각적 충실도를 얻기 위한 몇 가지 가능한 구성이 있습니다. PCF 및 VSM은 널리 사용되며 별칭을 줄이기 위해 CSM과 결합되어야 합니다.
참조
도넬리, 더블유, 로리첸, A. 가변성 그림자 지도. SI3D '06: 대화형 3D 그래픽 및 게임에 대한 2006 심포지엄의 절차. 2006. pp. 161-165. 뉴욕, 뉴욕, 미국: ACM 프레스.
로리첸, 앤드류, 맥쿨, 마이클. 계층화된 분산 그림자 맵. 그래픽 인터페이스 2008, 2008년 5월 28~30일, 캐나다 온타리오 주 윈저의 절차.
엥겔, 워플강 F. 섹션 4. 계단식 그림자 맵. ShaderX5 , Advanced Rendering Techniques, Wolfgang F. Engel, Ed. 찰스 리버 미디어, 보스턴, 매사추세츠. 2006. pp. 197-206.