| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | ||||
| 4 | 5 | 6 | 7 | 8 | 9 | 10 |
| 11 | 12 | 13 | 14 | 15 | 16 | 17 |
| 18 | 19 | 20 | 21 | 22 | 23 | 24 |
| 25 | 26 | 27 | 28 | 29 | 30 | 31 |
- forward
- SGPR
- atmospheric
- Nanite
- 번역
- scalar
- SIMD
- DX12
- scattering
- wave
- vulkan
- UE5
- Wavefront
- VGPR
- RayTracing
- Study
- Graphics
- ue4
- ShadowMap
- GPU Driven Rendering
- texture
- shader
- Shadow
- GPU
- DirectX12
- hzb
- deferred
- rendering
- optimization
- unrealengine
- Today
- Total
RenderLog
[번역][Nvidia White Paper] Cascaded ShadowMaps 본문
개인 공부용으로 번역한 거라 잘못 번역된 내용이 있을 수 있습니다.
또한 원작자의 동의 없이 올려서 언제든 글이 내려갈 수 있습니다.
출처 : http://developer.download.nvidia.com/SDK/10/opengl/screenshots/samples/cascaded_shadow_maps.html
Cascaded Shadow Maps
Rouslan Dimitrov
NVIDIA Corporation

쉐도우맵은 게임엔진에서 현실적인 쉐도우를 얻는데 아주 인기 있습니다. 넓은 공간에서 쉐도우를 사용하려하고 할때, 쉐도우맵을 조정하기 더 어려워지고 ance와 앨리어싱이 나타나는 경향이 있습니다. 케스케이드 쉐도우맵(CSM)은 뷰어와 가까이 있는 깊이 텍스쳐를 높은 해상도로 그리고 멀리 있으면 낮은 해상도를 제공하여서 앨리어싱 문제를 해결하는데 도움을 주는 접근법입니다. 이것은 장면의 오류를 일정하게 유지하기 위해서 카메라의 뷰프러스텀을 나누고 각 파티션별로 분리된 DepthMap을 생성합니다. CSM은 보통 넓은 지형에서 태양에 의해서 만들어 지는 쉐도우에 사용됩니다. 한장의 쉐도우맵에 모든것을 담는 것은 매우 높고 비현실적인 해상도를 요구합니다. 그래서, 여러 쉐도우맵이 사용되어집니다 - 한 쉐도우맵은 상세한 쉐도우맵을 만들기 위해서 근처에 있는 오브젝트만 다룹니다; 또다른 쉐도우맵은 coarse 해상도로 거리가 있는 모든 것을 담고, 선택적으로 더 많은 쉐도우를 그 사이에 사용합니다. 멀리서 만들어지는 쉐도우는 스크린공간의 몇몇 픽셀만 차지하고 근접한 오브젝트는 아마 스크린에서 아주 많은 부분을 차지할 것이므로 이러한 분할은 합리적입니다.
그림 1-1 은 병렬 분할된 CSM 의 구성도를 보여줍니다. 그리고 나뉘어진 것은 Near과 Far 평면에 평행하고 각각의 슬라이스는 프러스텀 그자체입니다. 태양은 Directional light의 한 종류 입니다, 그래서 연관된 라이트 프러스텀은 박스입니다(빨갈과 파랑으로 보여짐). 알고리즘은 아래에서 계속 됩니다:
- 라이트의 모든 프러스텀에 대해서, 라이트의 관점에서 장면의 Depth를 그립니다.
- 카메라의 관점에서 장면을 렌더링합니다. 프래그먼트의 Z값에 따라서, 적절한 쉐도우맵을 선택합니다.

