잡다

Order Independent Transparency

갈색바람 2020. 12. 3. 19:17

Transparent 한 물체, 즉 투명 물체를 렌더링하는 것은 일반적인 렌더 파이프라인과는 조금 다르다.

보통 transparency를 표현하기 위해서는 alpha blend 를 사용하게 되며, alpha blend 의 기본 원리는 현재 draw attachment 의 color 와 fragment 의 color 를 blend function 을 이용하여 더하는 것이다. 

만약 일반적인 렌더링과 같이 그린다고 가정하고, FBO clear -> draw transparent objects -> draw opaque objects 순으로 렌더링을 진행했다면, transparent obejcts 는 clear color 와 blending 될 것이며, 렌더링 결과물은 투명한 물체 뒤에 불투명한 물체가 그려진 것이 아닌, 투명한 물체뒤에 뻥 뚫린 clear color만 접하게 될 것이다.

파란 원이 translucent object이며, 노란색 사각형이 opaque object. 왼쪽 그림이 정상 렌더링 결과이며 draw order가 맞지 않으면 파란 원이 clear color인 회색과 blend 되어 제대로 렌더링 되지 않는다

따라서 transparent 렌더링은 일반적으로 opaque objects만 모아 모두 렌더링 한 후, transparent obejcts 만 그리는 식으로 진행되며, Unity나 UE4 등의 게임 엔진도 base pass 후에 translucency pass 를 진행하게 된다.

이러한 object space에서의 ordering 은 일시적인 해답이 될 수 있지만 모든 경우에서 해답은 될 수 없다. 만약 translucent object 가 서로 겹쳐있다면? 또는 translucent object가 복잡한 모양이라면? 이런 상황에서는 Primitive 의 가장 기본이 되는 triangle 이 겹쳐져 있는 경우가 발생할 수 있고, 위와 같은 방법으로는 정렬할 수 없다. 이러한 경우에는 나아가 fragment 단위의 ordering이 되어야 할 것이다.

Trangle primitives. 파란색 삼각형의 끝이 노란색 삼각형을 뚫고 나가는 등의 상황에서 object space의 ordering은 translucency를 제대로 표현할 수 없다,.

설명한 것과 같이 Object 를 모두 sort 할 수는 없으며 Object sorting 후에도 발생할 수 있는 문제점도 존재한다. 이를 해결하기 위해 정렬하지 않고도 translucency object를 그릴 수 있는 방법들(Order Independent Transparent, OIT)이 몇가지 소개되어 있다.

Interactive Order-Independent Transparency(api.semanticscholar.org/CorpusID:5813703) 는 가장 기초적인 방법으로, depth peeling을 이용한 OIT 방법을 소개한다.

간략하게 하자면 depth buffer를 이용하여 각 물체를 레이어 상태로 벗겨내고 (peeling), 결과물들을 blending 시켜서 투명한 렌더링 결과물을 얻는 것이다.

일반적인 depth test는 view frustum에서 가장 가까운 fragments만 보여줄 수 있다. 하지만 실질적으로 late-z 가 일어나기 전에는 두번 째, 세번 째 가까운 fragment 들이 존재할 것이다. 즉 일반적인 depth test로는 이러한 순차적인 fragment 를 보여줄 수는 없다. 

Depth peeling은 이를 해결하기 위한 기본적인 방법이며, n 번 반복하여 scene을 그린 뒤에는 n개의 순차적인 layer를 얻을 수 있다. Depth peeling은 multi-pass algorithm으로 이루어져있으며, 첫번 째 pass에서는 depth map과 함께 scene color를 그리고, 두번 째 pass는 첫번 째 pass에 그린 depth map을 이용하여 peeling된 depth를 생성하는 것이다.

for (i=0; i<num_passes; i++)
{
	clear color buffer
	A = i % 2
	B = (i+1) % 2
	depth unit 0:
		if(i == 0)
			disable depth test
		else
			enable depth test
		bind buffer A
		disable depth writes
		set depth func to GREATER
	depth unit 1:
 		bind buffer B
 		clear depth buffer
 		enalbe depth writes
		enalbe depth test
 		set depth func to LESS
 	render scene
 	save color buffer RGBA as layer i
}

