Deferred Rendering

 

  • 우리가 여태 하고있던 Rendering 방식은 Forward Rendering으로, 물체를 그릴 때 빛에 대한 연산을 각 조명을 찾아서 해주는 방식이었다.
  • 하지만 이는 불필요한 연산의 횟수가 늘어나 비효율적이라는 단점이 있다.
  • 이를 해결하기 위해 Deferred Rendering을 활용하는데, 이 방식은 빛을 그릴 때 각 물체를 찾아서 연산해주는 방식이다.
  • Deferred Rendering을 구현하기 위한 방법으로는 이전 시간에 MRT를 생성하여 그 곳에 Fragment 정보들을 저장하였는데, 저장된 정보들을 활용하여 빛 연산을 해주는 것이다.
    예를 들면 Lighting의 범위를 판단할 때 Position 정보를 통해서 물체가 범위내에 있는지를 판단하고, 존재한다면 Position, Normal, Diffuse 정보를 활용하여 Lighting 정보를 생성하여 주는 방식이다.

 

관련 포스트 : https://manduk8412.tistory.com/58

 


 

<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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
#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
// 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);
    output.diffuse = color.diffuse + color.ambient;
    output.specular = color.specular;
 
    return output;
}
 
// [Point Light]
// g_int_0 : Light index
// g_tex_0 : Position RT
// g_tex_1 : Normal RT
// g_vec2_0 : RenderTarget Resolution
// Mesh : Sphere
 
VS_OUT VS_PointLight(VS_IN input)
{
    VS_OUT output = (VS_OUT)0;
 
    output.pos = mul(float4(input.pos, 1.f), g_matWVP);
    output.uv = input.uv;
 
    return output;
}
 
PS_OUT PS_PointLight(VS_OUT input)
{
    PS_OUT output = (PS_OUT)0;
 
    // input.pos = SV_Position = Screen 좌표
    float2 uv = float2(input.pos.x / g_vec2_0.x, input.pos.y / g_vec2_0.y);
    float3 viewPos = g_tex_0.Sample(g_sam_0, uv).xyz;
    if (viewPos.z <= 0.f)
        clip(-1);
 
    int lightIndex = g_int_0;
    float3 viewLightPos = mul(float4(g_light[lightIndex].position.xyz, 1.f), g_matView).xyz;
    float distance = length(viewPos - viewLightPos);
    if (distance > g_light[lightIndex].range)
        clip(-1);
 
    float3 viewNormal = g_tex_1.Sample(g_sam_0, uv).xyz;
 
    LightColor color = CalculateLightColor(g_int_0, viewNormal, viewPos);
 
    output.diffuse = color.diffuse + color.ambient;
    output.specular = color.specular;
 
    return output;
}
 
// [Final]
// g_tex_0 : Diffuse Color Target
// g_tex_1 : Diffuse Light Target
// g_tex_2 : Specular Light Target
// Mesh : Rectangle
 
VS_OUT VS_Final(VS_IN input)
{
    VS_OUT output = (VS_OUT)0;
 
    output.pos = float4(input.pos * 2.f, 1.f);
    output.uv = input.uv;
 
    return output;
}
 
float4 PS_Final(VS_OUT input) : SV_Target
{
    float4 output = (float4)0;
 
    float4 lightPower = g_tex_1.Sample(g_sam_0, input.uv);
    if (lightPower.x == 0.&& lightPower.y == 0.&& lightPower.z == 0.f)
        clip(-1);
 
    float4 color = g_tex_0.Sample(g_sam_0, input.uv);
    float4 specular = g_tex_2.Sample(g_sam_0, input.uv);
 
    output = (color * lightPower) + specular;
    return output;
}
 
#endif
cs

 

 

  • 각 렌더타겟의 uv값을 활용하여 Position, Normal 등의 정보를 저장하고, 그것을 Shader에게 넘겨주어 연산에 사용하는 방식.
  • Directional Light = Vertex Shader에서는 Mesh 사각형의 크기가 1이기 때문에 투영 좌표계의 크기에 맞게 Scale Up을 해준 뒤 넘겨주게 되고 Pixel Shader에서는 범위에 상관없이 받는 빛이기 때문에, uv 값을 통해 물체가 존재하는지만 확인해준 뒤 존재한다면 tex_1을 통해 Normal을 추출해주어 빛연산을 해주게 된다.
  • Point Light =  Vertex Shader에서는 구 형태의 Mesh의 Position을 WVP 변환을 통해 변환시켜 넘겨주고,
    Pixel Shader에서는 input.pos / resolution을 통해서 screen좌표를 uv좌표로 변환해준다.
    이후 위와 마찬가지로 tex_1을 통해 픽셀에 물체가 존재하는지를 확인하고, 없다면 skip해준다.
    존재한다면 g_int_0를 통해 LightIndex를 가져와준 뒤 g_light[index]의 Position에 View 변행렬을 곱해 viewLightPos에 저장하여 현재 픽셀에 존재하는 물체와 조명의 거리를 계산하여 distance에 저장해준다.
    만약 distance가 조명의 range 범위 내에 존재한다면 빛연산을 시작해준다.
  • Final = 최종 결과물을 그려주는 부분으로, Vertex Shader 부분은 Directional Light 부분과 같다.
    Pixel Shader 에서는 우선 tex_1을 참고하여 lightpower를 받아주고 만약 빛이 존재하지 않는다면 skip,
    존재 한다면 color와 specular를 각각 tex_0, tex_2를 참고하여 받아온 뒤 output에 빛 연산을 해줌으로써 SV_Target에 출력해준다.

 

 

 

 

 


 

 

결과

 

 

  • 상단에 각 Position, Normal, Color (Diffuse), Diffuse Light, Specular Light 정보를 저장하고 출력하는 RenderTarget 5개를 생성하였다.
  • 좌측 3개의 렌더타겟에서는 각각 Position, Normal, Color (Diffuse)를 저장하고, 이 정보를 토대로 빛 연산을 하게 된 후에 Diffuse Light와 Specular Light 결과물을 우측 2개의 렌더타겟에 저장하였다고 보면 된다.
  • 마지막으로 모든 정보를 합쳐주면 평소와 같은 구체를 볼 수 있게 된다.

+ Recent posts