Related Work
스크린공간의 앨리어싱 에러를 향상시키기 위해서 시도한 여러 접근법이 있습니다. 여기에서 언급된 것은 전통적인 전체 뷰프러스텀에서 작동합니다. 비록 이 기술들이 모든 CSM 프러스텀 슬라이스에 적용되어질 수 있지만, 이것은 시각적 품질을 극적으로 향상시키지 않고 훨씬 더 복잡한 알고리즘 복잡도를 만듭니다. 실제로 CSM은 Perspective Shadow Maps의 이산화(discretization)으로 생각할 수 있습니다. Perspective Shadow Map(PSM) [2] - 그림 1-1은 라이트 프러스텀의 일부분이 Potential Occluders를 포함하지 않는 것을 보여주고 라이트 프러스텀이 카메라 뷰프러스텀의 바깥에 있어서 쉐도우맵의 일부분이 낭비되어집니다. PSM에 숨어있는 아이디어는 라이트 프러스텀을 뷰프러스텀과 정확하게 일치하도록 라이트 프러스텀을 감싸는 것입니다. 대충, 이것은 현재 카메라의 Post perspective space에서 Standard shadow mapping을 적용하므로써 얻을 수 있습니다. 이 방법의 단점은 위치와 라이트 소스의 종류가 직관적으로 바뀌지 않는 것입니다. 그래서 이 방법은 컴퓨터 게임에서 일반적이지 않습니다. Light Space Perspective Shadow Maps(LiPSM) [4]는 카메라 프러스텀을 라이트 소스의 방향을 바꾸지 않고 감쌉니다. Vewing ray와 라이트의 방향이 수직인(쉐도우맵에 수직) 새로운 라이트 프러스텀을 만듭니다. 프러스텀은 카메라 프러스텀과 Potential shadow caster들을 포함하기 위해서 적절한 크기로 만들어집니다. PSM에 비교해서, LiPSM은 특별한 경우가 많지 않습니다, 그러나 쉐도우맵의 텍스쳐를 가득차게 사용하지 않습니다. Trapezoidal Shadow Maps (TSM) [5]는 라이트에서 보여진 카메라 프러스텀의 'Bounding trapezoid'를 만듭니다 (LiPSM에서 프러스텀을 만드는 대신). 알고리즘은 다른 접근법과 유사하게 진행됩니다.
Detailed Overview
아래의 논의는 OpenGL SDK 데모의 Cascaded shadow maps을 기반으로 상세한 단계를 설명합니다. 쉐도우맵들을 각각의 분리된 쉐도우맵으로하여 텍스쳐 배열에다가 저장하는 것이 좋습니다. 이것은 픽셀쉐이더에서 더 효율적인 주소지정과 모든 레이어를 본질적으로 같은 방법으로 다룰 수 있기 때문에 합리적입니다.

알고리즘의 첫 단계는 카메라 eye 공간에서 뷰프러스텀의 나누는 z값을 계산하는 것입니다. 쉐도우맵의 픽셀은 측면의 길이 ds를 가진다고 가정합니다. 그림자를 받는 오브젝트의 normal와 position에 따라서 쉐도우가 스크린의 dp 부분을 차지합니다. 다이어그램 2-1 참조,

여기서 n은 뷰프러스텀의 Near 거리입니다. 이론적으로, 화면에 정확히 같은 에러를 주기 위해서, dp/ds는 상수여야 합니다. 게다가, Perspective 에러만 최소화 하기 때문에 우리는 코사인 의존 요소 역시 상수와 같이 다룰 수 있습니다. 그리고 Projection 에러에 대해 책임이 있습니다. (In addition, we can treat the cosine dependent factor also as a constant because we minimize only the perspective errors and it is responsible for projection errors), 그래서

P값은 상수 s ∈[0;1] 으로 강제되어집니다.
위의 z에 대한 방정식을 해결하는 것과 분할된 점의 이산화(discretizing) (분할한 N의 숫자가 크다고 가정)는 지수적으로 분포되어야만 합니다, 이것으로:

여기서 N은 총 나뉘어진 수 입니다. 더 자세한 유도는 [1]을 참조해주세요.

그러나, 보통은 N이 1~4 사이이기 때문에, 쉐도우의 해상도가 가파르게 변하기 때문에 방정식은 분할점을 보여줍니다. 그림 2-2는 불일치의 이유를 보여줍니다: 뷰 프러스텀의 바깥 영역, 그러나 라이트 프러스텀의 안쪽은 낭비되어집니다. 왜냐하면 보이지 않기 때문입니다; 그러나 N → ∞ 이기 때문에 이 영역은 0 이 됩니다.
이 효과에 대응하기 위해서, i에 선형항이 추가되고 차이는 더이상 거의 알아보기 힘듭니다:

여기서 λ 보정의 강도를 제어합니다. z로 나눈 후에, 현재 프러스텀 슬라이스의 코너 점들은 시야와 스크린의 종횡비로 부터 계산되어집니다. 자세한 것은 [3]을 참고하세요.

그동안, 라이트의 모델뷰 매트릭스 M은 라이트의 방향을 바라보게 설정되고 일반 Orthogonal 프로젝션 매트릭스 P=I로 설정됩니다. 그리고 나서, 카메라 프러스텀 슬라이스의 코너의 점 P는 라이트의 동차좌표 공간에서 ph=PMp로 투영된다. 바운딩 박스의 각 방향에서 최소의 mi와 최대 Mi 값은 라이트 프러스텀(box)에 정렬되어있습니다. 그리고 일반적인 라이트 프러스텀이 정확히 일치하도록 scaling과 offset을 결정합니다. 이것은 crop 매트릭스 C를 만들어서, 우리가 최고의 z에서의 정밀도를 얻도록 해주며 x, y에서 가능한 적은 정밀도를 잃도록 해줍니다. 최종적으로 라이트의 프로젝션 매트릭스 P는 P=CPz로 수정됩니다. Pz는 Near과 Far 평면이 mz 와 Mz인 Orthogonal 매트릭스입니다.

