| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 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 |
- VGPR
- ShadowMap
- scattering
- scalar
- optimization
- forward
- vulkan
- rendering
- SGPR
- unrealengine
- GPU Driven Rendering
- wave
- Study
- ue4
- Nanite
- deferred
- Wavefront
- Shadow
- atmospheric
- DX12
- DirectX12
- SIMD
- shader
- texture
- 번역
- RayTracing
- UE5
- hzb
- GPU
- Graphics
- Today
- Total
RenderLog
Exponential Shadow Map(ESM) 본문
Exponential Shadow Map(ESM)
최초작성 : 2020-04-23
마지막수정 : 2020-04-23
최재호
목표
ESM Shadow 에 대해 알아보고 실제 구현해봅니다.
개요
Standard shadow map 의 Shadow edge를 부드럽게 처리할 수 있는 기법 중 하나입니다.
구현방법도 VSM보다 간단하고, ShadowMap으로 R32 텍스쳐만 있으면 되므로 VSM 보다 더 적은 메모리를 사용합니다.
구현
SSM(Standard Shadow Map)의 경우 ShadowMap에 Depth 값을 저장합니다. 하지만 ESM의 경우 저장하는 값이 달라질 뿐 나머지 과정은 모두 동일합니다. 그리고 ShadowMap 의 필터링도 미리 처리해둘 수 있는 장점이 있습니다.
아래 그림에서 기존 SSM과 ESM의 차이를 잘보여줍니다.
기존 SSM의 그래프를 가리키는 빨간색 선을 주목해주세요.
- D < Z 식을 보면, D가 현재 Depth값 Z가 Shadow Map의 Depth 값인 것을 확인할 수 있습니다. 0.5 근처에서 Lit 여부가 판정되는데 거리의 비교이다 보니, Lit or Unlit 두 상태만 존재합니다. (Step Function)
ESM의 그래프를 가리키는 검은색 선을 주목해봅시다.
- exp(10*(d-z))식을 보면 0.5 근처에서 Lit 여부가 천천히 증가합니다.
- 이 부분은 상수 K값으로 천천히 혹은 급격하게 변경될 수 있습니다. 이 부분이 쉐도우의 Artifacts를 만들어낼 수 있는데, 쉐도우가 더 밝게 보이는 현상을 만듭니다. 이 부분은 뒤에서 왜 발생하는지 알아보겠습니다.

ESM의 또다른 특성으로 exp(k*(z-d)) = exp(k*z) * exp(k*d) 처럼 식을 분리할 수 있는점 입니다. Shadow map에 Depth 값으로 exp(k*z)를 저장하고, 실제 Shadow를 드리울때는 exp(-k*d) 값을 생성하여 Shadow map의 exp(k*z)와 곱하여 Lit 여부를 판정합니다.
exp(k*(z-d)) 두 값으로 분리할 수 있는 점은 Shadow map 을 미리 필터링 할 수 있다는 장점을 제공합니다. Gaussian Blur를 사용하여 Shadow map을 필터링 하였으며, 바로 이러한 이유 때문에 ESM이 Shadow map edge가 부드럽게 처리 됩니다.
Geogebra에서 exp(k*z) * exp(k*d) 를 사용해보면 아래와 같은 결과를 얻을 수 있습니다. 결과를 한번 분석 해봅시다.




실제 렌더링시에도 쉐도우를 만드는 오브젝트와 쉐도우가 드리우는 위치가 가까우면 쉐도우가 옅어지는 현상이 있다.

실제 구현
SSM에서 약간만 변경하면 바로 ESM을 사용할 수 있습니다. 아래와 같은 순서로 ESM을 사용합니다.
1. ESM Shadow map을 만든다. Depth 를 저장하는 대신 exp(K * Z) 저장
2. Gaussian blur로 ESM 쉐도우를 필터링 합니다.
3. Base pass의 Lighting 과정에서 ESM Shadow map을 사용하여 Shadow를 만듭니다.
- 현재 Shading 하고있는 픽셀의 exp(-K * Z) 값을 만들어 Shado w map에 있는 값과 곱하여 결과를 얻습니다.
ESM Shadow 생성 PixelShader
#version 330 core
#include "common.glsl"
precision mediump float;
in vec3 Pos_;
uniform float ESM_C;
layout (std140) uniform DirectionalLightShadowMapBlock
{
mat4 ShadowVP;
mat4 ShadowV;
vec3 LightPos; // Directional Light Pos 임시
float LightZNear;
float LightZFar;
};
void main()
{
vec3 lightDir = Pos_ - LightPos;
float distSquared = dot(lightDir.xyz, lightDir.xyz);
float distFromLight = (sqrt(distSquared) - LightZNear) / (LightZFar - LightZNear);
gl_FragData[0].x = exp(distFromLight * ESM_C);
gl_FragData[0].w = 1.0;
}
ESM Shadow를 Gaussan Blur 하는 과정
#version 330 core
precision mediump float;
uniform sampler2D tex_object;
uniform vec2 PixelSize;
uniform float IsVertical;
uniform float MaxDist;
#define FILTER_STEP_COUNT 5.0
#define FilterSize vec2(FILTER_STEP_COUNT, FILTER_STEP_COUNT)
#define COUNT (FILTER_STEP_COUNT * 2.0 + 1.0)
in vec2 TexCoord_;
out vec4 FragColor;
void main()
{
vec2 radiusUV = (FilterSize * PixelSize) / FILTER_STEP_COUNT;
if (IsVertical > 0.0)
{
radiusUV = vec2(0.0, radiusUV.y);
}
else
{
radiusUV = vec2(radiusUV.x, 0.0);
}
vec4 color = vec4(0);
for (float x = -FILTER_STEP_COUNT; x <= FILTER_STEP_COUNT; ++x)
{
vec2 offset = vec2(x, x) * radiusUV;
vec2 tex = TexCoord_ + offset;
if (tex.x < 0.0 || tex.x > 1.0 || tex.y < 0.0 || tex.y > 1.0)
{
color.x += exp(MaxDist);
color.y += exp(MaxDist * MaxDist);
color.w += 1.0;
}
else
{
color += texture(tex_object, tex);
}
}
FragColor = color / COUNT;
}
ESM Shadow 사용하여 그림자를 만드는 부분의 PixelShader
// ESM + Directional
float ESM(vec3 lightClipPos, vec3 lightPos, vec3 pos, float near, float far, float ESM_C, sampler2D shadow_object)
{
float lit = 1.0;
if (IsInShadowMapSpace(lightClipPos))
{
vec3 toLight = lightPos - pos;
float distFromLight = (sqrt(dot(toLight, toLight)) - near) / (far - near);
float expCZ = texture(shadow_object, lightClipPos.xy).r;
lit = clamp(exp(-distFromLight * ESM_C) * expCZ, 0.0, 1.0);
}
return lit;
}
최종 렌더링 결과

구현 코드
https://github.com/scahp/Shadows
레퍼런스
1. http://developer.download.nvidia.com/presentations/2008/GDC/GDC08_SoftShadowMapping.pdf
2. http://jankautz.com/publications/esm_gi08.pdf
'Graphics > Graphics' 카테고리의 다른 글
| Shadow Volume (Stencil Shadow) - 구현 (2/2) (0) | 2020.05.05 |
|---|---|
| Shadow Volume (Stencil Shadow) - 원리 (1/2) (0) | 2020.04.29 |
| Variance Shadow Map(VSM) (0) | 2020.04.16 |
| Signed Distance Fields (2) | 2020.04.13 |
| Light Indexed Deferred Rendering (0) | 2020.04.03 |