Shadow Mapping

 

  • 그림자를 구현하기 위한 방법으로, 우선 빛의 위치와 방향이 모두 일치하는 가상의 카메라를 두어
    카메라로 물체들을 찍은 뒤 물체들의 깊이값을 계산한 다음, 그 값을 텍스쳐에 저장해준다. (Shadow Map)

  • 이후 메인카메라로 찍은 물체들의 ViewPosWorld 좌표계로 변환한 뒤 가상 카메라의 VP 변환 행렬을 곱해 나온
    ClipSpace에서 W나누기를 통해 가상 카메라 기준Projection 좌표를 구하고, [-1 ~ 1], [1 ~ -1] 사이로 나타난 좌표를 [0 ~ 1], [0 ~ 1] 사이의 uv좌표계로 변환시켜 준다.
    다음으로 이 uv 좌표상의 깊이갚과 Shadow Map에 저장된 깊이값들을 비교하여 Shadow Map 상의 깊이가 더 가깝다라고 판별된다면, 그 곳에 그림자를 그려주게 되는 방식이다.

  • 이는 실제 세상에서 그림자가 지는 방식과 거의 동일하다고 생각할 수 있다.
    만약 어떠한 물체보다 빛이 먼저 닿는 물체가 있다면, 그 뒤에 있는 물체는 빛을 받지 못하여 그림자가 지게 되는 방식이다.

  • Shadow Mapping은 Shadow Map Texture의 해상도가 커지면 커질수록 더욱 고퀄리티의 그림자를 만들어낼 수 있게 된다.

 

 


 

 

<shadow.fx>

 

 

더보기
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
32
#ifndef _SHADOW_FX_
#define _SHADOW_FX_
 
#include "params.fx"
 
struct VS_IN
{
    float3 pos : POSITION;
};
 
struct VS_OUT
{
    float4 pos : SV_Position;
    float4 clipPos : POSITION;
};
 
VS_OUT VS_Main(VS_IN input)
{
    VS_OUT output = (VS_OUT)0.f;
 
    output.pos = mul(float4(input.pos, 1.f), g_matWVP);
    output.clipPos = output.pos;
 
    return output;
}
 
float4 PS_Main(VS_OUT input) : SV_Target
{
    return float4(input.clipPos.z / input.clipPos.w, 0.f, 0.f, 0.f);
}
 
#endif
cs

 

 

  • Shadow Map의 Texture를 설정해주기 위한 Shader이다.
  • 우선 VS_Main에서 각 정점에 WVP 변환 행렬을 곱하여 Clip Space 좌표를 구하고, 이를 clipPos에 저장한 뒤
    PS_Main에서 clipPos의 z값에 w를 나눠준 값을 저장하여 RT Texture에 저장해준다.

 

 

<lighting.fx>

 

 

더보기
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#ifndef _LIGHTING_FX_
#define _LIGHTING_FX_
 
#include "params.fx"
#include "utils.fx"
 
struct VS_IN
{
    float3 pos : POSITION;
    float2 uv : TEXCOORD;
};
 
struct VS_OUT
{
    float4 pos : SV_Position;
    float2 uv : TEXCOORD;
};
 
struct PS_OUT
{
    float4 diffuse : SV_Target0;
    float4 specular : SV_Target1;
};
 
// [Directional Light]
// g_int_0 : Light index
// g_tex_0 : Position RT
// g_tex_1 : Normal RT
// g_tex_2 : Shadow RT
// g_mat_0 : ShadowCamera VP
// Mesh : Rectangle
 
VS_OUT VS_DirLight(VS_IN input)
{
    VS_OUT output = (VS_OUT)0;
 
    output.pos = float4(input.pos * 2.f, 1.f);
    output.uv = input.uv;
 
    return output;
}
 
PS_OUT PS_DirLight(VS_OUT input)
{
    PS_OUT output = (PS_OUT)0;
 
    float3 viewPos = g_tex_0.Sample(g_sam_0, input.uv).xyz;
    if (viewPos.z <= 0.f)
        clip(-1);
 
    float3 viewNormal = g_tex_1.Sample(g_sam_0, input.uv).xyz;
 
    LightColor color = CalculateLightColor(g_int_0, viewNormal, viewPos);
 
    // 그림자
    if (length(color.diffuse) != 0)
    {
        matrix shadowCameraVP = g_mat_0;
 
        float4 worldPos = mul(float4(viewPos.xyz, 1.f), g_matViewInv);
        float4 shadowClipPos = mul(worldPos, shadowCameraVP);
        float depth = shadowClipPos.z / shadowClipPos.w;
 
        // x [-1 ~ 1] -> u [0 ~ 1]
        // y [1 ~ -1] -> v [0 ~ 1]
        float2 uv = shadowClipPos.xy / shadowClipPos.w;
        uv.y = -uv.y;
        uv = uv * 0.5 + 0.5;
 
        if (0 < uv.x && uv.x < 1 && 0 < uv.y && uv.y < 1)
        {
            float shadowDepth = g_tex_2.Sample(g_sam_0, uv).x;
            if (shadowDepth > 0 && depth > shadowDepth + 0.00001f)
            {
                color.diffuse *= 0.5f;
                color.specular = (float4) 0.f;
            }
        }
    }
 
    output.diffuse = color.diffuse + color.ambient;
    output.specular = color.specular;
 
    return output;
}
 
#endif
cs

 

 

  • 실험을 위해서 Directional Light 에게만 그림자를 적용시켰기 때문에, 일부 코드만을 발췌해왔다.

  • 우선 VS에선 바뀐점이 없고, PS의 그림자 부분부터 새로운 내용이 추가가 된다.

    • 메인카메라로 찍은 물체들의 ViewPos에 View 변환 역행렬을 곱해줌으로써 World 좌표계로 변환한 뒤
      가상 카메라의 VP 변환 행렬을 곱해 나온 ClipSpace를 추출해주고 ClipSpace의 z값에 w나누기 한 것을 depth로써 저장하며, ClipSpace의 xy값에 w나누기 하여 Projection 좌표를 추출해준 뒤
      [-1 ~ 1], [1 ~ -1] 사이로 나타난 Projection 좌표를 [0 ~ 1], [0 ~ 1] 사이의 uv 좌표계로 변환시켜 준다.

    • 다음으로 Shadow Map을 참고하여 uv 좌표상의 깊이갚을 추출한 값과 depth에 저장된 값을 비교하여 Shadow Map 상의 깊이가 더 가깝다라고 판별된다면, 그 영역의 diffuse와 specular를 조정하여 텍스처를 그림자와 같이 변경시켜 주는 방식이다.

 

 


 

 

결과

 

 

  • 상당의 가장 우측 텍스쳐를 Shadow Map으로 설정하였고,
    이 Shadow Map을 참고하여 구체의 상단에서 아래로 비추는 Directional Light의 그림자를 생성하였다.

'Grahpics' 카테고리의 다른 글

[Graphics] DirectX12 - Terrain  (0) 2022.02.17
[Graphics] DirectX12 - Tessellation  (0) 2022.02.16
[Graphics] DirectX12 - Instancing  (0) 2022.02.14
[Graphics] DirectX12 - Particle System  (0) 2022.02.14
[Graphics] DirectX12 - Compute Shader  (0) 2022.02.13

+ Recent posts