현재 SW Occlusion Culling + HW Occlusion Culling을 사용중이다.
일단 복셀지형을 놓고 볼때 어지간하면 오브젝트 개수가 5만에서 8만개씩 나오기 때문에 S/W Occlusion Culling을 먼저 걸어주지 않으면 H/W Occlusion Culling에서도 꽤 큰 손해를 본다. draw call이 엄청 증가하기 때문이다.
그런데 S/W Occlusion Culling은 또 CPU 처리에서 상당한 지연을 일으킨다. 그래서 이걸 async로 처리하고 있긴 한데 그 때문에 프레임간의 카메라 위치/방향 차이로 화면 모서리등에서 오브젝트가 누락되는 등 부정확한 화면이 렌더링될 수 있다.
그래서 생각을 좀 해봤다.
복셀 오브젝트는 모양이 결정되고나면 외형을 표현하는 삼각형들(사실상 충돌처리 삼각형)의 개수가 상대적으로 적다. 더 정확히는 복셀 오브젝트의 모양이 결정됐을때 그 모양을 이루는 삼각형들을 최적화할 수 있다.
실제로 내 엔진에서는 복셀의 모양이 결정될때마다 이 최적화를 수행한다. 그래서 상대적으로 삼각형이 적다.
그리고 각 삼각형이 차지하는 월드 공간상에서의 면적은 크다. 또한 복셀 오브젝트들끼리 서로를 가리는 일이 많다.
라고 하는 것은 픽셀 단위의 S/W Occlusion Culling에는 적합하지 않다는 얘기가 된다. 이런 경우 S/W Occlusion Culling에서는 depth buffer의 테스트와 쓰기가 너무 많이 일어난다.
복셀 오브젝트를 구성하는 모든 픽셀들을 테스트 하기보단 복셀 오브젝트를 구성하는 도형 단위로 검사하면 어떨까?
Raytracing으로 렌더링 하듯 화면 기준에서 픽셀별로 ray를 쏴서 충돌하는 오브젝트만 찾아내면 depth buffer검사로 낭비되는 시간을 아예 없앨 수 있다.
속도만 충분하다면 async로 처리할것도 없이 매 프레임마다 정확하게 보이는 오브젝트를 골라낼 수 있을것이다.
그래서 다음과 같이 계획을 세우고 구현중이다.
- 메모리에 적당한 사이즈의 화면 버퍼를 만든다(ex: 512×512)
- view matrix, projection matrix를 이용해서 각 픽셀로부터 월드공간의 ray를 만든다.
- 각 픽셀에서 출발한 ray로 복셀 오브젝트들로 구성된 KD-Tree를 순회하며 ray가 충돌하는 가장 가까운 오브젝트를 찾는다.
- 오브젝트를 찾으면 화면 버퍼에 오브젝트의 ID를 기록한다.(테스트 시에는 depth값-ray가 충돌했을때의 t값)
- 버퍼에 기록된 오브젝트 ID로 렌더링될 복셀 오브젝트들을 수집한다.
4번까지는 구현을 완료했다.
당연히 속도는 상당히 느릴것으로 예상했다. CPU로 가능할것이라고는 생각을 안했고 최종적으로는 CUDA에서의 작동을 전제로 한다. CUDA 작업 전에 레퍼런스 코드로 CPU코드부터 작성했다.
CUDA를 사용하는 시점에서 S/W Raytracing이라고 하기엔 좀 무리가 있긴 하지만 그래픽 API를 사용하지 않았고 CPU의 C코드를 거의 그대로 이용한다는 점에서 S/W Raytracing이라고 주장하고 싶다.
DXR버전이 아닌 DX11/12렌더러에서는 복셀 지형에 대해 라이트맵을 사용하고 있고 라이트맵 계산을 위해서 이미 CUDA상에서 복셀지형의 KD-Tree를 구축해놓고 있었으므로 CUDA로 포팅하는 작업은 어렵지 않았다.
CPU-Single Thread , CPU-Multi Threads, CUDA 이렇게 3가지 모드의 코드를 작성해서 테스트 했다.
[테스트 맵]
복셀 오브젝트 개수: 54056개
복셀 개수(50cm x 50cm x 50cm환산 : 7140968개
[테스트 장비]
CPU: i-7 8700k
GPU: GTX1660Ti
[결과]
CPU- Single Thread : 2426.76 ms (2.4초)
CPU-Multi Threads : 363.62 ms (0.36초)
GPU-CUDA : 9.68 ms (0.009초)-> 초당 60프레임 이상 호출 가능!
일단은 결과가 희망적이다. 9.68ms면 16ms미만이므로 초당 60회 이상 호출 가능하다. 즉 60프레임이 보장된다.
이렇게 저렇게 잘 튜닝하고 코드를 최적화하면 5ms 이하까지 가능하지 않을까..라고 조심스럽게 희망회로를 돌려본다.
이거 되면 S/W Occlusion Culling이고 H/W Occlusion Culling이고 다 필요없다. 물론 CUDA돌아가는 GPU에서만.
같은 방식으로 DXR API를 이용하는 것도 가능하다.
엔진에서 복셀지형의 트리구조를 CUDA와 DXR의 Acceleration Structure양쪽에 유지하고 있다. 따라서 DXR을 이용해도 깔끔하게 culling을 할 수 있다.아마 그쪽이 더 빠를것 같다. MS와 nvidia의 인간들이 GPU로 공간 순회하는 구조는 나보다 100만배쯤 더 잘만들었겠지.
근데 왜 DXR로 안하냐면
- GTX 900시리즈 같은 DXR 지원 안하는 GPU에서도 성능 향상을 보고 싶다.
- 렌더러 DLL없이도 이 기능을 사용하고 싶다. 예를 들면 서버라든가.
이번주 내내 CUDA코드 최적화에 매달린다. 당분간 심심치는 않겠네.

“S/W Raytracing을 이용한 복셀 오브젝트 Culling”에 대한 답글 1개