주목할 점은 우리가 라이트의 프러스텀을 프러스텀 슬라이스와 정확히 일치하게 할 수 있지만, 이것은 라이트의 방향과 종류를 perspective shadow map으로 변형시킵니다. 장면은 각각의 프러스텀 슬라이스 i로 컬링하고 모든 것들은 (CPfM)i를 모델뷰와 프로젝션 매트릭스처럼 사용하여 depth layer에 그려집니다. 그리고 전체 과정은 모든 프러스텀 파티션에 대해서 반복되어집니다.
Final scene rendering
이전 단계에서, 쉐도우맵은 1...N이 생성되어졌고 이제 오브젝트가 쉐도우안에 있는지 판단하기 위해서 사용되어집니다. 모든 그려지는 픽셀에 대해서, z 값은 이전에 계산되어진 N의 z범위와 비교되어 져야만합니다. 다음과 같이, 그것이 i번째 범위에 있다고 가정합니다. 주의할점은 픽셀 쉐이더는 이 값을 post-projection space에서 받는 것입니다, 반면에 이 값은 eye 공간에서 계산되어졌습니다. 그래서, 프래그먼트의 위치는 카메라의 Inverse modelview matrix Mc^-1 을 사용하여 월드공간으로 변환되어집니다(full inverse 는 필요없음 - scaling이 사용되지 않았다면, 상단의 3x3 부분의 transpose여도 됨). 이후에, 라이트 매트릭스의 i번째 슬라이스를 곱해줍니다. 변환은 (CPfM)i Mc^-1 의 매트릭스 조합으로 만들어집니다. 최종적으로, 투영되어진 위치는 [-1;1] 에서 [0, 1]로 선형적으로 조정 됩니다. 모든 변환 후에, 프래그먼트의 (x, y)위치는 i번째 depth map의 실제 텍스쳐 좌표이고 z 좌표는 라이트에서 입자(역주 : 현재 픽셀)까지의 거리 입니다. 조회를 수행하여서 우리는 같은 방향을 기준으로 라이트에서 가장 가까운 오클루더의 거리를 얻을 수 있습니다. 이 두 값을 비교하는 것으로 프래그먼트가 쉐도우안에 있는지 여부를 알 수 있습니다.