위의 pseudo code는 framebuffer가 attachment 로 multi detph buffer가 가능하다는 가정으로 작성되어 있다. 

0 pass에서는 0번째 depth attachment (depth unit 0) 에 depth test가 disable 되어있고 write도 하지 않으므로 아무 작용을 하지 않는다. Depth unit 1의 경우는 clear, enable depth write and test인 상황이며 이후 scene 을 render하게 되므로 layer 0은 온전한 scene 이 그려지며 depth buffer B (depth buffer[1])에는 nearest fragment 들의 정보가 있을 것이다.

1 pass에서는 depth unit 0에 depth buffer[1]이 binding되고 clear되지 않고, depth test가 enable되고 depth test funciton 이 GREATER로 설정되어있다. Depth unit 1 은 depth buffer[0]이 binding 되며 clear 및 depth write, test 되고 test function은 LESS가 된다. 이 상태에서 scene을 그리게 되면, depth unit 0에 의해 가장 nearest fragment는 그려지지 않게 될 것이며 (depth unit 0에는 nearest fragment 의 depth 가 저장되어 있고 test function 은 GREATER임), depth unit 1에 의해 가려진 fragment 들은 그려지지 않게 되어 depth peel 된 이미지가 그려질 것이다.

위 과정이 원하는 pass마다 반복된다면 각 layer 별로 depth peeling 된 fragment space 에서 ordering 된 렌더링 결과를 볼 수 있다. 이 layer를 back to front로 blending 시킨다면 최종적으로 translucent object의 렌더링 결과를 얻을 수 있을 것이다.

실제 렌더링 되는 object 들의 위치를 top 에서 바라본 것
Pseudo code에 따라 depth unit 과 실제 렌더링 되는 layer 변화. 굵은 검은 선은 현재 pass에서 nearest depth, 회색 선은 depth peeling 된 fragments (depth unit 0 값과 GREATER function 에 의해 peeling됨), 그리고 점선은 가려져 아직 그려지지 않은 fragments (depth unit 1에서 LESS fucntion 에 의해 그려지지 않음)

 

FBO에 2개의 depth buffer를 달 수 없는 경우에는 내부적으로 pass를 한번 더 쪼개어 해결할 수 있을 것이다. 예로 

bind buffer[0]
clear depth buffer
enalbe depth writes
enalbe depth test
set depth func to LESS
render scene
save color buffer RGBA as layer 0

for (i=1; i<num_passes; i++)
{
	clear color buffer
	A = buffer[i % 2]
	B = buffer[(i+1) % 2]
	bind buffer A
	clear depth buffer
	enalbe depth writes
	enalbe depth test
	set depth func to LESS
	copy B to texture T
	set T as sampler in shader
	render scene
		if depth of current fragment < texture(T)
			discard
	save color buffer RGBA as layer i
}

위처럼 이전 pass의 depth 결과를 shader에서 texture sampling 한 후, 해당 값보다 작은 depth를 가진 fragments를 discard해준다면, 이전 pass의 nearest layer 보다 뒤에 위치한 fragments들을 렌더링할 수 있다.

이러한 depth peeling 기법의 단점은 N 개의 layer 를 만들고 blend 하는 과정에서 N번의 geometry를 렌더링해야 한다는 것에 있다. Geometry의 complexity 및 draw call 이 많다면 그 만큼 부하는 늘어날 것이다.

이러한 단점을 극복하기 위하여 Dual depth peeling 기법이 사용 될 수 있으며, 그 외에 Weighted blended OIT, per-pixel linked list 를 이용한 OIT 등이 쓰일 수 있을 것이다.

 

'잡다' 카테고리의 다른 글

Bitonic sort  (0) 2020.12.22
OIT : Weighted, Blended OIT  (0) 2020.12.09
Android Vulkan layer 적용  (0) 2020.11.18
Vulkan Layer  (0) 2020.11.14
Ogg vorbis  (1) 2015.09.04