먼저 복셀화의 단계는 다음과 같다.
정점을 입력하고 VertexShader에서 각 정점을 세계공간으로 옮기고 Geometry Shader에 넘겨준다.
Geometry Shader은 받은 정점을 복셀 공간으로 옮긴다음 XYZ축에 투영을 하였을 경우 가장큰 넓이를 가지는 축에 프리미티브를 투영한다.
픽셀 쉐이더에서 레스터화된 복셀들에 색과 같은 정보를 기록하여 버퍼에 저장한다.
그런 다음 버퍼에 기록된 데이터를 3D 텍스처에 옮겨 기록한다.

그럼 여기서 기하 세이더에서 왜 복셀화를 하기전에 XYZ 축중에 투영 시킬 경우 제일 큰 넓이를 가지는 축을 찾고 그 축에 투영을 하는지에대해 확인할 필요가 있다.
우선 투영해야할 축을 찾는 방법은 다음과 같다. 들어온 세개의 정점의 세계공간의 노말 값을 전부 더한 뒤에 절대값이 제일 큰 값을 찾으면 해당 축에 제일 수직에 가까운 축이기 때문에 투영하였을 경우 넓이가 제일 큰 축을 찾을 수 있다.
가장 면적이 큰 축에 투영을 해야하는 이유는 다음과 같다.
일단 위의 면적이 제일 적은 면을 사용하게 된다면


다음과 같이 많은 부분을 복셀화를 할 수가 없다. 왜냐하면 실제로 복셀 데이터를 저장하는 픽셀 쉐이더 단계에서는 뷰포트에서 다루는 각 픽셀에 대해 연산이 되기때문이다. 래스터화 단계에서는 깊이 값을 래스터화 하지 않기 때문이다. 픽셀 쉐이더에서 연산은 한 픽셀마다 연산이 되기 때문에 확보되는 픽셀의 숫자가 적으면 적을 수록 기록될 복셀의 갯수가 줄어든다.

우리는 적절한 투영방향을 이용해 최대한 많은 복셀을 생성했다 그러나 투영방향을 x,y,z 축에 따로따로 하게됨으로서 다음과 같은 새로운 문제가 발생 한다.
만약 다음과 그림 5와 같은 매쉬를 복셀화하고 x와 z축에 투영을 하게되는 매쉬라고 가정을 해보자
그러면 빨간색 평면의 경우는 왼쪽으로 파란색 평면의 경우는 아래쪽으로 투영이 될 것이다.
우리는 픽셀 쉐이더의 경우 픽셀의 중심을 덮을 때 호출 된다는 것을 알아야한다.

그렇기때문에 그림 6과 같이 파란색 음영을 진 픽셀만 픽셀 쉐이더에서 연산이 될 것이다.
그럼 빨간색 평면의 경우는 왼쪽에 투영이 될 경우 픽셀의 중심을 지나지 않기 때문에 래스터화를 거친후 손실이 나게된다.

이와 같은 현상은 투영되는 방향이 계속해서 바뀌기 때문에 생기는 문제이다.
만약 모두 아래혹은 왼쪽으로 투영될 경우에는 구멍이 생기지 않는다.
이를 해결 하기 위해서는 평면이 픽셀에 닿을 경우 모두 래스터화을 해야한다.
이를 보수적 래스터화라고 한다.
보수적 래스터를 사용하기 위해선 NVAPI를 사용하여 ID3D11RasterizeState를 만들 수있다.
NvAPI_D3D11_RASTERIZER_DESC_EX NVAPI_RS_DESC;
NVAPI_RS_DESC.ConservativeRasterEnable = TRUE; // Enable conservative raster
NvAPI_D3D11_CreateRasterizerState(pD3D11Device,(const NvAPI_D3D11_RASTERIZER_DESC_EX*)&NVAPI_RS_DESC,&m_pConservativeRaster);
혹은 DX11.3혹은 12를 사용하면 보스적 래스터화를 할 수 있다.

다음과 같이 보수적으로 래스터화를 하게 되면 그림 8의 노란색 픽셀에 추가적인 픽셀 세이더 호출을 하게된다. 노란색 픽셀의 경우에는 복셀화된 데이터를 저장 할 필요가 없는 픽셀이기에 컬링을 할 필요가 있다.

GS에서 AABB를 계산한 다음 PS에서 복셀 프리미티브 AABB 테스트를 수행한다. 복셀의 BoundBox가 프리미티비의 AABB 테스트에 통과하지 못 할 경우 컬링처리를 한다.
**GS**
void getAABB(in float3 v1, in float3 v2, in float3 v3, out float3 primMin, out float3 primMax)
{
primMin = min(min(v1.x, v2.x), v3.x);
primMax = max(max(v1.x, v2.x), v3.x);
}
**PS**
float3 voxelPosf = float3(SV_Position.xy, SV_Position.z * voxelResolution_Z);float3 voxelMin = voxelPosf – 0.5;
float3 voxelMax = voxelPosf + 0.5;
if ((primMax.x – voxelMin.x) * (primMinX.x – voxelMax.x) >= 0.0 || (primMax.y – voxelMin.y) * (primMinX.y – voxelMax.y) >= 0.0 || (primMax.z – voxelMin.z) * (primMinX.z – voxelMax.z) >= 0.0)
discard;
Edge-Voxel Test

3개의 축 정렬 평면 각각에서 수행이된다. 복셀의 꼭지점까지의 부호있는 거리를 계산하고, 모든 복셀의 꼭지점에서 최대 부호 있는 거리 값이 테스트에서 사용이 된다.
세계 공간에 있는 프리미티브를 복셀 공간으로 옮기는 작업

일단 Geometry shader에서 정점 데이터를 넘겨준다. 정점을 복셀 공간으로 변환하고 제일 큰 축을 찾아 그 축에 스위즐링한다.
현재 사용하는 버전은 Directx 11 버전으로 보수적 확장은 하지 않았다.

Pixel Shader에서 래스터화된 복셀들을 1차원으로된 버퍼에 저장해준다.

그리고 3D 텍스처에 1D으로 저장된 복셀들의 정보를 컴퓨터 셰이더를 통해 복사해준다.

아래는 복셀들이 제대로 담겼는지 확인해보려고 복셀을 기하셰이더를 통해 큐브로 확장하여 그린 화면인다.



'DirectX11' 카테고리의 다른 글
Texture Compression(Block Compression) (0) | 2023.06.27 |
---|---|
GPU Instancing (0) | 2023.06.22 |
Voxel GI - Cone Tracing (0) | 2023.05.25 |
Cascade Shadow (0) | 2023.02.16 |
Shadow Mapping (0) | 2022.09.27 |