Code Overview
함께 제공된 OpenGL SDK 샘플은 아래의 소스를 포함합니다:
- terrain.cpp – 환경 로딩과 렌더링의 함수 정의를 포함합니다. 쉐도우 매핑 알고리즘에 필요한 함수는 Draw() 뿐입니다, 반면에 Load()와 GetDim()은 초기화 과정에 호출되어 적절히 바운딩 박스를 로드하고 설정합니다.
- utility.cpp – 메인코드를 더 읽기 쉽게 하기 위한 헬퍼 함수들을 포함합니다. 이들은 쉐이더로더;카메라 조작;메뉴, 키보드 그리고 마우스 조작 등등을 포함합니다.
- shadowmapping.cpp – 이 파일은 제시한 알고리즘의 메인 코어 코드를 포함합니다. 그리고 쉐도우맵을 만들고 최종 이미지를 화면에 출력하는 것을 포함합니다.
대략, terrain.cpp 와 utility.cpp는 게임엔진에 의해 제공되는 실제 게임 샘플을 실행하기 위해서 필요한 프레임워크를 제공합니다. 비슷하게, display()는 렌더링 루프의 부분입니다, 그리고 그 안에서 makdShadowMap()과 renderScene()을 호출합니다.
Listing 3-1. An excerpt from makeShadowMap() (Slightly modified)
void makeShadowMap()
{
/* ... */
// set the light’s direction
gluLookAt(0, 0, 0,
light_dir[0], light_dir[1], light_dir[2],
1.0f, 0.0f, 0.0f);
/* ... */
// compute the z-distances for each split as seen in camera space
updateSplitDist(f, 1.0f, FAR_DIST);
// for all shadow maps:
for (int i = 0; i < cur_num_splits; i++)
{
// compute the camera frustum slice boundary points
// in world space
updateFrustumPoints(f[i], cam_pos, cam_view);
// adjust the view frustum of the light, so that
// it encloses the camera frustum slice fully.
applyCropMatrix(f[i]);
// make the current depth map a rendering target
glFramebufferTextureLayerEXT(GL_FRAMEBUFFER_EXT,
GL_DEPTH_ATTACHMENT_EXT, depth_tex_ar, 0, i);
// clear the depth texture from last time
glClear(GL_DEPTH_BUFFER_BIT);
// draw the scene
terrain->Draw(minZ);
/* ... */
}
/* ... */
}renderScene()은 쉐이더 uniforms (listing 3-2를 보세요)을 설정하고 그리고나서 CSM 없이 장면을 그립니다. CSM 코드조각에서 중요한 점은 이 패스에서 적용되어지는 픽셀쉐이더 입니다.
Listing 3-2. shadow_single_fragment.glsl (Slightly modified)
uniform sampler2D tex; // terrain texture
uniform vec4 far_d; // far distances of
// every split
varying vec4 vPos; // fragment’s position in
// view space
uniform sampler2DArrayShadow stex; // depth textures
float shadowCoef()
{
int index = 3;
// find the appropriate depth map to look up in
// based on the depth of this fragment
if (gl_FragCoord.z < far_d.x)
index = 0;
else if (gl_FragCoord.z < far_d.y)
index = 1;
else if (gl_FragCoord.z < far_d.z)
index = 2;
// transform this fragment's position from view space to
// scaled light clip space such that the xy coordinates
// lie in [0;1]. Note that there is no need to divide by w
// for othogonal light sources
vec4 shadow_coord = gl_TextureMatrix[index] * vPos;
// set the current depth to compare with
shadow_coord.w = shadow_coord.z;
// tell glsl in which layer to do the look up
shadow_coord.z = float(index);
// let the hardware do the comparison for us
return shadow2DArray(stex, shadow_coord).x;
}
void main()
{
vec4 color_tex = texture2D(tex, gl_TexCoord[0].st);
float shadow_coef = shadowCoef();
float fog_coef = clamp(gl_Fog.scale * (gl_Fog.end + vPos.z),
0.0, 1.0);
gl_FragColor = mix(gl_Fog.color, (0.9 * shadow_coef *
gl_Color * color_tex + 0.1), fog_coef);
}Results
이 섹션은 4개로 나눠진 CSM로 큰 크기의 지형을 렌더링한 것의 스크린샷 몇장을 보여줍니다, 그리고 각각의 쉐도우맵은 1024x1024 입니다.





Conclusion
Cascaded Shadow Maps은 큰 크기의 환경의 쉐도우에서 유망한 접근법입니다. 이것은 특별한 케이스에서 의해서 고통받거나 다른 warping 방법에 비해서 어렵지 않으며 상대적으로 uniform under-sampling 에러를 스크린 공간에서 제공합니다. 그래서, 쉐도우맵의 해상도를 올리는 것만으로, 쉐도우의 jagged edges 문제를 장면의 모든 오브젝트에 대해서 크게 감소시킬 수 있습니다, 이것은 뷰어로 부터의 거리가 독립적입니다.
References
[1]Fan Zhang , Hanqiu Sun , Leilei Xu , Lee Kit Lun, Parallel-split shadow maps for large-scale virtual environments, Proceedings of the 2006 ACM international conference, June 14-April 17, 2006, Hong Kong, China
[2] Marc Stamminger , George Drettakis, Perspective shadow maps, Proceedings of the 29th annual conference on Computer graphics and interactive techniques, July 23-26, 2002, San Antonio, Texas
[3] António Ramires Fernandes, View Frustum Culling Tutorial, http://www.lighthouse3d.com/opengl/viewfrustum/
[4] Michael Wimmer, Daniel Scherzer, Werner Purgathofer. Light Space Perspective Shadow Maps. Eurographics Symposium on Rendering 2004
[5] Tobias Martin, Tiow-Seng Tan. Anti-Aliasing and Continuity with Trapezoidal Shadow Maps. Proceedings of Eurographics Symposium on Rendering, 21-23 June 2004, Norrköping, Sweden
Terrain Reference
http://www.cc.gatech.edu/projects/large_models/gcanyon.html
Palm Tree Reference
'Graphics > 참고자료' 카테고리의 다른 글
| [번역] Accurate Atmospheric Scattering (0) | 2020.08.07 |
|---|---|
| [번역] INTRO TO GPU SCALARIZATION – PART 1 (0) | 2020.07.20 |
| [번역]Performance of Array vs. Linked-List on Modern Computers (2) | 2020.07.08 |
| [번역]Understanding numerical precision (0) | 2020.06.30 |
| [번역] Perspective Shadow Maps: Care and Feeding (0) | 2020.06.28 |