본문 바로가기

포트폴리오 /DirectX 11

[Eunbi's Farm - DirectX11] 디퍼드 렌더링

* 포워드 라이팅 : 

각 Mesh에 대하여 각 광원에 관한 것을 모두 그려야 한다. 

N개의 광원과 M개의 Mesh가 포함된 씬을 표현하기 위해서는 NxM번의 드로우 콜이 필요하다. 

이 호출수가 크기 때문에 FPS를 확보하면서 그릴 수 있는 Object 수에 한계가 있다.


> 해결책? 

GPU가 다중 렌더링 타겟(MRT)을 보편적으로 지원함으로써 씬 렌더링과 광원 렌더링을 구분하는 해결책이 제시되었다.

 이 경우 드로우 호출이 M+N번으로 감소한다.

 이러한 접근 방식을 GBuffer라 한다. GBuffer는  씬의 각 점에 대한 정보를 갖는 최종 렌더링되는 픽셀 수와 같은 크기의 렌더타겟 이미지의 집합이며 기본적으로 씬의 각 픽셀에 대한 Depth, Normal정보를 가진다. 




*디퍼드 라이팅 : 

깊이와 노멀정보에 대한 GBuffer를 통해 씬을 렌더링하고 렌더타겟에 저장 후 색을 결합해 최종적으로 표현될 색상을 계산

씬을 2번 렌더링해야하지만 포워드 라이팅보다는 훨씬 낫다.


*디퍼드 셰이딩은 디퍼드 라이팅과 비슷하지만 픽셀의 조명 및 셰이더 색상을 계산하기 위해 GBuffer가 저장해야 하는 정보량이 늘어난다. 그리고 씬을 한번만 렌더링 하면 된다.


*디퍼드방식의 단점?

: GBuffer가 씬의 각 픽셀에 대해 '단일세트데이터'를 저장하기 때문에 디퍼드방식은 알파값을 적용할 수 없다. 즉 완전 불투명한 Object에 대해서만 적용이 가능하다.

하지만 반투명 엘리먼트에 대해서도 디퍼드 방식으로 렌더링 한 후 반투명 엘리먼트에 대해서만 포워드 렌더링을 추가로 적용하는 방법을 사용해서 적용 가능하다. (불투명->반투명 순서로 렌더링해도 문제없이 작동됨)


[ In My Project ]

포워드 렌더링을 사용하는 경우 Depth값을 자주 바꾸지 않아야 효율적이기 때문에 z값이 가까운 것 부터 정렬을 하였다.

디퍼드를 사용하는 경우 알파엘리먼트의 출력을 위하여 오브젝트 정렬을 enum으로 [Alpha가 있는 오브젝트, UI, 일반 오브젝트]로 나눈 후 Alpha가 있는 엘리먼트와 UI는 먼 곳에 있는것부터 정렬하고, 일반 오브젝트는 가까운 곳부터 정렬하였다.

정렬 후 렌더링 순서는 [일반 엘리먼트, 알파가 있는 엘리먼트, UI]순으로 렌더링을 하면 알파가 있는 엘리먼트가 제대로 출력이 된다. 




* GBuffer

: GBuffer구조는 결정에 따라 깊이, 베이스색상, 노멀, 전반사를 저장하기 때문에 매우 중요하다. 

 정규화 Normal은 XYZ컴포넌트 전체 범위를 값으로 사용하지 않고 정규화 된 소수값(0.xxxx)을 사용하므로 노멀 오차가 많다. 

             GBuffer에 깊이와 베이스는 간단하게 저장할 수 있다. 

깊이나 스텐실은 D24S, 베이스색상은 A8R8G8B8렌더링 타겟을 통해 값을 8비트 픽셀텍스쳐로 샘플링해서 저장한다. 


* GBuffer에서 노말값을 저장하려면 기교가 필요하다.

1. 간단한 방법: 렌더링 타겟의 세 채널 값이 아니라 [-1.1]을 [0,1]로 정규화시켜서 저장하는 것이다. 이 방법을 사용하면 노멀데이터를 채널당 1바이트로 줄일 수 있고, 인코딩 디코딩에 드는 연산이 가장 적지만 정확도가 낮다. 

특히 전반사광을 계산할 때에는 '밴딩'이라는 비연속적인 색상변화가 나타난다. 


[ In My Project ]

>포트폴리오에서는 이 방법을 사용하여  *0.5 + 0.5 연산을 하였습니다. 


2. KillZone게임에서의 방법

: 각 노멀의 XY컴포넌트를 뷰 공간에 저장한다. Z컴포넌트는 나머지 두 컴포넌트를 통해 계산한다. 

장점은 채널당 메모리 크기가 크기 채문에 퀄리티가 좋다. 

단점은 월드공간에서 조명을 계산할 경우 디코딩 연산의 양이 크게 증가한다. 또한 노멀디코딩을 항상 먼저 수행해야 하므로 인코딩된 노멀에 알파블렌딩을 적용할 수 없다.


3. 크라이텍의 크라이시스2에서의 방법

: 포물면 텍스처를 샘플링할 때 3D벡터를 2DUV로 변환하는 방식을 수행한다. 

GBuffer를 패킹할 때는 노멀의 XY컴포넌트를 정규화시킨 후 Z컴포넌트를 곱해준다. 언패킹은 반대로 수행하면 된다.

이 방법을 사용하면 알파블렌딩도 적용할 수 있고, 퀄리티가 뛰어나며 월드공간에서 노멀 저장이 가능하다.

단점은 연산이 많다.



[ In My Project ]


Terrain.fx 


Pixel의 Output이 이제 GBuffer로 4개의 타겟을 만들어냅니다. 

Target0 : Color(Albedo)

Target1 : Normal (위에 사용한 [0,1]정규화)

Target2 : Depth

Target3 : Shininess (Specular)




[ 화면에 출력된 결과 ]


GBuffer(Albedo, Normal, Depth, Shininess]를 만들어 주고, 

조명 누적버퍼(LightAccDiffuse)를 만들고 ,

조명버퍼와 GBuffer를 블렌딩 해 줍니다.

최종 합성된 버퍼를 화면에 출력해 줍니다.

정렬한 Element를 그려줍니다.


마지막으로 렌더타겟을 왼쪽상단에 출력하여 제대로 연산이 되고 있는지를 확인합니다.