개요
현재까지의 렌더링에서는 주변광을 고정된 상수로 사용했었다. 이러한 방식은 그림자에 가려진 부분들에 취약할 수 있다. 특정 광원에 대해 그림자가 든 부분은 해당 광원에 대한 분산광, 반영광 없이 주변광만으로 나타나게 되는데 이때 상수 주변광을 사용하면 입체감이 잘 느껴지지 않을 수 있다. 따라서 화면의 한점이 주변의 다른 기하구조에 얼마나 둘려싸여있는지에 따른 차폐도를 계산하고 이를 이용하여 주변광의 세기를 조절할 필요가 있다. 이러한 기법을 주변광 차폐라고 한다.
도달도
도달도는 1 - 차폐도에 해당하는 수치로 해당 점이 주변 기하구조에 얼마나 둘려싸여있지 않은지, 즉 주변광을 얼마나 많이 받는지를 나타내는 수치이다. 이러한 도달도는 기하구조 생성시점이나 프로그램 초기화 시점에 계산하여 기하구조의 각 정점에 기록하여 정적으로 사용할 수도 있고 매 프레임 픽셀별로 계산하여 동적으로 사용할 수 도있다.
정적 도달도
정적 도달도를 구현할 수 있는 대표적인 방법은 반직선 투사이다. 이는 기하구조의 한 삼각형이 무게중심에서 법선방향을 한 반구 방향으로 그 기하구조 자기자신의 다른 삼각형들에 의해 얼마나 둘러싸여있는지를 계산하여 구한다. 즉 그 기하구조 소속의 삼각형들에만 영향받는 도달도이다.
각 정점의 도달도를 계산하는 방법은 다음과 같다.
- 각 삼각형에 대하여 삼각형의 무게중심에서 출발하여 법선 방향과 90도 이하인 랜덤한 방향으로 가는 반직선 N개를 정의한다.
- 해당 반직선들이 해당 삼각형을 제외한 해당 기하구조의 다른 삼각형들중 하나라도 충돌하는지 체크한다.
- 충돌한 반직선 수 / 총 반직선 수 가 삼각형의 차폐도가 된다.
- 1 - 차폐도로 해당 삼각형의 도달도를 구하고 해당 삼각형을 이루는 각 정점에 누적시킨다.
- 누적된 정점의 누적 횟수도 1씩 늘려준다.
- 모든 삼각형에 대해 수행이 끝나면 각 정점의 누적횟수를 누적 도달도에 나눠준다.
해당 처리를 기하구조 생성 시점이나 프로그램 초기화 시점에 하여 기하구조의 각 정점에 저장한 후 정점 셰이더의 입력과 출력으로 넣어준다. 이후 픽셀 셰이더에서 각 픽셀의 색상을 계산할 때 사용하는 주변광의 크기에 보간된 도달도를 곱하여 계산해주면 주변광 차폐가 구현된다.
동적 도달도 (SSAO)
위의 반직선 투사 방식은 상대적으로 정확한 반직선 차폐가 가능하지만 성능이 많이 들어가 매 프레임 계산하기는 어렵다. 따라서 매 프레임 기하구조의 정점 위치가 변할 수 있는 애니메이션등에는 부적절하다. 이를 위해 덜 정확하지만 속도가 빠른 화면 공간 주변광 차폐 (SSAO) 기법으로 동적 도달도를 구한다.
이는 다음과 같은 렌더링 패스들을 사용한다.
- 법선, 깊이 버퍼 패스
- 주변광 차폐 패스
- 흐리기 패스
- 카메라 패스
패스들의 세부 구현은 다음과 같다.
법선, 깊이 버퍼 패스
주변광 차폐 패스에서 사용할 법선, 깊이 버퍼를 생성하는 패스이다.
- 해당 프레임에 렌더링할 장면을 렌더링 하되 rtv에는 해당 픽셀점의 법선벡터의 시야공간 기준 좌표를 넣고 깊이 버퍼는 정성적으로 렌더링한다.
주변광 차폐 패스
렌더링 될 화면의 각 픽셀에 대한 도달도가 적힌 도달도 버퍼를 생성하는 패스이다.
이때 출력 텍스처의 해상도는 원래 렌더링 텍스처의 가로 세로 각각 1/2이다. (성능 목적)
- 법선 깊이 버퍼 패스의 rtv와 깊이버퍼를 srv로 넣어 실행한다.
- 화면을 가득 채우는 시야공간 가까원 평면에 해당하는 사각형을 렌더링한다.
- 정점 셰이더에서 각 정점의 동차절단공간, 시야공간, 텍스처좌표를 넘겨준다.
- 화면의 각 픽셀에 대해 픽셀 셰이더가 호출된다.(1/2 해상도)
- 픽셀 셰이더에서 법선, 깊이 버퍼의 해당 픽셀 위치를 추출한다.
- 깊이 값과 시야공간 좌표를 이용하여 시야공간 상 그 픽셀의 투영됬던 점의 위치(p)를 구한다.
- 해당 점의 법선과 각도가 90도 이하이며 차폐 반지름(디자이너가 조절가능한 수치 값)보다 짧은 벡터만큼 움직인 주변 점을 n개 추출한다.(고르게 추출하는것이 좋기에 이런저런 기법으로 고른 랜덤 벡터를 얻으면 좋다.)
- 해당 랜덤 점들의 카메라 투영 텍스처 좌표를 구하고 깊이 값을 추출하여 해당 점으로 가는 시선에 렌더링된 픽셀의 시야공간 좌표(r)를 계산한다.
- p와 r사이의 시야공간 깊이 차이, p->r 벡터와 p의 법선사이의 거리에 기초하여 다음과 같이 차폐값을 구한다.
디자이너가 조절 가능한 수치값들 => eps, start, end
차폐값 =
p.z - r.z가 eps 이하면 0 (r의 깊이가 p보다 eps보다 카메라에 가까워야 차폐값이 생긴다.)
아니면 saturate( end - (p.z - r.z)/(end - start)) * max(dot(p.nomal, nomalize(r-p)), 0)
10. 각 랜덤 점의 차폐값을 평균하고 1-차폐값으로 도달도를 구한다.
11. 도달도에 적절한 지수를 거듭제곱하여 차폐정도를 적절히 조절한다.
12. 해당 도달도를 출력하여 텍스처를 완성한다.
해당 방식은 근사적이고 잘못된 결과를 내는 상황도 있지만 성능이 좋기에 나쁘지않은 도달도 알고리즘이다.
흐리기 패스
주변광 차폐 패스에서 만든 도달도 버퍼를 흐리는 패스이다.
이전 계산 셰이더 흐리기와 다른점은 계산, 렌더링 모드의 전환을 피하기위해 정점,픽셀 셰이더의 정상적인 렌더링 파이프라인으로 수행하는점과 기하구조의 테두리로 추정되는 부분은 흐리기를 하지않는 테두리 보존 흐리기 기법을 사용한다.
- 주변광 차폐 패스의 rtv를 src로 넣고 실행한다.
- 정점 셰이더에서는 화면을 꽉채우는 사각형을 받아 동차절단공간과 텍스처 좌표를 넘겨준다.
- 각 픽셀에 대하여 픽셀 셰이더가 호출된다.
- 픽셀 셰이더에서는 입력 srv의 각 픽셀에 수평, 수직 흐리기를 두번의 렌더링 파이프라인에 걸쳐 수행한다.
- 이때 흐리려는 픽셀의 주변 반지름*2 개수의 픽셀들을 추출하여 흐리기를 진행하되 주변 픽셀중 흐리려는 픽셀과 깊이 ,법선이 일정수준이상으로 차이나는 픽셀은 다른 기하구조에 있는 픽셀로 간주하여 흐리기에 포함시키지 않는다.
- 흐려진 픽셀을 출력하여 흐려진 텍스처를 완성한다.
카메라 렌더링 패스
실제 화면 렌더링이다.
- 흐리기 패스의 rtv를 srv로 받아 실행한다.
- 정상적인 렌더링을 수행하되 정점 셰이더에서 해당 정점의 카메라 기준 투영 텍스처 좌표를 넣어 출력한다.
- 픽셀 셰이더에서 해당 픽셀의 색상을 계산할때 주변광의 기본 세기에 흐리기 패스의 도달도 버퍼에서 해당 픽셀의 카메라 기준 투영 텍스처 좌표에서 추출한 도달도를 곱하여 계산한다.
- 최종 색상을 출력하여 화면 텍스처를 완성한다.
이때 법선, 깊이 패스의 출력 깊이 버퍼를 그대로 카메라 렌더링 패스의 초기 깊이버퍼로 설정하여 렌더링하고 깊이 판정 성공 여부를 =로 설정한다. 그리고 깊이 쓰기를 비활성화해도 정상적으로 렌더링 된다. 이렇게 하면 실제 그려질 가장 가까운 픽셀만 출력 텍스처에 그려지게 되고 깊이 쓰기도 하지않아 효율이 더 좋다.
참고서적
DirectX 12를 이용한 3D 게임 프로그래밍 입문
'그래픽스 > DirectX12' 카테고리의 다른 글
[Directx12][23장]캐릭터 애니메이션 (0) | 2023.03.21 |
---|---|
[Directx12][22장]사원수 (0) | 2023.03.21 |
[Directx12][20장]그림자 매핑 (0) | 2023.03.21 |
[Directx12][19장]법선 매핑 (0) | 2023.03.21 |
[Directx12][18장]입방체 매핑 (0) | 2023.03.21 |