언리얼 렌더링 시스템 분석(17) - 실시간 레이 트레이싱
🌐 원문 링크: 剖析虚幻渲染体系(17)- 实时光线追踪 (cnblogs.com/timlly)
📅 원문 발행일: 2022-09-12 | ✍️ 저자: Timlly (00 / )💡 시리즈 분류:
Unreal Engine Rendering| 국내 업계 표준 용어 감수 및 수식/도해 복원 적용 완료
UE의 레이 트레이싱은 항상 아동용 신발에서 높은 요청을 받은 기사였습니다. 블로거들이 수년 전부터 레이 트레이싱 기술과 UE4 구현을 탐구해 왔지만, 내용은 상대적으로 기본적이고 일방적입니다. 따라서 이 글에서는 UE의 실시간 Ray Tracing에 대한 보다 체계적이고 포괄적이며 심층적인 분석을 제공할 것입니다. 이 문서에서는 UE의 다음 내용을 주로 설명합니다.
레이 트레이싱의 기본 개념과 기술.
레이 트레이싱 구현.
레이 트레이싱 최적화 및 노이즈 감소 기술.
레이 트레이싱과 관련된 그래픽 API 및 GPU 구조.
레이 트레이싱의 UE 구현.
기존의 스캔 라인이나 래스터 렌더링 방법과 달리 레이 트레이싱은 광원 대신 카메라에서 방출되는 광선을 추적하는 3차원 컴퓨터 그래픽의 특수 렌더링 알고리즘입니다. 이러한 기술을 통해 안무 장면의 수학적 모델이 생성되어 표시됩니다.
img
레이 트레이싱 기술을 사용하여 렌더링된 사실적인 이미지.
이 방법은 기존 주사선 기술과 비교하여 반사 및 굴절에 대한 보다 정확한 시뮬레이션 효과 등 광학 효과가 더 우수하고 효율성이 매우 높기 때문에 고품질 효과를 추구할 때 자주 사용됩니다.
물리학에서는 레이 트레이싱을 사용하여 매체를 통한 광선의 전파를 계산할 수 있습니다. 매질 내에서 전파될 때 광선은 매질에 의해 흡수되거나, 전파 방향을 바꾸거나, 매질 표면에서 방출될 수 있습니다. 우리는 매체를 통과하는 이상적인 좁은 빔(빛)에 대한 상황을 계산하여 이 복잡한 상황을 해결합니다.
실제 응용에서는 다양한 전자기파나 작은 입자가 이상적인 좁은 광선(즉, 빛)으로 간주될 수 있습니다. 이 가정을 바탕으로 사람들은 레이 트레이싱을 사용하여 매체 내 빛의 전파를 계산합니다. 레이 트레이싱 방법은 먼저 광선이 매체에 흡수되거나 방향이 변경되기 전에 광선이 이동하는 거리, 방향 및 새로운 위치를 계산합니다. 그런 다음 이 새로운 위치에서 새로운 광선을 생성합니다. 동일한 처리 방법을 사용하여 최종적으로 매질에서 전파되는 빛의 전체 경로를 계산합니다.
17.1.2 레이 트레이싱 및 래스터라이제이션
래스터 파이프라인(래스터 파이프라인)은 전통적인 렌더링 파이프라인 프로세스입니다. 삼각형을 단위로 사용하여 삼각형을 픽셀로 변환하는 과정입니다(아래 그림 왼쪽). 이는 현재 이미지 API 및 그래픽 카드 하드웨어에 대한 광범위한 지원 및 적용을 제공합니다.
Ray Tracing Pipeline(Ray Tracing Pipeline)은 광선을 단위로 사용하여 광선과 물체의 교차점과 교차점 이후의 계산 과정을 설명합니다(아래 오른쪽 그림). 래스터라이제이션된 선형 파이프라인과 달리 레이 트레이싱 파이프라인은 재귀 호출을 통해 다른 광선을 파생하고 다른 파이프라인 인스턴스를 실행할 수 있습니다.

보다 자세한 비교표는 다음과 같습니다.
주요 개념 래스터라이제이션 레이 트레이싱
기본 질문 기하학은 어떤 픽셀을 포함합니까? 빛으로 볼 수 있는 물체는 무엇입니까?
주요 작업 픽셀이 삼각형 내부에 있는지 테스트합니다. 광선-삼각형 교차 테스트
스트리밍 작동 방식 스트리밍 삼각형(테스트 픽셀당) 스트리밍 광선(각 테스트 교차점)
비효율성 픽셀당 여러 삼각형 음영 처리(오버드로) 각 광선은 여러 삼각형과의 교차점을 테스트합니다.
가속 구조 (수준) Z-버퍼링 경계 상자 계층 구조(BVH)
단점 일관성이 없는 쿼리는 구현하기 어렵습니다. 메모리 탐색이 매우 일관성이 없습니다.
17.1.3 레이 트레이싱의 간략한 역사
레이 트레이싱 렌더링 기술은 빛 단순화, 레이 캐스팅 알고리즘, 자연계의 레이 트레이싱 알고리즘을 거쳐 단계적으로 발전해 왔습니다.
- 레이 캐스팅 알고리즘(1968)
Arthur Appel이 제안한 렌더링용 레이 캐스팅 알고리즘. 레이 캐스팅의 기본은 눈에서 나온 빛을 물체의 각 지점에 투사하고 빛을 차단하는 가장 가까운 물체를 찾는 것, 즉 이미지를 화면으로 취급하고 각 지점은 화면의 사각형입니다.
이 알고리즘은 재료의 특성과 장면의 조명 효과를 기반으로 물체의 음영을 결정할 수 있습니다. 한 가지 간단한 가정은 표면이 빛을 향하면 표면이 빛나고 그림자가 생기지 않는다는 것입니다.
스캔라인 렌더링에 비해 레이캐스팅의 중요한 장점은 비평면 표면은 물론 원뿔 및 구와 같은 솔리드 엔터티를 쉽게 처리할 수 있다는 것입니다. 수학적 표면이 광선과 교차하는 경우 레이 캐스팅을 사용하여 렌더링할 수 있습니다. 복잡한 객체는 솔리드 모델링 기술을 사용하여 구성하고 쉽게 렌더링할 수 있습니다.
- 클래식 레이 트레이싱 알고리즘(1980)
첫 번째 획기적인 시도는 1979년 Turner Whitted에 의해 이루어졌습니다. 이전 알고리즘은 눈에서 장면으로 광선을 투사했지만 이러한 광선을 추적하지 않았습니다. 레이 트레이싱 알고리즘은 이러한 광선을 추적하고 광선이 물체 표면과 교차할 때마다 모든 광원의 기여도를 계산합니다.

- Cook의 확률론적(분산) 레이 트레이싱(1984)
그림자 광선이 영역 조명의 임의 지점에 도달하도록 허용하고, 반사 광선이 이상적인 반사 주위에서 스페큘러 반사적으로 교란되도록 허용하고, 프레임의 특정 시간에 모션 블러를 캡처합니다.

- 가지야 스타일 디퓨즈(1986)
경로 추적: 각 광선을 가져와 일련의 상호 반사를 따라 추적하고 한계 내에서 정답을 보장하기 위해 렌더링 방정식을 제안합니다.

- Ray Tracing API 및 하드웨어 통합(2018)
초기에 NV는 Microsoft와 협력하여 차세대 하드웨어 기반 레이 트레이싱 렌더링 API 및 하드웨어를 공동으로 만들었습니다. 2018년에는 RTX(Ray Tracing X) 표준을 공동 발표했습니다. Direct X 12는 RTX를 지원하고, NV의 RTX 시리즈 그래픽 카드는 RTX 기술을 지원하여 실시간 레이 트레이싱의 등장을 알립니다.
img
NV RTX 시연 영상 스크린샷.
- UE4 통합 레이 트레이싱(2019)
UE는 2019년 4월에 버전 4.22를 출시했습니다. 이 버전의 가장 눈부신 새로운 기능은 의심할 여지 없이 레이 트레이싱 기술에 대한 지원입니다. 이는 UE를 사용하는 개인이나 팀이 사진처럼 사실적인 이미지를 보다 효과적으로 렌더링하는 데 도움이 될 것입니다.
img
UE의 레이 트레이싱 기술을 사용하여 사실적인 이미지를 렌더링합니다.
- UE5 Lumen 통합 하드웨어 레이 트레이싱(2021)
UE5의 핵심 기술 중 하나는 실시간으로 신뢰할 수 있는 전역 조명 효과를 구현하는 루멘(Lumen)입니다. 소프트웨어 레이 트레이싱 및 하드웨어 레이 트레이싱 모드를 지원합니다.

UE의 SSR(왼쪽)과 빛 추적 반사(오른쪽) 비교 차트.

UE5 원거리 필드의 대규모 GI 효과. 하드웨어 레이 트레이싱 모드를 지원합니다.
17.2 레이 트레이싱 기본
17.2.1 수학의 기초
원점
이미 삼각형을 형성할 수 있는 3개의 꼭지점
광선과 삼각형의 교차점은 광선을 따라야 하고 삼각형의 평면에 있어야 하므로
위의 공식은 특수한 경우, 즉
임의의 거리
해당 그림은 다음과 같습니다.

사변형(쌍선형 패치)의 경우 중심 좌표는 더 복잡합니다.
해당 범례:

위는 4개의 정점이 동일한 평면에 위치하는 경우에 대한 것이지만, 실제로는 동일한 평면에 있지 않아 2개의 평면이 될 수도 있습니다.

두 개의 삼각형으로 근사화된 사각형입니다.
경계 상자는 복잡한 장면의 레이 트레이싱을 가속화하는 데 매우 유용합니다. 일반 상자는 정점과 세 개의 벡터로 정의됩니다(아래 그림). 직접 교차 테스트는 6개의 면이 각각 교차하는지 테스트합니다. 광선 원점을 향하는 세 면만 테스트하면 테스트 속도가 빨라집니다.

(a) 일반적인 방향의 상자; (b) 축 정렬 상자.
축 정렬 상자를 사용하면 보다 효율적인 교차 테스트가 가능합니다. 축 정렬 상자는 xy, xz 및 yz 평면 각각에 있는 두 개의 직사각형으로 구성됩니다. 축 정렬 상자는 위의 (b)에 표시된 것처럼 최소 및 최대 버텍스 pmin 및 pmax로 정의됩니다. 우리는 상자를 세 개의 무한한 공간 석판의 교차점으로 생각할 수 있습니다. Smits는 IEEE 부동 소수점 규칙을 활용하여 0으로 나누기를 우아하고 효율적으로 처리하여 코드를 단순화하는 매우 효율적인 광선 교차 테스트를 설명합니다.
상자가 경계 상자로 사용되는 경우 가장 가까운 교차점과 법선을 알 필요가 없으며 광선이 상자와 교차하는지만 알면 됩니다.
이차방정식은 원반, 구, 원통, 원뿔, 타원체, 포물면 및 쌍곡면으로 구성됩니다.
디스크는 중심 c, 노멀 n 및 반경 r로 정의됩니다. 광선-디스크 교차점을 찾는 것은 광선-삼각형 교차점 테스트와 매우 유사합니다. 먼저 광선-평면 교차점 p를 계산하고 거리 t가 양수이고 이전 가장 가까운 교차점보다 작은지 확인합니다.
구는 중심 c와 반경 r로 정의됩니다. 교차점이 존재하는 경우 이는 광선을 따라 어딘가에 있어야 하며 구의 표면에 있어야 합니다. 교차점을 찾기 위해 광선 방정식을 구 방정식
가 음수이면 (실제) 솔루션이 없으며 광선이 구에 닿지 않습니다. 가 0이면 광선은 구에 접하고 교차점은 하나만 있습니다. 가 양수이면 두 개의 교차점이 있고 가장 가까운 교차점은 음이 아닌 가장 작은 값 t를 갖는 교차점입니다.
교차 거리
이 기사에서는 분석하지 않을 다른 형태의 2차 곡면이 있습니다. 관심 있는 학생들은 스스로 정보를 찾아볼 수 있습니다.
암시적 표면(Implicit Surface)은 함수
예를 들어 Newton-Raphson 반복 또는 기타 반복 방법을 사용하여 수행할 수 있으며 Sherstyuk은 효율적인 알고리즘을 설명합니다. 교차점의 표면 노멀은 해당 지점에서 함수의 기울기로 제공됩니다.
NURBS 표면, 세분화 표면, 변위 표면, 상자 등도 있지만 이 문서에서는 자세히 설명하지 않습니다.
광선 차등은 빛의 기본 속성이지만 레이 트레이싱에서는 상대적으로 새로운 것이며 텍스처 필터링 및 테셀레이션을 포함한 많은 응용 프로그램에 유용합니다. 광선 차이는 광선과 실제 또는 가상 "이웃" 광선 간의 차이를 설명합니다. 아래 그림에 표시된 것처럼 차이는 각 광선이 나타내는 빔의 크기를 나타냅니다.

조명과 광선.
Igehy의 광선 차별화 방법은 전파, 스페큘러 반사 및 굴절 중에 광선 차별화를 추적합니다. 표면 교차점의 곡률은 스페큘러 반사 및 굴절 후 빛의 차이와 관련 광선의 변화를 결정합니다. 예를 들어, 광선이 고도로 구부러진 볼록 표면에 부딪히면 스페큘러 반사된 광선은 큰 차이를 갖게 됩니다(매우 발산하는 인접 광선을 나타냄).
아래 이미지는 레이 트레이싱된 스페큘러 반사를 보여줍니다. 왼쪽 이미지에서는 광선 차별화가 계산되지 않고 텍스처 필터 너비가 0이므로 앨리어싱 아티팩트가 생성됩니다. 오른쪽 이미지에서는 광선 차별화를 사용하여 적절한 텍스처 필터 크기를 결정합니다. 차이점을 명확하게 보여주기 위해 이미지는 해상도가 매우 낮으며(200×200픽셀), 픽셀당 하나의 반사 광선만 촬영되고 픽셀 필터링이 꺼집니다.

반사: (a) 광선 구별 없음, (b) 있음.
Suykens와 Willems는 빛의 차별화를 광택 및 디퓨즈로 일반화했습니다. 확산 또는 앰비언트 오클루전(AO)을 사용한 분산 레이 트레이싱의 경우 광선 차이는 반구의 일부에 해당합니다. 동일한 지점에서 더 많은 광선을 추적할수록 반대쪽 반구 부분은 더 작아집니다. 반구 비율이 매우 작으면 곡률에 따른 파생물(예: 스페큘러 반사)이 지배적입니다.
17.2.2 부동 소수점 숫자
부동 소수점 수, 고정 소수점 수(예: 정수), **유리수(동종 표현)**를 포함하여 부동 소수점 수의 실수를 근사화해야 합니다. IEEE-754 단정밀도 데이터 레이아웃은 1비트 부호, 8비트 지수(오프셋), 23비트 분수(숨겨진 비트가 있는 24비트 가수)이며 다이어그램은 다음과 같습니다.

그것이 나타내는 수치 공식은 다음과 같습니다.
이는 표준화된 형식입니다. IEEE-754가 나타낼 수 있는 숫자는 다음과 같습니다.
일련번호 지수 분수 로그인 가치
1
2
3
4
5
6
7
위의 표는 다음 사항으로 보완됩니다.
일련 번호 1과 4의 차이점에 유의하십시오. 일련 번호 1은 일반 부동 소수점 값을 표현하는 반면, 일련 번호 4는 두 가지 특수 값을 표현합니다.
일 때 값은 입니다. 일 때 값은 입니다. 일련번호 2와 3의 값은 동일하지만 부호가 다릅니다.
일련번호 5, 6, 7로 표시되는 값은 각각 양의 무한대, 음의 무한대, 불법값(null 값)입니다. 예를 들면:
이면 입니다. .
NaN != NaN은 참입니다!
NaN과 관련된 다른 모든 비교는 거짓입니다!
다음 두 표현식은 동등하지 않습니다:
if (a > b) X(); else Y();
if (a <= b) Y(); else X();그러나 IA는 0으로 나누기 작업을 테스트할 필요가 없도록 하고 내부 루프에서 테스트 분기를 제거할 수 있는 좋은 기능도 제공합니다. 이는 SIMD 코드에 유용합니다. (동일한 접근 방식은 일반적으로 IEEE가 아닌 CPU에서도 작동합니다.)
IEEE-754의 특별한 표현은 불규칙한 숫자선으로 이어집니다. 즉, 0에서 멀어질수록 간격이 커집니다. 인덱스 k + 1을 갖는 숫자 범위의 간격은 인덱스 k의 두 배이며, 한 인덱스에서 다른 인덱스로 이동하는 것은 표현 가능한 여러 숫자와 동일합니다. (아래 사진)

불규칙한 간격의 결과:
따라서 비결합 법칙

부동소수점 연산에서는 간격이 불규칙하여 위치에 따라 동작이 달라집니다!

예를 들어 Sutherland-Hodgman 클리핑 알고리즘(다각형 분할 알고리즘 중 하나)은 다음과 같습니다.

부동 소수점 오류 입력 중:

ABCD 평면을 기준으로 분할:

물론 이 문제는 두꺼운 평면을 사용하여 해결할 수 있습니다.

두꺼운 평면은 오류를 제한하는 데에도 도움이 됩니다.

ABCD 두꺼운 평면을 기준으로 분할:

일관성 없는 순서로 인해 발생하는 균열:

또 다른 예는 BSP 트리 견고성입니다. 여기서는 다음 영역에 견고성 문제가 있습니다.
- 기본 요소를 삽입합니다.

- 쿼리(충돌 감지).

- 동일한 질문이 다음에도 적용됩니다.
모든 공간 구역화 솔루션!
- (k-d 트리, 그리드, 옥트리, 쿼드트리...).
견고성을 달성하는 방법: 기본 요소를 보수적으로 삽입하고 쿼리 및 삽입 오류를 고려한 다음 쿼리 문제를 무시합니다.
부동 소수점 값의 오류 예로는 광선 및 삼각형 감지가 있습니다. 일반적으로 사용되는 방법: 광선 R과 삼각형 T 평면의 교차점 P를 계산하고 P가 T의 경계 내에 있는지 테스트합니다. 그러나 이것은 견고하지 않습니다! 다음 그림은 예입니다.

R은 평면과 교차합니다.

R은 다른 평면과 교차합니다.

강력한 테스트는 공유 에지 AB의 계산을 공유하고 3D에서 직접 테스트를 수행해야 합니다. 프로세스는 다음과 같습니다.
R은 다음과 같이 표현될 수 있습니다:
. 그러면
기호는 AB의 왼쪽인지 오른쪽인지를 나타냅니다. R이 모든 변의 왼쪽에 있으면 R은 CCW(시계 반대 방향) 삼각형과 교차합니다.
그런 다음 P를 계산합니다.
여전히 버그가 있지만 관리가 가능합니다. 지방선 테스트도 튼튼해요!

견고성을 달성하는 방법은 다음과 같습니다.
올바른 공차(공차라고도 함)를 사용합니다.
계산 공유.
지방 프리미티브를 사용합니다.
공차 비교에는 다음 방법이 포함됩니다.
- 절대적인 허용오차. 두 개의 부동 소수점 값이 동일한지 비교합니다.
if (Abs(x –y) <= EPSILON)
(...)올바르게 사용된 적이 거의 없습니다! EPSILON은 무엇이어야 합니까? 일반적으로 임의로 작은 숫자를 사용하십시오! 다음 표현 가능한 숫자의 증분 단계 크기:
십진수 16진수 다음 대표 가능 번호
10.0 0x41200000 x + 0.000001
100.0 0x42C80000 x + 0.000008
1000.0 0x447A0000 x + 0.000061
10000.0 0x461C4000 x + 0.000977
100000.0 0x47C35000 x + 0.007813
1000000.0 0x49742400 x + 0.0625
10000000.0 0x4B189680 x + 1.0
위의 표에서 볼 수 있듯이 값이 클수록 필요한 EPSILON의 양이 많아집니다. EPSILON을 0.001(또는 다른 여러 0)로 설정하는 일반적인 관행은 분명히 문제가 있습니다! 예를 들어, Möller Trumbore 광선 및 삼각형에 대한 테스트 코드는 다음과 같습니다.
#define EPSILON 0.000001
#define DOT(v1,v2) (v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2])
(...)
// if determinant is near zero, ray lies in plane of triangle
det = DOT(edge1, pvec);
(...)
if (det > -EPSILON && det < EPSILON) // Abs(det) < EPSILON
return 0;EPSILON을 변경하지 않고 이중 쓰기로 전환하고 부동으로 변경하시겠습니까? DOT({10,10,10},{10,10,10})은 테스트를 파괴합니다!
- 상대적 공차. 두 개의 부동 소수점 값이 동일한지 비교합니다.
if (Abs(x–y) <= EPSILON * Max(Abs(x), Abs(y))
(...)EPSILON은 입력 진폭에 따라 크기가 조정되지만 Abs(x)<1.0, Abs(y)<1.0을 고려합니다.
- 결합 공차. 두 개의 부동 소수점 값이 동일한지 비교합니다.
if (Abs(x –y) <= EPSILON * Max(1.0f, Abs(x), Abs(y))
(...)절대값 테스트에는 Abs(x)≤1.0, Abs(y)≤1.0을 사용하고, 그렇지 않으면 상대 테스트를 수행하십시오!
- 정수 테스트.
경고: Intel은 별도로 명시하지 않는 한 내부적으로 80비트 형식을 사용합니다. 오류는 생성된 코드에 따라 다르므로 디버깅 및 게시 시 다른 결과가 나타납니다.
다음으로 정확한 연산(정확한 연산, 반정확한 연산)을 소개하겠습니다.
정수 산술은 오버플로가 없고 +, – 및 * 아래에서 닫혀 있는 한 정확하지만 나누기 여부는 일반적으로 교차 곱셈으로 제거할 수 있습니다. 예: C를 AB에 투영하는 방법은 무엇입니까?

부동 소수점과 정수를 사용한 연산은 다음과 같습니다.
// float
float t = Dot(AC, AB) / Dot(AB, AB);
if (t >= 0.0f && t <= 1.0f)
... /* do something */
// integer
int tnum = Dot(AC, AB), tdenom = Dot(AB, AB);
if (tnum >= 0 && tnum <= tdenom)
... /* do something */테스트는 부울 값이며 정확하게 계산될 수 있습니다. 구성은 불리언이 아니며 정확하게 실행될 수 없습니다. 테스트는 일반적으로 결정 요인으로 표현됩니다. 예를 들면 다음과 같습니다.
추정에는 확장 정밀 연산(EPA)을 사용합니다. EPA는 비용이 많이 들고 "부동 소수점 필터"를 통해 EPA 사용을 제한합니다. 일반적으로 사용되는 필터는 간격 산술입니다. 구간 계산의 예: x = [1,3] = { x ∈R | 1 ≤ x ≤ 3 }, 규칙은 다음과 같습니다.
[a,b] + [c,d] = [a+c, b+d]
[a,b] – [c,d] = [a–d, b–c]
[a,b] * [c,d] = [최소(ac, ad, bc, bd), max(ac, ad, bc, bd)]
[a,b] / [c,d] = [a, b] * [1/d, 1/c] for 0 ∉ [c, d]
신뢰할 수 있는 계산을 위해서는 간격 계산을 위한 간격을 가장 가까운 기계 표현으로 반올림/내림해야 합니다.
17.2.3 암시적 함수
구 추적은 다양한 형태의 레이 트레이싱 중 하나이며 래스터라이제이션 또는 복셀을 대체하는 것이 아니라 암시적 기능에 이상적입니다. 비효율적이지만 간단하고 매우 유연합니다. 구 추적에는 4단계만 필요합니다.
- 뷰를 빌드합니다.
필요한 것은 두 개의 삼각형과 UV 좌표뿐입니다. 관련 코드는 다음과 같습니다.
vec2 screen_coordinates = gl_FragCoord.xy;
screen_coordinates /= resolution;
screen_coordinates = screen_coordinates - .5;
screen_coordinates *= resolution/min(resolution.x, resolution.y);
float field_of_view = 1.5;
vec3 direction = vec3(screen_coordinates, field_of_view);
direction = normalize(direction);- 레이 트레이싱.
추적 단계와 광선 단계는 다음과 같습니다.

해당 코드:
vec2 origin = vec2(0.0);
vec2 position = origin;
float surface_threshold = 0.001;
for(int i=0; i<128; i++)
{
float distance_to_surface = map(position);
if(distance_to_surface < surface_threshold)
break;
position += direction * distance_to_surface;
}
float distance_to_scene = distance(origin, position);- 표면의 방향을 결정합니다.
광선의 끝 부분 근처에서 샘플을 채취하여 부분 도함수를 비교하고 피타고라스 정리의 결과로 나누어 정규화합니다.

표면 노멀은 다음과 같이 얻어집니다.

법선은 표면뿐 아니라 현장 어디에서나 샘플링할 수 있습니다.

- 조명을 추가하세요.
광원을 추가하는 코드와 효과는 다음과 같습니다.

위의 단계를 통해 (shadertoy에서) 복잡하고 흥미로운 장면을 실현할 수 있습니다.

명시적 데이터는 메쉬 버텍스, 텍셀 등과 같은 개별 값으로 메모리에 저장됩니다. 데이터는 메모리에서 읽혀집니다. 암시적 데이터에 대한 코드는 데이터이고, 모든 것은 절차적이며, 데이터는 계산을 통해 액세스됩니다.

복잡하고 자연스러운 모델은 간단한 덧셈, 뺄셈, 곱셈, 나눗셈, 모드, 최소, 최대, 노이즈 및 기타 연산을 통해 실현될 수 있습니다.

거리와 소음에 관해 주의할 점이 몇 가지 있습니다. 대부분의 경우 물체 표면을 찾는 데 여러 단계가 필요한데, 이 역시 문제입니다. 정현파를 추적할 때 공간은 선형이 아닙니다. Mod 및 Noise 작업에도 동일하게 적용됩니다(아래).


노이즈로 인해 렌더링 아티팩트가 발생할 수도 있습니다.

렌더링을 망칠 수 있으므로 노이즈를 피해야 한다는 사람도 있고, 동의하지 않는 사람도 있습니다. 다음은 노이즈가 없는 장면과 노이즈가 추가된 장면을 비교한 것입니다.

17.2.4 샘플링 방법
그래픽에서 샘플링은 다양한 방법을 포함하여 흥미롭지만 풍부한 기술입니다. 레이 트레이싱에서 일반적인 샘플링 방법에는 균일, 무작위, 저차이 시퀀스, 중요도 등이 포함됩니다.
균일 샘플링(Uniform Sampling)은 광원의 중요도를 구분하지 않는 평균 샘플링입니다. 생성된 빛 샘플은 모든 방향에서 동일한 확률을 갖습니다. 빛은 특별히 처리되지 않으며 실제 값과의 편차는 일반적으로 매우 큽니다. 몬테카를로 샘플링은 광원 방향의 샘플링에 중점을 두는데, 이는 광원이 픽셀에 미치는 영향을 강조할 수 있지만 과도한 광원 기여를 유발합니다. 중요도 샘플링은 샘플링 결과를 줄여 광원의 기여도가 너무 커지는 것을 방지하기 위해 확률 밀도 함수 pdf를 추가합니다.
img
왼쪽: 완전한 의사 무작위 시퀀스 생성을 위한 샘플링 지점; 오른쪽: 저차 시퀀스 생성을 위한 샘플링 포인트. 오른쪽이 더 균일한 것을 볼 수 있습니다.

몬테카를로 샘플링은 무작위 샘플을 사용하여 이 적분을 수치적으로 계산합니다. 중요도 샘플링의 아이디어는 유사한 모양을 가진 피적분 함수의 확률 밀도 함수(PDF)에 비례하는 무작위 샘플을 생성하려고 시도하는 것입니다.
UE는 TAA를 구현할 때 Halton, Sobal 및 기타 시퀀스를 채택합니다.
img
무작위 샘플링과 비교하여 Halton에서 얻은 샘플링 시퀀스는 더 균일하며 무제한의 샘플을 얻을 수 있습니다(UE는 기본적으로 8개로 제한됩니다). 그 외에도 Sobel, Niederreiter, Kronecker 등의 저차 시퀀스 알고리즘이 있습니다. 그들의 비교는 다음과 같습니다:
img
모든 샘플링 기술은 단위 사각형에서 다른 영역, 반구, 구, 구 주위의 원뿔, 디스크로 임의의 숫자를 뒤틀는 것을 기반으로 합니다. BSDF의 산란 분포를 기반으로 샘플을 생성하거나 IBL 광원의 방향을 선택할 수도 있습니다. 샘플링하는 방법은 엄청나게 많지만 모두 0과 1 사이의 값으로 시작하고 약간의 직교성이 있습니다. "시작하는 값은 무엇입니까?"와 "두 번째 몬테 카를로 추정을 사용하기 위해 샘플링하려는 분포로 이 값을 어떻게 왜곡합니까?"가 있습니다.
img
해당 샘플링 방법에는 일반적으로 사용되는 방법에는 균일, 저차 시퀀스, 계층화된 샘플링, 요소 간격, 블루 노이즈 디더링 등이 포함됩니다. 낮은 시차는 일반화된 층화와 유사하고 블루 노이즈는 서로 다른 샘플이 서로 얼마나 가까운지와 유사합니다. 절차 모드는 원하는 만큼의 접두사를 사용할 수 있으며 (일부) 접두사는 균등하게 배포됩니다.
img
분산 기반 샘플링 - 지금까지 수집된 샘플을 기반으로 각 픽셀의 분산을 주기적으로 추정하고, 차이가 큰 경우 오버샘플링하고, 분산/추정이 더 높은 경우 더 많은 샘플을 취하는 것이 좋습니다. 톤매핑 등을 수행한 후에 이 작업을 수행합니다. 오프라인(품질 기반): 픽셀의 분산이 충분히 낮아지면 처리를 중지합니다. 실시간(프레임 속도 기반): 분산이 가장 큰 곳에서 더 많은 샘플을 수집합니다. 표본 분산을 계산합니다(표본 분산은 실제 분산의 추정치입니다).
float SampleVariance(float samples[], int n)
{
float sum = 0, sum_sq = 0;
for (int i=0; i<n; ++i)
{
sum += samples[i];
sum_sq += samples[i] * samples[i];
}
return sum_sq/(n*(n-1))) - sum*sum/((n-1)*n*n);
}샘플 분산은 단지 추정치일 뿐이며, MC 렌더링 적응형 샘플링 및 재구성의 최근 발전인 노이즈 제거에 많은 작업이 이루어졌습니다. 일반적인 아이디어: 보조 기능(위치, 노멀 등)의 근접성에 따라 가중치를 부여할 수 있는 인근 픽셀에 샘플 분산을 추가합니다. 높은 분산은 저주입니다. 일단 고분산 표본이 도입되면 큰 문제에 봉착하게 됩니다. 데이터를 균일하게 샘플링하는 것을 고려하세요.
또한, 거칠기가 다른 표면에 따라 필요한 빛의 양과 방향도 다릅니다.

그림자, AO 및 기타 채널을 계산할 때 중요도 샘플링도 빛을 생성하는 데 사용되며 동일한 시각적 품질에는 더 적은 빛이 필요합니다. 반구형, 코사인 샘플링 및 거리 샘플링이 중요도 샘플링 프로세스에 사용됩니다.

왼쪽부터: 반구, 반구+코사인, 반구+코사인+거리.
또한 광원, BRDF, PDF 등과 같은 요소의 영향을 동시에 고려하고 샘플링 방향과 위치를 편향시키는 MIS(Multiple important Sampling)**가 있습니다.

다중 중요도 샘플링 공식:
여기서:
위의 방법 외에도 분산, 도메인 왜곡, 준랜덤 시퀀스, 낮은 차이, 계층화 등의 샘플링 방법이 있습니다. QMC(Quasi Monte Carlo)는 샘플 수를 알 필요가 없는 Sobol 또는 (0-2) 시퀀스와 같이 랜덤보다 빠르게 수렴하는 결정론적, 낮은 불일치 시퀀스/앙상블(Halton, Hammersley, Larcher-Pillichshammer)과 훌륭한 계층적 특성이 특징입니다.
img
계속 확장하면 양측, 블루 노이즈, 체커보드, 별 등 또는 이들의 조합과 같은 모든 모양의 탭을 원하는 만큼 샘플링할 수 있습니다.

계단 아티팩트를 방지하기 위해 Inside에서는 무작위 샘플링(블루 노이즈 + TRAA)을 사용합니다.
img
양방향 업샘플링 모드 중 하나입니다.
또한 회전하고 더 높은 차원으로 올리면 더 많은 샘플을 얻고 노이즈를 줄일 수 있습니다.



간단히 말해서, 현재 많은 샘플링 방법이 있으며, 모두 레이 트레이싱이 정확한 결과로 더 빠르게 수렴하여 노이즈를 줄이고 렌더링 성능을 향상시키도록 설계되었습니다.
17.2.5 복셀화
연속 함수

연속 함수, 대수 함수, 방향성 거리 필드(CSG 트리, 그리드의 삼선형 샘플링) 및 밀도 함수(그리드의 삼선형 샘플링)만 필요합니다.

밀도를 사용하는 것은 훨씬 쉽습니다(로컬 변경). 그러나 거리 필드에 대한 일부 유용한 작업(예: 확대, 볼륨 축소, 고품질 그라데이션 계산)에서는 실패합니다. 밀도는 약 하나의 샘플 셀에 대한 클램핑 거리를 갖는 거리 필드로 생각할 수 있습니다.
암시적 표면 또는 파라메트릭 메시를 복셀화하는 프로세스:
그리드에서 샘플링.
각 셀의 표면을 대략적으로 계산합니다.
표면이 셀 경계와 정렬되어 있는지 확인하십시오.
복셀화의 이상적인 특성은 구현 용이성, 로컬 독립성, 부드러움, LOD에 대한 적응성/맞춤성, 삼각형 막대 최소화, 날카롭고 얇은 특징 유지 등입니다.
간단한 큐브의 경우 각 그리드 셀의 중심에서

복셀화의 이상적인 특성에 있어서 이 표현방법의 장점과 단점은 다음과 같다.
구현이 용이함 지역 독립 부드러운 적응형 LOD 삼각형 최소화 날카로운 얇은
++ +
- 마링 큐브: 각 셀의 모서리에 있는 샘플
는 삼각형 토폴로지의 모서리 기호를 사용하여 보간 0점에서 가장자리를 따라 꼭지점을 배치합니다.

복셀화의 이상적인 특성에 있어서 이 표현방법의 장점과 단점은 다음과 같다.
구현이 용이함 지역 독립 부드러운 적응형 LOD 삼각형 최소화 날카로운 얇은
- 트랜복셀 알고리즘: 각 셀의 모서리에 있는 샘플
은 셀의 가장자리를 한 번 세분화할 수 있도록 하며(인접한 LOD 레벨을 연결하기 위해) 삼각형 토폴로지에 샘플링 포인트 기호를 사용하고 보간된 0 포인트의 가장자리를 따라 정점을 찾습니다.

Transvoxel은 이동하는 큐브가 다양한 LOD 레벨에 걸쳐 있을 수 있도록 하는 방법입니다. 모서리 조합의 세분화를 처리하기 위해 총 71개의 토폴로지 방법이 사용됩니다.
복셀화의 이상적인 특성에 있어서 이 표현방법의 장점과 단점은 다음과 같다.
구현이 용이함 지역 독립 부드러운 적응형 LOD 삼각형 최소화 날카로운 얇은
- 이중 윤곽선: 각 셀의 모서리에서
샘플, 각 가장자리의 교차 위치에서 샘플, 윤곽선의 각 셀에서 이상적인 점을 찾고 인접한 셀의 두 점을 연결합니다(다중 LOD 해상도 지원).

복셀화의 이상적인 특성에 있어서 이 표현방법의 장점과 단점은 다음과 같다.
구현이 용이함 지역 독립 부드러운 적응형 LOD 삼각형 최소화 날카로운 얇은
~
Dual Marching Cube: 미세한 그리드에서

복셀화의 이상적인 특성에 있어서 이 표현방법의 장점과 단점은 다음과 같다.
구현이 용이함 지역 독립 부드러운 적응형 LOD 삼각형 최소화 날카로운 얇은
Cubical Marching Square: 모든 복셀(모든 옥트리 수준)에 대해 오류 세분화(DMC와 유사)가 있는 옥트리를 구성합니다. 복셀을 펼치고 각 측면을 별도로 봅니다. 행진 큐브를 사용하여 곡선을 만들고 오류가 발생하면 테셀레이션합니다. 양면을 함께 접어 삼각형을 만듭니다.

구현이 용이함 지역 독립 부드러운 적응형 LOD 삼각형 최소화 날카로운 얇은
Windborne의 복셀화 개요:

결합되면 각 기능은 레이어가 되며 각 레이어는 빼거나 추가됩니다. 알파 블렌딩 밀도:

조합 외에도 뺄셈, 중첩, 합집합, 윤곽선 표시, 메쉬 밀도(광류, AO 등 동적 조명에 사용할 수 있음), 노출 매개변수, 활용 기능 등과 같은 연산이나 응용도 있습니다.
17.2.6 지향 거리 필드
SDF(방향 거리 필드)는 함수 SDF(P)에서 P에 있는 가장 가까운 표면까지의 부호 있는 거리입니다. 이 형식에는 분석적 거리 함수와 볼륨 텍스처의 두 가지 형식이 있습니다.
분석적 거리 기능은 장면 데모, 거대한 셰이더, 많은 수학, 데이터가 없는 경우에 널리 사용됩니다.
볼륨 텍스처는 삼선형 필터링을 사용하여 거리 함수를 저장합니다. 게임 Claybook은 밉 맵, 월드 SDF 해상도 = 1024x1024x512, 형식 = 8비트 부호 있음, 크기 = 586MB(5 밉 레벨), [-4, +4] 복셀 거리, 256 값/8 복셀, 1/32 복셀 정확도, 밉 레벨당 이중 최대 단계(월드 공간)와 함께 볼륨 텍스처를 사용합니다.
GPU에서 월드 SDF를 생성하는 Claybook 단계:
- SDF 브러시 메쉬를 생성합니다. 64x64x32 디스패치, 4x4x4 스레드 그룹.
타일 중심 T에서 브러시 볼륨을 샘플링합니다. SDF > 래스터 타일 경계 + 4복셀인 경우 컬링합니다. 승인되면 GSM에 원자적으로 추가 + 저장합니다.
- GSM에서 브러시를 순환합니다. 허용되는 경우 셀 중심 C의 샘플[i]는 그리드(선형), 로컬 + 전역 원자 압축에 저장됩니다.
img
- 일정 좌표를 생성합니다. 64x64x32 스케줄링, 4x4x4 스레드 그룹.
브러시 그리드 셀을 읽습니다.
- 비어 있지 않은 경우: 쓰기 인덱스를 얻기 위한 원자 추가(L+G), 셀 좌표를 버퍼에 씁니다.
img
- 밉 마스크를 생성합니다. 4x 스케줄링(mips), 4x4x4 스레드 그룹.
그룹화: 1개의 더 넓은 복셀 그리드 L-1 이웃을 로드하고 1=0 마스크를 다운샘플링하여 GSM에 저장합니다.
마스크를 1복셀(3x3x3)씩 확대합니다.
Mask=0은 그리드 셀 좌표를 씁니다.
8x8x8 타일에서 레벨 0(희소)을 생성합니다. 간접 스케줄링, 8x8 스레드 그룹.
그룹화: 그리드 셀 좌표(SV_GroupId)를 읽습니다.
그리드에서 브러시를 읽고 GSM에 저장합니다.
GSM, 샘플[i]의 브러시 루프를 통해 exp 평활화 최소/최대 작업을 수행합니다.
WorldSDF의 레벨 0에 복셀을 씁니다.
밉(희소)을 생성합니다. 4x 간접 스케줄링(mips), 8x8 스레드 그룹.
그룹화: 더 넓은 L-1 이웃의 복셀 4개를 로드합니다. 2x2x2 다운샘플링(평균)이며 GSM에 123123으로 저장되며 +-4 복셀 대역은 +-2 복셀 대역이 됩니다.
그룹화: GSM(아래)에서 3단계 에이코날 방정식을 실행합니다. 확장 순서: 2복셀이 4복셀이 됩니다.
8x8x8 동네의 중심을 저장하세요.
구형 추적 알고리즘:
D = SDF(P).
P += 광선 * D.
(D < 엡실론)인 경우 중단됩니다.
img
다층 볼륨 텍스처 추적:
Loop
D = volume.SampleLevel(origin + ray*t, mip)
t += worldDistance(D, mip)
IF D == 1.0 -> mip += 2
IF D <= 0.25 -> mip -= 2; D -= halfVoxel
IF D < pixelConeWidth * t -> BREAK
// 만약 픽셀(Pixel),이면 인터럽트 ,의 LOD!마지막 단계: 구 추적은 수렴하는 데 무한한 단계가 필요합니다. 마지막 두 샘플 Step=D/(1−(D−D−1))Step=D/(1−(D−D−1))을 사용하여 * 표면, 삼선형 필터링 = 조각별 선형 표면, 기하 급수를 만난다고 가정합니다.
img
콘 추적의 분석 솔루션:
img
대략적인 원뿔 추적 프리패스:
img
원뿔 추적은 빈 공간의 큰 영역을 건너뛰고 단계 크기를 크게 줄이며 볼륨 샘플링은 더 많은 캐시 지역성을 갖습니다. Mip 매핑은 캐시 지역성, Log8 데이터 스케일링을 향상시킵니다: 100%, 12.5%, 1.6%, 0.2%... 측정됨(1080p 렌더링), 8MB 데이터 액세스(512MB), 99.85% 캐시 적중률. 기존 문제에는 과도한 스테핑, 로드 밸런싱 등이 포함되며 자체적인 완화 계획이 있습니다.
17.3 레이 트레이싱 기술
기하광학에서는 빛의 파동성을 무시하고 직선으로 직접 단순화하여 빛의 물리적 특성을 연구할 수 있습니다. 마찬가지로 컴퓨터 그래픽에서도 이 기능을 활용하여 조명 셰이딩 프로세스를 단순화할 수 있습니다.
img
또한, 인간의 눈이 받아들이는 조명 정보는 픽셀로 제한되어 있으며, 대부분의 사람의 눈은 약 5억 픽셀에 이릅니다. 인간이 수신하는 이미지 정보는 5억 픽셀, 즉 5억 개의 아주 작은 광선으로 분할될 수 있습니다. 이러한 광선을 반대 방향으로 추적하면 해당 광선에 해당하는 장면 객체의 정보(위치, 방향, 재질, 조명 색상 및 밝기 등)를 감지할 수 있습니다.
img
레이 트레이싱 기술은 위의 물리적 원리에서 파생됩니다. 눈을 카메라로 추상화하고, 망막을 디스플레이 화면으로 추상화하고, 5억 픽셀을 화면 픽셀로 단순화하고, 카메라 위치의 광선을 화면의 각 픽셀에 연결하고, 이러한 광선과 장면 객체의 교차점에서 조명 정보를 추적합니다. 물론 실제 레이 트레이싱 알고리즘은 더 복잡할 것입니다. 레이 트레이싱의 의사 코드:
for each pixel do
compute ray for that pixel
for each object in scene do
if ray intersects object and intersection is nearest so far then
record intersection distance and object color
set pixel color to nearest object color (if any)각 픽셀은 광선을 방출하며, 알고리즘은 광선이 먼저 닿는 개체와 광선이 개체에 닿는 정확한 지점을 계산합니다. 이 점을 첫 번째 교차점이라고 하며 알고리즘은 여기서 두 가지 작업을 수행합니다.
교차점에서 입사광을 추정합니다. 들어오는 빛이 첫 번째 교차점에서 어떻게 보일지 추정하려면 알고리즘은 빛이 반사되거나 굴절되는 위치를 고려해야 합니다.
입사광에 대한 정보와 충돌하는 물체에 대한 정보를 결합합니다. 물체가 모두 동일한 속성을 갖지는 않기 때문에 각 물체에 대한 구체적인 정보가 중요합니다. 즉, 빛을 다양한 방식으로 흡수, 반사, 굴절합니다.
흡수 모드에 따라 물체의 색상이 달라집니다. 예를 들어 잎은 녹색광을 제외한 모든 빛을 흡수하므로 녹색입니다.
반사율이 다르기 때문에 일부 물체는 스페큘러 반사를 방출하고 다른 물체는 빛을 모든 방향으로 산란시킵니다.
굴절률이 다르면 일부 물체(예: 물)가 다른 물체보다 빛을 더 많이 왜곡하게 됩니다.
일반적으로 첫 번째 교차점에서 입사광을 추정하려면 알고리즘은 해당 빛을 두 번째 교차점까지 추적해야 합니다(물체에 닿는 빛이 다른 물체에 의해 반사되었을 수 있기 때문). 때로는 방출된 광선이 아무 것도 부딪치지 않는 경우가 있습니다. 이는 첫 번째 극단적인 경우이며 빛이 얼마나 멀리 이동하는지 측정하여 쉽게 커버할 수 있으므로 너무 멀리 이동하는 광선에 대해 추가 처리를 수행할 수 있습니다. 두 번째 엣지 케이스는 그 반대입니다. 빛이 너무 많이 반사되어 알고리즘 속도가 느려지거나 무한 횟수가 발생하여 무한 루프가 발생할 수 있습니다. 알고리즘은 각 단계 후에 광선이 추적되는 횟수를 추적하고 특정 횟수의 반사 후에 종료됩니다. 현실 세계의 모든 물체는 심지어 거울까지도 어느 정도의 빛을 흡수하기 때문에 우리는 이것을 정당화할 수 있습니다. 이는 빛이 너무 약해져서 눈에 띄지 않을 때까지 반사될 때마다 빛이 에너지를 잃음(약해진다)을 의미합니다. 따라서 가능하더라도 광선을 임의의 횟수만큼 추적하는 것은 의미가 없습니다.
기존 래스터 렌더링 기술과 비교할 때 레이 트레이싱의 알고리즘 프로세스는 비교적 명확합니다. 시점을 시작점으로 N개의 광선을 장면으로 방출한 후 충돌 지점의 재질에 따라 BXDF, BRDF를 계산한 후 디퓨즈, 스페큘러 반사 또는 굴절을 수행합니다. 이 재귀 루프는 광선이 장면을 벗어나거나 최대 반사 수에 도달할 때까지 계속됩니다. 마지막으로 N개 광선에 대해 몬테카를로 통합을 수행하여 결과를 얻습니다.
img
위의 그림과 결합하여 레이 트레이싱의 알고리즘 프로세스는 다음 의사 코드로 추상화될 수 있습니다.
遍历屏幕的每个像素 {
创建从视点通过该像素的光线
初始化 最近T 为 无限大,最近物体 为 空值
遍历場景中的每个物体 {
如果光线与物体相交 {
如果交点处的 t 比 最近T 小 {
设置 最近T 为交点的 t 值
设置 最近物体 为该物体
}
}
}
如果 最近物体 为 空值{
用背景色填充该像素
} 否则 {
对每個光源射出一条光线来检测是否处在阴影中
如果表面是反射面,生成反射光,并递归
如果表面透明,生成折射光,并递归
使用 最近物体 和 最近T 来计算着色函数
以着色函数的结果填充该像素
}
}위 의사코드에 포함된 셰이딩 함수는 Lambert, Phong, Blinn-Phong, BRDF, BTDF, BSDF, BSSRDF 등 모든 조명 모델을 사용할 수 있습니다. 한 단계 더 나아가 이를 컴퓨터 언어 형식의 의사코드로 설명하면 레이 트레이싱의 계산 과정은 다음과 같습니다.
-- 遍历图像的所有像素
function traceImage (scene):
for each pixel (i,j) in image S = PointInPixel
P = CameraOrigin
d = (S - P) / || S – P||
I(i,j) = traceRay(scene, P, d)
end for
end function
-- 追踪光线
function traceRay(scene, P, d):
(t, N, mtrl) ← scene.intersect (P, d)
Q ← ray (P, d) evaluated at t
I = shade(mtrl, scene, Q, N, d)
R = reflectDirection(N, -d)
I ← I + mtrl.kr ∗ traceRay(scene, Q, R) -- 递归追踪反射光线
-- 区别进入介质的光和从介质出来的光
if ray is entering object then
n_i = index_of_air
n_t = mtrl.index
else n_i = mtrl.index
n_i = mtrl.index
n_t = index_of_air
end if
if (mtrl.k_t > 0 and notTIR (n_i, n_t, N, -d)) then
T = refractDirection (n_i, n_t, N, -d)
I ← I + mtrl.kt ∗ traceRay(scene, Q, T) -- 递归追踪折射光线
end if
return I
end function
-- 计算所有光源对像素的贡献量(包含阴影)
function shade(mtrl, scene, Q, N, d):
I ← mtrl.ke + mtrl. ka * scene->Ia
for each light source l do:
atten = l -> distanceAttenuation( Q ) * l -> shadowAttenuation( scene, Q )
I ← I + atten*(diffuse term + spec term)
end for
return I
end function
-- 此处只计算点光源的阴影,不适用其它类型光源的阴影
function PointLight::shadowAttenuation(scene, P)
d = (l.position - P).normalize()
(t, N, mtrl) ← scene.intersect(P, d)
Q ← ray(t)
if Q is before the light source then:
atten = 0
else
atten = 1
end if
return atten
end function위의 distanceAttenuation 인터페이스는 일반적으로 BRDF의 조명 통합을 포함하지만 실시간 렌더링 분야에서는 각 교차점에 대한 통합을 수행하는 것이 거의 불가능합니다. 따라서 Monte Carlo 통합 및 중요도 샘플링을 도입하여("단순에서 심층까지 PBR의 원리 및 구현 학습"의 5.4.2.1장 Monte Carlo 통합 및 중요도 샘플링(중요도 샘플링) 참조) 로컬 샘플링과 함께 전체 조명 적분을 추정할 수 있습니다.
물론, 이 방법을 도입하더라도 샘플 수가 충분히 크지 않으면 조명 기여도와 실제 값 간의 편차가 여전히 커서 노이즈가 발생하게 됩니다. 샘플 수가 증가할수록 로컬 추정값은 실제 조명 적분값에 점점 가까워지고 노이즈는 점차 사라집니다(아래).
img
왼쪽에서 오른쪽으로 해당 픽셀 샘플은 각각 1, 16, 256, 4096 및 65536입니다.

각 픽셀 내에서 오프셋을 사용하여 추적 픽셀을 생성할 수 있으므로 보다 정확하고 앤티앨리어싱된 렌더링이 가능합니다.
몬테카를로 통합과 중요도 샘플링을 결합한 레이 트레이싱 기술을 **패스 트레이싱(Path tracing)**이라고도 합니다.
17.3.1 레이 트레이싱 방법
17.3.1.1 재귀적 레이 트레이싱
광선이 스페큘러 반사 또는 굴절이 있는 표면에 닿으면 그곳의 색상을 계산할 때 각각 반사 광선 및 굴절 광선이라고 하는 더 많은 광선을 추적해야 할 수 있습니다. 이러한 광선은 다른 반사 표면에 부딪혀 더 많은 광선을 추적하게 되므로 재귀 레이 트레이싱이라는 용어가 사용됩니다. 아래 이미지는 반사 광선의 재귀적 "트리"를 보여줍니다. 이 기술은 1980년 Turner Wheat가 도입한 이후 고전적인 레이 트레이싱 또는 Wheatian 레이 트레이싱으로도 알려져 있습니다.

재귀적 레이 트레이싱은 일반적으로 마지막에 최종 수집, 즉 대략적인 GI 솔루션에서 라디오시티 또는 광자 맵을 읽는 작업이 필요합니다.
17.3.1.2 몬테카를로 레이 트레이싱
몬테카를로 레이 트레이싱은 확률적 레이 트레이싱이라고도 알려져 있으며, 광선 원점, 방향 또는 시간은 난수를 사용하여 계산됩니다. 몬테카를로 레이 트레이싱은 일반적으로 분포 레이 트레이싱과 경로 추적의 두 가지 범주로 나뉩니다.
분산 레이 트레이싱은 각 표면 지점에서 여러 광선을 발사하여 영역 조명, 광택 및 확산 및 기타 여러 효과를 샘플링합니다. 아래 이미지는 분산 레이 트레이싱에 사용되는 반사 및 굴절 광선 트리를 보여줍니다. 그림에 표시된 것처럼 분산 레이 트레이싱의 광선 수는 여러 번의 반사 후에 폭발하는 경향이 있습니다. 이러한 상황을 피하기 위해 일반적으로 여러 수준의 반사 후에 광선 수를 줄입니다. 분산 레이 트레이싱을 사용하면 반사 지점에서 빛 방향의 올바른 분포(예: 레이어링 방향)를 쉽게 보장할 수 있습니다.

분산 레이 트레이싱을 위한 반사 및 굴절 트리.
경로 추적은 각 지점이 하나의 반사 및 굴절 광선만 방출하여 광선 수가 폭발적으로 증가하는 것을 방지하는 분산 레이 트레이싱의 변형이지만 간단한 구현으로 인해 매우 노이즈가 많은 이미지가 생성될 수 있습니다. 이를 보완하기 위해 각 픽셀을 통해 많은 가시 광선을 추적합니다. 경로 추적의 한 가지 장점은 각 픽셀이 많은 가시성 광선을 발사하기 때문에 뎁스 오브 필드(DoF) 및 모션 블러와 같은 카메라 효과를 추가 비용 없이 통합할 수 있다는 것입니다.
반면, 분산 레이 트레이싱보다 반사 광선의 양호한 분포를 보장하는 것이 더 어렵습니다(예: 레이어링을 통해). 간단히 말해서, 분산 레이 트레이싱은 광선 트리의 더 깊은 곳에서 가장 많은 광선을 방출하는 반면, 경로 추적은 가시성 광선을 가장 많이 방출합니다.
17.3.2 장면 가속 구조
레이 트레이싱과 관련된 데이터 구조에는 BVH(Bounding Volume Hierarchy), SBVH(Stackless Bounding Volume Hierarchy), KD 트리, BIH(Bounding Interval Hierarchy) 등이 포함됩니다.

스택 및 스택리스 데이터 구조와 메모리 레이아웃 비교 차트.
서로 다른 데이터 구조를 사용하여 동일한 시나리오를 테스트하는 시간 곡선은 다음과 같습니다.

BVH의 장점은 테스트를 벡터화하고, 빈 공간을 더 잘 처리할 수 있으며, 스택에 병목 현상이 발생하지 않는다는 것입니다.
17.3.2.1 BVH
복잡한 장면의 경우 모든 광선과 교차하는 모든 객체를 테스트하는 것은 절망적으로 비효율적입니다. 따라서 우리는 대부분의 개체가 신속하게 거부될 수 있도록 개체를 계층 구조로 구성합니다.
가속 데이터 구조의 가장 중요한 특징은 구성 시간, 메모리 사용량 및 광선 통과 시간입니다. 응용 분야에 따라 이러한 각 특성을 다르게 강조할 수 있습니다. 이미지 시퀀스의 렌더링(예: 대화형 시각화 또는 영화의 "스냅샷" 렌더링)의 경우 점진적인 기하학적 변경으로 효율적으로 업데이트할 수 있는 가속 데이터 구조를 선택해야 합니다.
가속 데이터 구조에는 경계 볼륨 계층, 균일 그리드, 계층 그리드, BSP 트리, kd-트리, 옥트리, 5D 원점 방향 트리, 경계 간격 계층 등 혼란스러운 배열이 있습니다. 여기서는 경계 볼륨 계층이라는 하나의 가속 데이터 구조만 자세히 설명합니다.
레이 트레이싱 장면은 다수의 광선 감지를 사용하며 효율적인 장면 가속 구조가 필요합니다. 실시간 Ray Tracing에서 가장 널리 사용되는 가속 구조는 BVH(Bounding Volume Hierarchy)입니다. BVH는 객체와 객체의 경계 볼륨을 트리로 구성합니다. 트리의 루트는 전체 장면을 포함하는 경계 볼륨입니다. 가장 일반적으로 사용되는 경계 볼륨은 축 정렬 상자입니다. 이러한 상자는 계산 및 결합이 쉽기 때문입니다.


BVH 트리의 예.
예를 들어, 찻주전자 장면의 BVH에는 5개의 경계 상자 레이어가 있습니다. 맨 위 레이어는 전체 장면에 대한 단일 경계 상자로 구성되고, 다음 레이어는 두 개의 찻주전자와 사각형의 경계 상자를 포함합니다. 각 찻주전자는 본체, 뚜껑, 손잡이, 주둥이의 네 부분으로 구성됩니다. 각 부분에는 경계 상자가 있습니다. 주전자의 본체는 8개의 베지어 패치로 구성되어 있으며 각 패치에는 자체 경계 상자가 있습니다. 테셀레이션된 베지어 패치의 경우 각 쿼드 세트는 효율적인 광선 교차 테스트를 위한 경계 상자를 가질 수 있습니다.
주전자 장면 예시에서처럼 장면 모델링 계층 구조를 직접 사용할 수 있습니다. 또 다른 전략은 각 부품이 대략 동일한 표면적을 갖도록 형상을 분할하는 것입니다.
광선이 장면의 객체와의 교차점을 테스트해야 할 때 첫 번째 단계는 전체 장면의 경계 상자와의 교차점을 확인하는 것입니다. 광선이 경계 상자에 닿으면 하위 객체의 경계 상자가 테스트되는 등의 작업이 수행됩니다. 계층 구조의 리프에 도달하면 해당 리프로 표시되는 개체의 교차 여부를 테스트해야 합니다.
이러한 가속 데이터 구조 중 어느 것도 항상 다른 것보다 빠르지는 않습니다. 특정 장면에 가장 적합한 것은 장면 특성과 빠른 빌드, 빠른 업데이트, 빠른 광선 탐색 또는 컴팩트 메모리 사용에 중점을 두는지 여부에 따라 달라집니다.
월드 오브 탱크가 조명 추적의 일부 기능(예: 부드러운 그림자)을 구현할 때 CPU 측 로직과 GPU 측 로직으로 구분됩니다. CPU 측에는 2단계 가속 구조가 포함되어 있습니다.
BLAS(저층 가속 구조) BVH. 메시 로딩 중에 한 번 생성되어 GPU에 업로드되는 모든 탱크 모델에 적용됩니다. 메시의 하드 스킨 부분은 여러 정적 BVH로 분할되고 소프트 스킨 부분은 건너뜁니다.
TLS(Top Top 가속 구조) BVH. Intel Embree 및 Intel TBB를 사용하는 멀티스레드 방식으로 각 프레임이 재구성되어 GPU에 업로드됩니다.

TLAS BVH(왼쪽) 및 BLAS BVH(오른쪽) 시각화.
실시간 Ray Tracing의 가시성 기반 알고리즘 및 가속 구조의 예는 다음과 같습니다.

2계층 가속 구조, 불투명(구현 정의) 데이터 구조, 효율적인 구성 및 업데이트:

RTX 구성, 업데이트 및 사용 가속 구조 다이어그램:

17.3.2.2 KD-트리
KD-Tree 구조를 통해 스택 탐색을 피할 수 있습니다. 다음 그림은 평면을 분할한 후 예시 장면으로 구성된 트리 구조입니다.

순회할 때 스택 순회를 방지하기 위해 트리 구조를 통해 교차하는 객체를 빠르게 감지할 수 있습니다.

동적 장면의 대화형 레이 트레이싱을 위한 고도로 병렬화된 고속 KD-트리 구성은 동적 형상의 레이 트레이싱을 위한 고도로 병렬적이고 선형 확장 가능한 kd-트리 구성 기술을 제안합니다. MLRTA 또는 프러스텀 추적과 같은 고성능 알고리즘과 호환되는 기존 kd-트리를 사용하여 렌더링 단계에 적합한 kd-트리 품질을 유지하면서 뛰어난 빌드 속도를 제공합니다. 이 알고리즘은 각 프레임에서 시작하여 kd-트리를 구축하므로 모션/변형 또는 모션 제약 조건에 대한 사전 지식이 필요하지 않습니다. 200K 동적 삼각형, 1024x1024 해상도, 그림자 및 텍스처를 갖춘 모델에 대해 7-12fps에 가까운 실시간 성능을 달성했습니다.
대화형 레이 트레이싱 성능을 달성하려면 고품질 kd-트리를 사용하는 것이 중요합니다. 따라서 목표는 품질 저하를 최소화하기 위해 가능한 한 빨리 kd-트리를 구축하는 것입니다. 일반적인 kd-트리 구성은 다음 작업 순서를 사용하여 현재 노드를 두 개의 하위 노드로 재귀적으로 분할하여 하향식 방식으로 진행됩니다.
특정 위치에서 분할 평면 후보를 생성합니다.
SAH를 사용하여 각 위치의 비용 함수를 평가합니다.
가장 좋은 후보(가장 낮은 비용)를 선택하고 이를 두 개의 하위 노드로 분할합니다.
형상을 건너뛰고 하위 노드에 할당합니다.
재귀적으로 반복합니다.
이 기사에서는 처음 세 단계를 살펴봅니다. SAH를 빠르게 추정하는 동안 삼각형 AABB가 삼각형의 프록시로 사용됩니다. 비용 함수는 조각별 선형이므로 현재 노드 내에 있는 AABB 경계에서만 평가하면 됩니다. 이러한 위치를 분할 후보 위치라고도 합니다.
2단계에서는 다수의 기하학적 프리미티브에 대해 적분 형태로 인해 이산화 설정에서 비용 함수를 계산할 수 있습니다. 알고리즘의 복잡성을 극복하기 위해 개념적으로 유사한 기술이 사용되지만 이 접근 방식은 크고 작은 개체 모두에 적용됩니다. 각 컨테이너에 개체 참조를 저장하는 대신 가변 크기 목록(또는 배열)이 개체 카운터로 대체됩니다. 이러한 구조를 구축하려면 정렬이 아닌 지오메트리를 통과하는 저렴한 단일 패스가 필요합니다.
처음에는 포인트에 대해 비닝 알고리즘(비둘기 구멍 정렬, 버킷 정렬)이 제안되었습니다. 아이디어는 1D 간격을 주어진 수의 동일한 크기 컨테이너로 나누어 일반 그리드를 형성하는 것입니다. 객체가 속한 Bin 인덱스는 해당 위치에서 직접 계산할 수 있습니다. 단일 선형 전송 형상을 사용하여 빈의 삼각형 수가 계산되고 빈의 후보 분할 값(빈 경계에 가장 가까운)이 아래 이미지에 표시된 대로 업데이트됩니다. 삼각형이 점으로 표현되면 점이 있는 빈이 업데이트되고, 알고리즘이 전체 삼각형에 대해 작동하는 경우 삼각형과 겹치는 각 빈이 업데이트됩니다. 이 데이터는 매우 부정확한 빠른 SAH 근사치를 위해 사용됩니다.

(a) 전통적인 비닝 알고리즘; (b) 이 알고리즘을 사용한 SAH 평가.
최소-최대 비닝 알고리즘의 아이디어는 각 삼각형 AABB가 두 개의 개별 비닝 세트에서 시작하고 끝나는 위치를 추적하는 것입니다(아래 이미지). 각 bin은 단지 카운터일 뿐이며, 각 기본 요소의 AABB에 대해 첫 번째 세트(AABB가 시작되는 곳)와 두 번째 세트(AABB가 끝나는 곳)에서는 하나의 bin만 업데이트됩니다. 따라서 총 컨테이너 수에 대한 의존도가 완전히 제거됩니다. 이 알고리즘의 기능은 초기 클러스터링 작업에 중요하며 최소-최대 비닝 알고리즘은 SAH를 추정하는 데 사용됩니다.

(a) 최소-최대 비닝 알고리즘; (b) 이 알고리즘을 사용하여 SAH를 평가합니다.
이 방법은 kd-트리의 다중 스레드 병렬 구성으로 쉽게 확장됩니다. 작업을 병렬로 실행하려면 전체 작업을 스레드에 할당된 더 작은 부분(작업)으로 나누어야 합니다.
간단한 접근 방식은 모든 단계에서 데이터 병렬성을 활용하는 것입니다. 실제로 각 스레드에 동일한 수의 기본 요소가 제공되면 비닝 및 지오메트리 분할 프로세스가 완벽하게 병렬로 실행됩니다. 메모리 관리도 간단합니다. 각 스레드에는 다수의 기본 요소에 적합한 자체 위 풀 세트가 있습니다. 또 다른 접근 방식은 스레드당 하위 트리를 구축하는 것인데, 이때 기하학의 일부 초기 분해가 필요합니다. 그러나 지금까지의 초기 분해는 순차적으로 수행되었으며 실제로 이 단계에서는 병렬 솔루션도 사용할 수 있습니다.
가장 간단한 분해는 4개의 스레드 각각이 장면의 1M 삼각형 중 250K 삼각형을 처리하도록 사용 가능한 스레드 간에 기본 요소를 균등하게 배포하는 것입니다. 좋은 메모리 위치성에도 불구하고 이러한 기하학적 분해에는 분명한 단점이 있습니다. 즉, 서로 다른 스레드로 작성된 Kd-트리가 공간적으로 겹치고, 겹치는 kd-트리를 병합하는 알려진 방법이 없으며, 광선을 사용하여 여러 트리를 통과하면 렌더링 속도가 느려집니다. 기하학적 분할이 아닌 공간적 분할은 단일 트리로 쉽게 병합될 수 있는 겹치지 않는 kd-트리를 생성합니다. 일반적인 공간 파티셔닝으로 인해 로드 밸런싱이 제대로 이루어지지 않을 수 있습니다. 따라서 공간 영역의 병렬 처리에는 영역 선택을 위한 기하학적 분포 정보의 사용이 필요합니다.
본 논문에서는 하이브리드 병렬화 방식을 사용하여 데이터의 초기 분해(클러스터링)를 병렬로 수행하여 독립적으로 처리되는 작업을 생성합니다.

그리고 초기 클러스터 균형 분해가 사용됩니다.

최적화된 KD-Tree를 사용하면 다양한 시나리오의 가속 비율은 다음과 같습니다.

실제로 KD-Tree의 구성이 크게 개선되었지만 렌더링 성능이 저하되었음을 알 수 있습니다.
17.3.3 레이 트레이싱 그림자
레이 트레이싱의 첫 번째 추가 용도는 그림자 계산입니다. 점에서 광원까지 광선을 추적하여 점이 그림자에 있는지 여부를 확인할 수 있습니다. 빛이 길을 따라 불투명한 물체에 닿으면 물체는 그림자 속에 있게 됩니다. 그렇지 않은 경우 조명이 켜집니다. 불투명한 그림자에 대한 조명 개체의 교차점을 계산할 때 우리는 적중 또는 실패에만 관심을 둡니다. 교차점과 법선이 아닙니다. 포인트 라이트와 스포트라이트의 경우 표면 포인트와 광원 위치 사이의 광선을 추적합니다. 방향성 조명의 경우 빛의 방향을 따라 표면 지점에서 평행한 광선을 추적합니다.

(a) 그림자 광선; (b) 레이 트레이싱 그림자가 있는 찻주전자.
객체가 불투명한 경우 어떤 적중이라도 그림자를 결정하는 데 충분합니다. 그러나 물체가 반투명한 경우(예: 스테인드 글라스) 점과 광원 사이의 모든 교차 표면의 투과 색상을 얻은 다음 각 색상 구성 요소를 곱하여 투과 색상을 합성해야 합니다.
영역 조명은 부드러운 그림자를 만들고 전체 그림자와 전체 조명 사이의 영역을 반그림자라고 합니다. 부드러운 그림자는 영역 조명 표면의 임의 지점에 그림자 광선을 방출하여 계산할 수 있습니다. 아래 이미지(a)는 세 표면 지점에서 삼각형 영역 조명까지의 그림자 광선을 보여주며 일부 광선은 물체에 닿습니다. 이미지(b)는 익숙한 찻주전자 장면의 부드러운 그림자를 보여줍니다. 이 이미지에서 광원은 구형이고 부드러운 그림자는 분산 레이 트레이싱을 통해 계산됩니다.

(a) 영역 조명에 조명을 투사합니다. (b) 부드러운 그림자가 있는 찻주전자.
표면과 광원 사이에서 광선을 방출합니다.
빛이 무엇이든 닿으면 아무 일도 일어나지 않습니다(영역이 어두워지고 조명이 꺼집니다).
빛이 아무 것도 부딪치지 않고 광선에 도달하면 픽셀을 비춥니다.

각 표면 지점에 대해 하나의 광선을 방출하는 대신 여러 광선이 방출됩니다. 각 광선은 하드 섀도우의 경우와 동일하게 동작하며 픽셀당 모든 광선에 대한 결과를 평균화합니다.
빛이 모두 차단되면 표면이 완전히 가려집니다.
모든 빛이 광원에 도달하면 표면이 완전히 비춰집니다.
일부 빛이 차단되고 일부 빛이 표면에 도달하면 표면은 반그림자 영역에 있습니다.

해당 영역의 광원이 빛인 경우 빛은 표면에서 보이는 광원의 단면에 분산됩니다. 무한대에서 평행광을 사용하여 일광을 근사화하려면 표면에서 광선의 원뿔을 선택하십시오. 완벽하게 맑은 날을 나타내기 위해 원뿔의 입체각은 0입니다. 흐린 일광을 표현하려면 입체각이 더 커집니다. 표면 지점에 도달하는 입사광이 추정되고 있으며, 좋은 추정을 얻으려면 샘플이 영역을 균일하게 덮어야 합니다.

부드러운 그림자를 정확하게 샘플링하려면 많은 양의 빛이 필요하지만 이 프로세스는 GBuffer의 연속성을 보장하고 불필요한 빛을 방지하려고 합니다. 대부분의 이미지에서 표면 특성은 한 픽셀에서 인접 픽셀로 거의 변하지 않습니다. 따라서 G-버퍼의 한 픽셀에서 전송된 광선은 인접한 픽셀에서 전송된 동일한 광선과 동일한 객체에 부딪힐 가능성이 높습니다. 확실히 이 사실을 활용하여 광선 수를 줄이면서 시각적 정확성을 유지할 수 있는 방법이 있습니까?
인터리브 샘플링을 시도하여 인접한 픽셀의 그림자 조명 데이터를 활용할 수 있습니다. 프레임 버퍼에서

기존의 경계 볼륨 계층 구조는 많은 광선 삼각형 적중 테스트를 건너뛸 수 있으므로 GPU에서 계층 구조를 재구성해야 하며 동적 개체의 경우 트리 순회가 본질적으로 느립니다.
경계 볼륨 계층 구조를 구축하지 않고도 레이 트레이싱을 위한 프리미티브를 저장하세요! 그림자 맵의 경우 광원의 깊이를 간단하고 일관된 조회로 저장합니다. 프리미티브를 저장하는 것과 유사하게 깊은 프리미티브 그래프인 전면 삼각형 세트는 텍셀 단위로 저장됩니다. 깊이 기본 지도 도면(N x N x d)에는 3가지 리소스가 포함되어 있습니다.
프림 카운트 맵: 교차하는 삼각형 수를 계산하기 위해 하나의 원자를 사용하여 텍스처에 삼각형이 몇 개 있습니다.
프림 인덱스 맵: 프리미티브 버퍼에 있는 삼각형의 인덱스입니다.
프림 버퍼: 변환 후 삼각형.
img
d는 충분히 큰가요? 점유 시각화 - 검은색은 비어 있음을 의미하고 흰색은 가득 찼음을 의미하며 빨간색은 한계 초과를 의미하며 이는 알려진 모델을 사용하여 쉽게 수행할 수 있습니다.
img
GS는 3개의 정점과 SV_PrimitiveID를 PS로 출력합니다.
[maxvertexcount(3)]
void Primitive_Map_GS( triangle GS_Input IN[3], uint uPrimID : SV_PrimitiveID, inout TriangleStream<PS_Input> Triangles )
{
PS_Input O;
[unroll]
for( int i = 0; i < 3; ++i )
{
O.f3PositionWS0 = IN[0].f3PositionWS; // 3 WS Vertices of Primitive
O.f3PositionWS1 = IN[1].f3PositionWS;
O.f3PositionWS2 = IN[2].f3PositionWS;
O.f4PositionCS = IN[i].f4PositionCS; // SV_Position
O.uPrimID = uPrimID; // SV_PrimitiveID
Triangles.Append( O );
}
Triangles.RestartStrip();
}PS는 SV_PrimitiveID를 사용하여 그리기 호출 ID(셰이더 상수)를 해시하여 프리미티브의 인덱스/주소를 생성합니다.
float Primitive_Map_PS( PS_Input IN ) : SV_TARGET
{
// Hash draw call ID with primitive ID
uint PrimIndex = g_DrawCallOffset + IN.uPrimID;
// Write out the WS positions to prim buffer
g_PrimBuffer[PrimIndex].f3PositionWS0 = IN.f3PositionWS0;
g_PrimBuffer[PrimIndex].f3PositionWS1 = IN.f3PositionWS1;
g_PrimBuffer[PrimIndex].f3PositionWS2 = IN.f3PositionWS2;
// Increment current primitive counter uint CurrentIndexCounter;
InterlockedAdd( g_IndexCounterMap[uint2( IN.f4PositionCS.xy )], 1, CurrentIndexCounter );
// Write out the primitive index
g_IndexMap[uint3( IN.f4PositionCS.xy, CurrentIndexCounter)] = PrimIndex; return 0;
}텍셀과 접촉하는 모든 프리미티브를 캡처하려면 보수적인 래스터가 필요하며 소프트웨어나 하드웨어에서 수행할 수 있습니다. 하드웨어 보존적 래스터라이제이션 - DirectX 12 및 11.3에서 활성화된 삼각형이 닿는 모든 픽셀을 래스터라이제이션합니다: D3D12_RASTERIZER_DESC, D3D11_RASTERIZER_DESC2.
img
소프트웨어 보수적 래스터라이제이션 - GS를 사용하여 클립 공간에서 삼각형을 펼치고 AABB를 생성하여 PS에서 삼각형을 클리핑합니다. GPU Gems 2 - 42장을 참조하세요.
img
레이 트레이싱 중에 기본 좌표가 계산되고(그림자 맵과 동일) 기본 인덱스 배열이 탐색되며 각 인덱스에 대해 광선 감지를 위해 삼각형이 사용됩니다.
float Ray_Test( float2 MapCoord, float3 f3Origin, float3 f3Dir, out float BlockerDistance )
{
uint uCounter = tIndexCounterMap.Load( int3( MapCoord, 0 ), int2( 0, 0 ) ).x;
[branch]
if( uCounter > 0 )
{
for( uint i = 0; i < uCounter; i++ )
{
uint uPrimIndex = tIndexMap.Load( int4( MapCoord, i, 0 ), int2( 0, 0 ) ).x;
float3 v0, v1, v2;
Load_Prim( uPrimIndex, v0, v1, v2 );
// See “Fast, Minimum Storage Ray / Triangle Intersection“
// by Tomas Möller & Ben Trumbore
[branch]
if( Ray_Hit_Triangle( f3Origin, f3Dir, v0, v1, v2, BlockerDistance ) != 0.0f )
{
return 1.0f;
}
}
}
return 0.0f;
}
img
왼쪽: 3k x 3k 셰이딩 맵; 오른쪽: 3k x 3k 셰이딩 맵 + 1K x 1K x 64 PM.
안티앨리어싱을 위해 추가 조명을 사용할 수 있습니까? 비용이 너무 많이 든다! 간단한 트릭을 사용할 수 있습니다. 화면 공간 AA 기술(예: FXAA, MLAA 등)을 적용합니다.
하이브리드 방법 - 레이 트레이싱된 그림자를 기존의 부드러운 그림자와 결합하고, CHS 또는 PCS와 같은 고급 필터링 기술을 사용하고, 차단기 거리를 사용하여 Lerp 계수를 계산합니다. 차단기 거리 -> 0인 경우 레이 트레이싱 결과가 널리 사용됩니다. 보간 인자 시각화:
img
L = saturate( BD / WSS * PHS )
L: Lerp factor
BD: Blocker distance (from ray origin)
WSS: World space scale – chosen based upon model
PHS: Desired percentage of hard shadow
FS = lerp( RTS, PCSS, L )
FS: Final shadow result
RTS: Ray traced shadow result (0 or 1)
PCSS: PCSS+ shadow result (0 to 1)축소 반그림자 필터링을 사용하십시오. 그렇지 않으면 레이 트레이싱 결과에 부드러운 그림자 결과가 완전히 포함되지 않아 두 시스템 간에 LERPS를 수행할 때 문제가 발생하게 됩니다.
img
효과 비교:
img
다양한 기본 복잡성의 효과, 소비 및 성능은 다음과 같습니다.
img
현재는 단일 광원으로 제한되어 있으며 전체 장면에 적용하도록 크기를 조정할 수 없습니다. 저장이 제한 요인이 되지만 가장 가까운 모델(현재 초점 모델, 가장 최근에 계단식으로 연결된 콘텐츠)에서 가장 잘 작동합니다. 간단히 말해서, AA 레이 트레이싱 하드 섀도우의 성능은 전통적인 섀도우 맵 문제를 해결하는 데 매우 좋으며, 하이브리드 섀도우는 엔진을 다시 작성하지 않고도 두 세계의 장점을 결합하며 게임이 충분히 빠릅니다!
2017년 월드 오브 탱크는 다양한 최적화 방법을 통해 DirectX 11 이상의 그래픽 플랫폼에서 레이 트레이싱 섀도우를 구현했습니다. 그들은 BVH를 구축하는 데 사용되는 Intel Embree를 사용하여 하드웨어 RT 코어 없이 물리적으로 정확한 부드러운 그림자를 실시간 레이 트레이싱으로 구현하여 World of Tanks를 D3D11에서 실시간 RT 그림자를 사용하는 최초의 게임으로 만들었습니다.

레이 트레이싱 소프트 섀도우를 켠 상태(왼쪽)와 껐을 때(오른쪽)를 적용한 월드 오브 탱크 비교.
라이트 트레이싱 소프트 섀도우를 구현할 때 CPU 측 로직과 GPU 측 로직으로 구분됩니다. CPU 측에는 2단계 가속 구조가 포함되어 있습니다.
BLAS(저층 가속 구조) BVH. 메시 로딩 중에 한 번 생성되어 GPU에 업로드되는 모든 탱크 모델에 적용됩니다. 메시의 하드 스킨 부분은 여러 정적 BVH로 분할되고 소프트 스킨 부분은 건너뜁니다.
TLS(Top Top 가속 구조) BVH. Intel Embree 및 Intel TBB를 사용하는 멀티스레드 방식으로 각 프레임이 재구성되어 GPU에 업로드됩니다.
CPU BVH는 TBB 스레드, SSE 4.2(원래 WoT 내부 BVH 빌더보다 5.5배 빠름)를 사용하고 프레임당 최대 5mb의 GPU 데이터와 최대 72mb의 정적 GPU 데이터를 업데이트하여 CPU 프레임 시간의 2.5%를 차지합니다. 다음 그림은 CPU 측의 각 단계 소비를 보여줍니다.

GPU 측에서 픽셀 셰이딩 또는 계산 셰이딩을 수행합니다.
균일한 원뿔 분포를 기반으로 하는 시간적 광선 지터.
BVH 횡단 및 광선 삼각형 교차점.
시간 축적.
디노이저(SVGF 기반).
시간적 안티앨리어싱(TAA).
다음 그림은 GPU 측의 각 단계 소비를 보여줍니다.

)
월드 오브 탱크에는 조명 추적 그림자가 최적화되어 있습니다. RT 그림자는 탱크에서만 투사할 수 있으며 알파 테스트를 거친 형상을 지원하지 않습니다. BLAS는 LOD를 사용하며 픽셀당 1개의 광선만 방출합니다. 다음 조건 중 하나가 발생하면 광선의 픽셀이 추적되지 않습니다.
NdotL <= 0.
픽셀이 그림자 맵에 의해 가려진 경우.
카메라로부터 300m 이상 떨어져 있습니다.
이 방법을 사용하여 얻은 실시간 조명 추적 그림자의 성능 매개변수는 다음과 같습니다.

Northlight Engine에서 구현한 빛 추적 그림자와 기존 Shadow Map 그림자의 비교는 다음과 같습니다.

1080p의 픽셀당 단일 광선은 4ms 미만만 소비합니다. 다음 그림은 픽셀당 단일 광선을 부분적으로 확대한 것입니다.

Claybook은 부드러운 그림자 영역 추적, 부드러운 반음영으로 그림자 확장, 광선을 따라 SDF를 단계적으로 진행하여 최대 원뿔 적용 범위 근사화, 데모씬 원뿔 범위 근사화를 사용합니다.
c = min(c, light_size * SDF(P) / time);
img
그리고 부드러운 그림자가 개선되었습니다. 즉, 가장 가까운 거리 삼각 측량, Demoscene=단일 샘플(최소), 현재 및 이전 샘플 삼각 측량, 밴딩 감소 등이 있습니다. 디더링된 그림자 조명, UE4 시간 누적, 남은 밴딩 아티팩트 숨기기, 더 넓은 내부 반그림자.
img
개선 전후 비교:
img
과거에는 LTC가 차단된 조명을 처리할 수 없었지만 보다 사실적인 빛과 그림자는 다음과 같은 기능을 갖추어야 합니다.
img
이전 문헌에서는 빛 추적만 사용하는 부드러운 그림자를 제안했습니다. 이 방법은 평균 가시성입니다.
img
그러나 BRDF를 사용하여 직접 조명을 얻은 다음 부드러운 그림자에 빛 추적의 평균 가시성을 곱하면 잘못된 결과를 얻게 됩니다.
img
올바른 접근 방식은 아래 그림의 오른쪽과 같습니다.
img
무작위화도 사용할 수 있지만 BRDF의 모든 용어는 강제로 무작위화되어야 합니다.
img
무작위화의 결과는 너무 많은 노이즈와 너무 많은 흐림입니다.
img
따라서 레이 트레이싱된 부드러운 그림자와 완전한 무작위화라는 두 가지 솔루션 모두 잘못된 결과를 얻게 됩니다. 올바른 부드러운 그림자 알고리즘은 다음과 같아야 합니다.
img
수학적으로, 우리는 모든 것이 확실히 정확하다는 것을 알 수 있습니다:
img
해당하는 올바른 무작위화 공식은 다음과 같습니다.
img
보다 정확한 방법은 다음과 같이 도출됩니다.
img
img
올바른 소음 감소를 위한 각 주파수의 기능은 다음과 같습니다.
img
소음 감소 범례:
img
샘플링 측면에서 다중 중요도 샘플링이 사용됩니다.
img
유전체(비금속) 전해질의 경우 다중 중요도 샘플링이 사용됩니다.
img
최종 효과 비교:
img
렌더링 패스 및 프로세스는 다음과 같습니다.
img
요약하면, 비율 추정기: 잡음 없는 편향 분석 + 편견 없는 잡음 확률론적, 강력한 잡음 추정으로서의 전체 변동(비분산), 분석적 색상 지정에 따른 음영 다중 중요도 샘플링. 실시간 레이 트레이싱 GPU 고려 사항에는 활동 상태, 대기 시간 및 점유, 다중 중요도 샘플링을 통한 분기, 파면 및 인라인, 혼합 광선+래스터 그래픽 예제가 포함됩니다.
17.3.4 레이 트레이싱 AO
앰비언트 오클루전(AO)은 흐린 날의 실외 조명과 유사하게 매우 넓은 면적의 광원(즉, 각 지점 위의 전체 반구)에서 나오는 조명으로 생각할 수 있습니다. 아래 이미지(a)는 두 표면 지점에서 나오는 앰비언트 오클루전(AO) 광선을 보여줍니다. 왼쪽 지점에서는 대부분의 빛이 물체에 닿으므로 폐색이 높습니다. 올바른 지점에서는 물체에 빛이 거의 닿지 않으므로 폐색이 거의 없습니다. 그림 (b)는 주전자 장면의 앰비언트 오클루전(AO)을 보여줍니다. 이 이미지는 순수한 앰비언트 오클루전(AO)을 보여줍니다. 물론 표면 색상, 질감 등을 결합할 수도 있습니다.

Northlight Engine에서 구현한 SSAO와 광 추적 AO 간의 비교 차트는 다음과 같습니다.

다른 rpp(픽셀당 광선 수)에서는 광 추적 AO 효과도 다릅니다.
img
Claybook은 표면 노멀 방향으로 원뿔을 구성하고 임의 변형 + 시간 누적을 더해 AO 광선은 낮은 SDF 밉, 더 나은 GPU 캐시 위치 및 더 적은 대역폭, 소프트 원격 AO를 사용합니다. Claybook은 UE4의 SSAO, 소규모 앰비언트 오클루전(AO)도 사용합니다.
img
상위: SSAO; 하단: SSAO + RTAO.
17.3.5 레이 트레이싱 반사
빛이 완벽하게 반사되는 표면에 닿으면 입사각과 동일한 각도로 반사됩니다. 물리학의 기본법칙은 기원전 3세기 유클리드에 의해 처음으로 성문화되었습니다. 반사 물체는 금속 공뿐만 아니라 현실 세계에서 흔히 볼 수 있습니다!
레이 트레이싱 반사의 경우 반사 표면에서 추가 광선이 방출되고 반사 법칙을 사용하여 입사 광선의 방향에서 반사 광선의 방향이 계산됩니다. 빛이 장면의 객체에 닿으면 직접 보이는 표면과 동일한 조명 계산을 사용하여 표면이 음영 처리됩니다.

간접광의 광택 반사는 광택 반사 분포 방향으로 광선을 방출하여 계산할 수 있습니다. 주어진 입사 방향과 한 쌍의 난수에 대해 반사 모델은 반사 방향을 제공합니다. 아래 이미지는 두 찻주전자의 광택 반사를 보여줍니다. 반사는 Ward(등방성) 광택 반사 모델을 사용하여 계산되었습니다.

마찬가지로 광택 굴절은 굴절 방향 주위로 광선을 분산시켜 계산할 수 있으며, 이는 약간 젖빛 유리처럼 보일 수 있습니다.
SSR과 광 추적 반사 사이에는 분명한 차이점도 있습니다. SSR은 물체의 뒷면을 반사할 수 없으며 화면 외부에 집계되지만, 광 추적 반사에는 이러한 제한이 없습니다.

Northlight Engine에서 구현한 빛 추적 반사의 구성 요소 및 복합 효과는 다음과 같습니다.


밝기가 다르면 다른 rpps가 사용됩니다. 즉, 밝기가 더 높은 픽셀에 더 많은 빛이 사용되며 억제 요소가 사용되며 최종적으로 최적화를 위해 그림자 맵과 결합됩니다. 조합 프로세스 다이어그램은 다음과 같습니다.




17.3.6 레이 트레이싱 굴절
Siggraph 2008부터 Zhou Kun의 팀은 이미 레이 트레이싱을 기반으로 한 굴절 효과를 연구하여 놀라운 굴절, 반사, 화선, 다중 굴절, 그림자, 분산 및 기타 효과를 달성했습니다(아래 그림). 결과는 Interactive Relighting of Dynamic Refractive Objects라는 논문에 게재되었습니다.

구현 프로세스에는 주로 객체 복셀화, 탐색 속도를 높이기 위한 옥트리 구조 생성, 적응형 광자 추적을 사용하여 효율적이고 현실적인 레이 트레이싱 효과 달성이 포함됩니다.

복셀화 프로세스는 삼각형 메시를 체적 데이터로 변환합니다.

복셀화는 GPU Gems III [Crane 07]의 기술을 사용하여 표면 근처에만 슈퍼샘플링을 추가하고 가우스 스무딩을 추가합니다.
옥트리를 구성할 때 희소 트리 대신 조밀한 3차원 배열을 사용하고 굴절률과 소광계수를 고려하며 구성은 밉맵과 유사하다.

광자를 생성할 때 경계 상자에 광자가 생성되고, 굴절 개체의 표면에 광자가 생성되며, 주변 볼륨은 비어 있어야 하며, 폐색을 완료하려면 그림자 맵이 필요합니다.

각 광자 단계에 대한 선분을 사용하여 방사선을 복셀에 직접 저장합니다.

그런 다음 표면적의 크기에 따라 다른 정밀도 데이터(즉, 옥트리의 다른 노드 데이터)를 사용합니다.


다양한 추적 기술의 효과 비교:

뷰 패스에서는 곡선이 빛의 궤적을 관찰하고, 광채를 모으고, 산란과 감쇠를 고려합니다. 또한 이미지는 단계 크기에 민감하고 성능이 충분하므로 옥트리를 무시합니다.
2008년경에는 128 x 128 x 128 볼륨 해상도, 1024 x 1024 초기 광자, 640 x 480 이미지 해상도 및 NVIDIA GeForce 8800 Ultra를 사용한 렌더링을 사용하여 2~7fps를 달성할 수 있었습니다.
17.3.7 레이 트레이싱 간접 확산
Wardet al. 간접 디퓨즈을 계산하려면 광역 분포 레이 트레이싱을 사용합니다. 반사 광선의 분포는 코사인 가중치 분포로 각 지점 위의 전체 반구를 덮으므로 적도(아래) 근처 방향보다 극 쪽 방향으로 더 많은 광선이 추적됩니다. 이 이미지에는 주변 광원이 없습니다. 그림자 영역의 모든 빛은 간접광의 디퓨즈로 인해 발생합니다. 특히 흰색 체커판이 찻주전자 바닥에 반사되는 방식과 오른쪽 찻주전자의 주둥이가 찻주전자 본체 근처 부분에 어떻게 빛을 투사하는지 확인하세요. 이 효과는 색상 번짐(이 경우 "색상"은 흰색임)이라고도 하며 근처 개체의 색상으로 표면에 색조를 줄 수 있습니다.

Northlight Engine은 간접 디퓨즈를 구현한다는 점에서 AO와 유사합니다. 일관되지 않은 광선이 많고 GI는 희박한 그리드 볼륨에 저장됩니다. 정적 기하학과 정적 조명 세트를 기반으로 계산된 방사조도입니다. 동적 형상은 빛을 받을 수 있지만 계산된 방사조도에는 영향을 미치지 않습니다. 동적 기하학은 기여하지 않습니다. 삼선형 샘플링은 계단형 패턴을 생성합니다. 얇은 형상으로 인해 빛샘이 발생할 수 있습니다. 조명은 코사인 분포에서 방사선을 샘플링하여 수집됩니다. 누락된 형상을 고려하십시오(아래).
img
직접 샘플링, AO 및 광 추적 수집의 효과는 다음과 같습니다.


17.3.8 레이 트레이싱 반투명도
진정한 투명성은 알파 블렌딩이 아닙니다! 실제 광학에서는 빛이 반투명 물체를 통과할 때 일부 빛은 흡수되고 일부 빛은 흡수되지 않습니다. 반사된 빛의 그림자처럼 표면의 뒷면에서 빛을 방출합니다. 시퀀스는 독립적입니다.

그림자 광선이 투명한 물체에 닿으면 빛을 향해 계속됩니다. 투명한 물체에 닿는 그림자 광선은 마치 그림자 광선이 아닌 것처럼 음영 처리되고 다시 방출되어야 합니다. 그림자 광선은 표면의 완전히 투명한 영역을 통과하며 그림자 광선은 반투명 개체에서 색상을 얻습니다.

위의 기능 외에도 레이 트레이싱은 상호 반사, 번짐, 화선, 분산, DOF, 모션 블러, 복잡한 반투명도, 체적 안개, 참여 미디어 및 기타 효과를 얻을 수도 있습니다.

안개를 통해 구에 빛나는 스포트라이트. 스포트라이트 조명 분포의 모양과 구 그림자는 참여 매체의 추가 산란으로 인해 명확하게 표시됩니다.
17.3.9 소음 감소 기술
노이즈 감소 기술은 BRDF의 가시적인 용어에만 사용됩니다(조명 용어는 분석적 근사치를 사용함).

실시간 레이 트레이싱 분야에는 유도된 흐림 커널을 사용한 필터링, 기계 학습 기반 필터 또는 중요도 샘플링, 더 나은 준 무작위 시퀀스(예: 블루 노이즈 및 시공간 누적)를 통한 향상된 샘플링 방식, 일부 공간 구조(예: 프로브, 방사 조도 캐시)로 결과를 정량화하려는 근사 기술과 같은 많은 노이즈 제거 알고리즘이 있습니다.
필터링 기술. 가우스, 양측, À-Trous, 유도 및 중앙값이 있습니다. 이러한 방법은 몬테카를로 추적의 흐릿한 사진을 필터링하는 데 자주 사용됩니다. 특히, 기능 버퍼(예: 디퍼드 렌더링 GBuffer)와 특수 버퍼(예: 첫 번째 바운스 데이터, 재투영 경로 길이, 뷰 위치)에 의해 구동되는 안내 필터가 널리 사용되었습니다.
샘플링 기술. TAA, 시공간 필터, SVGF(Spatio-Temporal Variance Guided Filter), A-SVGF(Adaptive SVGF), BMFR(Blockwise Multi-Order Feature Regression), ReSTIR(Spatiotemporal Importance Resampling for Many-Light Ray Tracing)과 같은 기술이 있습니다.
근사 기술. 경로 추적기 동작의 다양한 측면을 미세 조정하는 데 일반적으로 사용됩니다.
딥 러닝 노이즈 감소 기술. 일반적인 것에는 DLSS, OIDN, Optix 등이 있습니다. Intel 및 NVIDIA와 같은 업계 리더들은 기계 학습 기반 노이즈 제거기에 대한 연구를 후원했습니다. Intel Open Image Denoise와 NVIDIA Optix Autoencoder는 모두 잡음 제거 자동 인코더를 사용하여 이미지의 잡음을 제거하는 데 큰 성공을 거두었습니다. NVIDIA의 Deep Learning Super Sampling(DLSS 2.0)은 원본 이미지의 일부를 기본 해상도로 업샘플링하여 계산 비용을 줄이는 것을 목표로 Minecraft RTX, Remedy Entertainment의 Control 등과 같은 레이 트레이싱 애플리케이션을 업그레이드하는 데에도 사용됩니다. DLSS(Deep Learning Supersampling)는 작은 색상 버퍼와 방향 맵을 사용하여 출력 해상도를 2~4배로 늘리는 업스케일링 기술입니다. NVIDIA의 사전 승인을 받은 개발자에게만 제공되므로 현재 공개적으로 사용할 수 없습니다. 즉, DirectML의 SuperResolution 샘플과 같은 대안이 있습니다.

NVIDIA의 AI 기반 소음 감소 아키텍처 다이어그램.
아래에서는 분석을 위한 몇 가지 중요한 소음 감소 기술을 추출합니다.
17.3.9.1 SVGF/A-SVGF
SVGF(Spatiotemporal Variance Guided Filter)[Schied 2017]는 특징 버퍼(예: 일반, 깊이 및 분산 계산)와 함께 시공간 재투영을 사용하여 양측 필터를 구동하여 높은 분산 영역을 흐리게 하는 잡음 제거기입니다.
SVGF는 노이즈가 있는 입력을 전체 이미지로 변환하고 일반적으로 실행하는 데 10밀리초가 걸리므로 이를 실시간 레이 트레이싱기에 통합해도 이점이 없을 수 있습니다. 딥 러닝 필터는 더 짧은 시간에 유사한 작업을 수행할 수 있습니다. 그러나 이 기술은 최종 이미지, 특히 조정된 버전(A-SVGF, Adaptive SVGF)을 재구성하는 데 매우 효과적입니다. NVIDIA는 이전 대화형 재구성 필터보다 약 10배 더 빠른 시간 안정적인 결과를 제공하고, 참조 이미지를 5~47% 더 잘 일치시키며(SSIM에 따르면), 1920x1080 해상도의 최신 그래픽 하드웨어에서 단 10ms(15% 오류 이내) 만에 실행된다고 주장합니다.
적응형 시공간 분산 유도 필터링(A-SVGF) [Schied et al. 2018]은 SVGF를 개선하고 깜박임과 같은 문제를 제거하는 새로운 기술입니다. SVGF는 시간적 특징(예: 순간 버퍼에 인코딩된 분산, 시야각 변화 등)에 따라 공간적으로 재투영된 이전 샘플을 적응적으로 재사용하고 빠른 양방향 필터로 필터링하여 개선됩니다. 따라서 기록 길이를 기준으로 샘플을 축적하는 대신 모멘트 버퍼는 분산 변화를 사용하여 이전 샘플과 새 샘플의 비율을 구동하여 고스팅을 줄이는 대리 톤 역할을 합니다. SVGF는 블러링을 구동하기 위해 모멘트 버퍼만 사용하는 반면, A-SVGF는 필터링 및 축적 단계에 이를 사용합니다.
순간 버퍼를 도입하면 시간 지연을 제거하는 데 도움이 되지만 시간 지연이 완전히 제거되지는 않습니다. 누적된 샘플 수가 많은 영역과 새로운 영역 간에 밝기 차이가 발생할 수 있습니다. 이는 실내와 같이 레이 트레이싱 장면의 어두운 영역에서 특히 두드러집니다. 이를 완화하려면 픽셀당 1샘플(1spp)을 사용하는 것보다 장면의 어두운 영역에서 2spp를 사용하는 것이 좋습니다.
Quake 2 RTX는 A-SVGF를 노이즈 제거 솔루션으로 사용합니다.

경로 추적기/레이 트레이싱기는 재구성 필터를 통과하여 병합되는 직접 및 간접 조명을 제공합니다. 그런 다음 결과에 톤매핑 및 TAA를 적용합니다(톤매핑 + DLSS로 대체 가능).

지)
위에서 보인 것처럼 재구성 필터는 시간적 누적을 사용하여 적분된 색상/모멘트를 결정하고 분산 추정을 사용하여 필터링된 색상을 얻습니다. 즉, 노멀, 알베도, 깊이, 모션 벡터 및 메쉬 ID를 제공하려면 히스토리 버퍼(이전 프레임에서 재구성)와 래스터라이저가 필요합니다.
Minecraft RTX는 특수한 형태의 SVGF를 사용하고, 조도 캐싱을 추가하고, 광선 길이를 사용하여 반사를 더 효율적으로 구동하고, 물과 같은 투과 표면에 대해 분할 렌더링을 수행합니다. SVGF는 매우 효과적이지만 게임에서 눈에 띄는 시간 지연을 발생시킵니다.
17.3.9.2 ReSTIR
다중 레이 트레이싱(ReSTIR)을 통한 시공간 중요성 리샘플링 [Bitterli et al. 2020]은 인접한 샘플링 확률의 통계를 재사용하여 실시간 노이즈 제거 장치의 시공간 재투영 단계를 렌더링 초기로 이동하려고 시도합니다. 본질적으로 리샘플링 중요도 샘플링을 논의한 이전 논문과 시공간 노이즈 제거기에 의해 도입된 아이디어의 추가를 결합한 것입니다.
ReSTIR은 NVIDIA의 RTXDI SDK와 함께 사용할 수 있습니다. UE 브랜치에서 NV에 의해 구현되었으며, 소스 코드는 https://github.com/NvRTX/UnrealEngine/tree/NvRTX_Caustics-4.27에 있습니다. 자세한 원리는 시공간 저장소 리샘플링(ReSTIR) - 이론 및 기본 구현을 참조하세요.
17.3.9.3 DLSS
DLSS가 등장하기 전에 NV에는 이미 AI 슈퍼샘플링이 있었습니다.

DLSS는 AI의 학습 기능을 사용하여 저해상도 입력 이미지를 고화질(기본 해상도에 가까운) 이미지로 업샘플링합니다.


DLSS2.0을 사용하여 1080P를 4K로 업샘플링하면 기본 4K에 비해 성능이 크게 향상됩니다(2배~5배).

기존의 기본 앤티앨리어싱 알고리즘은 저해상도 픽셀을 보간하여 고해상도 이미지를 재구성합니다. 일반적인 선택은 쌍선형, 쌍입방형, 란초 및 대비 인식 선명화입니다. 심층 신경망은 이전 데이터 또는 훈련 데이터를 기반으로 하는 기존 픽셀을 기반으로 환각을 생성할 수 있습니다. 기본 고해상도 이미지에 비해 디테일이 부족한 이미지를 생성합니다. 이미지는 기본 렌더링과 일치하지 않을 수 있으며 아티팩트로 인해 일시적으로 불안정할 수 있습니다.


TAAU와 같은 추가 알고리즘은 이웃 차단을 사용하여 고주파 신호와 새로 나타나는 신호를 큰 변화로 재구성하여 모아레, 깜박임, 흐림 및 고스팅과 같은 결함을 발생시킵니다.

실시간 초해상도의 과제는 단일 프레임 방법, 흐릿한 이미지 품질, 기본 렌더링과의 불일치 및 시간적 불안정성입니다. 다중 프레임 방법의 경우 프레임 전체의 변경 사항을 감지하고 수정하는 데 사용되는 경험적 방법, 흐려짐, 시간적 불안정성 및 고스팅으로 이어지는 경험적 제한 사항입니다.
신호를 재구성할 때 안티앨리어싱과 같은 기존 알고리즘과 달리 DLSS 2.0은 수천 장의 고품질 이미지에서 훈련된 신경망을 사용하여 DL(딥 러닝)의 다중 프레임 재구성을 기반으로 합니다. 신경망은 손으로 만든 휴리스틱보다 더 강력하며 고품질 재구성을 위해 여러 프레임의 샘플을 사용하므로 보다 정확한 재구성 신호를 얻습니다(아래).

왼쪽: 원래 고주파 신호; 중간: 원래 신호와 큰 차이가 있는 비-DL 기술을 사용하여 재구성합니다. 오른쪽: 원래 신호에 더 정확하게 맞는 DLSS 재구성 방법.
DLSS 2.0은 동일한 렌더링 비용으로 더 나은 해상도와 이미지 품질을 달성합니다.

다음은 1080P+TAA, 540P+DLSS 2.0 및 540P+TAAU의 사진 비교입니다.

게임 엔진에 DLSS를 통합해야 하는 경우 단계 개요는 다음과 같습니다.

기하학/셰이딩 단계, TAA가 DLSS로 대체되기 때문에 TAA에 의존하는 잡음 제거 장치는 잡음 제거 장치를 개선하거나 잡음 제거 후 전용 TAA 채널을 추가해야 합니다.

DL 업샘플링을 사용하려면 NGX SDK를 통해 노이즈 감소 및 앤티앨리어싱 이미지에 입력하고 처리하려면 다음 정보가 필요합니다.

포스트(포스트 프로세싱) 단계는 확대된 해상도로 렌더링되며 엔진은 지오메트리 및 셰이딩과 다른 포스트 프로세싱 해상도를 처리해야 합니다.

DLSS의 주요 개발자는 Yan Lingqi의 남동생인 Wen Dao Qiuer입니다. 그는 블로거처럼 사진을 사랑하는 사람입니다.

.
17.3.9.4 소음 감소 구현
이상적인 노이즈 제거 장치는 최신 기술 문서의 아이디어와 범례 및 단계를 아래와 같이 통합합니다.

1.프리패스
장면의 NDC 공간 속도를 계산하고 알베도, 노멀 등과 같은 일반적인 G 버퍼 첨부 파일을 작성합니다. 래스터 기반 프리패스가 아닌 레이 트레이싱 기반 프리패스가 필요한 이러한 버퍼의 첫 번째 바운스 버전이 있을 수도 있습니다.

노이즈 제거 전에 일종의 범용 채널(G 채널)을 사용하여 정상, 알베도, 깊이/위치, 객체 ID, 러프니스/메탈릭 등과 같은 재료 정보를 인코딩하는 것이 중요합니다. 또한 액세스 속도는 이전 샘플을 현재 위치로 변환할 수 있습니다. 속도 버퍼는 렌더링된 각 정점의 이전 및 현재 NDC 공간 좌표 위치를 결정하고 둘 사이의 차이를 취하여 계산할 수 있습니다.
따라서 객체의 이전 프레임 modelViewProjection 행렬과 객체의 애니메이션 버텍스 속도(현재 애니메이션 샘플과 이전 애니메이션 샘플 간의 위치 차이)가 필요합니다.

// NDC space velocity
float3 ndc = inPosition.xyz / inPosition.w;
float3 ndcPrev = inPositionPrev.xyz / inPositionPrev.w;
outVelocity = ndc.xy - ndcPrev.xy;첫 번째 바운스 광택 반사에 모션 벡터를 사용하거나, 객체가 움직일 때 더 나은 그림자 재투영을 위해 그림자 모션 벡터를 사용하거나, 폐색에 이중 모션 벡터를 사용하는 등 이 개념을 더 발전시킬 수 있습니다. [Zeng 외, 2021]
2.레이 트레이스
AI 적응형 샘플링 및 샘플 매핑을 사용합니다 [Kuznetsov et al. 2018] [Hasselgren et al. 2020] 소금/후추와 같은 아티팩트를 방지하고 시간이 지남에 따라 밝기를 유지하는 데 도움이 되도록 더 많은 샘플(일반적으로 하이라이트/그림자)을 받아야 하는 영역을 더 잘 결정합니다. 반사 노이즈 제거는 첫 번째 바운스 데이터를 더 잘 처리하고 전역 조명, 앰비언트 오클루전(AO) 및 그림자는 더 적은 데이터를 기반으로 더 간단한 시공간 누적을 사용할 수 있기 때문에 별도의 첨부 파일로 반사 및 전역 조명을 별도의 노이즈 제거기에 기록하는 것이 이상적입니다.
3. 축적
시공간 재투영을 가능한 한 자주 사용하십시오. 이는 Lambertian 데이터(예: 전역 조명/앰비언트 오클루전(AO))에 대해 달성하기가 더 쉽고 반사광 데이터(예: 반사)에 대해서는 달성하기가 더 어렵습니다. 더 나은 결과를 얻으려면 휴리스틱 데이터(예: 노멀/알베도/객체 ID)를 사용하여 이전 샘플을 현재 위치로 변환하고 첫 번째 바운스 데이터(예: 보기 방향, 첫 번째 바운스 노멀, 반사율 등)를 사용하세요. 성공적인 재투영은 중요도 샘플에 사용될 수 있습니다 [Bitterli et al. 2020] 또는 휘도를 복사 이력 버퍼에 인코딩합니다 [Schied et al. 2018].
시공간 재투영은 이전 프레임의 데이터를 재사용하여 현재 프레임에 공간적으로 재투영합니다. 이전 샘플을 현재 프레임으로 변환하려면 먼저 뷰 공간에서 이전 프레임 데이터의 좌표를 찾아야 하며, 이는 속도 버퍼를 추가하여 수행할 수 있습니다. 이 화면 공간 좌표의 현재 위치/노멀/객체 ID 등과 이전 좌표 간의 차이를 비교하여 객체가 가려졌는지, 현재 표시되는지, 이전 샘플을 재사용하는지 알 수 있습니다.

시공간 재투영을 수행할 때 주어진 샘플이 누적되어야 하는 시간을 설명하는 버퍼, 즉 히스토리 버퍼를 갖는 것이 중요합니다. 누적된 샘플이 적은 영역에서 더 강하게 블러되도록 필터를 구동하거나 현재 이미지의 분산을 추정하는 데 사용할 수 있습니다(히스토리가 높을수록 분산이 작아짐을 의미함).
outHistoryLength = successfulReprojection ? prevHistoryLength + 1.0 : 0.0;그러면 히스토리 길이는 최종 방사선에 대한 현재 샘플의 기여 계수인 누적 계수
outColor = lerp(colorPrevious, colorCurrent, accumulationFactor);히스토리 버퍼는 유용하지만 성공적인 재투영 비율보다 누적 요소를 결정하는 더 좋은 방법이 있으며 통계 분석을 사용하여 시간 지연을 방지할 수 있습니다.
4. 통계 분석
현재 레이 트레이싱 이미지의 변화를 추정하고, 밝기/속도의 변화 변화를 계산하고, 이를 사용하여 시공간 재투영 및 필터링을 구동합니다. 이 차이 정보를 사용하여 반딧불(특이한 밝은 점)을 거부해 보세요.
분산은 신호 평균(평균)의 차이 제곱입니다. 현재 신호의 평균을 구한 다음 3x3 가우스 커널(기본적으로 텐서)을 사용한 다음 둘 사이의 차이를 구할 수 있습니다.
const float radius = 2; // 5x5 kernel
float2 sigmaVariancePair = float2(0.0, 0.0);
float sampCount = 0.0;
for (int y = -radius; y <= radius; ++y)
{
for (int x = -radius; x <= radius; ++x)
{
// Sample current point data with current uv
int2 p = ipos + int2(xx, yy);
float4 curColor = tColor.Load(p);
// Determine the average brightness of this sample
// Using International Telecommunications Union's ITU BT.601 encoding params
float samp = luminance(curColor);
float sampSquared = samp * samp;
sigmaVariancePair += float2(samp, sampSquared);
sampCount += 1.0;
}
}
sigmaVariancePair /= sampCount;
float variance = max(0.0, sigmaVariancePair.y - sigmaVariancePair.x * sigmaVariancePair.x);Christoph Schied는 A-SVGF의 공간적 변화를 가장자리 회피 guassian 필터(A-trous 유도 필터와 유사)의 조합으로 추정하고 이를 피드백 루프에서 사용하여 시공간 재투영 중에 누적 인자를 구동합니다. 축적을 관리하는 것 외에도 분산을 추정하면 일시적으로 필터의 가중치를 줄일 수 있습니다. [Olejnik et al. 2020]은 접촉 그림자의 더 나은 렌더링을 위해 A-SVGF와 유사한 포아송 디스크 필터를 사용합니다.
/**
* Variance Estimation
* Copyright (c) 2018, Christoph Schied
* All rights reserved.
* Slightly simplified for this example:
*/
// Setup
float weightSum = 1.0;
int radius = 3; // ⚪ 7x7 Gaussian Kernel
float2 moment = tMomentPrev.Load(ipos).rg;
float4 c = tColor.Load(ipos);
float histlen = tHistoryLength, ipos, 0).r;
for (int yy = -radius; yy <= radius; ++yy)
{
for (int xx = -radius; xx <= radius; ++xx)
{
// We already have the center data
if (xx != 0 && yy != 0) { continue; }
// Sample current point data with current uv
int2 p = ipos + int2(xx, yy);
float4 curColor = tColor.Load(p);
float curDepth = tDepth.Load(p).x;
float3 curNormal = tNormal.Load(p).xyz;
// Determine the average brightness of this sample
// Using International Telecommunications Union's ITU BT.601 encoding params
float l = luminance(curColor.rgb);
float weightDepth = abs(curDepth - depth.x) / (depth.y * length(float2(xx, yy)) + 1.0e-2);
float weightNormal = pow(max(0, dot(curNormal, normal)), 128.0);
uint curMeshID = floatBitsToUint(tMeshID, p, 0).r);
float w = exp(-weightDepth) * weightNormal * (meshID == curMeshID ? 1.0 : 0.0);
if (isnan(w))
w = 0.0;
weightSum += w;
moment += float2(l, l * l) * w;
c.rgb += curColor.rgb * w;
}
}
moment /= weightSum;
c.rgb /= weightSum;
varianceSpatial = (1.0 + 2.0 * (1.0 - histlen)) * max(0.0, moment.y - moment.x * moment.x);
outFragColor = float4(c.rgb, (1.0 + 3.0 * (1.0 - histlen)) * max(0.0, moment.y - moment.x * moment.x));반딧불 제거는 레이 트레이싱 중에 샘플링 방법을 조정하는 것부터 필터링 기술을 사용하거나 출력 광도에 대한 휴리스틱을 사용하는 것까지 다양한 방법으로 수행할 수 있습니다.
// 추가(Add)회 의 러프니스(Roughness)
//https://twitter.com/YuriyODonnell/status/1199253959086612480
//http://cg.ivd.kit.edu/publications/p2013/PSR_Kaplanyan_2013/PSR_Kaplanyan_2013.pdf
//http://jcgt.org/published/0007/04/01/paper.pdf
float oldRoughness = payload.roughness;
payload.roughness = min(1.0, payload.roughness + roughnessBias);
roughnessBias += oldRoughness * 0.75f;
// 기각(Rejection)
// Ray Tracing Gems Chapter 17
float3 fireflyRejectionClamp(float3 radiance, float3 maxRadiance)
{
return min(radiance, maxRadiance);
}
// 분산/표준편차(Variance)기각(Rejection)
// Ray Tracing Gems Chapter 25
float3 fireflyRejectionVariance(float3 radiance, float3 variance, float3 shortMean, float3 dev)
{
float3 dev = sqrt(max(1.0e-5, variance));
float3 highThreshold = 0.1 + shortMean + dev * 8.0;
float3 overflow = max(0.0, radiance - highThreshold);
return radiance - overflow;
}5.필터링
이 작업은 À-Trous 양측 필터를 사용하여 빠르게 수행할 수 있습니다. 원하는 흐림 강도에 따라 이 단계를 3~5회 반복하고, 매번 단계 크기를 2의 거듭제곱만큼 줄입니다(따라서 3회 반복의 경우 순서는 4, 2, 1입니다). 또는 속도는 느리지만 더 나은 필터링 결과를 생성하는 노이즈 제거 자동 인코더를 사용할 수 있습니다. 그런 다음 이 결과는 NVIDIA의 DLSS 2.0과 유사하게 결과를 업샘플링하는 슈퍼샘플링 자동 인코더에 공급될 수 있습니다.
À-Trous는 3x3 또는 5x5 가우스 커널에서 일반적으로 가능한 것보다 더 넓은 반경을 커버하기 위해 약간 지터링된 패턴의 샘플링을 방지하는 동시에 여러 번 반복할 수 있는 기능을 갖추고 다양한 입력 수로 인해 가장자리가 흐려지는 것을 방지합니다. 이 작업은 다음 방법과 함께 수행할 수 있습니다.
디더링 패턴을 기반으로 한 서브샘플링은 블러 커널의 샘플 수를 더욱 줄입니다.
표면 러프니스 [Abdollah shamshir saz 2018], 대략적인 반사 BRDF 로브 [Tokuyoshi 2015], 그림자 반암브라 [Liu et al. 2019] 등
6. 히스토리 블릿
알베도, 깊이 등과 같은 현재 전처리 데이터를 작성하여 다음 프레임을 재투영합니다.
NVIDIA는 ReSTIR을 사용하여 유사한 노이즈 제거기의 구현 예를 발표했습니다[Wyman et al. 2021]. 아래 그림은 NV의 AI 노이즈 감소를 보여줍니다. 1-샘플 고노이즈 맵을 사용하고 노이즈 감소 알고리즘을 사용하면 좋은 노이즈 감소 결과를 얻을 수 있습니다.
img
img
위: 1개 샘플링의 원본 노이즈 맵; 하단: 노이즈 감소를 켠 사진입니다.

상단 이미지의 앰비언트 오클루전(AO)은 레이 트레이싱을 위해 픽셀당 하나의 광선을 사용한 다음 노이즈 감소를 사용합니다. 크기가 조정된 이미지는 왼쪽에서 오른쪽으로 지상 실제값, 스크린 스페이스 앰비언트 오클루전(SSAO), 레이 트레이싱 앰비언트 오클루전(AO)을 보여줍니다. 그 중 레이 트레이싱 앰비언트 오클루전(Ambient Occlusion)은 프레임당 픽셀당 하나의 샘플을 가져와 픽셀당 하나의 샘플에서 노이즈 감소를 수행합니다. 노이즈가 제거된 이미지는 작은 접촉 그림자를 모두 캡처하지는 않지만 스크린 스페이스 앰비언트 오클루전(SSAO)보다 여전히 실제에 더 가깝습니다. (NVIDIA 제공)
이러한 모든 유형의 알고리즘은 데이터 재사용에 의존하므로 빠르게 움직이는 객체, 매우 복잡한 형상 또는 기록 정보가 거의 없는 영역과 같이 재사용된 데이터를 사용할 수 없는 경우 각 방법의 품질이 저하됩니다. 이를 방지하기 위해 일부 캐시된 데이터를 활용하는 방법이 있습니다. 예를 들어 방사조도 캐시를 사용하여 Minecraft RTX와 같은 더 나은 기본 색상을 얻는 등이 있습니다.

반사의 경우 시공간 재투영도 매우 어렵기 때문에 일반적으로 노이즈 제거 장치는 반사 표면의 노멀, 위치 데이터 등이 원래 표면이 아닌 첫 번째 반사를 기반으로 하는 첫 번째 바운스 데이터에 의존합니다.
노이즈 제거는 시공간 재투영을 통해 이전 샘플을 재사용하여 중요도 샘플링을 위한 광도 또는 통계를 적응적으로 리샘플링하고 빠른 가우시안/양측 필터와 같은 필터나 노이즈 제거 자동 인코더 및 슈퍼샘플링을 통한 업스케일링과 같은 인공 지능 기술을 사용하여 낮은 샘플/픽셀 이미지와 실제 실제 사이의 격차를 해소하는 데 도움이 될 수 있습니다.
잡음 제거는 완벽하지 않습니다. 시간적 기술이 방사성 지연을 유발할 수 있고 모든 필터가 원본 이미지를 흐리게 하여 선명도 손실을 초래할 수 있기 때문입니다. 가이드 필터는 선명도를 유지하는 데 도움이 될 수 있으며 적응형 샘플링 또는 프레임 픽셀당 샘플 수 증가는 잡음 제거된 이미지와 실제 이미지 간의 차이를 무시할 수 있게 만들 수 있습니다. 하지만 픽셀당 더 높은 샘플링 속도를 대체할 수 있는 방법은 없으므로 다양한 SPP(픽셀당 샘플) 수를 사용하여 이러한 기술을 실험해 보세요.
강력한 잡음 제거 장치는 이러한 기술을 모두 사용하는 것을 고려해야 하지만 응용 프로그램의 장단점과 요구 사항에 따라 달라집니다. 최근 연구에서는 샘플링 방식을 개선하고 캐시된 정보를 사용하여 픽셀을 다시 샘플링함으로써 렌더링 초기에 노이즈 감소를 이동하는 데 중점을 두었습니다. 이전 연구에서는 필터링, 기계 학습의 자동 인코더, 중요도 샘플링, 현재 상업용 게임 및 렌더러에서 생성되는 실시간 방법에 중점을 두었습니다.
UE는 레이 트레이싱에 국한되지 않고 SSGI, SSR, SSAO 등과 같은 화면 공간 기술도 포함하는 여러 가지 필터링 및 샘플링 기술(양방향 필터링, 공간 컨볼루션, 시간 컨볼루션, 랜덤 샘플링, 신호 및 주파수 등)을 포괄적으로 사용합니다. 아래 그림은 UE의 SSGI 시간이 누적된 후 그림의 노이즈가 점점 덜 분명해지는 것을 볼 수 있습니다.

17.3.9.5 소음 감소 문서
소음 감소는 앞으로 더욱 연구해 볼 가치가 있는 주제이자 분야입니다. 어린이들이 관심을 갖고 참여해 주기를 바랍니다. 이제 소음 감소와 관련된 몇 가지 샘플, 논문, 연설 및 문헌을 추천합니다.
- 샘플
Dihara Wijetunga(@diharaw94)는 하이브리드 렌더러의 다양한 측면에 대한 노이즈 제거를 논의하는 블로그 게시물을 발표했습니다.
NVIDIA는 제한된 검토를 위해 Real-Time Denoiser를 출시했습니다. 여기에서 가입하시면 보실 수 있습니다.
희소 볼륨의 대화형 경로 추적 및 재구성은 기계 학습 잡음 제거기를 사용하여 적응형 샘플링으로 볼륨의 잡음을 제거합니다.
Microsoft의 Peter Kristof는 여기에서 SVGF를 강력하게 구현하여 매우 강력한 DirectX Ray Tracing Ambient Occlusion 예제를 만들었습니다.
Apple Metal 팀은 Metal Performance Shaders, Temporal Anti-Aliasing(MPSTAA) 및 SVGF(MPSSVGF)를 사용하여 레이 트레이싱 장면의 노이즈를 제거하는 예를 공개했습니다.
AMD의 FidelityFX SSSR은 스크린 스페이스 리플렉션(SSR)를 제거하는 시공간 재투영 기능을 갖추고 있습니다. 여기 Github가 있습니다. AMD는 또한 Github Repo에서 반사 및 그림자를 위한 여러 가지 노이즈 제거 장치를 출시했습니다.
NVIDIA는 여기에 Deep Learning Super Sampling을 통합하기 위한 SDK를 출시했습니다. 개발자는 AMD의 Fidelity FX Super 해상도를 자신의 애플리케이션에 통합할 수도 있습니다.
Ingo Wald(@IngoWald))는 여기에서 Optix 디노이저를 사용하는 방법을 보여주는 Optix 과정을 공개했습니다.
NVIDIA의 Christof Shied(@c_schied)와 Alexey Panteleev(@more_fps)는 여기 Github에 있는 Quake 2 RTX용 노이즈 제거기를 작성했습니다.
Microsoft의 DirectML 초해상도 예제는 NVIDIA Deep Learning Super Sampling 2.0(DLSS 2.0)은 아니지만 둘 다 업스케일링을 수행한다는 점에서 유사합니다.
Xiaoxu Meng은 여기에서 신경 양측 그리드 논문의 소스를 공개했습니다.
여기에서 래스터 기반 렌더링의 그림자 및 반사를 위한 다양한 오픈 소스 디노이저를 출시했습니다.
펜실베니아 대학의 CIS565 과정에서 많은 학생, 조교 및 이전 학생들이 최신 소음 제거 연구를 구현하는 멋진 프로젝트를 만들었습니다.
Zheyuan Xie, Yan Dong 및 Weiqi Chen의 CUDA SVGF
실시간 경로 추적 재구성(BMFR)을 위한 블록별 다중 차수 특징 회귀(Jianping Xu, Gangzheng Tong 및 Tianming Xu 저)
Pytorch로 구현된 Dewang Sultania 및 Vaibhav Arcot의 순환 노이즈 제거 자동 인코더를 사용하여 Monte Carlo 이미지 시퀀스의 대화형 재구성.
DirectX 12의 ReSTIR 작성자: Sydney Miller, Sireesha Putcha, Thy Tran.
Jilin Liu(@Jilin18043110), Keyi Yu 및 Li Zheng이 작성한 DirectX 12의 ReSTIR입니다.
Xuanyi Zhou, Xuecheng Sun, Jiarui Yan의 Vulkan의 ReSTIR.
회담
ReSTIR에 관한 Chris Wyman의 고성능 그래픽 2020 강연은 여기에서 볼 수 있습니다.
Eric Haines(@pointinpolygon)는 노이즈 제거에 대한 훌륭한 소개인 Ray Tracing Essentials Part 7: Denoising for Ray Tracing을 출시했습니다.
Tomasz Stachowiak(@h3r2tic) 외. Ray Tracing gem에 포함된 놀라운 참조 하이브리드 렌더러인 Pica Pica 렌더러를 디자인했습니다. 그들의 이야기는 여기에서 볼 수 있습니다.
NVIDIA RTX를 사용한 게임의 레이 트레이싱(NVIDIA 제공)
기사
Kostas Anagnostou(@KostasAAA)는 Metro Exodus에서 부분적으로 영감을 받은 전역 조명 노이즈 제거 처리 방법을 설명하는 기사를 작성했습니다.
-레이 트레이싱 필터링
머신러닝 노이즈 제거
Real-Time_Rendering_4th-Real-Time_Ray_Tracing
레이 트레이싱 노이즈 제거
DirectML의 SuperResolution 샘플
시공간적 변화 유도 필터링
TURING RTX 레이 트레이싱 및 DLSS
DLSS 2.0 – 딥 러닝을 통한 실시간 렌더링을 위한 이미지 재구성
시공간 저장소 리샘플링(ReSTIR) - 이론 및 기본 구현
17.3.10 레이 트레이싱 최적화
실제 레이 트레이싱 장면은 복잡할 수 있습니다. 수천 개의 광원, 메모리가 저장할 수 있는 것보다 더 많은 텍스처, 메모리가 저장할 수 있는 것보다 더 많은 지오메트리(테셀레이션 형태), 변위, 조명 및 반사를 위한 매우 복잡한 프로그래밍 가능한 셰이더입니다.
17.3.10.1 광원 최적화
광원 측면에서 직접 조명의 주요 배수구는 일반적으로 그림자입니다. 그림자 맵을 사용하는 경우 각 광원에 대해 그림자 맵을 렌더링하고 관리해야 합니다. 레이 트레이싱을 사용하고 각 광원에 대해 최소한 하나의 그림자 광선을 추적해야 하는 경우 렌더링 시간이 허용할 수 없을 정도로 길어집니다. 다행스럽게도 많은 광원의 그림자는 기본 조명을 기준으로 정렬하여 처리할 수 있습니다. 일부 광원이 너무 멀리 떨어져 있고 조명이 너무 어두우므로 매우 대략적인 근사치를 얻을 수 있습니다. 각 표면 지점에서 각 광원의 직접 조명을 계산한 다음 조명 강도에 따라 조명을 정렬하고 마지막으로 그림자를 계산하는 조명, 그림자가 없는 조명을 계산하는 조명, 건너뛸 조명을 확률적으로 선택합니다.
17.3.10.2 텍스처 최적화
텍스처 측면에서 이미지를 렌더링하는 데 필요한 텍스처가 사용 가능한 메모리를 초과하는 경우 요청 시 디스크에서 텍스처를 읽어야 하고, 필요한 해상도에서만 읽어야 하며 메모리에 캐시해야 합니다.
텍스처 밉맵과 텍스처 타일링을 사용할 수 있습니다. 텍스처는 타일링되어 인접한 픽셀 그룹이 디스크에서 메모리로 함께 읽혀집니다. 아래 이미지는 타일 텍스처 MIP 맵의 세 가지 수준을 보여줍니다. 이 예에서 각 타일은 16×16 픽셀을 포함합니다. 가장 거친 밉 매핑 수준(레벨 0-3)은 단일 타일(여기에는 표시되지 않음)로 압축될 수 있으며, 밉 매핑 수준 4는 단일 타일로 구성되고, 다음 레이어에는 2×2 타일(여전히 각 타일에 16×16 픽셀)이 있고, 다음 레이어에는 4×4 타일이 있는 식입니다.

또한 다중 해상도 텍스처 타일 캐싱을 사용할 수 있습니다. 다중 해상도 텍스처 타일 캐시에 대한 텍스처 액세스는 직접적으로 보이는 형상을 렌더링하는 데 매우 일관되며 전체 텍스처 크기의 1% 캐시 크기이면 충분합니다. 광선 차별화를 사용하여 텍스처 조회를 위한 적절한 mIP 맵 수준을 선택하고 텍셀이 광선 빔 단면과 거의 동일한 크기인 수준을 선택하는 경우 레이 트레이싱과 유사한 결과가 관찰됩니다. 비간섭성 광선은 더 넓은 광선 묶음을 가지므로 거친 mIP 맵 수준이 선택됩니다. 더 미세한 MIP 맵 수준은 좁은 광선 묶음이 있는 광선에 의해서만 액세스됩니다. 다행스럽게도 이러한 광선은 일관성이 있으므로 결과 텍스처 캐시 조회도 일관성이 있습니다.
17.3.10.3 형상 최적화
복잡한 장면의 경우 인스턴싱, 광선 재정렬 및 음영 캐싱, 기하학적 스탠드인, 다중 해상도 테셀레이션과 같은 기술을 사용할 수 있습니다.
광선 재정렬 및 셰이딩 캐싱 측면에서 Toro 렌더러는 광선을 재정렬하여 기하학적 일관성을 높여 컴퓨터의 기본 메모리보다 더 큰 레이 트레이싱 장면을 가능하게 합니다. 광선을 재정렬하려면 각 광선의 이미지 기여가 선형이어야 하며, 이는 실제 물리적 반사에는 적용되지만 영화 제작에 사용되는 매우 예술적인 프로그래밍 가능 셰이더에는 적용되지 않는 경우가 많습니다. 스캔라인 렌더링을 위한 REYES 알고리즘에서 영감을 받은 Project Razor는 표면 점의 전체 메쉬를 한 번에 음영 처리하고 음영 결과의 뷰 독립적인 부분을 저장합니다. 다음 광선 중 일부가 동일한 표면 패치에 닿으면 음영 처리 결과를 재사용할 수 있습니다.
기하학적 이중의 사전 계산은 상당히 긴 프로세스이지만 일단 완료되면 장면을 대화식으로 레이 트레이싱할 수 있습니다.
다중 해상도 테셀레이션의 경우 실제 응용 프로그램에서는 광선 표면 교차점을 수치적으로 계산하는 대신 곡선, 베지어 패치, NURBS 표면, 세분화 표면 및 변위가 있는 모든 표면을 테셀레이션하는 것이 유리합니다. 이러한 표면은 텍스처 타일에 해당하는 제어 가능한 크기의 더 작은 표면 패치로 나뉩니다. 직접적으로 보이는 표면 패치의 테셀레이션 비율은 보는 거리와 표면 곡률, 선택적으로 보는 각도에 따라 달라집니다. 반사나 그림자의 경우 일반적으로 더 거친 테셀레이션을 사용할 수 있습니다. 아래 이미지는 표면 패치의 5개 분할 예를 보여줍니다. 가장 미세한 분할 비율은 14×11입니다. 더 거친 수준은 가장 미세한 분할 정점의 하위 집합으로 구성됩니다. 가장 거친 분할은 패치의 네 모서리에 불과합니다. 다양한 세분화 수준은 형상을 세분화하는 MIP 맵으로 생각할 수 있습니다.

표면 패치의 다중 해상도 테셀레이션 예: 14×11, 7×6, 4×3, 2×2 및 1 쿼드.
Pharr와 Hanrahan은 변위 표면의 테셀레이션 형상을 캐시했지만 다중 해상도 테셀레이션을 활용하지 않았습니다. 필요에 따라 원하는 해상도로 표면 패치를 세분화하고(그런 다음 적절한 정점을 재배치) 캐시에 하위 분할을 저장합니다. 세그먼트의 크기는 매우 다양하므로 캐시는 정밀한 세그먼트보다 더 많은 거친 세그먼트를 저장할 수 있습니다.
광선 교차 테스트의 경우 사변형의 크기가 광선 빔 단면과 거의 동일한 하위 분할을 선택할 수 있습니다. 미세 및 중간 테셀레이션에 대한 액세스는 일반적으로 매우 일관되고 거친 하위 분할에 대한 액세스는 매우 일관성이 없지만 거친 하위 분할의 캐시 용량은 크고 이러한 하위 분할은 어쨌든 빠르게 다시 계산됩니다. 미세한 테셀레이션은 직접적으로 보이는 형상, 평면의 스페큘러 반사 및 굴절, 광선 원점 근처의 확산 및 앰비언트 오클루전(AO) 광선에만 사용됩니다. 다른 모든 광선의 경우 광선 빔이 더 넓고 중간 및 거친 테셀레이션이 사용됩니다. (다른 텍스처에 해당하는 다른 거칠기의 Mipmap 수준과 유사합니다!)
17.3.10.4 병렬 컴퓨팅
레이 트레이싱은 병렬 가속에 매우 적합한 것 같습니다. 각 픽셀은 다른 모든 픽셀과 독립적으로 계산되므로 레이 트레이싱이 "당황스러울 정도로 평행하다"는 일반적인 인식이 생깁니다. 그러나 이는 장면 데이터가 메인 메모리에 맞는 경우에만 해당됩니다! 장면이 큰 경우 데이터 액세스 일관성을 유지하고 활용하는 데 매우 주의해야 하며, 후속 광선이 동일한 형상을 통과하고 동일한 텍스처에 액세스하는 경향이 있도록 실행 순서를 정렬하여 좋은 캐시 동작을 보장해야 합니다. 이러한 움직임은 그만한 가치가 있으며 캐시 적중률을 향상시킬 수 있습니다.
최신 CPU에는 4가지 작업을 병렬로 수행할 수 있는 SIMD 명령어(Intel의 SSE, IBM/Motorola의 AltiVec, AMD의 3dNow)가 있습니다. 삼각형에 평행한 4개의 광선에 대한 교차 테스트를 수행하려면 다음 지침을 사용하십시오. 빛이 일관적이면 가시광선의 경우 일반적으로 약 3.5배의 속도 향상으로 우수한 가속도를 제공합니다.
SIMD 명령어를 사용하는 또 다른 방법은 4개의 평행 삼각형에 대해 광선 교차 테스트를 수행하는 것입니다. 삼각형이 일관성이 있는 경우(세분 분할 표면의 인접한 위치에서 나온 것처럼) 속도가 향상되고 광선이 일관성을 가질 필요가 없습니다. SIMD 명령의 또 다른 용도는 경계 상자의 세 평면을 모두 교차 테스트 축에 평행하게 정렬하는 것입니다.
17.3.10.5 GPU 가속
PowerVR, RTX 및 기타 GPU 시리즈에는 레이 트레이싱 하드웨어 장치가 추가되어 실시간 레이 트레이싱의 출시가 가속화되었습니다. 또한 GPU 내에서 빛, 질감 및 기타 데이터의 일관성을 향상시키는 방법도 레이 트레이싱 개선의 주요 문제입니다. PowerVR에는 상관 관계가 높은 빛을 수집하고 처리하는 일관성 엔진이 내장되어 있습니다(아래 그림).

또한 GPU는 SIMD, SIMT, 일관성, 메모리 병합, 코어 점유, 파이프라인 병목 현상, 동기화 방법은 물론 물리적 온도(주파수 감소를 방지하기 위해)까지 고려해야 합니다. 자세한 내용은 병렬 아키텍처를 참조하세요.
구현 중에는 자체 교차, 데이터 정확성, 패치 교차, 로드 밸런싱, 다중 교차, LOD 등과 같은 문제나 기술에 주의를 기울이거나 사용해야 합니다. 자세한 내용은 다음을 참조하세요.
-레이 트레이싱 보석
- 레이 트레이싱 보석 II
17.3.11 종합 기술
17.3.11.1 루멘 GI
이전의 실시간 연구에는 Irradiance Fields 및 Screen Space Denoiser와 같은 방법이 포함되었습니다. UE5의 루멘은 Screen Space Denoiser를 사용합니다.

입사광은 일관성이 있지만 기하학적 법선은 그렇지 않은 입사 방사선을 다운샘플링하면 전체 해상도에서 BRDF에 입력 조명을 통합합니다.
img
화면 공간이 아닌 래디언스 캐시 공간에서 필터링합니다(왼쪽 아래). 첫 번째 일은 더 나은 샘플링을 수행하는 것입니다. 중요한 것은 들어오는 빛을 샘플링하는 것입니다(아래 이미지 참조). 안정적인 장거리 조명 및 월드 공간 방사선 캐시(아래, 오른쪽).
img
최종 수집 파이프라인:
img
화면 공간 방사조도 캐시는 다음 단계로 세분화될 수 있습니다.
img
스크린 프로브 구조: 테두리가 있는 팔면체 아틀라스(일반적으로 프로브당 8x8), 균일하게 분포된 월드 공간 방향, 2D 아틀라스에서 동일한 방향, 광도 및 교차 거리를 갖는 이웃:
img
스크린 프로브 배치: 계층적 개선을 통한 적응형 레이아웃 [Křivánek et al. 2007], 반복 보간법이 실패하는 경우 최종 수준 홍수 채우기.
img
적응형 샘플링 사용 - 실시간 성능에는 상한이 필요하며 적응형 프로브를 처리할 때 추가적인 장애물이 발생하는 것을 원하지 않습니다. 아틀라스 하단에 적응형 프로브를 배치합니다.
img
스크린 프로브 디더링 - 시간 디더링은 시간 필터링을 통해 화면 셀 내의 폐색 차이를 숨겨야 함을 드러내지 않고 그리드와 방향을 픽셀에 직접 배치합니다.
img
전경 누락이 배경으로 누출되는 것을 방지하기 위한 표면 거리 가중치, 동일한 표면에 있는 한 프로브 간의 차이를 공간적으로 분배하기 위한 보간 시 지터 오프셋, TAA 3x3 인접 영역을 확장하여 최종 조명의 시간적 안정화.
중요도 샘플링도 사용됩니다. 입사 광도
구조화된 중요도 샘플링 - 우수한 전역 계층화를 달성하려면 확률 밀도 함수(PDF)의 계층 구조 영역에 소수의 샘플을 할당합니다. 샘플 배치에는 오프라인 알고리즘이 필요합니다.
img
팔면체 밉 쿼드트리에 완벽하게 매핑되었습니다!
img
파이프라인에 통합 - 추적 스레드에 간접 경로를 추가하고, RayCoord, MipLevel을 저장하고, 추적 후 최종 통합을 위해 TraceRadiance를 균일한 프로브 레이아웃으로 결합합니다.
img
광선 생성 알고리즘은 균일하게 분포된 프로브 광선 방향에서 시작하여 각 팔면체 텍스처에 대한 BRDF의 PDF x 빛의 PDF를 계산하며 추적 스레드를 포화 상태로 유지하려면 고정된 출력 광선 개수가 필요합니다. PDF별로 광선을 정렬하고, PDF가 컬링 임계값보다 낮은 3개 광선마다 높은 PDF 광선과 일치하도록 슈퍼샘플링합니다.
img
개선된 점은 조명 PDF가 빛을 제거하는 것을 허용하지 않는다는 것입니다. 조명 PDF는 대략적인 값이고 BRDF는 정확한 값입니다. 공간 필터링을 사용하면 컬링을 보다 적극적으로 수행할 수 있습니다. 더 높은 BRDF 임계값을 사용한 컬링은 공간 필터링 프로세스 중에 컬링된 라이트의 가중치를 줄이고 모서리가 어두워지는 문제를 해결합니다.
img
중요도 샘플링 검토: 마지막 프레임의 조명과 원거리 조명을 사용하여 이 프레임의 광선을 안내하면 광선을 프로브에 묶으면 더 스마트한 샘플링을 제공할 수 있습니다.
다음으로 공간 필터링 기술에 대해 이야기해보겠습니다.
방사선 캐시 공간 필터링: 저렴한 대형 공간 필터링(프로브 공간 32x32, 화면 공간 482x482)은 공간적 이웃 간의 검색 차이를 무시하고 깊이 가중치만 무시할 수 있습니다. 복사는 인접 프로브의 일치하는 팔면체 셀, 오류 가중치로부터 인접 항목으로부터 수집됩니다. 인접 광선에 부딪힌 각도 오류는 재투영되고, 원거리 조명은 필터링되며, 로컬 그림자는 보존됩니다.
img
평평한 표면에는 효과가 좋지만 형상이 접촉하는 장소에서는 빛샘 문제가 있습니다.
img
접촉 그림자 유지 - 원거리 조명 쪽으로 편향된 각도 오류는 누출과 동일하며 원거리 조명에는 시차가 없으며 결코 거부되지 않습니다. 해결책은 재투영하기 전에 이웃의 적중 거리를 자신의 거리로 자르는 것입니다.
img
다음으로 월드 공간의 방사선 캐시에 대해 이야기해 보겠습니다.
원거리 조명에 문제가 있고, 반짝이는 형상의 노이즈가 거리에 따라 증가하고, 길고 고르지 못한 흔적이 느리고, 원거리 조명이 천천히 변합니다. 캐싱 기회, 근처 스크린 프로브의 중복 작동, 해결책은 원거리 방사선을 별도로 샘플링하는 것입니다. 원거리 조명을 위한 월드 공간 래디언스 캐시(The Tomorrow Children [McLaren 2015]의 기술), 월드 공간 이후 안정적인 오류 - 체적 라이트맵처럼 숨기기 쉽습니다.
img
파이프라인 통합 - 스크린 프로브 주위에 배치한 다음 추적하여 휘도를 계산하고 스크린 프로브 광선의 장거리 조명을 설명하기 위해 보간합니다.
img
월드 프로브 광선은 자체 조명을 방지하기 위해 보간 공간을 건너뛰어야 합니다.
img
스크린 프로브 광선은 보간된 공간 + 건너뛰기 거리를 커버해야 합니다.
img
빛샘 문제도 있고, 월드 프로브로부터의 방사선이 차단되어야 하는데, 시차가 잘못되어서는 안 됩니다.
img
해결책은 스크린 프로브 광선을 월드 프로브 구와 교차하도록 재투영하는 간단한 구형 시차입니다.
img
희박한 적용 범위 - 카메라 중심의 3D 클립맵 그리드는 프로브 인덱스를 아틀라스에 저장하고 클립맵 배포는 화면 크기로 제한됩니다.
img
팔면체 프로브 아틀라스는 방사선, 추적 거리 및 일반적으로 프로브당 32x32 방사선 속도를 저장합니다.
img
배치 및 캐싱 - 표시된 각 월드 프로브에 대해 나중에 클립맵 간접 참조에 어디서나 마커가 삽입됩니다. 이전 프레임의 추적을 재사용하거나 새 프로브 인덱스를 할당하고, 캐시 히트의 하위 집합을 다시 추적하여 조명 변경을 전파합니다.
img
여전히 남아 있는 문제는 매우 가변적인 비용, 빠른 카메라 이동, 캐시되지 않은 많은 프로브를 추적해야 하는 불연속적인 필요성입니다. 해결책은 낮은 해상도의 다른 프로브 트레이스에 대한 캐시 누락과 조명 업데이트를 건너뛰는 다른 프로브 트레이스를 포함하여 전체 해상도 프로브에 대한 고정 예산을 확보하는 것입니다.
BRDF의 중요한 샘플링 방법은 스크린 프로브로부터 BRDF를 축적하고, 프로브를 다이싱하여 타일을 추적하고, BRDF를 기반으로 추적 타일 해상도를 생성하는 것입니다. 슈퍼샘플링된 근접 카메라, 최대 64x64의 유효 해상도, 4096트랙! 매우 안정적인 장거리 조명.
프로브 간의 공간 필터링 - 다시 이웃 교차점을 거부합니다. 문제는 상호 가시성을 가정할 수 없다는 것입니다. 이상적으로는 단일 폐색 시도가 잘 작동하며 프로브 깊이를 재사용하여 프로브 깊이를 통해 인접한 광선 경로를 재추적함으로써 거의 무료입니다.
img
img
월드 공간 방사선 캐시는 스크린 프로브 중요도 샘플링, 머리카락, 반투명도, 다중 바운스를 안내하는 데에도 사용됩니다.
통합으로 돌아가서 이제 입사 방사선이 화면 공간의 방사선 캐시에서 더 낮은 해상도로 계산되었으므로 모든 기하학적 세부 사항을 얻으려면 전체 해상도로 통합해야 합니다.
img
중요도 샘플링 BRDF는 일관성 없는 획득, 8spp*4 인접 프로브 방향 조회로 이어지며 밉(필터링된 중요도 샘플링)을 사용할 수 있지만 특히 직접 조명이 있는 영역 주변에서 자체 조명이 발생합니다. 프로브 방사선을 3차 구면 고조파로 변환: SH는 스크린 프로브별로 계산되고, 전체 해상도 픽셀은 SH와 균일하게 로드되며, SH는 저비용 및 고품질로 통합됩니다.
img
높은 거칠기에서의 레이 트레이싱 반사의 경우 디퓨즈에 중점을 둡니다. 스크린 프로브 재사용 - GGX에서 방향을 생성하고, 프로브 방사선을 샘플링하고, 완료된 프로브 샘플링 및 필터링을 자동으로 활용합니다! 다운샘플링된 추적에서는 접촉 그림자가 손실됩니다. 전체 해상도 곡선 법선을 사용합니다. 빠른 화면 추적을 사용하여 계산되며, 화면 프로브 사이의 거리와 결합된 추적 거리(약 16픽셀)입니다. 스크린 공간 복사휘도 캐시와의 통합 - 스크린 프로브 GI를 원거리 장 복사조도로 취급하고, 전체 해상도 곡선 법선은 필드 수를 나타내며, 물 기반 간접 조명, 다중 바운스가 필드 복사조도를 제공합니다.
img
img
그런 다음 시간 필터링을 사용합니다. 프로브 위치를 디더링하려면 깊이 컬링을 사용하는 안정적인 시간 필터링이 필요합니다. 결과는 안정적이지만 빛 변화에 대한 응답도 느립니다. 추적 과정에서 빠르게 움직이는 물체의 투영 영역에 속하는 히트 속도와 히트 깊이가 추적됩니다. 빠르게 움직이는 개체를 추적하면 빠른 업데이트 모드로 전환하고 시간 필터링을 낮추고 공간 필터링을 높입니다.
img
최종 컬렉션 성능:
img
img
img
향후 작업은 노이즈 감소 품질, 매우 동적인 장면의 시간적 안정성, 다중 바운스 GI를 위한 Lumen의 표면 캐시에 화면 공간 방사선 캐싱 적용에 관한 것입니다.
Radiance Cache는 Lumen 기술의 일부일 뿐입니다. Lumen에는 표면 캐싱, 소프트웨어 레이 트레이싱, 하드웨어 레이 트레이싱, 반사, 투명 GI 등도 포함됩니다. Lumen의 소스 코드 분석은 Unreal Rendering System 분석(06) - UE5 Special Part 2(Lumen 및 기타)를 참조하세요.
17.3.11.2 서펠 GI
Surfel은 표면요소(Surface Element)입니다. 서핑은 위치, 반경 및 법선으로 정의되며 주어진 위치 근처 표면의 작은 이웃에 가깝습니다(아래 그림).

GBuffer에서 서핑을 생성하고, 지오메트리가 시야에 들어올 때 화면을 채우고, 월드 공간에서 지속되며 방사조도를 축적하고 캐시합니다. 화면 공간 채우기를 반복하고, 화면을 16x16 블록으로 분할하고, 적용 범위가 가장 낮은 타일을 찾고, 빈 적용 범위와 추적 가중치를 적용하고, 타일이 임의 임계값을 초과하는 경우 서핑을 생성합니다.

강체를 지원하는 것 외에도 스킨 뼈의 비닝도 지원합니다. 모든 것이 동적이라고 가정하기 때문에 스킨 처리된 형상과 이동된 형상 모두 정적 형상처럼 솔루션의 나머지 부분과 상호 작용합니다.
img
빈은 화면 공간 투영에 따라 크기가 조정되며 생성 알고리즘은 비선형 가속 구조를 통해 모든 거리에서 적용 범위를 보장합니다.
img
모든 것에는 고정된 크기의 버퍼, 예측 가능한 예산, 고정된 수의 저장소, 고정된 가속 구조 및 사용되지 않은 저장소의 재활용이 있습니다.
img
관련 저장소를 활성 상태로 유지하고, 마지막으로 확인했을 때 추적하고, 간격 감지 중에 표시되면 재설정하고, 위치 업데이트 중에 증가합니다. 휴리스틱은 활성화된 저장소의 총 수, 본 이후의 시간, 거리 및 적용 범위를 기반으로 합니다. 다음 그림은 거리 휴리스틱입니다.
img
조명을 적용하려면 각 픽셀에 대해 다음을 수행합니다. 표면 그리드 셀을 찾고, 셀에서 N개의 빈을 가져오고, 표면 방사조도, 거리별 가중치 및 법선을 누적하고, 방사 가중치 < 1인 경우 가중 평균 셀 방사조도를 추가합니다. 방사형 가우시안 깊이를 사용하여 해결되는 빛 오버플로 문제가 있습니다.
img
수리 전후 비교:
img
통합 방사조도 다이어그램:
img
수정된 지수 이동 평균 추정기[Barré Brisebois 2019]는 단기 평균 및 분산 추정치를 추적하고 단기 추정기를 사용하여 혼합 요인을 조정하므로 저잡음 지점으로 수렴하면서 변화에 신속하게 대응할 수 있습니다. 단기 분산을 기반으로 한 편향된 광선 계산, 광선 수를 사용하여 상대적인 신뢰도를 알려주는 다중 규모 평균 추정기, 변화와 변형에 신속하게 반응하여 광선 수를 작게 유지하면서 안정적으로 유지하는 피드백 루프입니다.
img
Lambert BRDF로 가정되는 누적 확산 방사조도는 코사인 로브의 중요한 샘플링을 통해 광선을 생성합니다.
img
광 안내 사용:
img
각 서펠은 반구에 대해 이동 평균 6x6 휘도 맵을 생성하며, 단일 4K 텍스처(모든 서펠을 지원하려면 7x7), 텍스처당 8비트 + 텍스처당 단일 16비트 스케일링, 프레임 기능별로 정규화되어 저장됩니다.
img
중요도 샘플링 변수를 사용하면 함수의 각 개별 부분이 해당 값과 해당 위치의 함수 값인 확률 밀도 함수에 비례하여 선택됩니다.
img
img
인근 서핑 데이터를 활용하여 Surfel이 Surfel VPL과 동일한 가중치, Mahalanobis 거리 및 깊이 함수를 사용하여 인접한 서핑의 방사선, 구조화된 가속도를 찾을 수 있도록 합니다.
img
조도 공유 전후 비교:
img
또한 BF5 방법을 사용하여 광선, 위치 및 방향별로 정렬된 상자 광선, 공간용 12비트, 방향용 4비트, 공간 해시의 셀 위치 지정, 광선 방향 방향, 총 상자 수 및 오프셋 계산, 광선 인덱스 및 이전에 계산된 빈 오프셋을 기반으로 광선 재정렬을 정렬할 수 있습니다.
img
다중 조명 샘플링은 중요도 샘플링(무작위 광원 분할, 예비 샘플링)을 사용합니다. 무작위 광원 절단은 작은 샘플로 빠르게 수렴되며 사전 구축된 데이터 구조가 필요하고 샘플링 비용이 많이 들 수 있습니다.
img
저장소 샘플링의 개략도:
img
레이 트레이싱 프로브 다이어그램:
img
투명한 개체에는 불투명 개체와 같은 대형 화면 지원이 필요하며, Clipmap은 클로즈업 세부 정보 유지, 대규모 장면 지원, 메모리 비용이 낮은 희소 프로브 배치, LOD의 가변 속도 업데이트 등의 요구 사항을 충족하는 최선의 선택입니다.
img
업데이트된 방향과 거리를 계산하고, 이동 후 유효한 프로브 데이터를 복사하고, 새로 생성된 프로브를 상위 레벨 프로브로 초기화합니다.
img
레벨 4 클립맵 배치의 개략도:
img
Clipmap 샘플링 프로세스는 다음과 같습니다.
img
추가적인 샘플링 최적화는 블루 노이즈 그래디언트 디더 샘플링을 사용하는 것입니다.
img
프레임 개요:
진행중입니다. 위치 업데이트, 재활용, 그리드 할당, 광선 정렬, 레이 트레이싱, 클립맵 업데이트, 프로브 추적.
만들다. 형상 일반 재구성, 간격 채우기, 광선 정렬, 레이 트레이싱, 영구 저장소에 쓰기, 프로브 볼륨에 쓰기.
필터. 공간적 소음 감소, 시간적 소음 감소.
애플리케이션. 새로운 생성물 주입, 조명 적용(1/4 영역 해상도로 실행), 조명 업샘플링, 클립맵 샘플링.
17.3.11.3 수집 및 합성
필터링은 일반적으로 평균화 프로세스로 간주되며, 흐릿한 픽셀의 인접 픽셀에 대한 가중 평균을 생성하는 데 사용됩니다. 우리는 이 방법을 수집이라고 부르는데, 많은 픽셀이 함께 모여 단일 출력을 생성합니다. Northlight Engine은 형상의 교차점에서 조명을 샘플링합니다. 광추적의 최종 구성요소와 조합 효과는 다음과 같습니다.


요약하자면, 최첨단 GPU 레이 트레이싱은 DXR을 통해 쉽게 접근할 수 있고, 성능은 목표에 부합하며, 래스터라이제이션에 적합하지 않은 알고리즘을 프로토타입화하기 쉽고, 기존 저주파 아키텍처와 결합할 수 있습니다.

클레이북의 가벼운 추적에 소요되는 시간은 다음과 같습니다.
img
SDF에서 메시로의 변환은 동일한 입자를 가리키는 여러 삼각형이 있는 2채널 근사치를 사용하며 입자를 먼저 생성해야 합니다. PBD 시뮬레이터를 위한 선형 입자 배열(표면) 및 삼각형 렌더링을 위한 인덱스 버퍼를 출력합니다. 단일 간접 그리기 호출을 사용하여 그려진 모든 메시입니다. 입자로 변환하려면 64x64x64 디스패치 및 4x4x4 스레드 그룹을 사용하십시오. 프로세스는 다음과 같습니다.
그룹화:
SDF 이웃을 GSM에 로드합니다. 가장자리 내부/외부에서 발견된 경우
GSM 이웃을 읽습니다.
P를 표면으로 이동합니다(경사하강법).
입자 ID를 할당합니다(L+G 원자).
배열[id]에 P를 씁니다.
입자 ID를 643643 그리드에 씁니다.
64x64x64 디스패치 및 4x4x4 스레드 그룹을 사용하여 삼각형으로 변환합니다. 프로세스는 다음과 같습니다.
그룹화:
SDF 지역을 GSM에 로드합니다. XYZ 에지가 발견되면
GSM 부근을 읽습니다.
각 XYZ 변에는 삼각형(L+G 원자)의 2배가 할당됩니다.
643643의 ID 그리드에서 입자 ID를 3번 읽습니다.
인덱스 버퍼에 삼각형을 씁니다(3x 입자 ID).
비동기식 계산:
- 프레임을 3개의 비동기 세그먼트로 분할합니다.
UE4의 GBuffer와 섀도우 캐스케이드가 겹칩니다.
UE4의 속도 렌더링 및 깊이 압축 해제를 오버레이합니다.
UE4용 오버레이 조명 및 포스트 프로세스.
작품은 즉시 제출됩니다.
펜스 시작(x3)을 기다리는 컴퓨팅 큐.
- 펜스 연속을 기다리는 메인 큐(x3).
img
비동기식 계산은 fps를 19% 이상 증가시킬 수 있습니다.
UE4 렌더러에 통합됨:
- GBuffer 조합.
전체 화면 PS 결합 레이 트레이싱 데이터.
샘플링 재료 맵(사용자 정의 Gather4 필터링).
UE4의 GBuffer+깊이 버퍼(SV_Depth)에 씁니다.
섀도우 마스크 조합.
전체 화면 PS에서 구체 추적 그림자까지.
- UE4 섀도우 마스크 버퍼에 쓰기(알파 블렌딩 사용)
UE4 RHI 커스터마이징:
- 암시적 동기화 없이 렌더 대상을 설정합니다.
겹치는 깊이/색상을 압축 해제할 수 있습니다.
드로잉은 여러 RT에 겹쳐질 수 있습니다(아래 이미지).
암시적 동기화 없이 RT/버퍼를 지웁니다.
비동기 계산 기능이 누락되었습니다.
버퍼/텍스처 복사 및 삭제.
- 셰이더 인덱스 버퍼 쓰기를 계산합니다.
img
또한 Claybook은 GPU->CPU 버퍼 리드백을 사용하여 UE4 RHI를 추가로 사용자 정의했습니다. UE4는 일시정지 없이 2D 텍스처 리드백만 지원합니다. 다른 리드백 API는 전체 GPU를 정지시킵니다. 버퍼에는 원본 보기와 입력된 보기가 있을 수 있습니다. 넓은 원본 쓰기는 좁은 유형의 버퍼를 효율적으로 채우는 것과 같습니다.
기타 UE4 최적화: 간접 디스패치/가져오기 겹침 허용, 지우기 및 복사 작업 겹침 허용, 서로 다른 RT 간의 그리기 겹침 허용, GPU 캐시 플러시 및 지연 감소(아래), 최적화된 스테이징 버퍼, 빠르고 명확한 개선. 최적화된 장벽 및 울타리, 최적화된 텍스처 배열 하위 리소스 장벽, 3D 텍스처를 위한 향상된 GPU 타일링 모드, 향상된 부분 2D/3D 텍스처 업데이트, 5배 더 빠른 히스토그램 + 눈 적응형 셰이더, 4배 더 빠른 오프라인 CPU SDF 생성기(베이킹).
img
물리 데이터는 대규모 원시 버퍼, 와이드 로드 4/Store4 명령(16바이트), 비트 압축: 입자 위치: 16비트 표준, 입자 속도: fp16, 입자 플래그용 비트 필드(활동, 충돌 등), 벤치마크 도구: https://github.com/sebbbi/perftest에 저장됩니다.
그룹 공유 메모리는 SDF 생성, 메쉬 생성, 물리학 및 동일한 데이터를 반복적으로 로드할 때 사용되는 거대한 성능 도구입니다. 스칼라 로딩은 AMD의 성능상 큰 승리입니다. 사용 사례: 일정한 인덱스 원시 버퍼 로드, 사용 사례: SV_GroupID를 기반으로 하는 원시 버퍼 로드, SGPR에 저장된 로드가 더 나은 점유율을 얻습니다.
17.3.11.4 광자 매핑
종합하면, 광자 매핑은 두 개의 패스로 나뉩니다.
- 패스 1: 광자 추적. 거친 GI 솔루션.

- 패스 2: 레이 트레이싱. 이미지 렌더링.

광자 추적 프로세스의 목적은 광원에서 광자를 방출하고 장면을 통해 광자를 추적한 후 확산 표면에 저장하여 확산 표면의 간접 조명을 계산하는 것입니다.
광원에서 방출된 광자는 방출된 광자가 동일한 플럭스를 전달하도록 광원에서 방출된 전력 분포에 해당하는 분포를 가져야 합니다. 즉, 저전력 광자에 계산 리소스를 낭비하지 않습니다.
확산 점 광원의 광자는 균일하게 분포된 무작위 방향으로 해당 점에서 방출됩니다. 평행광의 광자는 모두 같은 방향으로 방출되지만 장면 외부의 원점에서 방출됩니다. 확산 정사각형 광원의 광자는 반구로 제한된 방향으로 정사각형의 임의 위치에서 방출됩니다. 방출 방향은 코사인 분포에서 선택됩니다. 즉, 사각형 평면에 평행한 방향으로 광자를 방출할 확률은 0이고 사각형에 수직인 방향으로 방출할 확률은 가장 높습니다.
일반적으로 광원은 임의의 모양과 방출 특성을 가질 수 있습니다. 방출된 빛의 강도는 원점과 방향에 따라 다릅니다. 예를 들어, 전구는 모양이 사소하지 않으며, 전구에서 방출되는 빛의 강도는 위치와 방향에 따라 다릅니다. 광자 방출은 이러한 변화를 따라야 하므로 일반적으로 방출 확률은 광원 표면의 위치와 방향에 따라 달라집니다. 아래 그림은 다양한 유형의 광원에서 발생하는 방출을 보여줍니다.

광원 조명: 점 광원, 지향성 광원, 사각형 광원, 일반 광원.
광원의 출력은 광원에서 방출되는 광자 사이에 분산되어야 합니다. 광원의 전력이
확산점 광원으로부터의 광자 방출의 간단한 예에 대한 의사 코드는 다음과 같습니다.

계산된 간접 조명(렌더링 중)의 변화를 더욱 줄이려면 광자를 최대한 균일하게 방출하는 것이 바람직합니다. 예를 들어, 계층화 또는 낮은 불일치 준 무작위 샘플링을 사용할 수 있습니다.
희박한 형상이 있는 장면에서는 방출된 광자의 대부분이 어떤 것과도 부딪치지 않으며 이러한 광자를 방출하는 것은 많은 시간을 낭비하게 됩니다. 방출을 최적화하기 위해 투영 맵을 사용할 수 있습니다. 프로젝션 맵은 단순히 광원에서 본 기하학적 도형의 맵으로, 많은 작은 셀로 구성됩니다. 해당 방향에 형상이 있으면 셀이 "켜짐"이고 없으면 "꺼짐"입니다. 예를 들어, 투영 맵은 점 광원이 있는 장면의 구형 투영이고 방향성 광원이 있는 장면의 평면 투영입니다. 투영을 단순화하려면 각 개체나 개체 클러스터 주위에 경계 구를 투영하는 것이 편리합니다. 또한 장면의 모든 기하학적 요소를 확인할 필요가 없기 때문에 투영 맵 계산 속도가 크게 빨라집니다. 투영 다이어그램의 가장 중요한 측면은 광원에서 광자를 방출하는 데 필요한 방향에 대한 보수적인 추정치를 제공한다는 것입니다. 추정치가 보수적이지 않으면(예: 먼저 몇 개의 광자로 장면을 샘플링할 수 있음) 화선과 같은 중요한 효과가 손실될 수 있습니다.
프로젝션 맵을 사용하여 광자를 방출하는 것은 매우 간단합니다. 개체가 포함된 셀을 반복하여 셀이 나타내는 방향으로 임의의 광자를 방출할 수 있습니다. 그러나 이 접근 방식은 모든 셀을 방문하기 전에 광자 맵이 "채워질" 수 있으므로 약간 편향된 결과를 초래할 수 있습니다. 또 다른 접근 방식은 임의의 방향을 생성하고 해당 방향에 해당하는 셀에 개체가 있는지 확인하는 것입니다(그렇지 않은 경우 새로운 임의 방향을 시도해야 합니다). 이 접근 방식은 일반적으로 잘 작동하지만 희박한 장면에서는 비용이 많이 들 수 있습니다. 희박한 장면의 경우 객체가 있는 셀에 대해 무작위로 광자를 생성하는 것이 좋습니다. 간단한 접근 방식은 객체가 있는 무작위 셀을 선택한 다음 해당 셀에서 방출되는 광자의 방향을 무작위로 선택하는 것입니다. 모든 경우에 저장된 광자의 에너지는 투영 맵의 활성 셀 수와 방출된 광자 수에 따라 조정되어야 합니다. 따라서 광자 전력 공식을 수정해야 합니다.
프로젝션 맵의 또 다른 중요한 최적화는 반사 특성을 가진 객체(예: 화선을 생성할 수 있는 객체)를 식별하는 것입니다. 나중에 설명하는 것처럼 화선은 별도로 생성되고 반사성 개체의 간격이 희박한 경우가 많으므로 화선 투영 맵을 사용하는 것이 매우 유용합니다.

장면의 광자 경로: (a) 두 번의 디퓨즈 후에 흡수됨; (b) 스페큘러 반사 후 두 개의 디퓨즈로 변환됩니다. (c) 두 번의 스페큘러 반사 전송 후에 흡수됩니다.
광자가 방출된 후 광자 추적("광자 추적", "역방향 레이 트레이싱", "순방향 레이 트레이싱" 및 "역방향 경로 추적"이라고도 함)을 사용하여 장면을 통해 추적됩니다. 광자 추적은 광선이 방사선을 수집하는 동안 광자가 플럭스를 전파한다는 점을 제외하면 레이 트레이싱과 동일하게 작동합니다. 광자는 광선과 다르게 물질과 상호 작용할 수 있기 때문에 이는 중요한 차이점입니다. 주목할만한 예는 굴절인데, 여기서 상대 굴절률에 따른 복사 밝기의 변화는 광자에 대해 발생하지 않습니다.
광자가 물체에 닿으면 표면의 재료 매개변수에 따라 반사, 전송 또는 흡수될 수 있습니다. 상호 작용 유형을 결정하는 데 사용되는 기술을 러시안 룰렛이라고 합니다. 즉, 주사위를 굴려 광자가 살아남아 다른 광자 추적 단계를 수행할 수 있는지 여부를 결정합니다.
광자는 확산 표면(또는 더 정확하게는 비특수 표면)에 닿는 위치에만 저장됩니다. 그 이유는 스페큘러 반사 표면에 광자를 저장하는 것이 유용한 정보를 제공하지 않기 때문입니다. 스페큘러 반사 방향에서 일치하는 입사 광자를 가질 확률은 0이므로 정확한 스페큘러 반사를 렌더링하려는 경우 가장 좋은 방법은 표준 레이 트레이싱을 사용하여 스페큘러 반사 방향을 따라 광선을 추적하는 것입니다. 다른 모든 광자-표면 상호 작용의 경우 데이터는 전역 데이터 구조(광자 그래프)에 저장됩니다. 방출된 각 광자는 경로를 따라 여러 번 저장될 수 있습니다. 또한 광자에 대한 정보는 흡수된 표면에 저장됩니다(표면이 디퓨즈인 경우).
각 광자-표면 상호 작용에 대해 위치, 입사 광자 파워 및 입사 방향이 저장됩니다(실제로 광자 그래프에서 정렬 및 조회 중에 사용되는 각 광자 데이터 세트에 대해 태그 공간도 예약되어 있습니다).
struct Photon
{
float x,y,z; // position
char p[4]; // power packed as 4 chars
char phi, theta; // compressed incident direction
short flag; // flag used in kdtree
};위 이미지의 간단한 장면을 다시 생각해 보세요. (a)는 장면의 전통적인 레이 트레이싱 이미지(직접 조명, 스페큘러 반사 및 투과)를 보여줍니다. (b)는 장면에 대해 생성된 광자 맵의 광자를 보여줍니다. 유리 구 아래의 높은 광자 농도는 유리 구에 의한 광자의 초점에 의해 발생합니다.
데이터 저장은 다중 산란, 이방성 산란 및 비균질 매체뿐만 아니라 참여 매체로 확장될 수도 있습니다.
광자는 광자 추적 프로세스 중에만 생성되며, 렌더링 프로세스 중에 광자 맵은 장면의 여러 지점에서 입사 플럭스 및 반사 방사선의 추정치를 계산하는 데 사용되는 정적 데이터 구조입니다. 이렇게 하려면 가장 가까운 광자가 광자 맵에 있어야 합니다. 이는 매우 빈번한 작업이므로 가능한 한 빨리 가장 가까운 광자를 찾으려면 렌더링 프로세스 전에 광자 맵을 최적화해야 합니다.
먼저, 광자 그래프를 표현하기 위해 좋은 데이터 구조를 선택해야 합니다. 데이터 구조는 가장 가까운 이웃 검색을 빠르게 허용하면서 컴팩트해야 합니다. 또한 가성 광자 맵에서 매우 흔히 발생하는 매우 불균일한 분포를 처리할 수 있어야 합니다. 이러한 요구를 처리할 수 있는 자연스러운 후보는 균형 잡힌 kd-트리입니다. 광자 다이어그램의 균형을 맞추기 위한 의사 코드:

광자 매핑 방법의 기본 구성 요소는 특정 방향의 비정면 반사 표면 지점에서 방사선의 추정치를 계산하는 기능입니다. 광자 휘도 추정값은 클래식 BRDF에서 파생될 수 있습니다.
이 과정은

광자 맵에서 가장 가까운 광자를 사용하여 휘도를 추정합니다.
위 이미지는 구를 사용합니다. 표면이
여기서
이 추정치는 많은 가정을 기반으로 하며 정확도는 공식에 사용된 광자 맵과 광자 수에 따라 달라집니다. 구는 광자의 위치를 파악하는 데 사용되므로 특히 물체의 모서리와 날카로운 모서리에서 추정치에 잘못된 광자를 포함하기 쉽습니다. 측면과 각도로 인해 면적 추정에 오류가 발생할 수도 있습니다. 이러한 오류가 발생하는 영역의 크기는 추정치의 광자 맵과 광자 수에 따라 크게 달라집니다. 추정 및 광자 다이어그램에 더 많은 광자가 사용되면 공식이 더욱 정확해집니다. 위치, 방향 및 플럭스 표현의 유한한 정확도로 인한 오류를 무시하면 한계에 도달하고 광자 수를 무한대로 늘릴 수 있습니다. 다음과 같은 흥미로운 결과를 얻을 수 있습니다. 여기서
이 공식은 표면의 국부적으로 평평한 부분에 위치한 모든 점
위 공식은 충분한 광자를 사용하면 임의로 좋은 방사선 추정치를 얻을 수 있음을 의미합니다! 유한 요소 기반 방법에서는 오류가 메시의 해상도, 방사선 방향 표현의 해상도 및 광 시뮬레이션의 정확도에 따라 달라지기 때문에 임의의 정확도를 얻는 것이 더 복잡합니다.
위 이미지는 가장 가까운 광자를 찾는 것이 x 주위에 구를 펼치고 해당 구 내의 광자를 사용하는 것과 얼마나 유사한지 보여줍니다. 이 과정에서 구체 이외의 다른 볼륨을 사용할 수도 있습니다. 큐브, 실린더 또는 디스크를 사용할 수 있습니다. 이는 가장 가까운 광자를 찾기 위한 더 빠른 알고리즘을 얻는 데 도움이 되거나 광자를 선택하는 데 더 정확할 수 있습니다. 다른 부피가 사용되는 경우 Δ 방정식의 A는 x에서 표면과 접촉하는 탄젠트 평면과 부피 사이의 교차 영역으로 대체되어야 합니다.
구에는 투영된 면적과 거리 계산이 매우 간단하고 따라서 계산적으로 효율적이라는 분명한 이점이 있습니다. x에서 표면 노멀 방향을 따라 압축하여 구를 디스크(타원체)로 수정하면 보다 정확한 볼륨을 얻을 수 있습니다(아래 이미지 참조). 디스크를 사용하면 가장자리와 모서리 주변의 추정에 사용되는 "가짜 광자"가 더 적다는 장점이 있습니다. 예를 들어, 이는 벽의 광자가 바닥으로 누출되는 것을 방지하므로 방의 가장자리에서 매우 효과적입니다. 그러나 남아 있는 한 가지 문제는 면적 추정이 잘못되거나 광자가 속하지 않는 영역으로 누출될 수 있다는 것입니다. 이 문제는 주로 필터링을 사용하여 해결됩니다.

구(왼쪽)와 디스크(오른쪽)를 사용하여 광자를 찾습니다.
광자 맵의 광자 수가 너무 적으면 광도 추정치가 가장자리에서 흐려집니다. 이 아티팩트는 분산 레이 트레이싱기의 간접 조명을 추정하기 위해 광자 맵을 사용할 때 바람직할 수 있지만 방사 측정 추정이 화선을 나타내는 경우에는 바람직하지 않습니다. 화선은 종종 날카로운 모서리를 가지므로 너무 많은 광자를 요구하지 않고 이러한 모서리를 유지하는 것이 좋을 것입니다.
가장자리의 흐림 정도를 줄이기 위해 방사 측정 추정치가 필터링됩니다. 필터링의 기본 개념은 관심 지점 x에 가까운 광자의 무게를 늘리는 것입니다. 광자의 위치를 파악하기 위해 구를 사용하고 있으므로 필터가 3차원이어야 한다고 가정하는 것은 당연합니다. 그러나 광자는 2차원 표면에 저장됩니다. 또한 면적 추정은 광자가 표면에 위치한다는 가정을 기반으로 합니다. 따라서 광자 정의 영역에 대해 정규화되는 2D 필터(이미지 필터와 유사)가 필요합니다.
두 개의 방사상 대칭 필터를 사용하여 가성 필터를 필터링할 수 있습니다: 원뿔형 필터, 가우스 필터 및 특수 차동 필터. 처음 두 필터는 동일한 오래된 곡입니다. 차동 필터에 중점을 두겠습니다.
차등 검사 기반 필터의 아이디어는 추정 중에 가장자리 근처의 영역을 감지하고 이러한 영역에서 더 적은 광자를 사용하는 것입니다. 이렇게 하면 추정치에 약간의 노이즈가 발생할 수 있지만 일반적으로 가장자리를 흐리게 하는 것보다 낫습니다. 방사선 추정치는 가장자리 근처의 추정치에 광자를 추가할 때 추정치의 변화가 단조롭다는 관찰을 기반으로 수정됩니다. 즉, 우리가 화선 바로 바깥에 있고 추정치에 광자를 추가하기 시작하면(광자를 포함하는 x 중심 구의 크기를 늘려서) 더 많은 광자를 추가함에 따라 추정치가 증가하는 것을 관찰할 수 있습니다. 그리고 우리가 화선 내부에 있을 때는 그 반대도 마찬가지입니다. 이 관찰을 바탕으로 추정치에 차등 검사를 추가할 수 있습니다. 더 많은 광자가 추가됨에 따라 추정치가 계속 증가하거나 감소하는 것을 관찰하면 광자 추가를 중지하고 사용 가능한 추정치를 사용합니다.
가장 가까운 광자를 찾으려면 효율적인 알고리즘이 필요합니다. 그 중 하나에 대한 의사코드는 다음과 같습니다.

이 검색 알고리즘의 경우 초기 최대 검색 반경을 제공해야 합니다. 잘 선택된 반경은 검색을 줄이고 테스트되는 광자 수를 줄이는 좋은 방법이 될 수 있습니다. 반면에 최대 반경이 너무 작으면 광자 맵 추정에 노이즈가 발생합니다. 반경은 예를 들어 저장된 광자의 평균 에너지를 고려하고 방사선 추정에서 일부 허용 가능한 오차를 가정하여 해당 평균 에너지를 기반으로 최대 반경을 계산할 수 있는 오류 측정법을 기반으로 선택될 수 있습니다.
필요한 광자 수가 발견될 때까지 최대 힙 구성을 지연하는 등 일부 추가적인 최적화를 추가할 수 있습니다. 이는 요청된 광자 수가 클 때 특히 유용합니다. 초기 최대 검색 반경이 매우 낮은 값으로 설정될 수도 있으며, 해당 값이 너무 낮으면 더 높은 최대 반경을 사용하여 다른 검색이 수행됩니다. 검색 루틴의 또 다른 변경 사항은 이전에 설명한 디스크 검사를 사용하는 것입니다. 이는 잘못된 색상 유출을 방지하는 데 도움이 되며 수집 단계를 사용하지 않고 광자를 직접 시각화할 때 특히 유용합니다.
다음은 렌더링 부분입니다.
최종 이미지는 분산 레이 트레이싱을 사용하여 렌더링됩니다. 여기서 픽셀 광도는 눈에서 픽셀을 통해 장면으로 광선을 추적하는 것으로 구성된 여러 샘플 추정치를 평균하여 계산됩니다. 조명은 직접광, 스페큘러 반사 및 광택 반사, 화선, 다중 디퓨즈 및 참여 미디어로 나눌 수 있습니다. 이는 전통적인 PBR과 상대적으로 유사하므로 이 기사에서는 다루지 않습니다.

광자 매핑 렌더링.
Light Transport Simulation을 위한 Unbiased Photon Gathering은 광자 매핑의 Unbiased 렌더링을 효과적으로 달성하기 위한 새로운 광자 수집 방법을 제안합니다. 기존 광자 매핑처럼 수집된 광자를 예상 밀도로 모으는 대신, 각 광자를 개별적으로 처리하고 해당 광자 경로가 집결 지점을 생성하는 눈 하위 경로와 연결되어 편향되지 않은 경로 샘플을 생성합니다. 이러한 경로 샘플의 몬테 카를로 추정치는 모든 관련 용어를 엄격하고 편견 없는 방식으로 평가하여 계산되므로 독립적인 편견 없는 샘플링 기술이 탄생합니다. 우리는 우리 방법과 BDPT(양방향 경로 추적)를 최적으로 결합할 수 있는 다중 중요도 샘플링(MIS) 가중치 세트를 추가로 개발하여 다양한 빛 경로를 효율적으로 처리하고 이전 알고리즘과 비교할 수 있는 편견 없는 렌더링 알고리즘을 만듭니다. 실험은 이 방법의 효율성과 견고성을 보여줍니다.

1시간 렌더링 후 SPPM(확률적 프로그레시브 광자 매핑), UPS/VCM(통합 경로 샘플링/버텍스 결합 및 병합), 기사의 무편광 광자 수집 및 양방향 경로 추적(UPG+BDPT) 비교. SPPM은 편향된 광자 매핑을 활용하여 선명한 특징을 과도하게 흐리게 하는 대신 낮은 분산 결과를 생성합니다. UPS/VCM은 BDPT로부터 추가적인 이점을 얻지만 버텍스 병합 부분은 여전히 편향되어 있습니다. 우리의 방법은 편견이 없고 강력하여 참조와 가장 유사한 결과를 생성합니다. HDR 그림자 디테일을 표시하기 위해 왼쪽 삽입은 노출 1=64로 설정되어 있습니다.
17.3.11.5 포괄적인 구현
이 단계에서 래스터라이제이션는 여전히 레이 트레이싱보다 "더 빠르며" 레이 트레이싱은 반사, 부드러운 그림자, 전역 조명 등과 같은 래스터라이제이션보다 특정 효과를 더 잘 처리할 수 있습니다. 요즘에는 반사만 레이 트레이싱되고 다른 모든 것(주 광선 포함)은 래스터라이제이션되는 하이브리드 레이 트레이싱을 사용하는 것이 일반적입니다. 주류 GPU는 기본적으로 래스터라이제이션, 계산, 레이 트레이싱 및 딥 러닝과 같은 파이프라인 하이브리드 컴퓨팅을 지원했습니다.

게임 엔진은 아트 자산 및 머티리얼 셰이더를 포함하여 GPU용으로 설계되고 최적화되어 있으므로 게임 개발자가 기존 게임 엔진 인프라에 통합할 때 해결해야 하는 문제를 식별합니다.
전통적인 렌더링 파이프라인은 아래 그림과 같습니다. 파란색 부분은 간접광과 관련이 없으므로 무시해도 됩니다. 아래 사진의 붉은색은 간접광과 관련된 무대입니다.

아래 이미지의 노란색 단계의 경우 솔루션은 다중 바운스 또는 근사치입니다. 다음으로 살펴볼 것은 투명성입니다. 이는 레이 트레이싱에 적합한 후보인 것 같습니다. 그렇죠?

화면 공간 조명 문제는 투명한 재질(다차원성, 성능, 필터링)에도 적용되는 것으로 나타났습니다. SSS에 대한 체적 솔루션은 현재 탐색 중이지만 올바른 SSS 체적 솔루션은 없습니다. 하이브리드 렌더링 파이프라인의 프로세스는 다음과 같습니다.

간접 조명의 경우 분할 및 근사화 Karis 2013은 분산을 줄이는 데 도움이 되며 파란색은 래스터라이제이션 또는 레이 트레이싱을 사용하여 미리 계산되고 평가됩니다.

RTX의 렌더링 프로세스는 다음과 같습니다.

무작위 영역 조명 렌더링 프로세스는 다음과 같습니다.

Battlefield V의 레이 트레이싱에는 GPU 레이 트레이싱 파이프라인, DXR 엔진 통합, GPU 성능 등이 포함됩니다.
img
간단한 레이 트레이싱 파이프라인:
img
생성 파이프라인 단계에서는 GBuffer의 텍스처를 읽고 무작위 래스터라이제이션를 사용하여 조명을 생성합니다.
img
float4 light(MaterialData surfaceInfo , float3 rayDir)
{
foreach (light : pointLights)
radiance += calcPoint(surfaceInfo, rayDir, light);
foreach (light : spotLights)
radiance += calcSpot(surfaceInfo, rayDir, light);
foreach (light : reflectionVolumes)
radiance += calcReflVol(surfaceInfo, rayDir, light);
(...)
}그러나 이 간단한 조명 추적 파이프라인으로 렌더링된 이미지 품질에는 노이즈, 비효율성, 낮은 조명 기여도 등의 문제가 있습니다.
img
광선을 생성할 때 가변 속도 추적을 포함하도록 파이프라인을 개선할 수 있습니다.
img
변동금리 추적 프로세스는 다음과 같습니다.
img
가변 속도 추적을 통해 물 위에서 그리고 바라보는 각도에서 더 많은 빛을 얻을 수 있습니다. 하지만 여전히 문제가 있습니다.
img
Ray Binning(라이트 박스)을 추가하여 화면 오프셋과 각도를 빈의 인덱스로 사용할 수 있습니다.
img
img
img
img
SSR Hybridization, Defrag, 셀별 광원 목록 조명, 노이즈 감소(BRDF 노이즈 감소, 시간적 노이즈 감소) 등의 최적화를 차례로 추가할 수 있습니다.
img
SSR 하이브리드화 과정과 결과.
img
셀별 광원 목록 조명.
img
BRDF 노이즈 감소 프로세스.
최종 신규 파이프라인 및 소요 시간은 다음과 같습니다.
img
렌더링 효과:
img
DXR 기본 사항:
img
DXR의 성능 최적화에는 인스턴스 수 감소, 추론 휴리스틱 사용, (일부) 사소한 결함 수용이 포함됩니다. 컬링 휴리스틱은 일부 측정이 필요한 다리나 건물과 같은 대형 개체를 제외하고 멀리 있는 개체는 중요하지 않다고 가정합니다. 구 경계 상자를 투영합니다. θθ가 특정 임계값보다 작으면 제거됩니다.
img
다양한 임계값의 효과:
img
컬링 결과는 다음과 같습니다. 4도 컬링, 프레임당 5000->400 BLAS 및 20000->2800 TLAS 인스턴스 재구성을 사용하면 TLAS+BLAS 빌드(GPU)가 64밀리초에서 14.5밀리초로 줄어들지만 간헐적인 점프 및 개체 손실과 같은 결함이 발생합니다.
BLAS 업데이트는 여전히 비용이 많이 들고 다음 방법을 사용하여 최적화할 수 있습니다.
시차를 두고 전체 및 증분 BLAS 재구성. 완전히 재구성되기 전에 N 프레임이 증가합니다.
D3D12_RAYTRACING_ACCELERATION_STRUCTURE_BUILD_FLAG_PREFER_FAST_BUILD를 사용합니다.
반복적인 재구축을 피하세요. CS 입력(본 매트릭스), 400 -> 50, Gbuffer, 섀도우 맵과 같은 GFX와 BLAS 업데이트가 겹치는지 확인하세요.
TLAS+BLAS GPU 빌드 시간은 14.5밀리초에서 1.15밀리초로 단축되고, RayGen(GPU)은 0.71밀리초에서 0.81밀리초로 단축됩니다(인터리브 재구성 + 플래그 사용).
불투명 객체는 항상 ClosestHit 셰이더를 사용해야 하고, 알파 테스트 객체에 대해서만 Any Hit 셰이더를 사용해야 하며, 스키닝 및 파괴를 위해 셰이더를 계산해야 합니다.
광선 페이로드(RAY PAYLOAD)는 광선 교차점에서 반환됩니다. Gbuffer RTV와 동일한 형식을 가지며 재질 데이터, 노멀, 기본 색상, 매끄러움 등을 포함합니다.
struct GbufferPayloadPacked
{
uint data0; // R10G10B10A2_UNORM
uint data1; // R8G8B8A8_SRGB
uint data2; // R8G8B8A8_UNORM
uint data3; // R11G11B10_FLOAT
float hitT; // Ray length
};또한 정확성을 확인할 수도 있습니다. 즉, 출력을 래스터라이제이션하고, 메인 광선을 장면에 발사하고, 페이로드를 Gbuffer와 비교합니다. 출력이 0이 아닌 경우 버그가 있는 것입니다! 오류를 수정해야 합니다.
img
Embree는 Intel에서 개발한 Ray Tracing 오픈 소스 라이브러리입니다. 핵심 기능은 다음과 같습니다.
주로 전문 렌더링 애플리케이션을 대상으로 합니다.
고도로 최적화된 레이 트레이싱 커널(1.5x 6x 속도 향상).
풍부한 기능과 유연성을 제공합니다.
최신 CPU 및 ISA(예: Intel® AVX 512)를 지원합니다.
Windows*, macOS*10.x 및 Linux를 지원합니다.
애플리케이션에 쉽게 통합하기 위한 API입니다.
Apache 2.0 라이센스에 따른 오픈 소스입니다.
기술적 특성은 다음과 같습니다.
- 최신 레이 트레이싱 알고리즘을 사용합니다.
Intel® TBB를 사용하여 병렬화된 고품질 BVH 빌드.
넓은 BVH, 단일 광선 탐색, 혼합 광선 탐색…
하드웨어 측면의 최적화된 구현.
SIMD 및 기타 특수 명령어를 활용하려면 가능할 때마다 벡터화하세요.
가장 안쪽 루프의 명령어 종속성 체인을 줄입니다.
일반적인 상황에 대한 빠른 경로를 구현했습니다.
캐시 사용, 메모리 액세스 패턴 등에 대한 데이터 구조를 최적화합니다.
Embree에서 지원하는 기능은 다음과 같습니다.

해당 시스템의 개요는 다음과 같습니다.

World Of Tank와 같은 게임에서 성공적으로 사용되었습니다.
GPU 하드웨어 가속을 사용하는 레이 트레이싱의 단계와 다이어그램은 다음과 같습니다.

최신 래스터라이제이션 게임 엔진을 기반으로 한 광 추적 구현 프로세스는 다음과 같습니다.

GBuffer 정보를 결합한 후 하이브리드 렌더링 파이프라인이 생성됩니다.

다음은 래스터라이제이션와 레이 트레이싱의 효과를 비교한 것입니다.

2021년 11월 Imagination Tech는 IMG CXT 시리즈와 뛰어난 기능인 PowerVR Photon 아키텍처를 출시했습니다. 이 아키텍처는 매우 효율적인 하이브리드 레이 트레이싱을 제공하고 7nm, 5nm 또는 3nm 프로세스 설계를 제공할 수 있습니다. 그 기능에는 맵 기반 디퍼드 렌더링, Imagination별 이미지 압축, 초광폭 ALU, 슈퍼스칼라 ALU 처리, 광범위한 비동기 메커니즘, 펌웨어 기반 GPU, 분산형 멀티 코어 및 기타 하드 코어 기술이 포함됩니다. 이 아키텍처는 동시 비동기식 레이 트레이싱을 추가합니다. 즉, CXT GPU는 이제 GPU 내에서 기하학, 조각/픽셀, 컴퓨팅, 2D 및 레이 트레이싱 등 최대 5가지 작업 유형을 동시에 실행할 수 있습니다.

IMG CXT GPU의 높은 수준 보기는 위 이미지에서 볼 수 있습니다. GPU의 주요 구성 요소는 다음과 같습니다.
USC(Unified Shading Cluster): GPU의 컴퓨팅 코어는 픽셀 데이터, 기하학 데이터, 계산 데이터 및 2D/복사 하우스키핑 작업을 동시에 처리할 수 있는 다중 스레드 프로그래밍 가능 SIMT 프로세서입니다. GPU 구성의 경우 USC가 많을수록 컴퓨팅 성능이 높아집니다.
TPU(텍스처 처리 장치): 고도로 최적화된 로직으로 텍스처 주소 지정, 샘플링 및 필터링을 처리합니다. 텍스처 단위가 많을수록 시각적 복잡성이 높아지고 화면 주사율이 높아지며 디스플레이 해상도가 높아집니다.
래스터/지오메트리 블록: 컬링, 클리핑, 블로킹, 압축, 압축 해제, 반복 등을 포함하여 USC 처리 전후에 데이터를 포스트 프로세스 및 전처리할 수 있는 고정 기능 단위 집합입니다.
최상위 레벨(CXT RT3): L3 캐시, AXI 버스 인터페이스 및 펌웨어 프로세서를 포함합니다.
RAC(Ray Acceleration Cluster): 모든 Ray Tracing 처리 단계를 효율적으로 처리하기 위한 새로운 전용 블록입니다. 또한 CXT는 이전 IMG B 시리즈에 비해 50% 더 많은 ALU, TPU 및 기하학적 성능을 단일 코어 유닛에 담았습니다.

B 시리즈 GPU와 마찬가지로 CXT GPU에도 4개의 코어로 확장 가능한 멀티 코어 기능이 있습니다. 위에 설명된 "Beyond Desktop" 구성에서는 설계에 추가 옵션 IP 블록도 포함됩니다.
NNA: 당사의 신경망 가속 장치는 고전력, 고성능 및 효율적이고 최적화된 신경망 처리를 제공합니다. 이 장치는 IMG CXT GPU와 함께 작동할 수 있으며 최대 8개의 코어가 있는 멀티 코어 구성에서 최대 100개의 최고의 AI 성능을 제공합니다(위 그림).
OCM: IMG CXT GPU와 NNA 장치 간에 데이터를 효율적으로 교환하는 데 사용할 수 있는 온칩 공유 메모리입니다. 또한 OCM은 최고의 처리량, 최저 대기 시간 및 최고의 전력 효율성을 위해 데이터를 칩에 유지함으로써 다른 IP 블록과 상호 작용하는 데 사용할 수도 있습니다.
EPP: Imagination의 EPP(이더넷 패킷 프로세서) IP는 확장 가능한 다중 포트 IEEE 802.3 다중 기가비트 이더넷 스위치 및 라우터 솔루션 제품군입니다. 실리콘으로 입증된 IP는 고성능 관리형 및 비관리형 다중 포트 스위치 및 라우터의 까다로운 통신 요구 사항을 충족하도록 특별히 설계되어 자동차 산업 및 기타 네트워크 처리 시장에 이상적입니다. 여기에 표시된 설계에서 EPP는 GPU 그룹 및/또는 데이터 저장 장치 간의 고속 연결을 가능하게 하며 비디오 압축 게임 스트림의 직접 스트리밍도 허용합니다.
3D 초기부터 전통적인 렌더링은 삼각형 메쉬를 사용하여 개체의 기하학적 구조를 만든 다음 "음영 처리"하여 모양을 만드는 래스터라이제이션를 사용하여 수행되었습니다. 그러나 래스터라이제이션를 사용하면 세계가 조명되는 방식을 대략적으로만 추정할 수 있습니다. 레이 트레이싱은 광자가 광원에서 방출되어 보는 사람의 눈에 도달할 때까지 장면 주위를 튕겨내는 실제 세계에서 빛이 작동하는 방식을 시뮬레이션한다는 점에서 다릅니다. 레이 트레이싱은 관찰자(화면)에서 장면으로, 개체로, 그리고 거기에서 광원으로 광선을 보냅니다. 빛이 객체와 상호작용할 때, 빛은 그 물질 속성에 따라 객체에 의해 차단, 반사 또는 굴절되어 화면 밖의 객체에서도 그림자와 반사를 생성합니다. 빛이 장면에 닿으면 조명 프로세스가 자연스럽게 발생하므로 개발자는 "가짜" 조명 효과를 만드는 데 시간을 소비할 필요가 없습니다. 조명 장면에 대한 이러한 우아한 접근 방식은 보다 사실적인 그래픽을 제공하고 게임 및 시각적 애플리케이션을 개선하는 동시에 콘텐츠 제작자를 위한 조명 프로세스를 단순화하는 데 도움이 됩니다.
다양한 레벨에 따라 **RTLS(Ray Tracing Levels System)**에는 6가지 유형이 있습니다.

레벨 2의 경우 상자/삼각형 테스터를 추가합니다.

레벨 3에는 전체 하드웨어 BVH 순회가 있습니다.

PowerVR Photon의 경우 레벨 4 RTLS가 지원됩니다. PowerVR Photon 아키텍처는 스마트폰 전력 및 대역폭 예산 내에서 레이 트레이싱을 활성화하고 이러한 효율성을 모바일을 넘어 시장으로 확장할 수 있도록 설계되었습니다. 레이 트레이싱의 핵심 문제는 일관성이 부족하다는 것입니다. 광선은 기존 GPU에 설계된 병렬성과 충돌하는 임의의 방향을 도입할 수 있고 도입할 것이기 때문입니다. 이 문제에 대한 최선의 해결책은 작업량에 집중하는 것이며, 이를 위해 일관된 수집 단위가 도입됩니다.

이 장치를 사용하면 BVH 걷기는 여전히 완전히 오프로드되지만 이제 일정 문제가 됩니다. 많은 광선이 저장될 수 있으며 일관성 단위는 광선을 유사한 패킷 또는 묶음으로 그룹화합니다. 예를 들어 BVH 가속 구조를 통해 유사한 경로를 따르는 광선을 "일관성"이라고 합니다. 한 광선에서 다음 광선으로 일관되지 않을 수 있지만 여러 광선에 대한 평균을 계산할 때 항상 유사점과 상관 관계를 활용할 수 있습니다. 이것이 바로 PowerVR Photon 아키텍처가 수행하는 작업입니다.
PowerVR Photon에서는 광선을 처리 패킷으로 그룹화하여 처리 효율성뿐만 아니라 메모리 액세스 효율성도 높입니다. 이 순서는 또 다른 이점을 제공합니다. MIMD 아키텍처와 달리 GPU 내부 처리에 대한 공통적이고 효율적인 접근 방식으로 돌아갑니다. 즉, 많은 장치가 동일한 작업을 수행합니다.
따라서 상자에 대해 하나의 광선을 검사하는 대신 동일한 상자에 대해 여러 광선을 검사할 수 있기 때문에 병렬성을 활용할 수 있습니다. 이러한 움직임으로 인해 효율성이 크게 향상되고 캐시 및 메모리 하위 시스템에 대한 부담이 줄어듭니다. 삼각형 교차점의 경우에도 마찬가지입니다. 동시에 여러 삼각형에 대해 광선을 확인할 수 있습니다.
따라서 Photon 아키텍처에는 네 가지 기본 이점이 있습니다.
ALU 파이프라인에서 BVH 순회 및 빈/삼중 테스트를 완전히 오프로드합니다.
일관된 수집으로 인해 가벼운 처리가 병렬화됩니다.
일관된 수집은 높은 데이터 재사용을 보장하고 캐시 및 메모리 하위 시스템에 대한 부담을 크게 줄입니다.
실행 중인 광선이 많기 때문에 ALU 셰이딩 작업을 레이 트레이싱과 분리하여 지연 시간 흡수를 효과적으로 수행할 수 있습니다.
다음 표에는 다양한 수준과 해당 디자인 기능 지원이 나와 있습니다.
레벨 2 레벨 3 레벨 4
구현 예 2020 게임 콘솔 디자인 2021년 데스크탑 디자인 파워VR CXT
ALU 오프로딩 부분 전체 전체
HW 박스 테스터 Y Y Y
HW 트라이앵글 테스터 Y Y Y
HW BVH 처리 엔 Y Y
HW 일관성 정렬 엔 엔 Y
캐시 적중률 낮음 낮음/중간 높음
메모리 지연 허용 오차 낮음 낮음 높음
처리 효율성 낮음(SIMT 활용도) 낮음(MIMD) 높음
모바일 전력 예산 엔 엔 Y
PowerVR은 1996년 초에 타일 기반 디퍼드 렌더링(TBDR)을 개척했습니다. TBDR의 초점은 처리 효율성과 대역폭입니다. 타일 기반 렌더링은 렌더링하기 전에 모든 삼각형 형상을 화면 공간 타일로 정렬하여 작동합니다. 이는 각 삼각형이 즉시 변환되어 그려지는 IMR(Immediate Mode Rendering)과 다릅니다. 모든 형상을 정렬한 다음 화면 공간 타일 영역(일반적으로 16x16 또는 32x32 픽셀)에서 렌더링할 때의 이점은 깊이/스텐실 버퍼 및 색상 버퍼에 대해 온칩 메모리만 사용하여 타일 영역 렌더링을 수행할 수 있다는 것입니다. IMR은 이 모든 대역폭을 칩에서 밀어내고 캐시 히트에 의존하여 대역폭을 줄입니다. 그러나 이 캐싱 접근 방식은 화면 공간에서 기하학적 제출의 공간적 불일치로 인해 종종 실패하여 높은 대역폭, 대기 시간 민감도 및 낮은 전력 효율성을 초래합니다.
따라서 지오메트리를 먼저 정렬하면 캐시 적중률이 실제로 100%가 됩니다. 또한 깊이 및 스텐실 버퍼는 일반적으로 한 번만 사용되므로 삭제할 수 있습니다. GBuffer 및 MRT 렌더링을 사용하면 많은 MRT "색상" 대상이 중간 준비 데이터에만 사용되며 메모리에 기록할 색상 버퍼만 필요합니다. TBDR을 사용하면 이 모든 작업을 칩에서 수행할 수 있어 메모리 공간과 많은 대역폭이 절약됩니다. TBDR은 앤티앨리어싱 처리에도 상당한 이점을 제공합니다. 오버샘플링 버퍼는 온칩 메모리에만 존재하므로 다운샘플링된 색상 대상만 기록되므로 메모리 공간과 대역폭이 절약됩니다.
PowerVR Photon 레이 트레이싱 아키텍처는 광선이 2D 화면 공간이 아닌 BVH를 통해 유사한 경로를 따르는 패킷으로 분할된다는 점을 제외하면 공간 순서도 수행된다는 점에서 PowerVR TBDR 아키텍처와 여러 면에서 동일합니다. 여기서의 이점은 일관된 순서 지정과 유사합니다. 캐시 효율성이 크게 향상되고 대역폭이 감소하며 처리는 SIMD/SIMT 속성을 유지하여 로직 및 전체 처리의 높은 전력 효율성을 보장합니다.
PowerVR Photon 아키텍처는 PowerVR GPU에 RAC(Ray Acceleration Cluster)라는 새로운 블록을 추가합니다. 이는 광선 방출(셰이더/커널에서)부터 처리를 위해 적중(또는 누락) 결과를 ALU로 반환하는 것까지 전체 프로세스를 포함하여 PowerVR GPU의 모든 레이 트레이싱 활동을 담당합니다.

그래픽 셰이더나 컴퓨팅 커널 프로그램에 의해 광선이 생성되고 결과가 처리되므로 RAC는 GPU의 ALU 엔진과 긴밀하게 연결됩니다. 이들 장치는 광선 및 적중/실패 정보 교환과 밀접하게 연관되어 있지만 기술적으로는 완전히 "분리"되어 있습니다. 즉, 두 장치가 효율성과 활용도를 극대화하기 위해 동시에 작동한다는 의미입니다. RAC는 매우 계산 집약적인 상자/광선 및 삼각형 광선 교차점은 물론 일관성 정렬과 같은 효율성 최적화를 포함하여 전체 BVH 탐색을 효율적으로 처리합니다. RAC는 Khronos Vulkan® 확장 및 Microsoft DirectX Ray Tracing을 포함하여 현재 Ray Tracing API가 제공하는 모든 모드 및 기능과 완벽하게 호환됩니다.
RAC는 여러 성능 지점(예: RAC의 경우 1x, 0.5x, 0.25x)뿐만 아니라 여러 RAC를 ALU 장치 옆에 배치할 수 있는 멀티 코어 확장성(2x 이상)을 지원하는 확장 가능한 장치입니다. 현재 PowerVR GPU 설계에서는 RAC가 128폭 ALU 장치 2개에 의해 공유되므로 RAC, ALU 및 TPU(텍스처 처리 장치) 활용도가 향상됩니다. RAC 1개, ALU 2개, TPU 장치 2개(스케줄링 로직 및 기타 고정 기능 지원 포함)의 조합을 SPU(확장 가능 처리 장치)라고 합니다. 이는 GPU 코어당 1~4개의 SPU 장치 범위로 CXT GPU 제품군이 구축되는 기본 장치를 형성하며, 분산형 멀티 코어 시스템 덕분에 더욱 확장될 수 있습니다.
아래 표에는 다양한 수준과 실행 효율성에 대한 영향, 그리고 그에 따른 전력, 성능 및 대역폭에 대한 영향이 요약되어 있습니다.
GPU 블록 레이 트레이싱 작업 레벨 1 RTLS 레벨 2 RTLS 레벨 3 RTLS 레벨 4 RTLS
ALU 로딩 전체 높음 낮음 낮음
ALU 효율성 낮음 낮음 중간 높음
박스/트라이 테스터 해당 없음 중간 높음 전체
BVH 워킹 예 예 예 예
일관성 아니요 아니요 아니요 예
캐시 적중 낮음 낮음 낮음/중간 높음
대역폭 사용량 높음 높음 중간 낮음
전력 효율성 매우 낮음 낮음 중간 높음
Microsoft DirectX Ray Tracing(DXR)에서 인라인 Ray Tracing이라고도 하는 Ray 쿼리는 기본적으로 모든 셰이더 또는 코어(컴퓨팅)가 전체 Ray Tracing 프로세스를 시작하는 Ray 쿼리를 실행할 수 있기 때문에 이해하기 매우 쉽습니다. 이 시스템에서는 생성된 적중/실패 정보가 이를 처리해야 하는 동일한 셰이더/커널로 반환됩니다. 따라서 레이 트레이싱은 매우 간단하며 DXR 명명 스타일에 따르면 실제로는 인라인 프로세스입니다.
간단한 예는 그림자 조명입니다. 여기서 장면은 정상적으로 렌더링되지만 이제 프래그먼트/픽셀 셰이더에서는 광선이 광원을 향해 발사되고 광원에 닿으면 현재 픽셀이 조명되고 셰이더에서 올바른 코드를 실행할 수 있음을 알 수 있습니다. 장면의 다른 개체에 부딪히면 그것이 그림자에 있다는 것을 알 수 있으며 셰이더에서 올바른 코드가 실행될 수 있습니다. 이 시나리오에서는 반사가 더 어려울 것입니다. 왜냐하면 반사 개체에 부딪힐 때 해당 반사 개체에 대한 올바른 색상을 렌더링하는 방법을 파악하기 위해 많은 복잡성이 트리거되어야 하고 이 모든 것이 원래 투영 셰이더에서 처리되어야 하기 때문입니다.

레이 쿼리를 사용하는 것은 대부분의 초기 렌더링 알고리즘에 권장되며 기존 게임 엔진에 추가하기가 더 쉽고 구현 시 더 예측 가능한 성능을 제공할 가능성이 높습니다.

PowerVR Photon은 다음과 같이 테스트된 광선 상자 및 광선 삼각형의 수를 제거하는 상위 수준 구조에 대한 가속 구조 및 경계 볼륨 계층을 참조합니다.

그림에 표시된 것처럼 경계 볼륨 계층 구조는 경계 상자를 체계적으로 확인하는 속도 향상 메커니즘을 제공하며 상자가 누락된 경우 해당 수준 아래의 모든 상자/삼각형을 무시할 수 있음을 알고 있습니다. 이를 통해 광선 테스트 프로세스를 최소한으로 줄이는 가속 구조가 됩니다. 이 구조와 그 생성에 사용된 품질 및 경험적 방법은 하드웨어의 효율성에 상당한 영향을 미칩니다. 최고의 구조는 간단하고 잘못 구성된 구조보다 작업 부하를 더 효과적으로 줄일 수 있기 때문입니다. 따라서 API는 이러한 가속화된 구조를 생성하는 빠른 방법과 느린 방법을 모두 노출합니다.
빠른 구성 알고리즘은 애니메이션이 적용되고 높은 프레임 속도를 유지하기 위해 프레임마다 광범위하게 변경되는 객체에 매우 중요합니다. 정적 개체의 경우 로드 시 느린 빌드 방법을 사용해야 하며(또는 개발 중에는 오프라인이라도) 정적 개체는 전체 수명 동안 사용되므로 최대한 최적화해야 합니다. 이는 최상위 가속 구조(TLAS)와 다중 하단 가속 구조(BLAS)의 두 가지 요소로 구성됩니다. 위에서 설명한 내용은 예제의 토끼와 같은 물체의 가속 구조를 포함한다는 점에서 BLAS에 더 가깝지만 TLAS는 여러 BLAS 구조로 구성됩니다.
가속 구조를 구축하는 단계는 다음과 같습니다.

RAC로 이동하기 전에 GPU 내부에서 다양한 처리 단계가 필요합니다. 레이 쿼리를 사용하는 하이브리드 렌더링 워크로드의 경우 이는 다음과 같이 요약될 수 있습니다.
애플리케이션은 메모리에 명령 버퍼와 데이터 구조(텍스처, 셰이더, 버퍼)를 구성하여 GPU 드라이버에서 처리하는 API 호출을 실행하여 장면을 렌더링합니다. 또한 드라이버는 하드웨어를 시작하여 절전 모드에서 절전 모드를 해제하거나 단순히 처리해야 할 추가 작업으로 표시합니다. 이 트리거는 모든 내부 활동 관리를 처리하고 모든 작업이 설정된 우선 순위를 준수하는지 확인하는 내장형 펌웨어 프로세서를 트리거합니다.
일반적으로 가장 먼저 해야 할 일은 지오메트리 처리를 시작하는 것입니다. 즉, 드로우 콜은 GPU 내에서 작업이 되고, 각 작업은 GPU 내에서 예약되며 처리를 위해 USC 내에서 필요한 리소스를 예약하도록 설계되었습니다. 그런 다음 버텍스/기하학 데이터가 추출되고, 데이터를 사용할 수 있게 되면 작업이 활성화되고 셰이더 프로그램이 실행됩니다. 이는 출력 기하학을 생성한 다음 컬링, 클리핑, 타일링 및 기하학 압축과 같은 일련의 고정 기능 블록에 도달한 다음 중간 매개변수 데이터를 메모리에 씁니다.
이 매개변수 데이터는 각 타일에서 볼 수 있는 타일당 지오메트리의 링크된 목록으로, 타일 기반 디퍼드 렌더링이 마법을 발휘할 수 있게 해줍니다. 이 모든 작업은 처리의 첫 번째 단계로 흔히 기하학 단계 또는 타일 가속기(TA) 단계라고 합니다. 이 단계는 다음 렌더링 단계와 동시에 실행됩니다.

타일 기반 디퍼드 렌더링 아키텍처의 3D 처리는 HSR에서 시작됩니다. 모든 3D 처리는 하나씩 수행됩니다. 즉, 파라메트릭 데이터 연결 목록 구조를 사용하여 위치 데이터를 얻습니다. 타일링된 깊이/템플릿 내의 모든 지오메트리 데이터에 대해 각 픽셀에 대해 표시되는 객체를 나타내는 마커 버퍼 내에 가시성 목록을 생성하는 테스트가 수행됩니다. 모든 형상이 처리되고 픽셀로 표시된 가시성 목록이 있으면 논리적으로 단일 불투명 개체(뒤에 있는 모든 항목이 숨겨지거나 제거되므로)이고 불투명 개체 앞에 있는 여러 알파 블렌딩 레이어입니다.
그런 다음 렌더링은 셰이더별로 정렬된 올바른 깊이 순서로 시작되며 각 셰이더는 작업을 나타냅니다. 작업 처리는 먼저 스케줄러가 처리를 위해 USC 내에 필요한 리소스를 예약한 다음 작업이 활성화되고 올바른 셰이더 프로그램 명령을 실행하기 전에 작업과 데이터를 미리 가져오는 것을 의미합니다. 작업의 셰이더 프로그램에 광선 쿼리 호출이 포함된 경우 여기에서 RAC가 트리거됩니다.
레이 쿼리 호출이 있는 셰이더의 경우 작업은 USC 리소스뿐만 아니라 RAC 리소스도 요청합니다. 실제 레이 트레이싱은 셰이더가 USC/Ray 인터페이스(URI)를 사용하여 필요한 광선 정보를 RAC에 내보낼 때 수행되며, 이 정보는 광선 저장소에 저장됩니다.
텍스처 작업과 유사하게 필요한 조명 정보를 RAC로 전송한 후 USC는 작업을 예약되지 않은 대기 상태로 설정합니다. 즉, RAC가 작업을 수행하는 동안 USC는 다른 작업/작업 처리를 시작합니다. 상상할 수 있듯이, 이 모든 작업은 하나의 조각/작업 항목 또는 광선이 처리되는 것이 아니라 각 작업(워프) 내에서 여러 스레드가 병렬로 처리되므로 대규모 병렬입니다. 또한 하드웨어는 대기 시간 흡수 및 높은 활용도를 보장하기 위해 이러한 많은 작업을 수행합니다. RAC는 처리해야 하는 많은 광선을 효율적으로 저장합니다.
이 시점에서 각 광선은 필요한 각 테스트에 따라 증가하는 광선 참조 카운터에 의해 추적됩니다. 가속 구조에 따르면 이러한 테스트는 처음부터 시작하여 더 많은 상자가 교차할수록 증가하므로 더 많은 상자 테스트가 트리거됩니다. 광선 처리는 일관성 그룹에서 발생합니다. 즉, 그룹화된 일관성 수집 블록이 광선을 스캔하여 구조를 일관성 있게 통과하는 광선 그룹을 구축한다는 의미입니다. 패킷이 가득 차면 필요에 따라 상자 및/또는 삼각형 및/또는 기본 테스터를 통해 광선을 실행하여 실행됩니다. 이 처리는 전용 ASC(가속 구조 캐시)를 통해 실행되므로 데이터가 패킷 내에서도 재사용됩니다.
물론 ASC는 단지 캐시 수준일 뿐입니다. 추가 캐싱은 가장 큰 SLC 캐시 수준과 SoC 수준의 시스템 수준 캐시를 포함하여 GPU 메모리 계층 전체에서 발생합니다. 이 프로세스가 완료되면 참조 카운트가 0에 도달하고 광선 결과가 준비될 때 프로세스가 종료될 때까지 테스트가 예약되고 완료됨에 따라 광선 참조 카운터(RRC)가 증가하고 감소합니다.
이 시점에서 하나 이상의 광선이 추가 셰이더 처리를 위해 제어권을 USC로 반환하도록 예약됩니다. 즉, USC 작업이 재개됩니다. 그런 다음 USC는 모든 처리를 위해 리소스를 예약하는 광선 저장소의 URI를 통해 생성된 광선 데이터를 읽을 수 있습니다.
이 단계에서는 레이 쿼리가 있거나 없는 셰이더/커널 혼합을 수행하여 타일이 완전히 그려질 때까지 셰이더 처리가 정상적으로 계속됩니다. 이 과정에서 텍스처 처리 장치와 같은 다른 고정 기능 블록을 사용하여 셰이더를 실행합니다.
이 시점에서 실행은 여러 작업이 혼합되어 있다는 점을 인식하는 것이 중요합니다. 지오메트리가 처리되고, 컴퓨팅 작업이 실행될 수 있으며, RAC가 광선을 추적하고 적중/실패를 찾고, 셰이더 코어가 이러한 모든 작업의 일부로 코드를 실행합니다. 2D 및 관리 작업을 사용하여 데이터를 복사하거나 MIPMAP을 생성할 수도 있습니다. 이러한 다양한 작업의 목표는 모든 처리 장치에서 최대의 효율성을 얻고 다른 독립적인 작업을 처리하여 모든 처리 작업 및 메모리 액세스의 대기 시간을 완전히 숨기는 것입니다.
청킹이 완료되면 픽셀 백엔드가 트리거되고 IMGIC(Imagination Image Compression) 프레임 버퍼 압축을 사용하여 완료된 청킹이 메모리에 기록됩니다.
레이트레이싱 시 숨겨진 일관성
Ray Tracing은 본질적으로 "당황스러울 정도로 병렬"이지만, 실시간 Ray Tracing이 실용화되기까지 오랜 시간이 걸린 이유 중 하나는 병렬이 존재하더라도 종종 서로 다르고 일관되지 않기 때문입니다. 아래 그림을 보면 이해가 가능합니다.

실제 세계에서 재료는 서로 다른 속성을 가지고 있습니다. 일부는 매끄럽지만 대부분은 러프니스 때문에 실제 표면에서는 빛이 같은 방식으로 반사되지 않고 다른 방향으로 반사됩니다. 그 결과 발산이 발생합니다. 즉, 빛이 한 픽셀에서 다음 픽셀로 바운스되어 빛이 다른 방향으로 이동합니다. 따라서 광선은 BVH 상자를 통해 서로 다른 경로를 따르므로 서로 다른 메모리 액세스가 발생하고 논리적으로 서로 다른 방향으로 이동하는 광선도 서로 다른 삼각형과 교차하므로 서로 다른 셰이더 프로그램이 트리거되어 셰이더 실행에 차이가 발생합니다.
GPU는 고도의 병렬 워크로드를 처리하는 데 매우 능숙하지만 SIMD 아키텍처는 해당 워크로드가 일관되고 유사한 경우에만 의미가 있기 때문에 GPU에는 좋지 않습니다. 각 픽셀이 다른 작업을 수행하려는 경우 GPU가 높은 실행 및 대역폭 효율성을 위해 의존하는 트릭은 실패합니다. 즉, 무차별 접근 방식(예: 많은 ALU 및 레이 트레이싱 장치 사용)으로 끝나고 처리 과정에서 이를 효율적으로 사용하는 데 문제가 있는 경우 보상해야 합니다(예: 이론적 최대 처리량은 높지만 실제 사용에서는 활용도가 낮으면 처리량이 낮아집니다).
그러나 한 픽셀에서 다음 픽셀로 광선이 발산할 수 있다고 해서 튀어오르는 광선 묶음 사이에 "일관성"이 없다는 의미는 아닙니다. 다시 말하지만, 이는 아래 이미지에 가장 잘 설명되어 있습니다. 아래 반사 모양은 이 물체에서 반사된 빛의 숨겨진 일관성을 보여줍니다. 예를 들어, 노란색 옷을 입은 사람이 여러 번 반사되는 것을 볼 수 있습니다. 이는 이러한 광선이 같은 방향으로 가고 실제로 일관성이 있음을 의미합니다. 더 중요한 것은 이러한 광선을 그룹화할 수 있으면 BVH를 통해 유사한 경로를 따라 캐시 적중률과 데이터 재사용률을 제공한다는 것입니다. 또한 결국 동일한 삼각형을 치고 교차하며 동일하거나 유사한 셰이더 프로그램을 실행할 수도 있어 기존 병렬 GPU ALU 파이프라인에서 높은 효율성을 제공할 수 있습니다.

약 10년 전, 멀티 패스 래스터라이제이션는 긴 반복 시간, 아티스트를 위한 투박한 작업 흐름, 가시성 관점에서 렌더링 아티팩트의 근사치, 자주 작동했던 사전 베이킹 및 캐시 조명 등의 문제로 전환점에 도달했습니다. 의도한 대로 빛 전송을 정확하게 시뮬레이션할 수 없을 때까지는 작동하지 않았습니다. 경로 추적 사용 - 모든 것을 처리하는 통합 조명 전송 알고리즘, 기본 요소에는 표면, 머리카락, 볼륨 측정이 포함됩니다. 반사에는 모든 유형의 BSDF, BSSRDF가 포함됩니다. 조명에는 점 조명, 영역 조명, 환경 맵 조명이 포함됩니다.
분산의 개념과 공식:
img
모든 샘플링 기술은 단위 사각형에서 다른 영역, 반구, 구, 구 주위의 원뿔, 디스크로 임의의 숫자를 뒤틀는 것을 기반으로 합니다. BSDF의 산란 분포를 기반으로 샘플을 생성하거나 IBL 광원의 방향을 선택할 수도 있습니다. 샘플링하는 방법은 엄청나게 많지만 모두 0과 1 사이의 값으로 시작하고 약간의 직교성이 있습니다. "시작하는 값은 무엇입니까?"와 "두 번째 몬테 카를로 추정을 사용하기 위해 샘플링하려는 분포로 이 값을 어떻게 왜곡합니까?"가 있습니다.
img
해당 샘플링 방법에는 일반적으로 사용되는 방법에는 균일, 저차 시퀀스, 계층화된 샘플링, 요소 간격, 블루 노이즈 디더링 등이 포함됩니다. 낮은 시차는 일반화된 층화와 유사하고 블루 노이즈는 서로 다른 샘플이 서로 얼마나 가까운지와 유사합니다. 절차 모드는 원하는 만큼의 접두사를 사용할 수 있으며 (일부) 접두사는 균등하게 배포됩니다.
img
분산 기반 샘플링 - 지금까지 수집된 샘플을 기반으로 각 픽셀의 분산을 주기적으로 추정하고, 차이가 큰 경우 오버샘플링하고, 분산/추정이 더 높은 경우 더 많은 샘플을 취하는 것이 좋습니다. 톤매핑 등을 수행한 후에 이 작업을 수행합니다. 오프라인(품질 기반): 픽셀의 분산이 충분히 낮아지면 처리를 중지합니다. 실시간(프레임 속도 기반): 분산이 가장 큰 곳에서 더 많은 샘플을 수집합니다. 표본 분산을 계산합니다(중요 사항: 표본 분산은 실제 분산의 추정치입니다).
float SampleVariance(float samples[], int n)
{
float sum = 0, sum_sq = 0;
for (int i=0; i<n; ++i)
{
sum += samples[i];
sum_sq += samples[i] * samples[i];
}
return sum_sq/(n*(n-1))) - sum*sum/((n-1)*n*n);
}샘플 분산은 단지 추정치일 뿐이며, MC 렌더링 적응형 샘플링 및 재구성의 최근 발전인 노이즈 제거에 많은 작업이 이루어졌습니다. 일반적인 아이디어: 보조 기능(위치, 노멀 등)의 근접성에 따라 가중치를 부여할 수 있는 인근 픽셀에 샘플 분산을 추가합니다. 높은 분산은 저주입니다. 일단 고분산 표본이 도입되면 큰 문제에 봉착하게 됩니다. 예를 들어 데이터를 균일하게 샘플링하는 것을 고려해 보세요.
6개의 샘플: (1, 1, 1, 1, 1, 100) ≒ 17.5, 그리고 6개의 샘플을 더 채취합니다: (1, 1, 1, 1, 1, 100, 1, 1, 1, 1, 1, 1) ≒ 9.25. 분산은 샘플 수에 따라 선형적으로 감소한다는 점을 기억하십시오. 이렇게 높은 분산 샘플에 직면했을 때 가장 해학적이지만 가장 효과적인 방법은 아래 그림과 같이 고정하는 것입니다.
img
보다 정교한 옵션은 밀도 기반 이상값 거부, 모든 샘플 저장, 이상값 분석 및 필터링입니다. 샘플은 밝기에 따라 별도의 이미지로 분할된 다음 통계 분석에 따라 가중치가 다시 부여됩니다.
img
오프라인에서 실시간으로의 레이 트레이싱의 몇 가지 중요한 측면:
조명을 현명하게 선택하세요. 몬테카를로 통합법, 분산, 중요도 샘플링, 다중 중요도 샘플링 등이 사용됩니다.
(비)난수를 신중하게 선택하세요. 도메인 왜곡, 준랜덤 시퀀스, 낮은 비유사성, 계층화.
레이 예산을 가장 유용한 곳에 사용하세요. 적응형 샘플링.
오류를 이해하고 방지합니다. 강도 클램핑, 경로 정규화.
몬테카를로 간략한 검토:
img
Quasi Monte Carlo(QMC): 결정론적, 낮은 불일치 시퀀스/앙상블(Halton, Hammersley, Larcher-Pillichshammer)은 무작위보다 빠르게 수렴합니다. Sobol 또는 (0-2) 시퀀스는 샘플 수, 환상적인 계층적 속성을 알 필요가 없습니다.
img
아래 이미지는 경사지고 디퓨즈되는지면에 빛의 넓은 영역을 보여줍니다. 카메라 바로 앞에는 굴절률이 1인 얇은 유리판이 있습니다(따라서 완벽한 투과율). 이는 장면의 대부분(전부는 아니지만)이 창 뒤에 있는 상당히 일반적인 시나리오의 디버그 버전입니다.
img
광선에 대한 고정된 분할 요소가 있기 때문에 인덱스도 쉽게 추적할 수 있습니다. 각 조명 샘플 계산에 대해 샘플 i부터 i+3까지 사용할 수 있습니다. Cornell 상자의 경우처럼 각 조명 위치가 해당 위치와 매우 다르다면 큰 차이가 없을 것입니다(그러나 속성이나 주문을 고려하면 적어도 그만큼 좋을 것입니다). 그러나 위치가 더 유사하거나 동일한 경우. . .
img
카메라에 직접 표시되는 지상의 64개 광원 샘플을 사용합니다.
img
아래 이미지는 샘플링 예산을 더 쉽게 테스트할 수 있도록 하는 약간 다른 시나리오입니다. 장면의 얇은 유리 조각은 더 거칠며, 이 거친 효과를 더 잘 이해하기 위해 바닥에 텍스처를 적용했습니다.
img
이전과 동일한 샘플링 방법을 사용하면 거친 유리에 의해 생성된 BSDF 광선의 일관성은 물론 이전만큼 좋지 않아 아래와 같이 더 거친 이미지가 생성됩니다.
img
결과적으로 우리는 픽셀 간의 상관 관계가 매우 좋지 않은 상황에 직면하고 4D 시퀀스의 일부 차원은 픽셀 내에서 상관 관계가 좋지 않습니다. 이를 위해서는 Jarosz et al.이 사용한 시각화 규칙에 따라 4D 점의 다양한 2D 투영을 살펴봐야 합니다. 직교 배열 종이에서는 축의 각 차원에 해당하는 인덱스를 볼 수 있으며 배열의 각 셀에는 해당 2D 슬라이스가 표시됩니다. 이는 샘플링 루틴에 사용된 슬라이스인 차원 (0,1) 및 (2,3)의 2D 슬라이스이며 실제로는 동일합니다.
img
이제 아래의 "진단" 슬라이스를 살펴보세요. (0,3)과 (1,2)도 동일합니다. 나머지 (0,2)와 (1,3)은 (0,0)과 (1,1)과 동일합니다. . .
img
실제로는 고위도 Sobol 시퀀스가 필요합니다. 가능한 Sobol 시퀀스는 많이 있으며 [Grünschloß] 및 [Joe 2008]의 Sobol 시퀀스는 낮은 샘플 수에 대해 저차원 2D 투영의 계층적 속성을 최적화합니다.
img
표본 크기는 작지만 모든 차원 쌍을 사용하면 매우 좋은 결과를 얻을 수 있습니다! 이전의 다양한 충전 시도에서 발견된 문제가 사라졌습니다.
img
차원을 올리면 품질이 가장 낮은 2D 슬라이스를 찾을 수 있으며, 피적분 함수의 가장 중요한 부분에 가장 낮은 차원이 사용됩니다.
img
추가 개선 사항에는 모든 차원에 Owen 지시문을 적용하는 것이 포함되어 있으며, 이는 시퀀스 기능의 정렬 패턴을 깨고 수렴 속도를 향상시키는 데 도움이 됩니다. 빠른 프로세스가 아니기 때문에(특히 실시간의 경우) 많은 수의 샘플을 사전 계산하고 저장할 수 있습니다. 이는 256D에서 256포인트로 HDRP에서도 수행되는 작업입니다.
img
가장 중요한 점은 화면 공간에 블루 노이즈를 추가하는 것입니다.
img
백색소음과 청색소음의 비교:
img
더 높은 차원으로 들어갈 때 모든 차원을 동시에 고려하고 재귀적인 저차원 적분을 고려하지 마십시오(이것이 더 자연스럽게 시작되더라도). 실시간(사전 계산된) 선택 시퀀스는 Monte Carlo 분산을 화면 공간의 블루 노이즈로 배포하는 불일치가 낮은 샘플러[Heitz 2019]와 점진적 다중 지터 샘플 시퀀스[Christensen 2018]입니다. 고차원 컬렉션에 대한 주목할만한 최근 작업은 Monte Carlo 렌더링을 위한 직교 배열 샘플링[Jarosz 2019]이며, 실시간 경로 추적의 미래는 실시간 재구성 조명 전송[Wyman 2020]입니다.
T-ReX: Interactive Global Illumination of Massive Models on Heterogeneous Computing Resources에서도 하이브리드 렌더링 파이프라인을 사용하는 아이디어에 대해 언급했습니다. 즉, CPU는 기하학적 표현식을 사용하여 완전한 세부 사항이 포함된 직접광을 계산하는 반면, GPU는 대략적인 간접광을 계산하기 위해 구성된 볼륨 표현식으로 희소 복셀 옥트리를 사용합니다(아래). 그 중 직접광과 간접광에 대한 데이터 변환 및 전송은 있으나 기하학적 표현과 복셀 표현에는 해당되지 않습니다.

아래 그림은 원본 메쉬와 대략적인 볼륨을 각각 이용하여 계산된 원본 메쉬, 대략적인 볼륨, 조명 효과를 보여줍니다. (노란색 원은 볼륨을 사용하여 계산된 간접광을 나타냅니다.)

이 기사에서는 빛을 C-Ray와 G-Ray로 구분합니다. 그 중 C-Ray(아래 그림의 파란색)는 기하학적인 디테일에 더 민감하며 완벽한 거울 소재에 반사되는 고주파 시각 효과, 1차 광선 및 2차 광선을 생성하는 데 사용됩니다.

G-Ray(아래 그림의 어두운 보라색과 주황색)는 기하학적 세부 사항에 덜 민감하며 저주파 시각 효과를 생성하는 데 사용됩니다. C-Ray 이외의 모든 광선(수집 광선, 그림자 광선 등)

데이터 구조 측면에서 CPU는 HCCMesh를 사용하고 GPU는 ASVO를 사용합니다.

HCCMesh는 C-Ray의 고품질 기하학적 구조 처리, 무작위 접근 압축(압축 비율 7:1~20:1) 및 고성능 압축 해제를 지원하는 데 사용됩니다.
ASVO는 G-Ray의 GPU 측 볼륨 표현인 Augmented SparseVoxel Octree입니다. GPU에서 효율적으로 탐색하고 기하학 및 광자 매핑을 근사화합니다. ASVO는 해상도를 높이고, 폐색 비트맵을 저장하고, 이를 LOD 표현으로 사용하여 노드별로 재료와 법선을 나타낼 수 있습니다.

ASVO는 2단계 구조를 채택합니다. 최상위 ASVO는 항상 GPU 메모리(예: 300MB)에 로드되고, 최하위 ASVO는 프로그레시브 렌더링이 필요할 때 비동기적으로 로드됩니다. Occlusion 비트맵을 사용하기 전과 후를 비교하면 다음과 같습니다.

전반적인 렌더링 과정은 다음과 같습니다.
- GPU 측은 복셀을 사용하여 광자를 추적하고 광자 정보를 ASVO에 저장합니다.

- CPU 측은 HCCMesh를 사용하여 C-Ray를 추적하고 완료 후(블로거의 추측) GPU 측과 동기화합니다.

- GPU 측은 복셀 추적 G-Ray를 사용하고 결과를 광자 정보에 저장합니다.

- GPU 측은 ASVO의 2레벨 구조에서 광자 정보 색상을 사용하여 최종 조명 결과를 생성합니다.

이 방법은 분리된 표현(CPU의 HCCMeshes 및 GPU의 ASVO)을 사용하여 대규모 모델을 처리하고 값비싼 전송 비용을 줄이며 CPU 및 GPU의 높은 활용도를 달성하는 대규모 모델의 전역 조명을 위한 통합 프로그레시브 렌더링 프레임워크를 제안합니다. 한계는 체적 표현이 기하학적 모델보다 더 큰 공간에 걸쳐 편향되고 일관성이 없다는 것입니다.

대규모 장면을 위한 확장 가능한 실시간 전역 조명에서는 대규모 장면을 위한 확장 가능한 실시간 GI 솔루션을 설명합니다. 해결책은 장면의 복셀 표현(예: 복셀 원뿔 추적), 충돌 기하학, 엔터티의 낮은 LOD, 높이 맵 데이터, 화면의 피드백과 같이 카메라 주변의 초기 복셀화된 조명 장면(예: 복셀 콘 추적)입니다. GBuffer 복셀화된 조명 장면, 실제 강력한 레이캐스팅(빛 없음)을 사용하여 매 프레임마다 부분적으로 재계산된 가시 복사 조도 volmap(볼륨 맵).
방사조도 맵: 카메라 주변의 중첩된 볼륨 맵(3d 클립맵)에 방사조도를 저장합니다. 각 캐스케이드는 셀 크기가 0.45m 3^i(또는 저사양 장치에서는 0.9m 3^i)인 ~64x32x64입니다. i는 캐스케이드 인덱스입니다. HL2 환경 큐브 베이스, 직교 베이스가 선택되었지만 GPU는 샘플에 매우 친숙하며 다른 것으로 쉽게 변경할 수 있습니다. 기지.
기본 장면 매개변수화: 장면을 카메라 주변의 중첩된 볼륨 맵(3d 클립맵)에 저장합니다. 각 캐스케이드는 128x64x128이고 셀 크기는

Sponza 장면 매개변수화:

초기 장면 채우기: 카메라가 이동하면 새 복셀이 "링 안에" 채워지고(텍스처 랩과 유사) 새 복셀은 높이 맵 데이터와 충돌 형상(버텍스 음영 처리) 또는 엔터티의 낮은 수준 LOD로 채워집니다. 그런 다음 이 새 복셀을 햇빛, 간접 조명 방사조도 및 해당 영역의 가장 중요한 빛으로 즉시 비춥니다.
장면 피드백 루프: 중간 설정에서 장면은 32k GBuffer 픽셀을 무작위로 선택하여 지속적으로 업데이트됩니다. 무작위로 선택된 각 GBuffer 픽셀에 대해 알베도, 노멀, 위치, 직접광 및 간접 조사량 맵을 사용하여 조명을 받습니다. 이동 평균은 이 새로운 글로우 색상의 복셀화된 장면 표현을 업데이트하는 데 사용됩니다. 이는 장면 복셀이 다시 조명된 GBuffer 픽셀로 업데이트되고, 현재 방사조도 볼륨 맵으로 업데이트되고, 방사조도 볼륨 맵이 현재 장면 복셀로 업데이트되기 때문에 피드백 루프를 제공합니다. 다중 바운스를 제공할 뿐만 아니라 복셀화 문제(벽이 2복셀보다 얇고 정확도가 높음)도 해결하며 환경 프로브(렌더링 시)는 "기본" 카메라가 캡처할 수 없는 더 많은 데이터를 제공합니다.
방사조도 맵 초기화: 카메라가 이동함에 따라 더 미세한 계단식의 경우 새 텍스처(프로브)를 채우고, "장면 교차점" 및 가장 거친 계단식 맵의 경우 더 거친 계단식에서 데이터를 복사하고, 더 나은 초기 근사치를 얻기 위해 64개의 광선을 추적하고, 시간적 수렴 가중치를 마법("실제로 계산되지 않은") 값으로 표시하므로 표시되자마자 다시 활성화됩니다.
방사조도 맵 - 계산 루프: 방사량 맵에서 눈에 보이는 수백 개의 "프로브"(위치)를 무작위로 선택합니다. 선택 확률은 "프로브"의 가시성과 프로브의 수렴 인자(마지막 변경 정도)에 따라 달라집니다. 선택한 "프로브"에 대해 장면에서 1024~2048개의 광선을 캐스팅하고(설정에 따라) 이동 평균 방법으로 결과를 축적합니다.
Irradiance Map - 계산 대기열: 빠른 수렴을 위해서는 Irradiance Map에서 서로 다른 "프로브"에 대해 서로 다른 대기열을 갖는 것이 중요합니다. 처음 볼 때 품질이 낮더라도 가능한 한 빨리 계산되지는 않습니다. 256개의 광선을 사용하지만 대기열에 4096개의 프로브가 있는 분리된 장면 프로브는 조명 전송에 참여하지 않지만 여전히 동적 개체, 볼륨 측정 및 입자에 대해 계산해야 합니다. 1024개의 광선을 사용하면 대기열 크기는 64~128개의 프로브에 불과합니다.
초기화 조명 - 닭고기와 달걀 문제: 카메라가 순간 이동할 때 주변의 모든 캐스케이드가 유효하지 않으므로 초기 장면을 방사조도(두 번째 바운스)로 조명할 수 없으며 초기 장면 없이는 초기 방사조도를 계산할 수도 없습니다. 이 작업은 두 가지 단계로 이루어집니다. 장면을 복셀화하고, 직접 조명으로만 조명하고, 채광창과 두 번째 바운스에 대한 방사조도를 계산한 다음, 거의 발생하지 않는 장면의 크기를 조정합니다(실루엣).
방사조도 맵을 사용한 렌더링: 노멀 부호를 기반으로 6개의 방사조도 볼륨 맵 텍스처 중 3개를 샘플링하려면 최상의 캐스케이드를 선택합니다(HL2 주변 큐브 참조). 경계에서 다음 캐스케이드와 지연 및 전달 패스(및 체적 조명)를 혼합합니다.
또한 볼록 오프셋 필터링을 사용하여 빛 누출 문제를 해결합니다.

내부의 지나치게 어두운 문제의 경우 "구멍/창" 볼륨(복셀로 채워지지 않음)을 추가하여 다음을 해결하세요.

요약하면, 이 방법은 다중 바운스, 조정 가능한 품질, 게임 플레이를 저하시키지 않고 저가형 PC에서 최고급 하드웨어까지 지원, 확장 가능한 세부 크기 및 레이 트레이싱 품질을 갖춘 일관된 간접 조명을 갖춘 GI를 생성합니다. 동적(어느 정도), 벽을 폭파하고, 건물을 파괴하여 빛이 비치도록 하고, 벽을 만들어 반사와 간접 그림자를 만들고, 빠르게 반복합니다.
17.4 그래픽 API 및 GPU
이 장에서는 현재 시장에 나와 있는 여러 인기 그래픽 API에 대한 레이 트레이싱 지원의 현재 상태와 기술을 설명합니다.
17.4.1 DirectX RayTracing(DXR)
DXR(DirectX RayTracing)은 하드웨어 레이 트레이싱을 지원하기 위해 DirectX 12에 도입된 그래픽 API 기능 세트입니다. 최고 수준에서 DXR은 DirectX 12 API에 네 가지 새로운 개념을 도입합니다.
가속구조는 GPU 순회에 가장 적합한 형식으로 완전한 3D 환경을 표현하는 객체입니다. GPU에 최적화된 광선 탐색과 애플리케이션별 동적 개체의 효율적인 수정을 제공하는 2단계 계층 구조로 표현됩니다.
DispatchRays는 광선을 장면으로 추적하기 위한 시작점이자 게임이 DXR 워크로드를 GPU에 제출하는 방법인 새로운 명령 목록 방법입니다.
레이 트레이싱 파이프라인 상태는 레이 트레이싱 셰이더 및 레이 트레이싱 워크로드와 관련된 기타 상태를 캡슐화하는 오늘날의 그래픽 및 컴퓨팅 파이프라인 상태 개체의 정신적 동반자입니다.
광선 생성, 가장 가까운 적중, 적중 및 미스 셰이더를 포함한 새로운 HLSL 셰이더 유형 세트. DXR 워크로드가 실제로 계산적으로 수행하는 작업을 지정합니다. DispatchRays가 호출되면 광선 생성 셰이더가 실행됩니다. 광선 생성 셰이더는 HLSL의 새로운 TraceRay 내장 기능을 사용하여 장면으로 광선을 추적합니다. 장면의 빛 위치에 따라 여러 히트 또는 미스 셰이더 중 하나가 교차점에서 호출될 수 있으며, 이를 통해 게임은 각 객체에 고유한 셰이더 및 텍스처 세트를 할당하여 고유한 재질을 생성할 수 있습니다.
img

GPU 내부의 레이 트레이싱 처리 흐름도.
DX12의 새로운 그래픽 API에는 프로그래밍 가능한 레이 트레이싱 렌더링 파이프라인(위)이 추가되었습니다. 기존 래스터라이제이션 파이프라인과 마찬가지로 레이 트레이싱 파이프라인에는 고정된 논리와 프로그래밍 가능한 부분이 있습니다. 5개의 새로운 셰이더(Shader)가 새 파이프라인에 추가되었습니다.
Ray Generation: 광선을 생성하는 데 사용됩니다. 이 셰이더에서 TraceRay()를 호출하여 광선을 재귀적으로 추적할 수 있습니다. 모든 레이 트레이싱 작업의 시작점, 호스트에서 시작된 스레드의 간단한 2D 그리드, 레이 트레이싱 및 최종 출력 작성.
교차: TraceRay()가 빛이 객체와 교차하는 것을 감지하면 이 셰이더가 호출되어 사용자가 교차하는 객체가 특수 프리미티브(구, 세분화 표면 또는 기타 프리미티브 유형)인지 감지할 수 있습니다. 애플리케이션 정의 프리미티브, 내장 광선 삼각형 교차점을 사용하여 광선 교차점을 계산합니다.
Any Hit: TraceRay()가 빛이 객체와 교차하는 것을 감지하면 이 셰이더가 호출되어 사용자는 교차하는 객체가 특수 프리미티브(구, 세분화 표면 또는 기타 프리미티브 유형)인지 감지할 수 있습니다. 교차점이 발견된 후 호출되며 여러 교차점이 임의의 순서로 호출됩니다.
Closest Hit 및 Miss: TraceRay()가 전체 장면을 횡단할 때 이 두 셰이더는 광선의 교차 여부에 따라 호출됩니다. Cloesit Hit는 재질, 텍스처 조회, 조명 계산 등과 같은 픽셀 셰이딩 처리를 수행할 수 있습니다. Cloesit Hit와 Miss 모두 계속해서 TraceRay()를 재귀적으로 호출할 수 있습니다. Closest Hit는 광선의 가장 가까운 교차점에서 호출되며 속성을 읽고 광선을 추적하여 페이로드를 수정할 수 있습니다. Miss 적중이 발견되지 않고 허용되지 않으면 호출되며, 광선을 추적하고 광선 페이로드를 수정할 수 있습니다.
다음은 위 셰이더 중 일부의 사용법을 더 잘 설명하기 위한 적용 예입니다.
// 을(를) 활용하여 의 데이터 ,정의 의 데이터 。
struct Payload
{
float4 color;
float hitDistance;
};
// 의 가속 구조체 ,테이블 의 。
RaytracingAccelerationStructure scene : register(t5);
[shader("raygeneration")]
void RayGenMain()
{
// 페치/가져오기(Fetch)스케줄링 의 위치 (매핑 까지 픽셀(Pixel),로써 테이블 픽셀(Pixel)좌표 )。
uint2 launchIndex = DispatchRaysIndex();
// 정의 개 광선(Ray),에 의해 포인트 、 및 t。
RayDesc ray;
ray.Origin = SceneConstants.cameraPosition.
ray.Direction = computeRayDirection( launchIndex ); // 계산/산출(Calculate)(함수 ,구현 무시/스킵(Ignore))
ray.TMin = 0;
ray.TMax = 100000;
Payload payload;
// 을(를) 활용하여 정의 의 유효(Valid)타입/유형 광선(Ray),에 의해 트리거 의 셰이딩 필수: 의 페이로드(Payload)타입/유형 。
TraceRay( scene, 0 /*flags*/, 0xFF /*mask*/, 0 /*hit group offset*/,
1 /*hit group index multiplier*/, 0 /*miss shader index*/, ray, payload );
outputTexture[launchIndex.xy] = payload.color;
}
// 히트(Hit)정보 ,에 의해 교차(Intersection)셰이딩 패딩/채우기 。교차(Intersection)셰이딩 ,에 의해 히트(Hit)포인트 의 좌표 。
struct Attributes
{
float2 barys;
};
[shader("closesthit")]
void ClosestHitMain( inout Payload payload, in Attributes attr )
{
// 로드/읽기(Read)교차(Intersection)결과 기록/쓰기(Write)페이로드(Payload)。
payload.color = float4( attr.barys.x, attr.barys.y, 1 - attr.barys.x - attr.barys.y, 1 );
// 개 의 HLSL:현재(Current)의 거리。
payload.hitDistance = RayTCurrent();
}AnyHit과 CloseHit 간의 작동 메커니즘 및 차이점에 대한 개략도:

광선은 페이로드와 함께 제공될 수 있습니다. 즉, 광선 생성의 적중 단계와 최종 교차 정보를 광선 생성 셰이더에 반환하는 데 사용되는 셰이더 단계 간에 데이터를 전달하는 데 사용되는 애플리케이션 정의 구조입니다.

광선에는 교차점 셰이더에서 적중 셰이더로 교차점 정보를 전달하는 데 사용되는 응용 프로그램 정의 구조인 속성도 있을 수 있습니다.

DXR은 단일 입력 광선만 볼 수 있고 실행 중인 다른 광선의 처리 순서를 보거나 의존할 수 없는 다양한 유형의 셰이더를 포함하여 광선을 독립적으로 처리할 수 있도록 구현하도록 설계되었습니다. 일부 셰이더 유형은 지정된 호출 중에 여러 광선을 생성할 수 있으며(원하는 경우) 광선 처리 결과를 볼 수 있습니다. 그럼에도 불구하고, 즉석에서 생성된 광선은 결코 서로 의존하지 않습니다. 이러한 가벼운 독립성은 병렬성의 가능성을 열어줍니다. 실행 중에 이를 활용하기 위해 일반적인 구현에서는 스케줄링과 기타 작업의 균형을 유지합니다.

실행의 일정 부분은 하드 와이어링되어 있거나 적어도 하드웨어에 맞게 사용자 정의할 수 있는 불투명한 방식으로 구현됩니다. 작업 순서 지정과 같은 전략은 스레드 간의 일관성을 최대화하는 데 자주 사용되며, API 관점에서 레이 스케줄링은 기본 제공 기능입니다.
레이 트레이싱의 다른 작업은 고정 기능과 완전히 또는 부분적으로 프로그래밍 가능한 작업의 조합입니다. 가장 큰 고정 기능 작업은 잠재적인 광선 교차점을 효율적으로 찾는 것을 목표로 애플리케이션에서 제공하는 형상으로 구축된 가속 구조를 탐색하는 것입니다. 고정 함수는 삼각형 교차점도 지원합니다. 셰이더 프로그래밍 기능은 광선 생성, 암시적 형상과의 교차점 결정(고정 기능 삼각형 교차점 옵션과 반대), 광선 교차점(예: 표면 음영 처리) 또는 누락 처리의 형태로 제공됩니다. 또한 응용 프로그램은 주어진 상황에서 셰이더 풀에서 실행되는 셰이더에 대한 높은 수준의 제어뿐만 아니라 각 셰이더 호출에서 액세스할 수 있는 텍스처와 같은 리소스에 대한 유연성도 제공합니다.
다음 그림은 하드웨어 레이 트레이싱 시스템과 관련된 개념, 가속 구조, 메모리 레이아웃 및 작동 메커니즘을 보여줍니다.

위 그림에는 장면의 모든 기하학적 객체 정보를 저장하는 데 사용되는 가속 구조(Acceleration Structure)가 포함되어 있으며 GPU에서 객체 탐색, 교차 테스트, 조명 구성 등에 대한 극한 가속 알고리즘을 제공하여 레이 트레이싱이 실시간 렌더링 수준에 도달할 수 있습니다. BuildRaytracingAccelerationStructure() 인터페이스를 통해 애플리케이션에서 빌드할 수 있습니다.
img
위에 표시된 것처럼 장면의 각 형상에 대해 GPU 내부에는 두 가지 수준의 가속 구조가 있습니다.
입력된 삼각형, 사각형 등의 기본 정보를 바탕으로 BLAS(Bottom-level Acceleration Structure)를 구성합니다.
최상위 가속 구조(TLAS)는 기본 가속 구조에서 생성됩니다. 이는 기본 가속 구조의 인스턴스와 동일하며 기본 구조의 변환 행렬과 셰이더 오프셋을 저장합니다.
응용 프로그램은 BuildRaytracingAccelerationStructure()의 D3D12_RAYTRACING_ACCELERATION_STRUCTURE_BUILD_FLAGS 플래그를 통해 가속 구조를 업데이트 가능하게 만들거나 업데이트 가능한 가속 구조를 업데이트할 수 있습니다. 레이 트레이싱 성능 측면에서 업데이트 가능한 가속 구조(업데이트 전후)는 처음부터 정적 가속 구조를 구축하는 것만큼 최적이 아니지만, 처음부터 가속 구조를 구축하는 것보다 업데이트가 더 빠릅니다.
셰이더 바인딩 테이블(Shader Binding Table, SBT)은 셰이더가 연결된 장면의 개체를 설명하고 셰이더와 관련된 모든 리소스(텍스처, 버퍼, 상수 등)도 포함합니다.

GPU 하단에 있는 셰이더 매핑 테이블은 동일한 크기의 레코드입니다. 각 레코드는 리소스 세트가 있는 셰이더(또는 교차 그룹, 히트 그룹)와 연결됩니다. 일반적으로 기하학당 하나의 레코드 볼륨이 있습니다.
img
위 그림에서 볼 수 있듯이 각 레코드는 셰이더 번호로 시작하고 CBV, UAV, 상수, 설명 테이블과 같은 셰이더 리소스를 포함합니다. 이 2계층 아키텍처의 장점은 리소스와 인스턴스화를 분리하고, 인스턴스 생성 및 초기화를 가속화하며, 대역폭과 비디오 메모리 사용량을 줄이는 것입니다.
SBT는 두 가지 광선 유형을 사용하여 일반적인 레이 트레이싱기에 대한 히트 그룹 레코드 레이아웃을 목표로 하며 두 인스턴스가 있는 장면을 렌더링하며 그 중 하나에는 두 형상이 모두 있습니다. 다음 그림을 예로 들면 각 히트 그룹 레코드는 32바이트이고 단계 크기는 64바이트입니다. 광선을 추적할 때

더 자세하고 심층적인 SBT 메커니즘을 보려면 RTX 셰이더 바인딩 테이블 세 가지 방법을 참조하세요.
DXR의 TraceRay 작업 과정은 다음과 같습니다.

위 그림에서:
[1] 이 단계에서는 가속도 구조를 검색하여 광선과 교차할 수 있는 프리미티브를 보수적으로 열거합니다. 프리미티브가 광선과 교차하고 현재 광선 범위 내에 있으면 결국 열거되는 것이 보장됩니다. 기본 요소가 광선과 교차하지 않거나 현재 광선 범위를 벗어나는 경우 기본 요소는 열거되거나 열거되지 않을 수 있습니다. 적중이 커밋되면 TMax가 업데이트된다는 점에 유의하세요.
[2] 교차 셰이더가 실행 중이고 ReportHit()가 호출되면 후속 로직이 교차를 처리한 다음 [5]를 통해 교차 셰이더로 돌아갑니다.
[3] 불투명도는 교차점의 형상 및 인스턴스 플래그와 광선 플래그를 검사하여 결정됩니다. 또한 히트 셰이더가 없으면 형상이 불투명한 것으로 간주됩니다.
[4] RAY_FLAG_ACCEPT_FIRST_HIT_AND_END_SEARCH 조명 플래그가 설정되거나 AcceptHitAndEndSearch()라는 히트 셰이더가 설정된 경우 AcceptHitandSearch() 호출 지점에서 히트 셰이더의 실행이 중단됩니다. 최소한 이 적중이 커밋되었으므로 현재까지 가장 가까운 적중에는 가장 가까운 적중 셰이더가 실행됩니다(RAY_FLAG_SKIP_CLOSEST_HIT_SHADER를 통해 비활성화되지 않음).
[5] 교차하는 기본 요소가 삼각형이 아닌 경우 교차 셰이더는 활성 상태로 유지되고 ReportHit()에 대한 추가 호출이 포함될 수 있으므로 실행을 계속합니다.
DXR은 TraceRay()의 변형인 TraceRayInline()을 호출하여 실행되는 인라인 파이프라인 추적 모드도 지원합니다. 운영 흐름도는 다음과 같습니다.

DXIL 라이브러리 및 상태 개체 예:

Microsoft의 오래되고 강력한 그래픽 디버깅 소프트웨어인 PIX는 출시 이후 DXR 디버깅을 지원해 왔습니다. PIX는 다양한 호출 스택, 렌더링 상태, 리소스 및 기타 정보를 쉽게 디버깅하는 데 사용할 수 있습니다.
img
DXR을 사용하는 단계는 다음과 같습니다.
- 첫 번째 단계는 2단계 계층으로 작동하는 가속 구조를 구축하는 것입니다. 구조의 내부에서 애플리케이션은 세계의 다양한 개체를 나타내는 일련의 형상(기본적으로 버텍스 및 인덱스 버퍼)을 지정합니다. 구조의 최상위 수준에서 애플리케이션은 특정 형상에 대한 참조를 포함하는 인스턴스 설명 목록과 변환 행렬과 같은 추가 인스턴스별 데이터를 지정합니다. 이러한 데이터는 현재 게임이 동적 개체 업데이트를 수행하는 방식과 유사한 방식으로 프레임별로 업데이트될 수 있습니다. 함께 사용하면 여러 복잡한 형상을 효율적으로 탐색할 수 있습니다.

각각 자체 변환 행렬이 있는 두 개의 도형의 인스턴스입니다.
두 번째 단계는 레이 트레이싱 파이프라인 상태를 생성하는 것입니다. 오늘날 대부분의 게임은 효율성을 위해 모든 금속 개체를 먼저 렌더링한 다음 모든 플라스틱 개체를 렌더링하는 등 그리기 호출을 일괄 처리합니다. 그러나 특정 광선이 어떤 물질에 닿을지 정확하게 예측할 수 있는 방법이 없기 때문에 레이 트레이싱에서는 이러한 일괄 처리가 불가능합니다. 이와 대조적으로 레이 트레이싱 파이프라인 상태를 사용하면 여러 레이 트레이싱 셰이더 및 텍스처 리소스 세트를 지정할 수 있습니다. 예를 들어, 객체 A와의 광선 교차는 셰이더 P와 텍스처를 사용해야 합니다.
세 번째 단계는 조명 생성 셰이더를 호출하는 DispatchRays를 호출하는 것입니다. 이 셰이더 내에서 애플리케이션은 TraceRay 내장 함수를 호출하여 가속 구조의 순회를 트리거하고 궁극적으로 적절한 적중 또는 실패 셰이더를 실행합니다. 또한 TraceRay는 히트 및 미스 셰이더에서 호출하여 가벼운 재귀 또는 다중 바운스 효과를 허용할 수 있습니다.

장면에서의 빛의 재귀에 대한 설명
레이 트레이싱 파이프라인은 입력 어셈블러 및 출력 결합기와 같은 그래픽 파이프라인의 고정된 기능 단위 중 다수를 생략하므로 기하 도형이 해석되는 방법을 지정하는 것은 애플리케이션에 달려 있습니다. 셰이더에는 이 작업을 수행하는 데 필요한 최소 속성 집합이 제공됩니다. 이는 기본 요소의 교차점의 무게 중심 좌표입니다. 궁극적으로 이러한 유연성은 특정 형식이나 구성을 강요하지 않고도 다양한 기술을 가능하게 하는 DXR의 큰 장점입니다.
모든 레이 트레이싱 관련 GPU 작업은 애플리케이션 예약 명령 목록 및 대기열을 통해 예약됩니다. 결과적으로 Ray Tracing은 래스터라이제이션 또는 계산과 같은 다른 작업과 긴밀하게 통합되며 멀티스레드 애플리케이션에 의해 효율적으로 대기열에 포함될 수 있습니다. 레이 트레이싱 셰이더는 컴퓨팅 셰이더와 유사한 작업 항목 그리드로 예약되므로 구현 시 GPU의 대규모 병렬 처리 처리량을 활용하고 지정된 하드웨어 조건에 따라 작업 항목의 하위 수준 예약을 수행할 수 있습니다.
응용 프로그램은 필요할 때 래스터라이제이션 및 컴퓨팅과 같은 GPU 작업과 리소스를 명시적으로 동기화하는 책임을 유지하므로 개발자는 레이 트레이싱, 래스터라이제이션, 컴퓨팅 작업 및 메모리 전송 간의 최대 중첩량을 최적화할 수 있습니다. 레이 트레이싱 및 기타 디스패치 유형은 텍스처, 버퍼 및 상수와 같은 모든 리소스를 공유하며 레이 트레이싱 셰이더에서 리소스에 액세스하려면 변환, 복사 또는 매핑이 필요하지 않습니다. 가속 구조, 셰이더 테이블, 메모리 할당 또는 전송과 같은 레이 트레이싱 특정 데이터를 보유하는 리소스는 암시적으로 발생하지 않으며, 셰이더 컴파일은 명시적이며 완전히 애플리케이션 제어하에 있습니다. 셰이더는 필요한 경우 개별적으로, 일괄적으로, 여러 CPU 스레드에서 병렬로 컴파일할 수 있습니다.
17.4.2 Vulkan RayTracing
Vulkan 레이 트레이싱은 새로운 셰이더 유형, 가속 구조 등을 포함하여 DirectX와 유사합니다.

Vulkan의 2층 가속 구조 개략도.

Vulkan 레이 트레이싱 셰이더 프로세스.
또한 Vulkan 레이 트레이싱은 Vulkan의 다양한 확장 기능을 사용하여 구현됩니다.
// Vulkan extension specifications
VK_KHR_acceleration_structure
VK_KHR_ray_tracing_pipeline
VK_KHR_ray_query
VK_KHR_pipeline_library
VK_KHR_deferred_host_operations
// SPIR-V extensions specifications
SPV_KHR_ray_tracing
SPV_KHR_ray_query
// GLSL extensions specifications
GLSL_EXT_ray_tracing
GLSL_EXT_ray_query
GLSL_EXT_ray_flags_primitive_culling주요 유형:
VkPhysicalDeviceAccelerationStructureFeaturesKHR: 구현에서 지원할 수 있는 가속 구조 기능을 설명하는 구조입니다.
VkPhysicalDeviceRayQueryFeaturesKHR: 구현에서 지원할 수 있는 광선 쿼리 기능을 설명하는 구조입니다.
VkPhysicalDeviceRayTracingPipelineFeaturesKHR: 구현에서 지원할 수 있는 레이 트레이싱 기능을 설명하는 구조입니다.
VkPhysicalDeviceAccelerationStructurePropertiesKHR: 가속 구조에 사용되는 물리적 장치의 속성입니다.
VkPhysicalDeviceRayTracingPipelinePropertiesKHR: 레이 트레이싱에 사용되는 물리적 장치의 속성입니다.
기타 특수 유형:
- VK_KHR_deferred_host_Operations: 비용이 많이 드는 드라이버 작업을 응용 프로그램 관리형 CPU 스레드 풀로 오프로드하여 작업을 백그라운드 스레드에서 완료하거나 여러 코어에 걸쳐 병렬화할 수 있으며, 이는 레이 트레이싱 파이프라인 컴파일 또는 CPU 기반 가속 구조 구성에 사용할 수 있습니다.

VkDeferredOperationKHR 객체는 지연된 명령의 실행 상태를 캡슐화하며 수명 주기 동안 두 가지 상태(완료됨 또는 보류 중) 중 하나에 있습니다.
- VK_KHR_pipeline_library: 셰이더 세트를 파이프라인에 연결하는 기능을 제공하며, 레이 트레이싱 파이프라인을 점진적으로 구축할 때 유용합니다.
호스트 가속 구조 구축은 유휴 CPU를 활용하여 성능을 향상시킬 수 있는 기회를 제공합니다. 게임의 가상 시나리오를 생각해 보십시오(아래 그림 왼쪽). 가속 구조 구축 및 업데이트는 장치에서 구현되지만 응용 프로그램의 CPU 유휴 시간이 상당합니다. 이러한 작업을 호스트로 이동하면 CPU가 이전 프레임의 렌더링과 병렬로 다음 프레임에 대한 가속 구조를 수행할 수 있습니다. 이는 CPU가 동일한 작업을 수행하는 데 더 많은 벽시계 시간이 필요한 경우에도 처리량을 향상시킬 수 있습니다(아래 이미지, 오른쪽).

Vulkan에서 광선을 추적하려면 가속 구조에 따라 여러 논리적 단계를 거쳐야 하므로 광선을 추적하는 방법에 어느 정도 유연성이 허용됩니다. 교차점 후보는 처음에 기하학적 특성을 토대로 순수하게 발견됩니다. 가속도 구조에 설명된 기하학적 객체와 광선을 따라 교차점이 있습니까?
교차점 테스트는 Vulkan에서 방수됩니다. 즉, 가속 구조에 설명된 단일 기하학적 객체의 경우 빛이 삼각형 사이의 틈을 통해 누출될 수 없으며 동일한 위치에 있는 서로 다른 삼각형의 여러 히트가 보고될 수 없습니다. 이는 연속된 인접 개체를 보장하지는 않지만 단일 모델에 구멍이나 과도한 음영이 없음을 의미합니다.
후보 지점이 발견되면 교차점이 결정되기 전에 일련의 컬링 작업이 수행되며, 이는 횡단에 사용되는 플래그와 가속 구조의 속성을 기반으로 후보를 삭제합니다. 나머지 불투명 삼각형 후보는 유효한 교차점으로 확인되는 반면, AABB 및 불투명하지 않은 삼각형은 적중 발생 여부를 프로그래밍 방식으로 결정하기 위해 셰이더 코드가 필요합니다.
가능한 모든 후보가 발견되어 확인되거나 폐기될 때까지 순회가 계속되며 가장 가까운 적중이 결정됩니다. 불필요한 처리를 피하기 위해 순회를 조기에 종료시키는 것도 가능합니다. 이는 폐색을 감지하는 데 사용되거나 경우에 따라 최적화로 사용될 수 있습니다.
레이 트레이싱 및 통과 결과 획득은 Vulkan의 두 가지 메커니즘, 즉 레이 트레이싱 파이프라인 및 광선 쿼리 중 하나를 통해 수행될 수 있습니다(아래 이미지).
광선 쿼리는 모든 셰이더 단계에서 광선 탐색 논리에 대한 직접적인 액세스를 제공하여 기존 셰이더에 삽입하고 해당 셰이더에서 표현되는 효과를 향상시킬 수 있습니다.
레이 트레이싱 파이프라인은 동적 셰이더 선택이 가능한 전용 레이 트레이싱 메커니즘을 제공하여 장면에 사용되는 재료와 프로그래밍 가능한 교차 논리에 뛰어난 유연성을 허용합니다.

광선 쿼리를 사용하면 광선 탐색을 수행하고 모든 셰이더 단계에서 결과를 반환할 수 있습니다. 가속 구조가 필요할 뿐만 아니라 새로운 셰이더 명령 세트를 사용하여 광선 쿼리가 간단하게 실행됩니다. 광선 쿼리는 쿼리할 가속도 구조, 횡단 속성을 결정하는 광선 플래그, 컬링 마스크 및 추적된 광선의 기하학적 설명으로 초기화됩니다. 순회 중에 셰이더는 광선 쿼리 자체의 속성뿐만 아니라 잠재적인 교차점 및 제출된 교차점의 속성에 액세스할 수 있으므로 형상이 교차하는 위치, 방법 및 위치를 기반으로 복잡한 결정을 내릴 수 있습니다(아래 이미지).

더 자세한 튜토리얼: NVIDIA Vulkan Ray Tracing 튜토리얼, 샘플 코드: Vulkan의 Ray Tracing.

Vulkan 레이 트레이싱 효과의 예.
17.4.3 금속 레이 트레이싱
Metal Ray Tracing의 프로세스는 다음과 같습니다.

Metal 성능 셰이더는 고성능 교차점(MPSRayIntersector)을 사용하여 GPU에서 광선 삼각형 교차 테스트를 가속화함으로써 높은 교차점 소비 문제를 해결합니다. GPU는 Metal 버퍼를 통과하는 광선을 허용하고 금속 버퍼를 통과하는 각 광선을 따라 가장 가까운 교차점(주 광선의 경우) 또는 교차점(그림자 광선의 경우)을 반환합니다. Metal 성능 셰이더는 교차점 계산을 최적화하는 데 사용되는 가속 구조라는 데이터 구조를 구축합니다. Metal 성능 셰이더는 장면의 삼각형을 설명하는 정점에서 가속 구조를 구축합니다. 교차점을 검색하려면 교차점에 가속도 구조를 제공합니다.

가속 구조를 지원하는 MPSRayIntersector의 교차점 감지 프로세스에 대한 개략도:

버텍스 버퍼(GPU에 구축 가능)의 삼각형에 가속 구조를 구축하고 가속 구조를 MPSRayIntersector에 전달합니다.

가속도 구조와 교차점 검출기를 사용하면 프로세스는 다음과 같습니다.

동적 개체의 경우 처음부터 새로 만드는 것보다 훨씬 빠른 Refit 메커니즘이 활성화됩니다. GPU에서 실행하면 지오메트리를 추가하거나 삭제할 수 없으므로 가속 구조의 품질이 저하될 수 있습니다.

2단계 가속 구조의 경우 시나리오 예시와 데이터 구조는 다음과 같습니다.

노이즈 감소 측면에서 입력은 이번 프레임과 이전 프레임의 노이즈 이미지, 깊이, 노멀 및 모션 벡터입니다. 결과가 디노이저에 의해 처리된 후 노이즈 감소 이미지가 출력됩니다.

노이즈 감소 알고리즘은 고품질 SVGF 노이즈 감소 알고리즘인 MPSSVGF를 사용합니다. MPSSVGFDenoiser는 소음 감소 프로세스와 낮은 수준의 제어를 조정합니다.

렌더링 프로세스에서는 다른 경쟁업체와 마찬가지로 하이브리드 렌더링 파이프라인이 사용됩니다.

광선을 생성할 때 Metal은 지정된 순서로 광선을 처리합니다. 블록 선형 레이아웃은 광선의 일관성을 향상시키고 캐시 적중률을 향상시켜 성능을 향상시킬 수 있습니다.

그림자, AO 등을 계산하는 과정에서도 중요도 샘플링을 사용하여 빛을 생성하며, 동일한 시각적 품질을 유지하려면 빛이 덜 필요합니다. 반구형, 코사인 샘플링 및 거리 샘플링이 중요도 샘플링 프로세스에 사용됩니다.

왼쪽부터: 반구, 반구+코사인, 반구+코사인+거리.
잡음을 줄이기 위해 Halton, Sobol 등의 저차 시퀀스를 사용한다. 인접한 픽셀은 서로 다른 샘플링 방향을 갖습니다. 모든 픽셀에 대해 동일한 저차 샘플링을 사용할 수 있습니다.

GI의 경우 렌더링 프로세스는 다음과 같습니다.

Metal의 최적화 기술에는 다음이 포함됩니다.
대역폭 사용량을 줄입니다. 로드와 저장을 결합하고, 가능하면 더 작은 데이터 유형을 사용하고, 구조를 분할합니다. 반직관적 - 필요하지 않은 구조 멤버 로드/저장을 피하기 위해 자체 원점 및 방향 버퍼를 사용합니다.
레지스터 압력을 줄입니다. 또한 라이브 변수의 수와 크기를 추적하고, 구조적 데이터를 보존하지 말고, 루프 카운터 및 함수 호출에 주의하세요.
비활성 광선을 제거하십시오. 장면을 벗어나 더 이상 측정 가능한 효과를 나타낼 만큼 충분한 에너지를 전달하지 못하는 경우 투명한 표면에서 내부 전반사가 발생합니다. 많은 반복 후에 최종적으로 살아남은 광선의 23%만이 다음과 같습니다.

스레드 그룹은 거의 사용되지 않고, 광선 교차기는 여전히 비활성 광선을 처리해야 하며, 제어 흐름 명령문은 비활성 광선을 제거하는 데 사용됩니다. 광선을 압축할 수 있습니다. 활성 광선만 다음 광선 버퍼에 추가되고 스레드 그룹은 완전히 활용되며 그림자 광선에도 작동합니다.

버퍼 인덱스는 더 이상 일정한 픽셀 위치에 매핑되지 않으며 각 광선의 픽셀 좌표를 추적해야 합니다.

또한 Metal은 여러 GPU에 대한 인터리브 타일링도 지원합니다.

더 작은 타일은 GPU 전체에서 더 균등하게 렌더링되고, 의사 무작위 할당은 장면 의존성을 방지하며, 동일한 GPU는 매 프레임마다 동일한 타일을 렌더링합니다.

ng)
타일을 할당할 때 각 타일에 난수를 할당하고 임계값과 비교하여 GPU를 선택합니다.

데이터 전송 프로세스는 다음과 같습니다.


Metal의 레이 트레이싱 장면은 일반적으로 다음 단계를 따릅니다.
카메라에서 장면으로 주 광선을 투사하고 가장 가까운 교차점에서 그림자를 계산합니다. 즉, 광선이 형상에 닿는 카메라에 가장 가까운 지점입니다.
교차점에서 광원까지 그림자 광선을 투사합니다. 교차하는 형상으로 인해 그림자 광선이 광원에 도달하지 못하는 경우 교차점은 그림자에 있습니다.
교차점에서 임의의 방향으로 2차 광선을 투사하여 빛 바운스를 시뮬레이션합니다. 보조 광선이 형상과 교차하는 곳에 조명 기여를 추가합니다.
Metal Ray Tracing 관련 샘플 코드:
Metal을 이용한 레이 트레이싱 가속화
레이 트레이싱을 사용하여 실시간으로 반사 렌더링
교차 쿼리를 사용하여 레이 트레이싱 프로세스 제어
Metal을 사용하여 레이 트레이싱 및 모션 블러 가속화
17.4.4 레이 트레이싱 X(RTX)
그래픽 커뮤니티의 세계적 수준의 탐험 선구자로서 NV는 레이 트레이싱에 대한 심층적인 연구 개발을 수행했으며 마침내 이를 기술 표준 RTX 플랫폼으로 추상화했습니다.
DirectX 12의 DXR 및 Vulkan의 지원으로 하드웨어 수준의 레이 트레이싱 기술이 점차 대중화되고 있습니다. NV는 Turing 아키텍처 GPU에서 RTX 기술을 최초로 지원합니다.
img
위 그림에서 볼 수 있듯이 최상위 레이어는 딥러닝과 일반 애플리케이션 개발을 포함하는 사용자 레이어(MDL 및 USD)입니다. 중간 레이어는 OptiX, DXR 및 Vulkan을 포함한 RTX를 지원하는 그래픽 API 레이어입니다. OpenGL은 RTX를 지원하지 않습니다; 맨 아래 레이어는 RTX 플랫폼으로, 기존 래스터라이저, 레이 트레이싱(RT 코어), CUDA 계산기, AI 코어의 4개 부분으로 구성됩니다.

물론 Turing 아키텍처 GPU 외에도 PASCAL, VOLTA, TURING RTX 및 RTX 기술을 지원하는 기타 아키텍처를 갖춘 GPU도 많이 있습니다. (아래 사진)
img
다음 그림은 동일한 데모(Battlefield)를 실행하는 RTX 기술을 지원하는 여러 GPU의 성능 비교입니다.
img
또한 레이 트레이싱을 사용하면 각 레이 트레이싱 기능의 로드가 다릅니다.
img
위 그림에 포함된 BVH(Bounding Volume Hierarchy)는 계층적 경계 상자(Hierarchical Bounding Box)로, 장면 객체 검색을 가속화하는 알고리즘이자 구조입니다.
개발자의 경우 다양한 화질 수준의 장치에서 프로그램이 잘 실행될 수 있도록 품질 수준에 따라 다양한 표시기를 사전 선택해야 합니다.
TURING RTX의 세 가지 핵심 기능은 다음과 같습니다.

Turing은 또한 새로운 작업 흐름 및 성능 테스트 표준을 도입했습니다.

RT Core 및 Tensor Core(DLSS)를 사용하면 렌더링 성능을 크게 향상하고 총 시간을 줄일 수 있습니다.

NVIDIA Ampere 아키텍처 GPU 제품군의 최신 멤버인 GA102 및 GA104는 Ampere 아키텍처 GPU의 새로운 NVIDIA "GA10x" 클래스의 일부입니다. GA10x GPU는 혁신적인 NVIDIA Turing GPU 아키텍처를 기반으로 합니다.
GeForce RTX 3090은 GeForce RTX 시리즈 중 최고 성능의 GPU이며 8K HDR 게임용으로 설계되었습니다. 10496개의 CUDA 코어, 24GB GDDR6X 메모리 및 새로운 DLSS 8K 모드를 통해 8K@60fps에서 성능을 발휘할 수 있습니다. GeForce RTX 3080은 GeForce RTX 2080보다 2배 높은 성능을 발휘하여 GPU 역사상 가장 큰 세대 도약을 달성했습니다. GeForce RTX 3070의 성능은 NVIDIA의 이전 세대 플래그십 GPU인 GeForce RTX 2080 Ti와 비슷합니다. GA10x GPU의 새로운 HDMI 2.1 및 AV1 디코딩 기능을 통해 사용자는 HDR을 사용하여 8K 속도로 콘텐츠를 스트리밍할 수 있습니다.
NVIDIA A40 GPU는 오늘날의 디자인, 창의적, 과학적 과제를 해결하기 위해 동급 최고의 전문 그래픽과 강력한 컴퓨팅 및 AI 가속을 결합하여 데이터 센터의 성능과 다중 워크로드 기능에 있어서 혁신적인 도약입니다. RTX A6000과 동일한 코어 수와 메모리 크기를 갖춘 A40은 차세대 가상 워크스테이션과 서버 기반 워크로드를 지원합니다. NVIDIA A40은 이전 세대보다 에너지 효율성이 2배 더 높아 전문가에게 레이 트레이싱 렌더링, 시뮬레이션, 가상 제작 등을 위한 가장 진보된 기능을 제공합니다.
img
Ampere GA10x 아키텍처는 큰 도약입니다.
GA102의 주요 기능에는 2x FP32 프로세싱, 2세대 RT Core, 3세대 Tensor Core, GDDR6X 및 GDDR6 메모리, PCIe Gen 4 등이 포함됩니다.
이전 NVIDIA GPU와 마찬가지로 GA102는 GPC(그래픽 처리 클러스터), TPC(텍스처 처리 클러스터), SM(스트리밍 멀티프로세서), ROP(래스터 연산자) 및 메모리 컨트롤러로 구성됩니다. 전체 GA102 GPU에는 7개의 GPC, 42개의 TPC 및 84개의 SM이 포함되어 있습니다.
GPC는 주요 고급 하드웨어 블록이며 모든 주요 그래픽 처리 장치는 GPC 내부에 있습니다. 각 GPC에는 전용 래스터 엔진이 포함되어 있으며 이제 NVIDIA Ampere Architecture GA10x GPU의 새로운 기능인 2개의 ROP 파티션(각각 8개의 ROP 장치 포함)도 포함됩니다. GPC에는 6개의 TPC가 포함되어 있으며, 각 TPC에는 2개의 SM과 PolyMorph 엔진이 포함되어 있습니다.
img
GA102 GPU에는 168개의 FP64 장치(SM당 2개)도 있으며 FP64 TFLOP 속도는 FP32 작동 TFLOP 속도의 1/64입니다. FP64 Tensor Core 코드를 포함하여 FP64 코드가 있는 모든 프로그램이 올바르게 실행되도록 보장하기 위해 소수의 FP64 하드웨어 장치가 포함되어 있습니다.
GA10x GPU의 각 SM에는 128개의 CUDA 코어, 4개의 3세대 Tensor 코어, 256KB 레지스터 파일, 4개의 텍스처 유닛, 2세대 레이 트레이싱 코어, 128KB의 L1/공유 메모리가 포함되어 있으며, 이는 컴퓨팅 또는 그래픽 워크로드의 요구 사항에 따라 다양한 용량으로 구성할 수 있습니다. GA102의 메모리 하위 시스템은 12개의 32비트 메모리 컨트롤러(총 384비트)로 구성되며, 각 32비트 메모리 컨트롤러와 쌍을 이루는 512KB의 L2 캐시, 전체 GA102 GPU에서 총 용량은 6144KB입니다.
Ampere 아키텍처는 ROP에서도 최적화를 수행합니다. 이전 NVIDIA GPU에서 ROP는 메모리 컨트롤러 및 L2 캐시에 연결되었습니다. GA10x GPU부터 ROP는 GPC의 일부이며 총 ROP 수를 늘리고 스캔 변환 프런트엔드와 래스터 작업 백엔드 간의 처리량 불일치를 제거하여 래스터 작업 성능을 향상시킵니다. GPC당 7개의 GPC 및 16개의 ROP 장치를 갖춘 전체 GA102 GPU는 이전 세대 TU102와 같은 384비트 메모리 인터페이스 GPU에서 사용할 수 있었던 96개의 ROP 대신 112개의 ROP로 구성됩니다. 이 방법은 다중 샘플 앤티앨리어싱, 픽셀 채우기 속도 및 혼합 성능을 향상시킵니다.
SM 아키텍처 측면에서 Turing SM은 NVIDIA의 첫 번째 SM 아키텍처이며 레이 트레이싱 작업을 위한 전용 코어를 포함합니다. Volta GPU에는 텐서 코어가 도입되었으며 Turing에는 향상된 2세대 텐서 코어가 포함되어 있습니다. Turing 및 Volta SM이 지원하는 또 다른 혁신은 FP32 및 INT32 작업의 병렬 실행입니다. GA10x SM은 위의 모든 기능을 개선하는 동시에 강력한 새 기능을 많이 추가합니다. 이전 GPU와 마찬가지로 GA10x SM은 4개의 처리 블록(또는 파티션)으로 나뉘며, 각 블록에는 64KB 레지스터 파일, L0 명령 캐시, 워프 스케줄러, 스케줄링 장치, 수학 및 기타 장치 세트가 있습니다. 4개의 파티션은 128KB L1 데이터 캐시/공유 메모리 하위 시스템을 공유합니다. 파티션당 2개의 2세대 텐서 코어를 포함하여 총 8개의 텐서 코어를 포함하는 TU102 SM과 달리 새로운 GA10x SM은 파티션당 1개의 3세대 텐서 코어를 포함하여 총 4개의 텐서 코어를 포함합니다. 각 GA10x 텐서 코어는 Turing 텐서 코어보다 2배 더 강력합니다. Turing에 비해 GA10x SM의 1차 데이터 캐시와 공유 메모리를 합친 용량은 33% 더 큽니다. 그래픽 워크로드의 경우 캐시 파티션 용량이 Turing의 두 배로 32KB에서 64KB로 증가합니다.
img
GA10x 스트리밍 멀티프로세서(SM).
GA10x SM은 Turing 지원 2단 FP16(HFMA) 작동을 계속 지원합니다. TU102, TU104 및 TU106 Turing GPU와 유사하게 표준 FP16 작업은 GA10x GPU의 텐서 코어에 의해 처리됩니다. FP32 처리량의 비교 X 요소는 다음과 같습니다.
튜링 GA10x
FP32 1X 2X
FP16 2X 2X
앞서 언급했듯이 이전 세대 Turing 아키텍처와 마찬가지로 GA10x는 공유 메모리, L1 데이터 캐시 및 텍스처 캐시를 위한 통합 아키텍처를 갖추고 있습니다. 이 통합 설계는 워크로드에 따라 재구성되어 필요에 따라 L1 또는 공유 메모리에 더 많은 메모리를 할당할 수 있습니다. 레벨 1 데이터 캐시 용량이 SM당 128KB로 증가되었습니다. 컴퓨팅 모드에서 GA10x SM은 다음 구성을 지원합니다.
128KB L1 + 0KB 공유 메모리
120KB L1 + 8KB 공유 메모리
112KB L1 + 16KB 공유 메모리
96KB L1 + 32KB 공유 메모리
64KB L1 + 64KB 공유 메모리
28KB L1 + 100KB 공유 메모리
Ampere 아키텍처의 RT Core는 광선/삼각형 교차 테스트에서 Turing의 RT Core보다 두 배 빠릅니다.
img
GA10x GPU는 RT 코어와 그래픽 또는 RT 코어와 컴퓨팅 워크로드를 각 GA10x GPU SM에서 동시에 처리할 수 있는 새로운 기능을 통해 이전 NVIDIA GPU의 비동기 컴퓨팅 기능을 향상시킵니다. GA10x SM은 두 가지 컴퓨팅 워크로드를 동시에 처리할 수 있으며 이전 GPU 세대와 같은 동시 컴퓨팅 및 그래픽에 국한되지 않으므로 컴퓨팅 기반 노이즈 제거 알고리즘과 같은 시나리오를 RT Core 기반 레이 트레이싱 작업과 동시에 실행할 수 있습니다.
img
Turing 아키텍처와 비교하여 NVIDIA Ampere 아키텍처는 동일한 게임에서 동일한 프레임을 렌더링할 때 성능을 크게 향상시킬 수 있습니다.
img
위: Turing 기반 RTX 2080 Super GPU에서 셰이더 코어(CUDA 코어), 셰이더 코어 + RT 코어, 셰이더 코어 + RT 코어 + 텐서 코어만 사용하는 Wolfenstein: Youngblood의 프레임입니다. 다른 RTX 처리 코어를 추가하면 프레임 시간이 점차 감소합니다.
하단: Ampere 아키텍처 기반의 RTX 3080 GPU는 셰이더 코어(CUDA 코어), 셰이더 코어 + RT 코어, 셰이더 코어 + RT 코어 + 텐서 코어만을 사용하여 Wolfenstein: Youngblood의 프레임을 렌더링합니다.
img
GA10x RT Core는 Turing RT Core에 비해 광선/삼각형 교차 테스트 속도를 두 배로 높이고 레이 트레이싱 모션 블러 작업을 지원하기 위해 새로운 보간 삼각형 위치 가속 장치를 추가합니다.
희소성이 활성화된 GeForce RTX 3080은 밀도가 높은 Tensor 코어 작업을 갖춘 GeForce RTX 2080 Super에 비해 FP16 Tensor 코어 작업의 최대 처리량을 2.7배 제공합니다.
img
세밀하게 구조화된 희소성은 0이 아닌 4개 패턴 중 2개 패턴을 사용하여 훈련 가중치를 잘라내고, 0이 아닌 가중치를 미세 조정하는 간단한 일반적인 방법이 뒤따릅니다. 데이터 공간과 대역폭을 2배로 줄이기 위해 가중치가 압축되고, 희소 텐서 코어 연산은 0을 건너뛰어 수학 처리량을 두 배로 늘립니다. (아래 사진)
img
아래 그림은 GDDR6(왼쪽)과 GDDR6X(오른쪽) 간의 데이터 아이 비교를 보여줍니다. GDDR6X 인터페이스는 GDDR6 주파수의 절반으로 동일한 양의 데이터를 전송할 수 있습니다. 또는 특정 작동 주파수에서 GDDR6X는 GDDR6보다 유효 대역폭을 두 배로 늘릴 수 있습니다.
img
GDDR6X는 PAM4 신호를 사용하여 성능과 효율성을 향상시킵니다.
PAM4 신호로 인한 신호 대 잡음비 문제를 해결하기 위해 고속 신호 전송을 제한하기 위해 MTA(Maximum Transmission Abolition, 아래 그림 참조)라는 새로운 코딩 방식이 개발되었습니다. MTA는 신호가 최고 수준에서 최저 수준으로 또는 그 반대로 전환되는 것을 방지하여 인터페이스 신호 대 잡음비를 향상시킵니다. 이는 인코딩 핀에 전송된 바이트 중 데이터 버스트(시간 인터리브)의 일부를 각 핀에 할당한 다음 신중하게 선택한 코드워드를 사용하여 데이터 버스트의 나머지 부분을 최대 변환 없이 시퀀스로 매핑함으로써 이를 수행합니다. 또한 새로운 인터페이스 교육, 적응 및 균등화 체계가 도입되었습니다. 마지막으로 패키지 및 PCB 설계에는 더 높은 데이터 속도를 달성하기 위한 신중한 계획과 포괄적인 신호 및 전력 무결성 분석이 필요합니다.
img
기존 스토리지 모델에서는 게임 데이터를 하드 디스크에서 읽은 다음 시스템 메모리와 CPU에서 전송한 다음 GPU로 전송하므로 IO가 게임의 성능 병목 현상이 되는 경우가 많습니다.
img
기존 스토리지 모델을 사용하면 게임 압축 해제 시 Threadripper CPU의 코어 24개를 모두 사용할 수 있습니다. 최신 게임 엔진은 기존 스토리지 API의 기능을 뛰어넘었습니다. 차세대 입출력 아키텍처가 필요합니다. 데이터 전송 속도는 회색 막대이고, 필요한 CPU 코어는 검은색/파란색 블록입니다. 데이터를 압축해야 하는데 CPU가 따라잡을 수 없습니다.
img
NVIDIA RTX IO는 최신 게임에 필요한 복잡한 워크로드와 최첨단 NVMe SSD를 갖춘 게임용 PC용으로 설계된 차세대 스토리지 아키텍처인 Microsoft의 곧 출시될 DirectStorage API에 연결됩니다. 요약하면, 게임용으로 특별히 맞춤화된 간소화되고 병렬화된 API는 IO 오버헤드를 크게 줄이고 NVMe SSD에서 RTX IO 지원 GPU까지 성능/대역폭을 최대화할 수 있습니다. 특히 NVIDIA RTX IO는 GPU 기반 무손실 압축 해제 기능을 제공하므로 DirectStorage를 통한 읽기가 압축된 상태로 유지되고 압축 해제를 위해 GPU로 전달됩니다. 이 기술은 CPU에서 로드를 제거하고, 보다 효율적이고 압축된 형태로 데이터를 메모리에서 GPU로 이동하며, I/O 성능을 최대 2배까지 향상시킵니다.
img
RTX IO는 100배의 처리량과 20배의 CPU 사용률을 제공합니다. 데이터 전송 속도는 회색 및 녹색 막대이며, 필요한 CPU 코어는 검은색/파란색 블록입니다.
img
레벨 로딩 시간 비교. 부하 테스트는 24코어 Threadripper 3960x 플랫폼, 프로토타입 Gen4 NVMe m.2 SSD, 알파 소프트웨어에서 실행되었습니다.
17.4.5 Radeon Rays/ProRender
Radeon Rays는 Radeon ProRender에 표시된 대로 AMD의 효율적인 고성능 광선 교차 감지 가속 라이브러리입니다. 게임 개발 워크플로우를 위한 대화형 라이트 베이킹 및 실시간 간접 사운드 시뮬레이션을 포함한 다양한 사용 사례를 지원합니다. 특정 기능은 다음과 같습니다:
DirectX 12 및 Vulkan을 지원합니다.
맞춤형 AABB, GPU BVH 가속, 전체 재구축 없이 지오메트리 업데이트.
전체 스펙트럼 렌더링.

AI 가속.
쉬운 디버깅을 위한 로깅 메커니즘, 소스 코드 변경 사항을 확인하기 위한 테스트 스위트.
MIT 기반 오픈소스.
Radeon Rays는 하이브리드 전역 조명 솔루션을 사용하여 조명 캐시를 계산하는 데 사용됩니다. 조명 캐시는 계층 구조를 사용하여 화면 공간에서 광선을 추적하고 최후의 수단으로 월드 공간 레이 트레이싱, BVH 스트리밍 데이터를 사용합니다. Radeon ProRender는 게임 콘텐츠 제작을 가속화할 수 있는 잠재력을 지닌 빠른 GPU 가속 전역 조명 렌더러입니다. 개발자 SDK(C API)와 제작자 플러그인을 제공합니다.


Radeon ProRender의 일부 기능.

Radeon ProRender 렌더링 샘플.
Radeon ProRender는 새로운 OpenCL 하드웨어 가속 렌더링 기능을 활용합니다. 성능 향상은 장면에 따라 달라지며, 셰이더가 더 복잡한 장면은 일반적으로 하드웨어 가속 레이 트레이싱의 이점을 덜 받습니다. 다음은 AMD Radeon을 사용하여 하드웨어 가속 스위치 RX 6800 XT 그래픽 카드를 테스트하는 몇 가지 간단한 벤치마크 시나리오입니다.

GPU 하드웨어 측면에서 ZEN 2 마이크로아키텍처의 고급 기능은 다음과 같습니다.

IPC가 ZEN에서 ZEN 2로 15% 증가했습니다.
컴퓨팅 캐시 용량이 2배 증가합니다.
L1I 캐시(L1 명령어 캐시)를 다시 최적화했습니다.
3세대 주소 생성 장치.
2x FP 데이터 경로 폭.
L3 용량 2배.
분기 예측 정확도를 향상시킵니다.
하드웨어 최적화 보안 완화.
게스트 모드 실행 트랩(GMET)을 통한 보안 가상화.
SMT 공정성이 향상되었습니다(ALU 및 AGU 스케줄러용).
쓰기 결합 버퍼가 개선되었습니다.
"르누아르" 8코어 프로세서 흐름도는 다음과 같습니다.

"MATISSE" 16코어 프로세서 흐름도는 다음과 같습니다.

"CASTLE PEAK" 64코어 프로세서 흐름도는 다음과 같습니다.

명령어 세트의 진화 과정은 다음과 같습니다.

지원 소프트웨어 프리패치 수준 지침:
지정된 메모리 주소의 캐시 라인을 위치 참조 T0, T1, T2 또는 NTA로 지정된 데이터 캐시 레벨로 로드합니다.
메모리 오류가 감지되면 버스 사이클이 시작되지 않고 명령이 NOP로 처리됩니다.
프리페치 수준 T0/T1/T2는 "Zen" 및 "Zen 2" 마이크로아키텍처에서 동일한 방식으로 처리됩니다.
프리페치 NTA로 표현되는 비일시적 캐시 채우기 힌트는 한 번만 사용되는 데이터의 캐시 오염을 줄입니다. 소규모 데이터 세트의 캐시 차단에는 적합하지 않습니다. 프리페치 NTA를 사용하여 L2 캐시에 채워진 라인은 L2 캐시에서 더 빠르게 제거되도록 표시되며 L2 캐시에서 제거될 때 L3 캐시에 삽입되지 않습니다.
이 지시문의 작동은 구현에 따라 다릅니다. 프리페치 채우기 및 제거 전략은 다른 프로세서 공급업체 또는 마이크로아키텍처 세대에 따라 다를 수 있습니다.

다양한 명령어의 캐시 지연은 다음과 같습니다.

리필은 동일한 CCX 내, 로컬 DRAM 및 다른 CCX의 세 가지 모드를 지원합니다. (아래 사진)

AMD Instinct MI200 그래픽 컴퓨팅 다이(GCD)는 아래와 같습니다:

그림과 같이 2개의 그래픽 컴퓨팅 다이(GCD)를 포함하는 MI200 멀티 칩 모듈(AMD Instinct™ MI250/MI250X).

주력 HPC 노드 구조 다이어그램:

HPC/ML 노드 구조:

ML 최적화 후 노드 구조 다이어그램:

AMD의 오픈 소스 ROCm 스택에는 개발자가 과학 컴퓨팅 및 기계 학습을 위한 고성능 애플리케이션을 구축하는 데 필요한 도구가 포함되어 있습니다.

17.4.6 PowerVR
2014년경 ImageTech는 레이 트레이싱 관련 장치를 PowerVR GR6500 GPU 칩 시리즈에 통합했습니다: 광선 데이터 관리, 장면 가속 구조 생성, 레이 트레이싱 장치 및 캐시 가속 등.

PowerVR 그래픽 마법사 하드웨어 아키텍처, 새로 추가된 레이 트레이싱 관련 장치 및 처리.
Wizard의 3가지 고유 기능: 고정 기능 광선 상자 및 광선 삼각형 테스터, 일관성 기반 작업 형성 및 예약, 스트리밍 장면 계층 생성기. Coherency Engine을 사용하면 아래 그림과 같이 모든 광선을 동시에 처리할 수 있습니다.

아래 그림은 렌더링 효과입니다.
img
PowerVR은 픽셀이 아닌 광선과 평행합니다.


레이는 "Plucker Coordinates를 사용한 Fast Ray-Axis Aligned Bounding Box Overlap Tests with Plucker Coordinates"(Jeffrey Mahovsky 및 Brian Wyvill)에서 영감을 받은 AABB 테스트를 사용합니다. 6개의 선은 AABB의 윤곽선, 광선 원점 및 각 모서리 벡터에 대한 6개의 평면, 평면 법선의 내적 및 광선 방향 벡터를 구성하며, 6개의 기호는 일치하고 음수여야 합니다. 테스트 과정은 다음과 같습니다.

USC 명령어 그룹 패키징에서 다음 그림은 26개의 명령어를 보여줍니다(압축된 데이터 형식을 사용하는 경우 32개).

이 기능의 영역은 44배로 줄어듭니다.

레이 트레이싱 장치와 일관성 엔진의 아키텍처는 다음과 같습니다.

오름차순 가상 메모리 주소에는 가변 삼각형을 포함하여 백만 개의 삼각형에 대해 약 100M의 AABB 블록, 버텍스 블록, 변형 등이 포함됩니다.

다음 그림은 스트리밍 장면 계층 생성기의 효과 및 트리 구조입니다.


일관된 대기열이 사용됩니다.

일관된 경로를 자동으로 찾습니다.

장면 계층 생성기는 다음과 같습니다:

제한 사항: 장면은 삼각형으로 표시됩니다. 오늘날 BVH는 구현 및 순회를 위해 최적화된 정의 형식을 사용하고 삼각형 순서는 일반적으로 공간적으로 일관된 흐름을 따라야 하며 대략적인 장면 규모 추정이 필요하며 지오메트리 셰이더는 레이 트레이싱 파이프라인과 인라인되지 않습니다.
향상된 점은 다음과 같습니다: 버텍스 셰이더보다 높지 않은 셰이딩 클러스터 워크로드, 월드 공간에서 실제로 움직이는 지오메트리만 처리하면 됨, 고유 알고리즘은 작업 세트를 내부 레지스터로만 제한, 단일 패스 작업: 버텍스 셰이더 실행과 일치, "길고 마른 삼각형" 문제를 잘 처리, 외부 메모리로 스트리밍, 빌드 알고리즘으로 인한 무손실 압축 출력 형식, 컴팩트 로직.
희소 log2 옥트리 계층 구조를 기반으로 합니다.

전반적인 실행 과정은 다음과 같습니다.

삼각형 처리 후의 모습은 다음과 같습니다.

상위 노드를 조립한 후:

상위 노드 수준은 조립 시 다음과 같습니다.

광추적 하드웨어 아키텍처의 각 구성요소의 성능은 다음과 같습니다.

PowerVR Ray Tracing은 어떤 삼각형이 어떤 광선과 교차하는지 감지하는 효율성을 크게 향상시키도록 설계된 경계 볼륨 계층 구조 데이터 구조를 생성하는 하드웨어 내 SHG(장면 계층 생성기) 기능을 갖추고 있습니다. 무차별 접근 방식을 사용하려면 전 세계의 모든 삼각형을 사용하여 모든 광선을 테스트해야 하는데, 이는 실시간으로 수행하기에는 비용이 너무 많이 듭니다. 다음 그림은 PowerVR GPU 기반의 실시간 Ray Tracing 흐름도입니다.

비교를 위해 다음은 각각 NV와 AMD의 실시간 Ray Tracing 아키텍처 다이어그램입니다.


17.5 UE 레이 트레이싱
이 장에서는 먼저 Ray Tracing을 통합하고 구현하는 과정에서 UE의 경험, 교훈, 최적화 기술 및 기타 내용을 설명합니다.
17.5.1 UE 레이 트레이싱 통합
17.5.1.1 레이 트레이싱 개요
게임과 같은 실시간 애플리케이션에서 레이 트레이싱을 사용하는 것은 어렵고 BVH(바운드 볼륨 계층 구조) 구성, BVH 순회 및 레이/프리미티브 교차 테스트를 포함하여 레이 트레이싱 알고리즘의 여러 단계에는 비용이 많이 듭니다. 또한 레이 트레이싱 기술에 일반적으로 적용되는 무작위 샘플링은 수렴된 이미지를 생성하기 위해 픽셀당 수백에서 수천 개의 샘플이 필요한 경우가 많으며 이는 현대 실시간 렌더링 기술의 계산 예산을 훨씬 뛰어넘습니다. 또한 최근까지 실시간 그래픽 API에는 레이 트레이싱이 지원되지 않았기 때문에 현재 게임에 레이 트레이싱을 통합하는 것이 어려웠습니다. 2018년에는 DirectX 12 및 Vulkan의 레이 트레이싱 지원 발표로 이러한 상황이 바뀌었습니다.
2018년 초에 NVIDIA의 Edward Liu와 Epic Games의 Juan Cañada 등은 RTX 기반 하드웨어 레이 트레이싱을 UE에 통합했습니다.
DXR(DirectX Ray Tracing)을 채택하고 이를 UE4에 통합하여 기존 머티리얼 셰이더 코드를 재사용할 수 있습니다.
하드웨어 가속 BVH 탐색 및 광선/삼각형 교차 테스트를 위해 NVIDIA Turing 아키텍처의 RT 코어를 활용합니다.
픽셀당 단 하나의 입력 샘플을 사용하여 부드러운 그림자, 광택 반사, 확산 전역 조명, 앰비언트 오클루전(AO) 및 반투명을 포함한 고품질 확률론적 렌더링 효과를 위한 새로운 재구성 필터를 발명했습니다.
하드웨어 가속과 소프트웨어 혁신의 결합을 통해 개발자는 Reflections(Lucasfilm) 및 Speed of Light(Porsche)와 같은 실시간 영화 품질 레이 트레이싱을 기반으로 두 가지 애플리케이션을 만들 수 있습니다.
레이 트레이싱 프레임워크를 Unreal Engine과 같은 대규모 애플리케이션에 통합하는 것은 어려운 작업이며 실제로 UE4 출시 이후 가장 큰 아키텍처 변경 중 하나입니다. 레이 트레이싱을 UE4에 통합할 때 목표는 다음과 같습니다:
성능: UE4의 핵심 요소이므로 레이 트레이싱 기능은 사용자 기대를 충족해야 합니다. 성능에 도움이 되는 결정은 G-버퍼가 기존 래스터라이제이션 기반 기술을 사용하여 계산된다는 것입니다. 이 외에도 광선을 추적하여 반사나 영역 조명 그림자와 같은 특정 프로세스를 계산합니다.
호환성: 레이 트레이싱 프로세스의 출력은 기존 UE4의 셰이딩 및 포스트 프로세스 파이프라인과 호환되어야 합니다.
셰이딩 일관성: UE4의 기존 셰이딩과 일치하는 셰이딩 결과를 생성하려면 UE4에서 사용하는 셰이딩 모델을 레이 트레이싱으로 정확하게 구현해야 합니다. 구체적으로, BRDF 평가, 중요도 샘플링, BRDF 확률 분포 함수 평가는 UE4에서 제공하는 다양한 셰이딩 모델에서 수행되며, 기존 셰이딩 코드의 동일한 수학을 엄격하게 따릅니다.
중단 최소화: 기존 UE4 사용자는 통합을 쉽게 이해하고 확장할 수 있어야 하므로 UE 설계 패러다임을 따라야 합니다.
다중 플랫폼 지원: 초기에 UE4의 실시간 레이 트레이싱은 완전히 DXR 기반이었지만, UE4의 다중 플랫폼 특성으로 인해 결국 대대적인 리팩토링 없이 다른 미래 솔루션으로 포팅될 수 있도록 새로운 시스템을 설계해야 했습니다.
통합 프로세스의 가장 어려운 측면은 성능, API, 지연된 셰이딩 광선의 통합, RHI(렌더링 하드웨어 인터페이스)에 필요한 변경 사항, 각 하드웨어 플랫폼의 세부 정보에서 사용자를 추상화하는 씬 레이어, 셰이더 API 변경, 확장성 등입니다.
실험적인 UE4 구현에서 RHI(렌더링 하드웨어 인터페이스)는 NVIDIA OptiX API에서 영감을 받은 추상화로 확장되었지만 약간 단순화되었습니다. 이 추상화는 rtScene, rtObject 및 rtGeometry의 세 가지 개체 유형으로 구성됩니다. rtScene은 실제로 각각 rtGeometry를 가리키는 인스턴스인 RTObject로 구성됩니다. rtScene은 TLAS를 캡슐화하고 rtGeometry는 BLAS를 캡슐화합니다. rtGeometry와 주어진 RTGeometrics를 가리키는 모든 rtObject는 여러 부분으로 구성될 수 있으며, 모두 동일한 UE4 프리미티브 객체(정적 메시 또는 골격 메시)에 속하므로 동일한 인덱스와 버텍스 버퍼를 공유하지만 다른 (머티리얼) 히트 셰이더를 사용할 수도 있습니다. rtGeometry 자체에는 연관된 히트 셰이더가 없습니다. rtObject 섹션에서 히트 셰이더와 해당 매개변수를 설정합니다.
엔진 재질 셰이더 시스템과 RHI도 확장되어 DXR의 새로운 레이 트레이싱 셰이더 유형(Ray Generation, Closest Hit, Any Hit, Intersection 및 Miss)을 지원합니다. Closest Hit 및 Any Hit 셰이더 외에도 기존 VS(Vertex Shader) 및 PS(Pixel Shader) 사용을 지원하도록 엔진이 확장되었습니다. VS 및 PS용으로 사전 컴파일된 DXIL 표현에서 Closest Hit 및 Any Hit 셰이더를 생성하는 메커니즘을 제공하는 Microsoft DirectX 컴파일러용 확장 오픈 소스 유틸리티를 활용합니다. 이 유틸리티는 VS 코드, 어셈블리 단계의 입력 레이아웃(버텍스 및 인덱스 버퍼 형식 및 스트라이드 포함) 및 PS 코드를 입력으로 사용합니다. 이 입력이 주어지면 인덱스 버퍼 추출, 버텍스 어트리뷰트 추출, 형식 변환 및 VS 평가(삼각형의 세 버텍스 각각에 대해)를 수행하는 최적의 코드를 생성한 다음 적중 시 무게중심 좌표를 사용하여 VS 출력을 보간하고 그 결과는 PS에 입력으로 제공됩니다. 또한 이 도구는 알파 테스트를 수행하기 위해 최소한의 임의 적중 셰이더를 생성할 수 있으므로 엔진의 렌더링 코드가 G 버퍼를 래스터라이제이션하고 셰이더 매개변수를 평소처럼 설정하는 데 사용되는 버텍스 및 픽셀 셰이더를 계속 사용할 수 있습니다.
UE에 레이 트레이싱을 통합하는 데 있어 중요한 부분은 다양한 엔진 프리미티브의 기하학적 구조를 등록하는 것입니다. 가속화된 구조 구축을 위해 지오메트리를 등록하려면 UE4의 다양한 프리미티브에 RHI 레벨 rtGeometry 및 rtObject가 생성되어 있는지 확인해야 합니다. 일반적으로 rtGeometry 및 rtObject를 생성하기 위한 올바른 범위를 결정해야 합니다. 대부분의 기본 요소의 경우 rtGeometry는 버텍스 및 인덱스 버퍼 기하 도형과 동일한 범위에서 생성될 수 있습니다. 정적 삼각형 메시의 경우 간단하지만 다른 프리미티브의 경우 더 복잡할 수 있습니다. 예를 들어 파티클 시스템, 랜드스케이프(지형) 프리미티브 및 골격 메시(예: 모프 대상 또는 천을 사용하여 시뮬레이션된 스킨 처리된 형상)에는 특별한 처리가 필요합니다. UE4에서 개발자는 래스터라이제이션 프로세스, 가속화된 구조 업데이트 및 적중 셰이더에 대한 입력으로 사용할 수 있는 임시 GPU 버퍼에 모든 프레임 스키닝을 수행하는 컴퓨팅 셰이더 기반 시스템인 기존 GPUSkinCache를 활용했습니다. 또한 각 스켈레탈 메시 인스턴스에는 자체 별도의 BLAS가 필요하다는 점도 참고하세요. 따라서 이 경우 스켈레탈 메시의 각 인스턴스에는 별도의 rtGeometry가 필요하며 정적 메시처럼 이러한 인스턴스를 인스턴스화하거나 공유할 수 없습니다.
또 다른 중요한 단계는 장면의 레이 트레이싱 표현을 업데이트하는 것입니다. 매 프레임마다 UE4 렌더러는 렌더링 루프 바디를 실행하여 G 버퍼 래스터라이제이션, 직접 조명 적용, 포스트 프로세스 등 여러 프로세스를 수행합니다. 개발자는 레이 트레이싱 목적으로 사용되는 장면 표현을 업데이트하기 위해 이 루프를 수정했습니다. 이 표현은 가장 낮은 수준에서 셰이더 바인딩 테이블, 관련 메모리 버퍼 및 리소스 설명자, 가속 구조로 구성됩니다.
높은 수준의 렌더러 관점에서 볼 때 첫 번째 단계는 장면의 모든 개체의 셰이더 매개변수가 최신인지 확인하는 것입니다. 이를 위해 기존 기본 절차적 렌더링 로직(일반적으로 래스터라이제이션된 디퍼드 셰이딩 렌더러의 G 버퍼)이 활용됩니다. 주요 차이점은 레이 트레이싱을 사용하는 경우 이 루프는 오클루전 컬링 결과에 따라 카메라 프러스텀 내부에 있고 잠재적으로 표시되는 개체뿐만 아니라 장면의 모든 개체에 대해 수행되어야 한다는 것입니다. 두 번째 차이점은 첫 번째 구현에서는 디퍼드 셰이딩 G-버퍼 렌더가 있는 VS 또는 PS가 아닌 포워드 셰이딩 렌더러가 있는 VS 및 PS를 사용한다는 것입니다. 왜냐하면 셰이딩이 반사에 도달할 때 자연스럽게 일치하는 것처럼 보이기 때문입니다. 세 번째 차이점은 여러 광선 유형에 대한 셰이더 매개변수를 업데이트해야 하며 경우에 따라 약간 다른 셰이더를 사용한다는 것입니다.
17.5.1.2 레이 트레이싱과 래스터라이제이션
대규모 장면에서는 모든 객체의 셰이더 매개변수를 업데이트하는 데 많은 CPU 시간이 소요될 수 있습니다. 이를 방지하려면 전통적으로 유지 모드 렌더링이라고 불리는 것을 위해 노력해야 합니다. 즉시 모드 렌더링에서 CPU는 동일한 개체를 차례로 그리기 위해 매 프레임마다 많은 동일한 명령을 다시 제출하는 반면, 유지 모드 렌더링에서는 각 프레임에서 수행되는 작업을 수행하려면 마지막 프레임 이후 발생한 변경 사항으로 장면의 지속적인 표현만 업데이트하면 됩니다. 보존 모드 렌더링은 래스터라이제이션와 달리 레이 트레이싱에서는 전체 장면에 대한 전역 정보가 필요하기 때문에 레이 트레이싱에 더 적합합니다. 따라서 NVIDIA RTX(OptiX, DirectX 레이 트레이싱 및 Vulkan 레이 트레이싱)에서 지원하는 모든 GPU 레이 트레이싱 API는 보존 모드 렌더링을 지원합니다. 그러나 오늘날 대부분의 실시간 렌더링 엔진은 여전히 OpenGL 이후 지난 20년 동안 사용된 래스터라이제이션 API의 한계를 중심으로 설계되었습니다. 따라서 렌더러는 매 프레임마다 모든 셰이더 매개변수 설정과 그리기 코드를 다시 실행하도록 작성되어 이상적으로는 카메라에 표시되는 객체만 렌더링합니다. 이 접근 방식은 실시간 레이 트레이싱을 시연하는 데 사용되는 작은 장면에 적합하지만 거대한 세계로 확장되지는 않습니다. 이러한 이유로 UE4 렌더링 팀은 보다 효율적인 보존 모드 렌더링 방법을 달성하는 것을 목표로 고급 렌더러를 변환하는 프로젝트를 시작했습니다.
레이 트레이싱과 래스터라이제이션된 렌더링의 두 번째 차이점은 히트 셰이더가 레이 트레이싱 목적에 맞게 약간 사용자 정의된 VS 및 PS 코드로 구축되어야 한다는 것입니다. 원래 접근 방식은 UE4의 포워드 셰이딩에 사용되는 코드를 기반으로 했으며, 화면 공간 버퍼와 관련된 정보에 의존하는 로직을 건너뛰었습니다. 즉, 화면 공간 버퍼에 접근하는 노드를 사용하는 머티리얼은 레이 트레이싱에서 제대로 작동하지 않으며, 래스터라이제이션와 레이 트레이싱을 결합할 때는 그러한 머티리얼의 사용을 피해야 한다는 뜻입니다. 초기 구현에서는 UE4의 포워드 렌더링 셰이더를 기반으로 한 히트 셰이더를 사용했지만, 시간이 지나면서 개발 팀은 셰이더 코드를 리팩터링하여 히트 셰이더가 디퍼드 셰이딩에서 G-버퍼 렌더링에 사용되는 셰이더에 더 가깝게 보이도록 했습니다. 이 새로운 모드에서는 모든 동적 조명이 히트 셰이더 대신 조명 생성 셰이더에서 수행됩니다. 이 움직임은 히트 셰이더의 크기와 복잡성을 줄이고, 히트 셰이더에서 중첩된 TraceRay() 호출을 방지하며, 수천 개의 머티리얼 픽셀 셰이더를 다시 빌드할 때까지 기다릴 필요가 없기 때문에 반복 시간을 크게 단축하여 레이 트레이싱 셰이딩에 대한 조명 코드를 수정할 수 있게 해줍니다. 이 외에도 타격 시(원점 + t * 방향) 광선 정보를 사용하여 계산된 위치가 VS에서 메모리 부하 및 위치 관련 계산을 피할 수 있도록 VS 코드가 최적화되었습니다. 또한 변환 노멀 및 접선을 계산할 때와 같이 가능한 경우 VS에서 PS로 계산을 이동합니다. 전반적으로 VS 코드를 주로 데이터 수집 및 형식 변환으로 줄입니다.
세 번째 차이점은 여러 조명 유형에 대한 매개변수를 업데이트하면 어떤 경우에는 조명 유형 중 하나에 완전히 독립적인 VS 및 PS 세트가 필요한 경우 장면의 모든 객체를 여러 번 반복해야 한다는 의미입니다. 그러나 어떤 경우에는 추가 조명 유형으로 인한 오버헤드가 크게 줄어들 수 있습니다. 예를 들어, RHI 추상화가 여러 광선 유형에 대한 셰이더 매개변수를 동시에 제출하도록 허용하여 가장 일반적인 두 가지 광선 유형(재료 평가 및 모든 히트 셰이딩)에 대한 업데이트를 처리하는 기능이 있으며, 이는 호환 가능한 셰이더 매개변수와 함께 히트 셰이더를 사용할 수 있습니다. 이 요구 사항은 VS 및 PS 쌍을 적중 셰이더로 변환하는 DirectX 컴파일러 유틸리티에 의해 보장됩니다. 이는 Closest Hit 셰이더와 Any Hit 셰이더의 VS 및 PS 매개변수 레이아웃이 동일하도록 보장하기 때문입니다(둘 다 동일한 VS 대 PS 쌍에서 생성되기 때문). 이를 염두에 두고 "Any Hit Shadow" 조명 유형이 재료의 평가된 조명 유형과 동일한 Any Hit 셰이더를 빈(null) Closest Hit 셰이더와 결합하여 사용한다는 사실을 고려하면 두 조명 유형에 대해 동일한 셰이더 바인딩 테이블 로깅 데이터를 사용하는 것이 간단하지만 서로 다른 셰이더 식별자를 사용합니다.
셰이더 바인딩 테이블 레코드를 채우는 과정에서 개발팀은 관련 rtObject에 오프셋을 기록하는 데도 주의를 기울였습니다. DXR 구현은 이 정보를 사용하여 실행할 히트 셰이더와 매개변수를 결정하기 때문에 이 정보는 TLS 빌드 작업에 제공되어야 합니다. 모든 셰이더 매개변수를 업데이트하는 것 외에도 개발 팀은 각 rtObject와 관련된 인스턴스 변환 및 플래그도 업데이트해야 했습니다. 이는 셰이더 매개변수를 업데이트하기 전에 별도의 루프에서 수행되었습니다. 인스턴스 수준 플래그를 통해 마스킹 및 후면 컬링 논리를 제어할 수 있습니다. 조명 채널 지원은 마스킹 비트를 사용하여 UE4에서 구현되어 아티스트가 특정 조명 세트를 제한하여 특정 오브젝트 세트와만 상호작용하도록 할 수 있습니다. 후면 컬링 비트는 래스터라이제이션와 레이 트레이싱 결과가 시각적으로 일치하는지 확인하는 데 사용됩니다(컬링은 래스터라이제이션 성능에는 좋지만 레이 트레이싱에는 반드시 필요한 것은 아닙니다).
모든 레이 트레이싱 셰이더 매개변수, rtObject 변환, 비트 컬링 및 마스킹을 업데이트한 후 히트 셰이더를 포함하는 셰이더 바인딩 테이블이 준비되고 모든 rtObject는 해당 셰이더 바인딩 테이블 기록을 알게 됩니다. 이 시점에서 다음 단계는 기본 가속 구조의 구축 또는 업데이트와 TLS 재구축을 예약하는 것입니다. 실험적 구현에서 이 단계는 가속 구조와 관련된 지연된 메모리 할당도 처리합니다. 이 단계에서 중요한 최적화는 BLAS 업데이트 후에 필요한 리소스 변환 장벽이 각 BLAS 업데이트 직후가 아니라 TLS 빌드 이전까지 연기되도록 하는 것입니다. 각 변환 장벽은 GPU에서 동기화된 단계이므로 지연 시간이 중요합니다. 변환을 명령 버퍼의 단일 지점으로 병합하면 빈번한 GPU 유휴를 초래할 수 있는 중복 동기화가 방지됩니다. 변환을 병합하면 모든 BLAS 업데이트 후에 단일 동기화가 수행되고 GPU에서 실행되는 동안 여러 BLAS 업데이트(잠재적으로 많은 작은 삼각형 메시의 경우)가 중첩될 수 있습니다.
Miss 셰이더의 사용은 제한되어 있으며 Miss 셰이더가 RHI 레벨에서 노출되지만 개발 팀은 RHI의 엔진 측에서는 이를 사용하지 않습니다. 개발 팀은 RHI 구현을 사용하여 동일한 기본 미스 셰이더 세트(각 광선 유형에 대해 하나씩)를 사전 초기화합니다. 이는 단순히 페이로드의 HitT 값을 특정 음수 값으로 초기화하여 광선이 무엇이든 놓쳤음을 나타냅니다.
17.5.1.3 계층
두 가지 고급 레이 트레이싱 데모 제작 경험을 쌓은 후, 개발 팀은 코드를 프로젝트 전용 코드에서 모든 UE4 사용자의 요구 사항을 잘 충족하는 코드로 전환하는 대규모 리팩토링을 수행할 수 있었습니다. 이 단계의 최종 목표 중 하나는 UE4 렌더링 시스템을 즉시 모드에서 유지 모드로 이동하는 것입니다. 이렇게 하면 주어진 프레임에서 변경되는 객체만 효과적으로 업데이트할 수 있으므로 효율성이 더 높아질 것입니다. 래스터라이제이션 파이프라인의 제한으로 인해 UE4는 원래 즉시 모드 스타일로 작성되었습니다. 그러나 이 스타일은 대부분의 경우 작은 부분만 변경되었음에도 불구하고 매 프레임마다 항상 모든 개체를 업데이트하므로 대규모 장면의 레이 트레이싱에 대한 심각한 제한 사항입니다. 따라서 스키마 스타일을 유지하려는 움직임은 이 단계의 주요 성과 중 하나였습니다. 향후 모든 플랫폼에 Ray Tracing을 통합한다는 궁극적인 목표를 달성하기 위해 개발 팀은 요구 사항을 여러 계층으로 나누어 각 기능을 지원하는 데 필요한 것이 무엇인지, 그리고 고급 하드웨어를 사용할 수 있을 때 기능을 희생하지 않고 특정 장치의 한계에 직면하는 방법을 이해했습니다.
계층 1은 Radeon Rays 또는 Metal Performance Shaders와 같은 기존 레이 트레이싱 API와 유사하게 기본 레이 트레이싱 기능을 통합하는 데 필요한 최소 수준의 기능을 설명합니다. 입력은 광선 정보(원점, 방향)를 포함하는 버퍼이고 셰이더 출력은 교차점 결과를 포함하는 버퍼입니다. 이 레이어에는 내장된 TraceRay 내장 기능이 없으며 사용 가능한 히트 셰이더도 없습니다. Tier 1은 불투명한 그림자 또는 앰비언트 오클루전(AO)과 같은 간단한 레이 트레이싱 효과를 구현하는 데 적합하지만 이를 넘어서는 것은 어렵고 코드를 복잡하게 변경해야 하므로 제한 사항이 발생하고 우수한 실행 효율성을 달성하기가 어렵습니다.
Tier 2는 TraceRay 내장 함수를 호출할 수 있고 추적 호출 직후에 출력을 사용할 수 있는 광선 생성 셰이더를 지원합니다. 이 수준의 기능은 RTPSO 및 셰이더 바인딩 테이블을 사용하여 추상화되는 동적 셰이더 예약도 지원합니다. Tier 2는 재귀적 레이 트레이싱을 지원하지 않으므로 적중 셰이더에서 새 광선을 생성할 수 없습니다. 1단계에서 개발팀은 이것이 실제로 큰 제한이 아니며 히트 셰이더의 크기와 복잡성을 줄이는 긍정적인 부작용이 있다는 것을 발견했습니다. Tier 2를 사용하면 UE4의 Ray Tracing 통합에 정의된 대부분의 목표를 달성할 수 있습니다. 따라서 UE4 레이 트레이싱 파이프라인의 설계는 Tier 2 기능을 가정하여 완료되었습니다.
Tier 3는 DXR 사양을 엄격하게 준수하며 모든 Tier 2 기능과 사전 정의된 최대 깊이가 있는 재귀 호출을 지원합니다. 또한 광선 생성 셰이더 외부의 다른 셰이더 유형의 레이 트레이싱은 물론 사용자 정의 가능한 가속 구조 탐색과 같은 고급 기능도 지원합니다. Tier 3는 당시(2018년경) 가장 강력한 기능 세트였으며, 광자 매핑 및 방사조도 캐싱과 같은 오프라인 렌더링을 위한 고급 레이 트레이싱 기능의 모듈식 통합을 지원했습니다. UE4의 레이 트레이싱 통합은 하드웨어에서 지원하는 경우 Tier 3 기능을 사용하도록 설계되었습니다.
17.5.1.4 광원 및 노이즈 감소
UE4의 레이 트레이싱 통합에서 얻은 교훈 외에도 초기 실험 단계는 실시간 레이 트레이싱의 가능성을 탐색하는 데 매우 중요했습니다. 개발 팀은 스페큘러 반사 및 하드 그림자로 시작한 다음 제한된 조명 예산으로 광택 반사 및 영역 조명 그림자에 근접하도록 노이즈 감소를 추가한 다음 앰비언트 오클루전(AO), 확산 전역 조명 및 반투명도를 추가했습니다.
래스터라이제이션 기반 렌더러(오프라인 및 실시간)는 일반적으로 렌더링 방정식을 여러 광선 경로 세그먼트로 분할하고 각 세그먼트를 별도로 처리합니다. 예를 들어 스크린 스페이스 리플렉션(SSR)를 위한 별도의 패스와 직접 조명을 위한 또 다른 패스를 수행합니다. 이 방법은 레이 트레이싱 렌더러, 특히 수십, 수백 또는 수천 개의 빛 경로를 축적하여 렌더링하는 오프라인 경로 추적기에서는 덜 일반적으로 사용됩니다. 일부 레이 트레이싱 렌더러는 가상 점 조명(순간 복사), 경로 공간 필터링 및 다양한 노이즈 감소 알고리즘과 같은 수렴 또는 상호 작용을 개선하는 기술을 사용합니다.
Zimmeret al. 전체 광선 트리를 별도의 버퍼로 분할하고 최종 프레임을 합성하기 전에 각 버퍼에 노이즈 제거 필터를 적용했습니다. UE 장면에서 개발팀은 유사한 접근 방식을 따랐습니다. 렌더링 방정식을 풀려고 할 때 나타나는 빛의 경로를 분할하고 다양한 빛 유형(예: 그림자, 반사, 확산 광선)의 결과에 사용자 정의 필터를 적용했습니다. 각 효과에 대해 픽셀당 적은 수의 광선을 사용하고 적극적으로 노이즈를 제거하여 부족한 샘플 수를 보완합니다. 로컬 속성을 활용하여 노이즈 감소 품질(예: 광원 크기 또는 광택 BRDF 로브 모양)을 개선하고 결과를 결합하여 오프라인 렌더러에서 생성된 이미지에 가까운 이미지를 생성합니다. 개발팀에서는 이 기술을 분할된 광경로 필터링이라고 부릅니다.
레이 트레이싱 시연에서 영역 조명에 대한 조명 추정은 조명 항의 무분산 추정을 제공하지만 가시성을 포함하지 않는 LTC(선형 변환 코사인) 방법을 사용하여 계산됩니다. 영역 조명에서 그림자를 렌더링하기 위해 개발 팀은 레이 트레이싱을 사용하여 가시성 용어에 대한 노이즈 추정치를 수집한 다음 고급 이미지 재구성 알고리즘을 결과에 적용했습니다. 마지막으로 조명 결과를 기반으로 노이즈 제거 가시성 용어를 합성합니다. 수학적으로 렌더링 방정식을 다음과 같이 나누고 근사할 수 있습니다.
그 중에는:
는 방향으로 표면을 떠나는 복사휘도입니다. 는 방향의 이진 가시성 용어입니다. 표면 특성
은 BRDF(양방향 반사 분포 함수)입니다. 는 방향을 따르는 입사광입니다. 는 표면 노멀과 입사광의 방향 사이의 각도입니다. 여기서 는 이 각도로 인한 기하학적 감쇠를 고려합니다.
이 근사치는 확산 표면에 대한 편향을 무시할 수 있으며 일반적으로 그림자 맵 기술에 사용됩니다. 광택 있는 표면에 폐색이 있는 영역 조명 음영의 경우 Heitz et al.의 비율 추정기를 사용하면 보다 정확한 결과를 얻을 수 있습니다. 대조적으로, "빛의 속도"(Porsche) 데모에서는 개발팀이 레이 트레이싱 반사와 노이즈 감소를 직접 사용하여 폐색 정보와 함께 반사 영역 빛 그림자를 처리했습니다.
17.5.1.5 그림자
큰 반그림자를 사용하여 고품질 레이 트레이싱 영역 조명 그림자를 얻으려면 일반적으로 큰 노이즈 없이 추정치를 얻기 위해 픽셀당 수백 개의 가시성 샘플이 필요합니다. 필요한 빛의 양은 광원의 크기와 장면 내 차단기의 위치 및 크기에 따라 달라집니다. 실시간 렌더링의 경우 조명 예산이 더 부족하고 수백 개의 광선이 성능 예산을 훨씬 초과합니다. Reflections(Lucasfilm) 및 Speed of Light(Porsche) 데모는 광원당 픽셀당 하나의 샘플을 사용하며 이 샘플 수에 대한 결과에는 많은 노이즈가 포함됩니다. 개발팀은 고급 노이즈 제거 필터를 적용하여 실제에 가까운 노이즈 없는 이미지를 재구성했습니다.
개발팀은 반그림자 영역의 빛 그림자 전용 노이즈 제거 알고리즘을 설계했습니다. 섀도우 디노이저에는 공간적 구성요소와 시간적 구성요소가 있습니다. 공간 구성 요소는 부드러운 그림자에 대한 축 정렬 필터링 및 Yan et al의 전단 필터와 같은 국부 폐색의 주파수 분석을 기반으로 한 효율적인 필터에 대한 최근 연구에서 영감을 받았습니다. 노이즈 제거기는 광원의 크기, 모양, 방향, 수신기와의 거리, 그림자 광선이 도달하는 거리 등 광원에 대한 정보를 알고 있습니다. 디노이저는 이 정보를 사용하여 각 픽셀에 대한 최적의 공간 필터 공간을 도출하려고 시도합니다. 발자국은 이방성이며 각 픽셀은 서로 다른 방향을 가지며 아래 이미지는 이방성 공간 커널의 대략적인 시각화를 보여줍니다. 커널 모양이 반그림자 방향을 따라 확장되어 노이즈 감소 후 고품질 이미지를 얻을 수 있습니다. 디노이저의 시간적 구성 요소는 픽셀당 유효 샘플 수를 약 8~16개로 늘립니다. 시간 필터가 활성화된 경우 변화는 약간의 시간 지연이지만 Salvi가 제안한 대로 시간 잘림을 수행하여 대기 시간을 줄입니다.

섀도우 디노이저(녹색)에 사용된 필터 커널의 시각화입니다. 이것이 어떻게 이방성이며 각 반그림자의 방향을 따라 확장되는지 확인하세요.
노이즈 제거 장치가 각 광원의 정보를 사용한다고 가정하면 각 광원이 드리우는 그림자는 별도로 노이즈 제거해야 하므로 노이즈 제거 비용은 장면의 광원 수에 비례합니다. 그러나 노이즈 감소 결과의 품질은 여러 광원에 공통 필터를 사용하려는 개발팀의 시도보다 높았으므로 두 가지 광 추적 데모에서는 광원당 하나의 필터가 선택되었습니다.
아래 이미지의 입력 이미지는 자동차 위에 있는 거대한 직사각형 광원이 투사하는 부드러운 조명을 시뮬레이션하기 위해 픽셀당 하나의 그림자 광선으로 렌더링되었습니다. 이러한 샘플링 속도에서는 결과 이미지에 노이즈가 매우 뚜렷하게 나타납니다. 개발팀의 공간 디노이저는 대부분의 노이즈를 제거했지만 일부 아티팩트가 남아 있습니다. 시간적 및 공간적 노이즈 감소 구성요소를 결합한 결과는 픽셀당 2048개의 광선으로 렌더링된 실제 이미지에 가깝습니다.

(a) 디노이저는 픽셀당 하나의 그림자 광선으로 렌더링된 노이즈 입력에 대해 작동합니다. (b) 디노이저의 공간적 구성요소만 일부 저주파 아티팩트가 여전히 존재합니다. (c) 시공간 노이즈 제거기는 결과를 더욱 향상시키며, (d) 실제값과 매우 잘 일치합니다.
중간 크기 광원의 경우 공간 디노이저가 고품질 결과를 생성합니다. Reflections(Lucasfilm) 데모에서는 공간 노이즈 감소만으로도 그림자 품질의 결과를 생성하기에 충분했습니다. "Speed of Light"(Porsche) 데모에 사용된 거대 광원 유형의 경우 순수 공간 노이즈 감소 결과는 품질 기준을 충족하지 못했습니다. 따라서 "Speed of Light"(Porsche) 시연에도 시간적 성분 노이즈 제거 장치가 사용되어 약간의 시간 지연을 희생하면서 재구성 품질이 향상되었습니다.
17.5.1.6 반사
사실적인 반사는 레이 트레이싱 렌더링의 또 다른 핵심 약속입니다. SSR(Screen Space Reflection)과 같은 현재 래스터라이제이션 기반 기술은 화면 외부 콘텐츠의 아티팩트로 인해 어려움을 겪는 경우가 많습니다. 미리 계산된 라이트 프로브와 같은 다른 기술은 동적 장면에 맞게 확장되지 않으며 표면 노멀을 따라 늘어나거나 접촉 경화되는 등 광택 반사에 존재하는 모든 기능을 정확하게 시뮬레이션할 수 없습니다. 또한, 레이 트레이싱은 임의의 모양의 표면에서 다중 바운스 반사를 처리하는 가장 효율적인 방법임이 틀림없습니다. 아래 이미지는 "Reflections"(Lucasfilm) 데모에서 레이 트레이싱 반사를 사용하여 생성된 효과 유형을 보여줍니다. Phasma의 갑옷 부분 사이에 상호 반사되는 여러 개의 총알 구멍을 확인하십시오.

레이 트레이싱을 사용하여 렌더링된 Phasma의 반사입니다. 갑옷 부분 사이의 정확한 상호 반사와 디노이저로 재현된 약간 광택 있는 반사를 확인하세요.
레이 트레이싱을 사용하면 임의의 표면에서 동적 반사를 더 쉽게 지원할 수 있지만 화면 외부 콘텐츠의 경우에도 반사 바운스의 적중점에서 음영 및 조명을 계산하는 데 비용이 많이 듭니다. 반사 히트 포인트에 대한 머티리얼 평가 비용을 줄이기 위해 개발팀은 레이 트레이싱된 반사 셰이딩에 대해 아티스트가 단순화한 다양한 머티리얼을 사용할 수 있는 옵션을 제공했습니다. 이러한 재료 단순화는 최종 인식 품질에 거의 영향을 미치지 않습니다. 반사 물체는 일반적으로 볼록 반사판에서 최소화되고 재료의 미세한 세부 사항을 제거하는 것은 시각적으로 눈에 띄지 않지만 성능 이점이 있는 경우가 많습니다. 아래 이미지는 기본 뷰(왼쪽)의 여러 텍스처 맵에서 풍부한 마이크로 디테일이 있는 일반 복잡한 재질과 반사 히트 셰이딩(오른쪽)에 사용된 단순화된 버전을 비교합니다.

왼쪽: 완전한 마이크로 디테일을 갖춘 오리지널 Phasma 소재; 오른쪽: 반사된 빛을 음영 처리하는 데 사용되는 단순화된 재질입니다.
광택 반사를 제거하는 측면에서 레이 트레이싱을 사용하여 완벽하게 부드러운 스페큘러 반사를 얻는 것이 좋지만 실제로는 대부분의 스페큘러 반사 표면이 미러링되지 않습니다. 표면은 일반적으로 거칠기와 불균일 정도가 다양합니다. 레이 트레이싱을 사용하면 재료의 로컬 BRDF는 일반적으로 러프니스 및 입사 광도에 따라 수백에서 수천 개의 샘플을 사용하여 무작위로 샘플링됩니다. 이렇게 하는 것은 실시간 렌더링에 비현실적입니다.
개발팀은 반사광 생성을 구동하기 위해 적응형 다중 바운스 메커니즘을 구현했습니다. 반사된 반사 광선의 방출은 충돌 표면의 거칠기에 의해 제어되므로 더 거친 형상에 충돌하는 광선은 더 일찍 종료됩니다. 평균적으로 개발 팀은 두 개의 반사 바운스에 대해 픽셀당 두 개의 반사 광선만 지정했으므로 표시되는 각 음영 지점에 대해 하나의 BRDF 샘플만 있었습니다. 결과는 매우 눈에 띄는 노이즈였으며 개발 팀은 복잡한 노이즈 감소 필터를 다시 적용하여 기본 진실에 가까운 광택 반사를 재구성했습니다.
개발팀은 반사된 입사 방사선 조건에만 효과적인 소음 감소 알고리즘을 설계했습니다. 광택 반사는 그림자 점을 둘러싸는 반구에 대한 입사 방사선 항 L과 BRDF
이는 입사 방사선 항
필터 스택에는 시간적 및 공간적 구성 요소가 있습니다. 공간 부분의 경우 개발 팀은 로컬 그림자 지점에서 BRDF 분포를 따르는 화면 공간의 이방성 모양 커널을 파생했습니다. 적중 거리, 표면 러프니스 및 법선을 기반으로 BRDF 로브를 화면 공간으로 다시 투영하여 커널을 추정하고, 아래 그림과 같이 픽셀당 커널 크기와 방향이 다른 커널을 생성합니다.

BRDF 기반 반사 필터 커널의 시각화.
BRDF 기반 필터 커널의 또 다른 주목할만한 특성은 아래 이미지와 같이 거울 표면에서만 필터링하여 적당히 거칠고 매끄러운 표면을 생성할 수 있다는 것입니다. 필터는 16384 spp의 기본 진실 렌더링 결과와 거의 일치하는 1 spp 입력에서 설득력 있는 결과를 생성합니다. 아래 중간 및 아래쪽 이미지를 참조하세요.

위: 마치 완벽한 스페큘러 반사 이미지인 것처럼 반사 공간 필터에 입력됩니다. 중간: 스페큘러 반사 이미지에 적용된 왼쪽의 반사 공간 필터 출력. GGX 제곱 러프니스 0.15를 시뮬레이션하여 접촉 경화 및 노멀 방향 확장과 같은 광택 반사의 예상되는 모든 특성을 생성합니다. 하단: 편향되지 않은 무작위 BRDF 샘플링(픽셀당 수천 개의 광선)을 사용하여 렌더링된 0.15의 GGX 제곱 거칠기입니다.
이 공간 필터는 적당한 러프니스(GGX 제곱 러프니스 약 0.25 미만)로 매끄러운 표면을 충실하게 재구성할 수 있습니다. 더 높은 러프니스 값의 경우 Stachowiak et al.에서와 같이 편향된 무작위 BRDF 샘플링이 채택되고 시간적 구성 요소가 공간 구성 요소와 결합되어 더 나은 노이즈 감소 품질을 얻습니다.
반사 표면의 시간적 재투영에는 반사 물체의 모션 벡터가 필요하며 이를 얻기 어려울 수 있습니다. 이전에는 Stachowiak et al. 평면 반사경 내에서 반사된 물체의 카메라 움직임에 의해 유도된 모션 벡터를 재구성하기 위해 반사 가상 깊이를 사용했습니다. 그러나 이 방법은 곡면 반사경에는 덜 효과적입니다. Hirvonenet al. 각 로컬 픽셀 이웃을 얇은 렌즈로 모델링한 다음 얇은 렌즈 방정식을 사용하여 반사 물체의 모션 벡터를 유도하는 새로운 방법을 제안했습니다. 곡선형 반사경에 적합합니다. 개발팀은 이 방법을 사용하여 시간 필터의 모션 벡터를 계산했습니다.
LTC(선형 변환 코사인)는 임의의 거칠기에 대한 실제 영역 조명 그림자를 분석적으로 생성하는 기술이지만 폐색을 처리하지는 않습니다. 반사 솔루션은 픽셀당 하나의 샘플에 대한 합리적인 광택 반사를 생성하므로 영역 조명 재료 음영의 반사 성분을 직접 평가하는 데 사용할 수 있습니다. LTC를 사용하는 대신 개발팀은 단순히 면광을 방출하는 물체로 처리하고 이를 반사 히트 포인트에 음영처리한 다음 노이즈 제거 필터를 적용하여 폐색 정보를 포함한 반사광 음영처리를 재구성했습니다. 아래 이미지는 두 가지 방법을 비교한 것입니다.

장면 바닥은 GGX 정사각형 거칠기가 0.17인 순수 거울 표면입니다. (a) LTC를 사용하여 두 영역 조명의 조명을 계산합니다. LTC가 올바른 하이라이트를 생성하면 하이라이트의 일부를 차단했을 자동차의 반사가 사라져 자동차가 바닥에 닿지 않은 것처럼 보입니다. (b) 레이 트레이싱 반사의 경우 레이 트레이싱이 자동차의 올바른 폐색을 처리하는 동시에 두 영역 조명 모두에서 합리적으로 보이는 광택 하이라이트를 생성하는 방법에 유의하세요.
17.5.1.7 전역 조명
포토리얼리즘을 추구하는 Reflections(Lucasfilm) 및 Speed of Light(Porsche) 데모에서는 레이 트레이싱을 사용하여 간접 조명을 계산하여 렌더링된 이미지의 사실성을 높였습니다. 그러나 두 데모에 사용된 기술은 약간 다릅니다. Reflections(Lucasfilm)는 레이 트레이싱을 사용하여 미리 계산된 체적 라이트맵에서 방사 조도 정보를 얻어 동적 캐릭터에 대한 간접 조명을 계산합니다. "Light Speed"(Porsche)는 경로 추적을 위해 G-Buffer에서 두 개의 간접 확산 광선을 직접 사용하는 보다 폭력적인 방법을 사용합니다. 그들은 모두 수렴 속도를 높이기 위해 다음 이벤트 추정을 사용합니다.
AO의 경우 앰비언트 오클루전(AO)은 물리적으로 영감을 받고 아티스트가 제어할 수 있는 대략적인 전역 조명을 제공합니다. 조명을 폐색에서 분리하면 물리적 정확성이 손상되지만 측정 가능한 효율성이 제공됩니다. 앰비언트 오클루전(AO)은 수십 년 동안 영화에 사용된 것과 동일하고 잘 문서화된 알고리즘을 직접 적용합니다. 즉, 후보 지점의 음영 법선을 중심으로 코사인 반구 분포에서 여러 광선을 발사하면 조명 기여도를 전역적으로 감쇠시키는 화면 공간 폐색 마스크가 생성됩니다.
언리얼 엔진은 스크린 스페이스 앰비언트 오클루전(SSAO)을 지원하지만 상당한 단점이 있습니다. 뷰 프러스텀에 의존하면 경계에서 비네팅이 발생하고 주로 뷰 방향에 평행한 얇은 폐색을 정확하게 캡처할 수 없습니다. 또한 프러스텀 외부의 장애물은 SSAO에 영향을 미칠 수 없습니다. 그러나 DXR을 사용하면 뷰 프러스텀와 관계없이 방향성 폐색을 캡처할 수 있습니다.
UE는 라이트맵의 간접 디퓨즈도 사용합니다. Reflections(Lucasfilm)의 경우 효과적인 색상 번짐을 제공할 수 있는 앰비언트 오클루전(AO) 기술이 필요했습니다. 개발팀은 참고 비교로 간접 디퓨즈 프로세스를 구현했습니다. 이 알고리즘의 경우 광선의 코사인 반구 분포가 기존 앰비언트 오클루전(AO)과 유사한 방식으로 후보 G 버퍼 샘플에서 캐스팅됩니다. 적중률은 기록되지 않지만, 가시광선이 이미터에 닿을 때의 BRDF 가중치 결과는 기록됩니다. 예상한 대로 의미 있는 결과를 얻는 데 필요한 광선 수는 해결하기 어렵지만 보다 대략적인 기술에 대한 기준을 제공합니다.
개발팀은 대략적인 간접적인 기여를 제공하기 위해 무차별 계산을 포기하고 UE의 라이트 매핑 솔루션을 채택했습니다. 체적 조명 맵의 평가를 앰비언트 오클루전(AO)광 방출로 대체하면 합리적인 간접적인 결과를 얻을 수 있습니다. 결과적인 방사 조도 프로세스는 기존 앰비언트 오클루전(AO) 알고리즘의 가중치 가시성 프로세스보다 노이즈 제거가 더 쉽습니다. 비교 이미지는 아래와 같습니다.

글로벌 조명 기술 비교. 위: 스크린 스페이스 앰비언트 오클루전(SSAO); 중간: 라이트 맵의 간접 디퓨즈; 하단: 참조용 바운스 경로 추적.
개발팀은 미리 계산된 라이트맵을 사용하여 간접 확산 조명을 렌더링하는 것 외에도 전역 조명 효과를 더욱 향상시키는 경로 추적 솔루션도 개발했습니다. 우리는 경로 추적과 다음 이벤트 추정을 사용하여 잡음이 있는 방사조도에 재구성 필터를 적용하기 전에 바운스 간접 확산 조명을 렌더링하여 이전보다 더 정확한 색상 번짐을 제공했습니다.
Mehtaet al. 확산 간접 조명을 위한 축 정렬 필터를 제안했으며 개발 그룹에서는 유사한 디노이저를 사용했습니다. "Lightspeed"(Porsche) 데모의 경우 소음 감소가 더 어려웠습니다. 무차별 경로 추적은 사전 계산 없이 사용되므로 Mehta et al. 원하는 품질을 얻기 위해 시간 필터와 결합됩니다. Reflections(Lucasfilm) 데모의 경우 공간 필터와 결합된 템포럴 안티앨리어싱(TAA)을 사용하면 근처 라이트맵 텍셀에서 추출되었기 때문에 충분한 품질을 제공했습니다.
개발팀은 텍스처 디테일, 그림자 또는 반사 하이라이트가 과도하게 흐려지는 것을 방지하기 위해 조명의 간접 확산 구성요소에만 디노이저를 적용합니다. 이러한 요소는 다른 전용 디노이저에서 별도로 필터링되기 때문입니다. 공간 필터의 경우 Mehta et al.이 제안한 적중 거리에서 파생된 발자국을 갖는 월드 공간 공간 커널입니다. 적용되었습니다. 적중 거리에 따라 필터 크기를 조정하면 간접 조명의 세부 사항이 과도하게 흐려지는 것을 방지하고 간접 그림자와 같은 기능을 더욱 선명하게 만들 수 있습니다. 시간 필터와 결합하면 픽셀에 누적된 재투영 샘플 수에 따라 공간 커널 공간도 줄어듭니다. 시간적으로 더 많은 샘플이 축적된 픽셀의 경우 개발팀은 더 작은 공간 필터 공간을 적용하여 결과를 실제에 더 가깝게 만들었습니다. 아래 이미지는 일정한 반경을 사용한 필터링과 광선 적중 거리 및 시간 샘플 수를 기반으로 필터 반경을 조정하는 것을 비교한 것입니다. 확실히, 적응된 필터 풋프린트를 사용하면 접촉 영역에서 더 나은 세부 정보를 얻을 수 있습니다.

동일한 아이디어는 레이 트레이싱 앰비언트 오클루전(AO) 노이즈 감소에도 도움이 됩니다. 아래 (a) 일정한 월드 공간 반경을 갖는 노이즈 제거된 레이 트레이싱 앰비언트 오클루전(AO), 아래 (b) 히트 거리 및 시간 샘플 수에 따라 안내되는 적응형 커널 반경을 사용하는 노이즈 제거된 앰비언트 오클루전(AO).

적응형 필터 크기를 사용하면 앰비언트 오클루전(AO)을 제거할 때 연락처 세부 정보를 더 잘 보존할 수 있다는 것이 다시 한 번 분명해졌습니다.
17.5.1.8 반투명
“”(),의 는 렌더링(Render),의 반투명(Translucent)렌더링(Render)방법은 및 디퍼드 셰이딩(Deferred Shading)법상입니다. 일반적으로 개발자는 별도의 순방향 패스에서 반투명 지오메트리를 렌더링하고 기본 지연된 패스에서 결과를 합성해야 합니다. 렌더링합니다.,로부터 ,반투명(Translucent)과 반투명(Translucent)의 통합 .
다행스럽게도 레이 트레이싱은 반투명도를 표현하기 위한 자연스러운 프레임워크를 제공합니다. 레이 트레이싱을 사용하면 반투명 지오메트리를 통합 지오메트리 제출 시 디퍼드 렌더링과 쉽게 결합할 수 있습니다. 이는 임의의 반투명 깊이 복잡성과 굴절 및 흡수를 올바르게 모델링하는 기능을 제공합니다.
개발팀은 레이 트레이싱 반사와 유사한 별도의 레이 트레이싱 패스를 사용하여 언리얼 엔진에서 레이 트레이싱 반투명을 구현했습니다. 실제로 대부분의 셰이더 코드는 이 두 패스 간에 공유됩니다. 그러나 두 사람의 행동 방식에는 약간의 미묘한 차이가 있습니다.
는 을(를) 활용하여 (early-ray) 종료),로써 weight;예: ,만약 ,로써 무시/스킵(Ignore)。
또 다른 차이점은 반투명 레이 트레이싱에는 완전히 음영 처리되고 해당 위치에 저장되는 불투명 형상과의 충돌을 방지하는 최대 광선 길이가 있다는 것입니다. pixel.그러나 ,만약 실행(Execute)굴절 ,이면 ming히트(Hit)의의있는 새로운 빛, 새로운 빛, 또는 히트(Hit)color의반투명(Translucent)。지금 반투명(Translucent)히트(Hit)실행(Execute)の,반투명(Translucent)히트(Hit)포인트 투영/프로젝션 까지 버퍼 ,만약 단계/스텝 까지 유효(Valid)데이터 ,이면 을(를) 활용하여 。 uffer 내에서 반투명(Translucent)실행(Execute)레이트레이싱(Ray Tracing) Lighting 와디노이징(Denoising)weight.을(를) 활용하여 의 굴절 weight,그러나 에 의해 을(를) 활용하여 의 계산/산출(Calculate)면조명,결과 。
반사 프로세스의 또 다른 주요 차이점은 반투명 광선이 후속 인터페이스에 도달한 후 반사 광선을 반복적으로 생성하는 기능입니다. HLSL을 사용하여 이를 구현하는 것은 언어의 재귀 지원이 부족하기 때문에 완전히 간단하지 않습니다. 재귀란 적중 셰이더에서 광선을 추적하는 기능이 아니라 간단한 HLSL 함수가 자신을 호출하는 기능을 의미합니다. 이는 HLSL에서는 허용되지 않지만 간결한 레이 트레이싱 알고리즘을 구현할 때는 바람직합니다. HLSL의 이러한 제한 사항을 해결하기 위해 개발 팀은 동일한 코드를 다른 이름을 가진 두 개의 함수로 인스턴스화하여 관련 함수 코드를 별도의 파일로 효과적으로 이동하고 해당 파일을 두 번 포함하고 매번 함수 이름을 설정하는 전처리기 매크로로 둘러싸여 다른 이름을 가진 동일한 함수의 두 개의 다른 인스턴스를 생성했습니다. 그런 다음 두 함수 인스턴스화 중 하나가 다른 하나를 호출하도록 하여 하드 코딩된 제한 수준으로 재귀를 효과적으로 수행할 수 있습니다. 결과 구현에서는 선택적 굴절을 사용하여 반투명 경로를 허용하며, 경로를 따른 각 히트는 그림자 광선뿐만 아니라 "재귀" 반사 광선을 추적할 수 있습니다. 반투명 표면에서 이 경로를 따라 추적된 반사는 선택한 횟수만큼 튕겨 나올 수 있습니다. 그러나 이러한 바운스 중에 반투명 표면에 부딪히면 추가로 재귀적으로 반사되는 광선을 추적하는 것이 허용되지 않습니다.
Beer-Lambert의 법칙에 따른 균일한 체적 흡수가 반투명 채널에 추가되어 두꺼운 유리를 시뮬레이션하고 기판에 근접하게 만듭니다. 균일하게 제한된 볼륨을 올바르게 모델링하기 위해 형상에 추가 제약 조건이 적용됩니다. 교차하는 비다각형 형상의 문제를 극복하기 위해 전면 및 후면 다각형을 명시적으로 추적하도록 광선 탐색이 수정되었습니다. 향상된 시각적 현실감은 "Lightspeed"(Porsche) 데모의 추가 비용을 감당할 가치가 없는 것으로 간주되어 최종 버전에서는 구현되지 않았습니다.
최근 레이 트레이싱 가속을 위한 전용 하드웨어가 도입되고 그래픽 API에 레이 트레이싱 지원이 추가되면서 개발 그룹은 래스터라이제이션와 레이 트레이싱을 결합한 새로운 하이브리드 렌더링 접근 방식을 혁신하고 시도하도록 장려되었습니다. 픽셀당 하나의 경로만으로 광택 반사, 부드러운 그림자, 앰비언트 오클루전(AO) 및 확산 간접 조명과 같은 무작위 효과를 렌더링하기 위해 혁신적인 재구성 필터가 개발되어 이러한 값비싼 효과를 실시간 사용에 더 적합하게 만들었습니다.
또한, 언리얼 엔진 4의 레이 트레이싱 콘텐츠 호환성을 위한 실용적인 솔루션에서는 레이 트레이싱 및 래스터라이제이션 하이브리드 파이프라인을 사용하여 다층 반투명도와 폴리지를 구현하는 기술, 프로세스, 최적화에 대해 자세히 설명합니다. (아래 사진)

17.5.2 포트나이트 레이 트레이싱
17.5.2.1 개요
2020년 초, Unreal Engine Ray Tracing은 베타에서 프로덕션으로 전환 중이었고 엔지니어링 팀은 까다로운 조건에서 전투 테스트를 수행하기로 결정했습니다. Fortnite는 엄청난 크기뿐 아니라 게임에서 레이 트레이싱을 구현하기 어렵게 만드는 다른 많은 기능을 가지고 있기 때문에 이 작업에 이상적으로 적합합니다. 즉, 콘텐츠 제작 파이프라인은 잘 정의되어 있으며 레이 트레이싱을 포함하도록 변경하는 것은 선택 사항이 아닙니다. 또한 콘텐츠 업데이트가 자주 발생하므로 특정 버전을 보기 좋게 만들기 위해 매개변수를 조정할 수는 없지만 모든 수정 사항은 상대적으로 영구적이며 향후 업데이트에서 올바르게 작동해야 합니다.
이 프로젝트의 주요 목표는 UE4 레이 트레이싱 기술을 사용하여 게임의 비주얼과 전투 검증을 개선하기 위해 Fortnite에서 레이 트레이싱을 출시하는 것입니다. 기술적 관점에서 볼 때 초기 목표는 8코어 CPU(i7-7000 시리즈 또는 동급) 및 NVIDIA 2080 Ti 그래픽 카드가 장착된 시스템에서 다음 요구 사항을 충족하여 게임을 실행하는 것입니다.
프레임 속도: 60FPS.
해상도: 1080p.
레이 트레이싱 효과: 그림자, 앰비언트 오클루전(AO) 및 반사.
프로젝트 중에 발생한 개선은 더 큰 목표를 달성하는 데 도움이 되었습니다. NVIDIA Deep Learning Super Sampling(DLSS)의 통합과 함께 레이 트레이싱 반사, 전역 조명 및 노이즈 감소의 새로운 개발을 통해 레이 트레이싱 전역 조명(RTGI)과 같이 더 높은 해상도와 더 복잡한 조명 효과를 목표로 삼는 것이 가능해졌습니다.
예술적 및 콘텐츠 제작 관점에서 팀의 목표는 외관에 큰 변화를 주는 것이 아니었지만 화면 공간 효과로 인해 발생하는 일부 아티팩트를 제거하여 시각적으로 더 즐겁게 만드는 주요 조명 효과의 구체적인 개선을 달성하는 것이 목표였습니다. Fortnite는 레이 트레이싱 및 래스터라이제이션로 인해 너무 다르게 보일 수 있는 경험을 피하기 위해 비사실적인 게임입니다.
성능 측면의 초기 테스트에 따르면 레이 트레이싱이 활성화된 경우 CPU와 GPU 모두 초기 성능 목표에 크게 미치지 못하는 것으로 나타났습니다. 대상 하드웨어에서 실행할 때 CPU 시간은 프레임당 평균 약 24ms였으며 일부 최고 시간은 30ms를 초과했습니다. GPU 성능도 목표와는 거리가 멀다. 일부 장면은 충분히 빠르지만 조명이 더 복잡한 다른 장면은 30-40ms/프레임 범위에 있습니다. 동적 형상(예: 나무)이 많은 일부 부정적인 사례는 프레임당 100ms 정도로 매우 느립니다.
성능 외에도 콘텐츠 제작 파이프라인과 같이 흥미로운 과제를 제시하는 다른 영역이 있습니다. Fortnite는 매우 높은 빈도로 업데이트되는 수많은 자산을 관리하며, 콘텐츠 팀의 과부하가 용납될 수 없기 때문에 레이 트레이싱에서 자산을 변경하여 더 보기 좋게 만드는 것은 불가능합니다. 예를 들어, 레이 트레이싱 반사의 성능을 향상시키기 위해 팀은 개체가 반사 광선을 투사하는지 여부를 설정하는 플래그를 추가하는 것을 고려했습니다. 그러나 추가 평가를 통해 이 솔루션이 확장되지 않는다는 것이 분명해졌습니다. 기존 및 미래의 모든 콘텐츠와 마찬가지로 개선 사항도 자동으로 잘 작동해야 합니다.
17.5.2.2 반영
Fortnite 시즌 15의 출시는 Epic이 게임에서 레이 트레이싱 반사를 사용한 최초의 사례입니다. 이전 사용 사례에서는 실시간 성능이 필요했지만 게임과는 목표가 완전히 달랐습니다. NVIDIA 2080 Ti에서 Fortnite 레이 트레이싱은 1080p(DLSS 포함 4K)에서 최소 60Hz를 목표로 합니다. 이 목표를 달성하려면 일부 최적화와 희생이 이루어져야 합니다. 팀은 특별히 게임을 위해 실험적인 레이 트레이싱 반사 구현을 만들었습니다. 이는 원래 반사 셰이더의 주요 아이디어를 공유하지만 다중 바운스 반사, 반사의 반투명 재질, 물리적 기반 클리어 코팅 등과 같은 고급 렌더링 기능을 대부분 제거합니다. 아래 이미지는 스크린 스페이스 리플렉션(SSR) 및 반사가 없는 새로운 레이 트레이싱 반사 모드를 비교한 것입니다.

알고리즘 개요: 언리얼 엔진 리플렉션 파이프라인은 정렬된 지연 머티리얼 평가 체계(아래)를 사용합니다. 먼저, G-버퍼 데이터를 기반으로 반사 광선이 생성된 다음 가장 가까운 표면과 관련 재질 ID를 추적합니다. 그런 다음 재질 ID/셰이더별로 히트를 정렬합니다. 마지막으로, 정렬된 적중은 전체 레이 트레이싱 파이프라인 상태 객체(RTPSO)를 사용하여 재질 평가 및 조명을 수행하는 또 다른 레이 트레이싱 패스를 예약하는 데 사용됩니다.
그래프 LR A(추적 광선) --> B(재료별 히트 정렬) B --> C(재료 평가) C --> D(조명)
이 시퀀싱 파이프라인의 목표는 머티리얼 셰이더 실행 일관성(SIMD 효율성)을 향상시키는 것입니다. 반사된 광선은 무작위로 지정되므로 화면 공간에서 서로 가까이 있는 픽셀은 멀리 떨어져 있는 표면에 닿는 광선을 생성하여 서로 다른 재질을 사용할 가능성이 높아집니다. 서로 다른 재질의 히트가 동일한 GPU 웨이브로 끝나는 경우 성능은 대략 고유 재질 수에 비례하여 저하됩니다. 이론적으로는 DirectX Ray Tracing과 같은 고급 레이 트레이싱 API를 통해 자동 시퀀싱을 통해 이러한 성능 문제를 피할 수 있었지만 실제로는 당시 사용 가능한 드라이버나 하드웨어 중 어느 것도 이 최적화를 구현하지 못했습니다. 나중에 설명하겠지만 애플리케이션 수준에서 명시적 정렬을 구현하면 성능이 크게 향상됩니다.
G-버퍼 데이터를 기반으로 한 GGX 분포 샘플링을 사용하여 반사 광선을 생성합니다. GPU 시간을 절약하려면 러프니스 임계값을 사용하여 레이 트레이싱 대신 간단한 반사 환경 맵 조회를 사용할 수 있는지 결정합니다. Fortnite 그래픽 옵션의 반사 품질 매개변수에 대한 임계값 매핑 - 중간, 높음 및 에픽 품질 사전 설정에 대해 0.35, 0.55 및 0.75가 선택됩니다. 시각적 개선에 비해 성능 비용이 너무 높기 때문에 거칠기가 0.75를 초과하는 표면은 모두 제거됩니다. 물을 제외한 모든 물체에 대해 레이 트레이싱 반사를 비활성화하는 특수 낮음 사전 설정도 있습니다. 아래 이미지는 이러한 품질 사전 설정과 GPU 성능을 시각화한 것입니다.

다양한 러프니스 임계값 수준과 해당 GPU 성능의 시각화. 녹색: 중간 반사 품질 사전 설정, 러프니스 <0.35, 0.7ms. 노란색: 높은 사전 설정, 러프니스 <0.55, 1.28ms. 빨간색: 에픽 사전 설정, 러프니스 <0.75, 1.72밀리초. 마젠타색: 거칠기가>0.75, 2.09ms인 컬링된 표면. 타이밍은 1920×1080 해상도의 NVIDIA RTX 3090을 기준으로 합니다.
Fortnite 콘텐츠는 레이 트레이싱 반사 기술을 염두에 두고 설계되지 않았기 때문에 대부분의 자산은 순수 미러 광선을 사용하여 매우 선명하게 보이는 스크린 스페이스 리플렉션(SSR)용으로 마스터링됩니다. 단순 러프니스 임계값은 거칠거나 분산되어 나타나는 대부분의 표면에서 스크린 스페이스 리플렉션(SSR)를 제거하는 데 사용됩니다. 불행하게도 이는 물리적 기반의 레이 트레이싱 반사가 대부분의 상황에서 다소 느리게 나타난다는 것을 의미합니다. 게임에는 콘텐츠가 너무 많기 때문에 모든 자료를 수동으로 조정하는 것은 선택 사항이 아닙니다. 다음 코드에서 볼 수 있듯이 GGX 샘플링 중에 표면 러프니스를 바이어스하여 표면을 약간 반짝이게 만드는 자동 솔루션이 구현됩니다.
float ApplySmoothBias(float Roughness , float SmoothBias)
{
// SmoothStep -러프니스(Roughness)SmoothBias의 함수 ,이면 로 러프니스(Roughness)。.
float X = saturate(Roughness / SmoothBias);
return Roughness * X * X * (3.0 - 2.0 * X);
}아래 그림에서 볼 수 있듯이 이 리매핑 기능은 러프니스 값을 특정 임계값 아래로 0에 가깝게 밀어넣지만 더 높은 값은 변경되지 않은 채로 둡니다. 이 특정 기능은 전체 러프니스 범위에서 부드러움을 유지하면서 클리핑 없이 재료 러프니스 맵 기여도를 유지하도록 설계되었습니다. Fortnite에서는 바이어스 값 0.5가 사용되며 이상적인 외관과 물리적 정확성 사이의 적절한 절충안입니다. 작은 보너스로 GPU 성능은 일부 장면에서 약간 향상됩니다. 왜냐하면 반사광이 자연스럽게 더 일관성이 있기 때문입니다(추적 속도가 더 빨라짐).

아래 이미지는 Smoothness Bias 값을 변경하여 생성된 시각적 결과를 비교합니다.

17.5.2.3 재료
언리얼 엔진은 초기 반사 레이 트레이싱을 위해 특화된 경량 파이프라인 상태 객체를 사용합니다. 이는 광선 생성 셰이더, 작은 미스 셰이더 및 장면의 모든 형상에 대한 공통의 작은 가장 가까운 적중 셰이더로 구성됩니다. 아래에 표시된 것처럼 이 셰이더의 목표는 셰이딩 오버헤드를 발생시키지 않고 가장 가까운 교차점을 찾는 것입니다.
struct FDeferredMaterialPayload
{
float HitT; // Ray hit depth or -1 on miss
uint SortKey; // Material ID
uint PixelCoord; // X in low 16 bits, Y in high 16 bits
};
[shader("closesthit")]
DeferredMaterialCHS(FDeferredMaterialPayload Payload, FDefaultAttributes Attributes)
{
Payload.SortKey = GetHitGroupUserData(); // Material ID
Payload.HitT = RayTCurrent();
}재질 ID 수집 프로세스의 결과는 후속 정렬을 위해 64×64 청크 순서로 지연된 재질 페이로드 버퍼에 기록됩니다.
반사된 광선 적중은 컴퓨팅 셰이더를 사용하여 재질 ID별로 정렬되며 정렬은 64×64픽셀 화면 공간 타일(총 4096픽셀)에서 수행됩니다. 전체 정렬을 수행하는 대신 광선은 타일당 버킷으로 병합됩니다. 버킷 수는 블록의 총 픽셀 수를 예상 스레드 그룹 크기로 나눈 값입니다. 4096픽셀 / 32개 스레드 = 128개 버킷. 장면 전반에 걸쳐 더 많은 재료가 있을 수 있지만(평균 Fortnite RTPSO의 경우 약 500개 재료) 특정 타일에 이러한 재료가 모두 포함될 가능성은 거의 없습니다. 타일에 128개 이상의 서로 다른 재료가 있는 경우 어쨌든 완전히 일관된 그룹으로 분류하는 것은 불가능합니다. 빈 수를 늘려도 재질 ID → 버킷 ID 매핑에서 충돌 가능성이 줄어드는 것 외에는 크게 개선되지 않습니다. 실제로 버킷 수를 늘려도 효율성이 향상될 수는 없습니다.
Bin은 개별 컴퓨팅 셰이더 패스로 수행되며 각각은 스레드 그룹으로 분리되며 그룹 공유 메모리를 사용하여 중간 결과를 저장합니다. 여기서는 간단한 비닝 알고리즘이 사용됩니다.
(1) 지연된 머티리얼 버퍼에서 요소를 로드합니다.
(2) 원자를 사용하여 각 분류 버킷의 요소 수를 계산합니다.
(3) 정렬 지수를 계산하기 위해 개수에 대한 추정을 구축합니다.
(4) 재료 평가를 위해 동일한 지연 재료 버퍼에 요소를 다시 씁니다.
원본 광선 디스패치 인덱스는 파이프라인 전체에 걸쳐 보존되어야 하며 히트 셰이더에 의해 광선 페이로드 구조에서 읽어야 합니다(DispatchRaysIndex() 내장 속성은 원본(정렬되지 않은) 광선 생성 셰이더 외부에서 사용될 수 없습니다).
아래 두 그림에서 볼 수 있듯이 순서 지정 체계는 셰이더 실행 차이를 매우 효과적으로 줄입니다. 정렬을 사용할 때 일반적인 Fortnite 프레임의 대부분의 Wave에는 단일 재질 셰이더가 포함됩니다. 효과적이긴 하지만 GPU 성능에 미치는 영향은 장면에 따라 크게 달라집니다. 빛이 자연스럽게 동일한 재질이나 하늘에 닿는 대부분의 경우 정렬 오버헤드로 인해 성능이 향상되지 않거나 약간의 속도 저하가 발생할 수 있습니다. 그러나 거칠기가 높은 표면이 많은 복잡한 장면의 경우 속도 향상은 최대 3배까지 높을 수 있으며 Fortnite의 평균 성능 향상은 약 1.6배입니다. 분류는 물을 제외한 모든 것에 대한 참조로 사용되며, 물에서 반사된 빛은 일관성이 높고 일반적으로 하늘에 도달하므로 분류로 인한 이점이 거의 없습니다.

SIMD 실행 효율성을 향상시키기 위한 광선 정렬 시각화. 진한 파란색 영역은 머티리얼 셰이더가 전혀 필요하지 않은 파도(하늘에 닿는 광선 또는 러프니스 임계값에 의해 선별되는 광선)에 속하며, 밝은 색상은 1(밝은 파란색)과 8+(짙은 빨간색)의 서로 다른 셰이더를 포함하는 파도를 표시합니다.

1920×1080 해상도에서 NVIDIA RTX 3090의 정렬 성능 비교.
머티리얼 평가 단계에서는 초기 조명 생성 단계에서 작성된 버퍼에서 조명 매개변수를 로드하지만, 이전에 적중된 삼각형 주위의 작은 세그먼트만 덮도록 광선을 줄입니다. 그런 다음 TraceRay를 사용하여 전체 재질 셰이더를 호출합니다. 두 번째 광선을 추적하는 데 드는 추가 비용에도 불구하고 이 접근 방식은 전체 반사 파이프라인에서 원래 TraceRay보다 훨씬 빠릅니다.
언리얼 엔진은 플랫폼별 API를 사용하여 최대한 적은 순회 비용으로 가장 가까운 셰이더를 직접 실행합니다. PC에서 실행 가능한 대체 경로는 가장 가까운 모든 셰이딩에 DXR 호출 가능 셰이더를 사용하고, 알파 마스크 평가에는 모든 적중 셰이더를 사용하는 것입니다. 여기에는 모든 조명 생성 셰이더가 페이로드를 통해 적중 매개변수를 호출 가능한 셰이더에 명시적으로 전달하도록 요구하는 것과 같은 일련의 절충안이 있습니다. 궁극적으로 단축된 광선 접근 방식은 당시 성능과 단순성 사이의 좋은 절충안이었습니다.
레이 트레이싱 시 적중 셰이더 처리를 최대한 피하는 것은 중요한 성능 최적화입니다. 불행하게도 일반적인 게임 장면에는 반사 시 올바르게 보여야 하는 알파 매트 재질이 포함되어 있습니다. 실제로 대부분의 반사 광선은 완전히 불투명한 재질이나 알파 마스크 재질의 불투명한 부분(예: 나뭇잎 마스크 텍스처의 단색 부분)에 부딪히는 경향이 있습니다. 가장 가까운 셰이더의 불투명도를 평가하고 라이트 페이로드에 불투명도 상태를 기록할 수 있습니다. 모든 초기 레이 트레이싱은 RAY_FLAG_FORCE_OPAQUE를 사용할 수 있으며 광선 생성 셰이더는 아래와 같이 FORCE_OPAQUE 플래그를 사용하지 않고 "full-fat" 광선을 추적해야 하는지 결정할 수 있습니다.
TraceRay(TLAS, RAY_FLAG_FORCE_OPAQUE , ..., Payload);
if (GBuffer.Roughness <= Threshold && Payload.IsTransparent())
{
TraceRay(TLAS, RAY_FLAG_NONE , ..., Payload);
}Fortnite는 0.1의 공격적인 임의 적중 러프니스 임계값을 사용합니다. 이는 알파 매트 재질(예: 초목)이 거친 표면의 반사에서 완전히 불투명하다는 것을 의미합니다. 아래 이미지에 표시된 것처럼 거의 완벽한 반사판만이 올바른 알파 컷아웃을 표시할 수 있습니다. 품질에 대한 양보이지만 실제로는 Fortnite에서 매우 잘 작동합니다. 이 최적화를 통한 성능 향상은 시나리오에 따라 다르지만 Fortnite에서는 평균적으로 약 1.2배의 속도 향상이 측정되며 일부 시나리오에서는 2배에 가깝습니다. FORCE_OPAQUE의 이점은 일반적인 프레임에서 일부 광선을 역추적하는 비용을 쉽게 상쇄합니다.

히트 자료 평가의 시각화. 녹색 영역은 불투명한 형상이나 알파 마스크 재질의 불투명한 부분(가장 가까운 히트 셰이더에 의해 보고됨)에 부딪힌 반사 광선을 보여주고, 노란색 영역은 러프니스 임계값으로 인해 불투명하지 않은 레이 트레이싱을 건너뛴 위치를 보여주며, 빨간색 영역은 불투명하지 않은 광선이 추적된 위치를 보여줍니다. 1920×1080 해상도에서 NVIDIA RTX 3090의 성능은 1.8밀리초였으며, 불투명 조명 최적화 없이는 2.4밀리초였습니다.
17.5.2.4 조명
언리얼 엔진의 레이 트레이싱 효과에서 직접 조명 평가는 주로 광선 생성과 미스 셰이더로 구분됩니다. 방출 및 간접 조명만 가장 가까운 히트 셰이더에서 나오며 텍스처 또는 라이트 맵에서 읽는 작업이 포함될 수 있습니다. 조명 생성 셰이더에는 래스터 기반 컬링 및 조명 모양 샘플링이 포함된 조명 루프가 포함되어 있으며 항상 레이 트레이싱(그림자 맵 대신)을 사용하여 반사의 조명 그림자와 미스 셰이더의 조명 방사조도를 계산합니다. TMin=TMax 및 InstanceInclusionMask=0을 설정하여 조명이 그림자를 사용하지 않는 경우 조명은 호출 가능한 셰이더를 시작하는 것과 유사하지만 조명 생성 셰이더의 추가 변환을 피하면서 공통 및 강제 누락을 통해 광선 경로를 계속 추적합니다. 이러한 방식으로 미스 셰이더를 사용하면 광선 생성 셰이더 코드가 간소화되고 점유율이 향상되어 모든 대상 플랫폼의 성능이 향상됩니다.
또한 이 설계는 조명 중 SIMD 효율성을 향상시킵니다. 한 파장의 빛이 다른 재료에 닿을 수 있기 때문입니다. 재질 평가 중에는 실행이 분기되지만 조명을 켜면 다시 수렴됩니다. 재료 분류는 발산 문제를 완전히 해결하지 못합니다. 왜냐하면 항상 파동을 완벽하게 채울 수는 없고 부분 파동만 남게 되기 때문입니다.
모든 조명 계산을 조명 생성 셰이더에 유지하면 가장 가까운 적중 셰이더를 더 작게 만들 수 있습니다. 이는 반복 속도부터 코드 모듈성 및 게임 패치 크기에 이르기까지 모든 면에서 이점을 얻는 움직임입니다. 언리얼 엔진은 다음 코드에서 볼 수 있듯이 모든 레이 트레이싱 효과에 공통 머티리얼 히트 셰이더 세트와 마스터 머티리얼 레이 페이로드 구조를 사용합니다. 래스터 그래픽 파이프라인에는 순방향 및 디퍼드 렌더링과 유사한 절충점이 있습니다. 여기서 G-Buffer는 서로 다른 렌더링 단계 간에 불투명한 인터페이스를 제공하고 분리/핫 스왑 가능한 알고리즘을 허용합니다. 그러나 이는 대규모 G-버퍼 메모리 공간이나 더 큰 레이 페이로드 구조를 희생해야 합니다.
struct FPackedMaterialClosestHitPayload
{
float HitT // 4 bytes
uint PackedRayCone; // 4 bytes
float MipBias; // 4 bytes
uint RadianceAndNormal[3]; // 12 bytes
uint BaseColorAndOpacity[2]; // 8 bytes
uint MetallicAndSpecularAndRoughness; // 4 bytes
uint IorAndShadingModelIDAndBlendingModeAndFlags; // 4 bytes
uint PackedIndirectIrradiance[2]; // 8 bytes
uint PackedCustomData; // 4 bytes
uint WorldTangentAndAnisotropy[2]; // 8 bytes
uint PackedPixelCoord; // 4 bytes
}; // 64 bytes total재료 평가 비용을 최적화하는 것 외에도 조명 반사 표면 비용의 균형을 맞출 필요도 있습니다. Fortnite의 광대한 세계는 많은 수의 조명이 표면에 영향을 미칠 수 있는 시나리오를 생성하며, 표면은 최대 256개의 광원(반사에서 지원되는 최대값)의 영향을 받는 경우가 많으므로 표면에 의미 있는 영향을 미치는 광원만 선택하는 전략이 필요합니다. 우리가 선택한 방법은 월드 정렬, 카메라 중심 3D 메시를 사용하여 라이트 컬링을 수행합니다.
Fortnite의 넓은 세계에는 셀 크기가 상충됩니다. 큰 셀은 선별 효율성을 저하시키지만 작은 셀은 필요한 적용 범위로 인해 비실용적입니다. 단점은 카메라로부터의 거리에 따라 세포의 크기가 기하급수적으로 증가한다는 것입니다. 셀이 메시에서 함께 이동할 수 있도록 크기 조정이 각 축에 개별적으로 적용됩니다. 카메라 근처에는 적당한 8m^3(2×2×2) 수의 셀이 생성되었지만 카메라에서 100m 떨어진 곳에는 여전히 별개의 셀이 있었습니다. 추가 조정을 통해 계산이 단순화되어 셀의 처음 두 레이어가 동일한 크기로 유지됩니다. 아래 이미지는 그리드의 2D 레이아웃을 보여주고, 아래 코드는 월드 공간 어디에서나 셀의 주소를 계산하는 데 사용되는 HLSL 셰이더 코드를 보여줍니다.

4개의 가장 가까운 세포 고리를 보여주는 래스터의 2D 조각.
int3 ComputeCell(float3 WorldPosition)
{
float3 Position = WorldPos - View.WorldViewOrigin;
Position /= CellScale;
// Use symmetry about the viewer.
float3 Region = sign(Position);
Position = abs(Position);
// Logarithmic steps with the closest cells being 2x2x2 scale units
Position = max(Position , 2.0f);
Position = min(log2(Position) - 1.0f, (CellCount/2 - 1));
Position = floor(Position);
Position += 0.5f; // Move the edge to the center.
Position *= Region; // Map it back to quadrants.
// Remap [-CellCount/2, CellCount/2] to [0, CellCount].
Position += (CellCount / 2.0f);
// Clamp to within the volume.
Position = min(Position , (CellCount - 0.5f));
Position = max(Position , 0.0f);
return int3(Position);
}아래 그림과 같이 라이트 컬링 데이터의 최종 표현은 GPU에서 3단계 구조로 구현됩니다. 그리드는 그리드 셀당 128비트를 저장하는 최상위 구조입니다. 각 그리드 셀은 각각 10비트로 구성된 최대 11개의 인덱스 목록을 인코딩하거나 보조 버퍼에 인코딩된 카운트 및 오프셋을 인코딩합니다. 컴팩트 형식의 경우 이 보조 버퍼는 너무 많은 조명을 포함하는 셀의 인덱스를 보유합니다. 가장 낮은 레벨은 인덱스가 가리키는 광 데이터 매개변수의 구조화된 버퍼입니다. 다음 코드는 그리드 셀의 인덱스를 검색하는 방법을 보여 주며, 그리드 구조 및 보조 버퍼는 메쉬 컬링 라이트에 대한 컴퓨팅 셰이더에 의해 생성됩니다.

int GetLightIndex(int3 Cell, int LightNum)
{
int LightIndex = -1; // Initialized to invalid
const uint4 LightCellData = LightCullingVolume[Cell];
// Whether the light data is inlined in the cell
const bool bPacked = (LightCellData.x & (1 << 31)) > 0;
const uint LightCount = bPacked ? (LightCellData.w >> 20) & 0x3ff : LightCellData.x;
if (bPacked)
{
// Packed lights store 3 lights per 32-bit quantity.
uint Shift = (LightNum % 3) * 10;
uint PackedLightIndices = LightCellData[LightNum / 3];
uint UnpackedLightIndex = (PackedLightIndices >> Shift) & 0x3ff;
if (LightNum < LightCount)
{
LightIndex = UnpackedLightIndex;
}
}
else
{
// Non-packed lights use an external buffer
// with the offset in the cell data.
if (LightNum < LightCount)
{
LightIndex = LightIndices[LightCellData.y + LightNum];
}
}
return LightIndex;
}17.5.2.5 전역 조명
글로벌 일루미네이션은 Fortnite의 레이 트레이싱에 대한 초기 요구 사항이 아닙니다. 원래 Unreal Engine 4.22에서 실험적인 알고리즘으로 출시된 강력한 전역 조명은 실시간 성능이 필요한 응용 프로그램에는 적합하지 않습니다. 대신 무차별 대입 알고리즘은 대화형 및 영화 프레임 속도에서만 작동하며 원래 알고리즘은 Unreal Engine 4.24에서 "Final Collection" 알고리즘으로 재구성되었습니다. 지속적인 개발, 엄격한 조명 제약, 소음 감소 및 증폭을 통한 실질적인 품질 개선으로 인해 Fortnite의 잠재적인 실시간 전역 조명 솔루션으로 최종 수집 방법이 채택되었습니다. 아래 이미지는 게임에서 얻은 GI 시각 효과를 보여줍니다.

Fortnite의 Risky Reels에서는 레이 트레이싱 전역 조명을 적용하기 전후에 잔디에서 격자까지 바운스 조명이 나타나는 것을 확인하세요.
실험적인 무차별 대입 알고리즘은 몬테 카를로 통합을 사용하여 렌더링 방정식의 확산 구성 요소를 해결합니다. 다른 레이 트레이싱 패스와 마찬가지로 전역 조명 패스는 G 버퍼로 시작합니다. 여기서 확산 광선은 래스터라이제이션된 깊이 버퍼 위치의 월드 공간 법선을 기반으로 생성됩니다. 이러한 방식으로 무차별 대입 알고리즘은 앰비언트 오클루전(AO) 알고리즘과 매우 유사하게 동작합니다. 그러나 하늘 폐색을 표로 작성하기 위해 가시 광선을 투사하는 대신 전역 조명 알고리즘은 가장 가까운 적중 셰이더를 호출하여 표면 재질 정보를 평가하는 더 비싼 광선을 투사합니다.
비용이 많이 드는 가장 가까운 적중 셰이더 평가 외에도 알고리즘은 보조 표면에 직접 조명을 적용해야 합니다. 전역 조명 알고리즘은 기존 광원 주기를 적용하는 대신 NEE(Next Event Estimation)를 사용합니다. NEE(Next Event Estimation)는 특정 확률로 후보 조명을 선택하는 확률론적 프로세스입니다. 광원 선택이라고 하는 첫 번째 프로세스는 일부 선택 확률을 기반으로 샘플링할 조명을 결정합니다. 두 번째 프로세스는 광원의 출구 방향을 샘플링하는 전통적인 조명 샘플링과 유사합니다. NEE 프로세스는 선택한 조명과 관련된 그림자 점의 가시성을 테스트하기 위해 그림자 광선을 구성합니다. 가시 광선이 광원에 성공적으로 연결되면 확산 조명 평가가 기록됩니다.
NEE는 총 후보 광원 수의 하위 집합만 평가하므로 기존 조명 루프보다 광선당 비용이 더 저렴합니다. NEE는 일반적으로 호출당 하나의 광원을 선택하는 것으로 간주되지만 여러 NEE 샘플을 그리기 위해 또 다른 보조 확률론적 프로세스를 호출할 수 있습니다. 여러 샘플을 그리는 것은 광선당 분산을 줄이는 효과가 있는 동시에 값비싼 운영 평가 광선을 구성하는 비용도 절감합니다. 재료 평가 광선당 두 개의 NEE 샘플을 그리는 것은 실제로 잘 작동합니다. 다음 코드 예제입니다.
float3 CalcNextEventEstimation(float3 ShadingPoint, inout FPayload Payload, inout FRandomSampleGenerator RNG, uint SampleCount)
{
float3 ExitantRadiance = 0;
for (uint NeeSample = 0; NeeSample < SampleCount; ++NeeSample)
{
uint LightIndex;
float SelectionPdf;
SelectLight(RNG, LightIndex , SelectionPdf);
float3 Direction;
float Distance;
float SamplePdf;
SampleLight(LightIndex , RNG, Direction , Distance , SamplePdf);
RayDesc Ray = CreateRay(ShadingPoint , Direction , Distance);
bool bIsHit = TraceVisibilityRay(TLAS, Ray);
if ( !bIsHit )
{
float3 Radiance = CalcDiffuseLighting(LightIndex , Ray, Payload);
float3 Pdf = SelectionPdf * SamplePdf;
ExitantRadiance += Radiance / Pdf;
}
}
ExitantRadiance /= SampleCount;
return ExitantRadiance;
}아티스트가 허용하는 최대 바운스 수에 따라 무차별 대입 알고리즘은 보조 표면에서 또 다른 확산 광선을 방출하고 프로세스를 반복하여 경로 체인을 확장할 수 있습니다. 후속 집회는 조기 종료될 수 있으며 러시안 룰렛 프로세스에 따라 관리됩니다.
이런 방식으로 계산된 전역 조명은 경로 추적 적분기와 매우 잘 일치합니다. 그러나 경로 추적 통합기와 마찬가지로 이 프로세스에도 수렴하려면 많은 수의 샘플이 필요합니다. 강력한 노이즈 감소 커널은 매끄러운 최종 결과를 생성하는 데 도움이 되지만 실제로는 조명 조건과 전체 환경에 따라 적절한 품질을 위해 픽셀당 16~64개의 샘플이 여전히 필요한 것으로 나타났습니다. 이 접근 방식은 프레임당 여러 재료 평가 광선을 캐스팅하면 알고리즘을 실시간 대화형 프레임 속도 이상으로 빠르게 밀어붙일 수 있고 필름 마모에만 적합하기 때문에 문제가 있습니다. 아래 이미지는 Fortnite의 개발 수준에서 작동 중인 Brute Force Global Illumination을 보여줍니다.

개발 테스트 환경의 무차별 전역 조명 기술, 화면 해상도 = 50에서 픽셀당 2개의 샘플로 렌더링. 노란색이 근처 벽에서 관목으로 쏟아지는 것을 확인하세요.
무차별 적분기의 우수한 성능을 유지하기 위해 Archviz 내부 렌더링 샘플[4]에 대해 24Hz 영화 프레임 속도로 디퓨즈를 렌더링하도록 알고리즘이 2019년 9월에 수정되었습니다.
무차별 대입 알고리즘을 가속화하는 핵심 통찰력은 값비싼 재료 평가 광선을 상대적으로 저렴한 가시성 광선으로 변환하는 것입니다. 이를 위해 Fortnite 개발팀은 연속 프레임에 걸쳐 재료 평가 광선을 시간 분할합니다. 무차별 적분기가 호출되지만 픽셀당 하나의 샘플만 있으며 마치 각 픽셀이 실제로 필요한 수의 샘플에 대해 실행된 것처럼 이전 프레임의 시뮬레이션 데이터를 사용하여 누적됩니다. 이전 프레임 데이터를 축적하려면 샘플 재투영이 필요하며 특히 이전 프레임의 시뮬레이션 데이터가 더 이상 유효하지 않은 경우 심각한 고스팅 아티팩트가 발생할 수 있습니다. 이전 프레임 데이터 축적과 관련된 차이점을 조정하기 위해 마스터 경로 재연결을 사용하여 이전에 추적된 경로를 재사용하기로 결정했습니다. 이전 경로 데이터는 수집 지점이라는 중간 구조에 캐시됩니다. 각 수집 지점은 기록된 방사조도 및 경로 생성 확률 밀도 함수(PDF)와 함께 hyposurface의 위치를 인코딩합니다. 픽셀의 월드 위치도 캐시되어 후속 프레임에서 재사용하기 위한 재투영 기준을 테스트하는 데 사용됩니다. 수집 지점 버퍼는 원형 버퍼로 해석되며, 여기서 버퍼 길이는 알고리즘의 픽셀당 샘플 수에 의해 결정됩니다. Bekaert et al.과 유사한 방식으로, 성공적인 경로 재연결을 테스트하기 위해 보조 가시 광선이 방출됩니다. 이를 올바르게 수행하려면 여기 방사선과 집계 지점을 모두 전달하는 이전 시뮬레이션의 활성 음영 지점에서 생성된 확률 밀도가 필요합니다. 다음 이벤트 추정과 유사하게 성공적인 경로 재연결 이벤트는 집합 지점에서 확산 조명을 기록합니다.
확산 광선의 파동은 프레임당 별도의 채널로 예약됩니다. 이 패스의 실행 흐름은 무차별 대입 알고리즘과 유사하지만 조명 데이터가 보조 수집 지점 버퍼에 기록됩니다. "집합점 버퍼"는 확률론적 광 평가에서 2차 표면 위치와 확산 여기 방사선뿐만 아니라 시뮬레이션에서 생성된 축적점의 확률 밀도도 기록합니다. 현재 프레임에서 재사용하기에 충분한 기준을 충족하지 못하는 수집 포인트가 거부되도록 원본 생성 포인트도 제공됩니다. 이 데이터를 기반으로 경로 재연결 이벤트를 해당 지점에 투영하고 캐시된 조명 평가를 병합할 수 있습니다. 레이 트레이싱 반사와 동일한 정렬된 지연 재질 평가 파이프라인을 사용하여 수집 지점 프로세스 속도를 높입니다. (아래 코드 참조)
struct FGatherSample
{
float3 CreationPoint;
float3 Position;
float3 Irradiance;
float Pdf;
};
struct FGatherPoint
{
float3 CreationPoint;
float3 Position;
uint2 Irradiance;
};
uint2 PackIrradiance(FGatherSample GatherSample)
{
float3 Irradiance = ClampToHalfFloatRange(GatherSample.Irradiance);
float Pdf = GatherSample.Pdf;
uint2 Packed = (uint2)0;
Packed.x = f32tof16(Irradiance.x) | (f32tof16(Irradiance.y) << 16);
Packed.y = f32tof16(Irradiance.z) | (f32tof16(Pdf) << 16);
return Packed;
}
FGatherPoint CreateGatherPoint(FGatherSample GatherSample)
{
FGatherPoint GatherPoint;
GatherPoint.CreationPoint = GatherSample.CreationPoint;
GatherPoint.Position = GatherSample.Position;
GatherPoint.Irradiance = PackIrradiance(GatherSample);
return GatherPoint;
}집결지 생성 후 최종 집결 패스가 진행됩니다. 파이널 게더링 패스는 현재 픽셀과 연관된 모든 게더링 포인트를 반복하고 이를 활성 프레임에 재투영합니다. 성공적으로 재투영된 집합 지점은 경로 재연결의 후보입니다. 음영 지점의 표준 위치를 수집 지점의 표준 위치에 잠재적으로 연결하기 위해 가시성 광선이 방출됩니다. 경로 재연결 시도가 성공하면 음영 지점은 집합 지점의 확산 조명을 기록합니다. 개발팀은 좋은 정성적 결과를 얻으려면 약 16개의 경로 재연결 이벤트가 여전히 필요하다는 것을 발견했습니다. 다음 두 그림은 각각 두 가지 전역 조명 방법에 대한 시각적 및 런타임 비교를 제공합니다.

두 가지 전역 조명 알고리즘의 결과입니다. 위: 무차별 대입 방식; 하단: 최종 수집 방법. 명확성을 위해 각 결과는 화면 백분율 = 100으로 표시되며 픽셀당 하나의 샘플(왼쪽)과 픽셀당 16개의 샘플(오른쪽)으로 표시됩니다. 이미지는 시각화 목적으로 조명되었습니다.
SPP 무차별 대입(ms) 파이널 게더링(ms)
1 19.78 11.63
2 46.95 13.39
4 121.33 13.49
8 259.48 15.86
16 556.31 20.15
파이널 게더링 알고리즘은 확산 상호반사의 한 번의 바운스로 제한됩니다. 이 기술은 주어진 이벤트에서 무작위 수집 지점을 생성함으로써 다중 바운스로 확장될 수 있지만 단순화를 위해 개발 팀은 단일 바운스를 선택했습니다. 바운스 횟수를 인위적으로 제한하면 런타임 비용이 낮아지고 분산 영향이 낮아지므로 기술의 일반적인 비용을 고려하면 적절한 절충안입니다. 불행하게도 재투영 및 경로 재연결이 실패하면 무차별 방식보다 수렴 속도가 느려집니다. 카메라나 물체의 움직임이 심할 때 발생할 수 있습니다.
17.5.2.6 타당성
UE 4.24 출시 직후 레이 트레이싱형 전역 조명 개발이 중단되었습니다. Unreal Engine 5 기술이 본격적인 개발에 들어감에 따라 결국 새로운 접근 방식과 경쟁하게 될 알고리즘을 확장하는 것은 더 이상 의미가 없습니다.
그러나 DLSS(NVIDIA Deep Learning Super Sampling)와 같은 기술이 새로운 논의를 시작했습니다. Fortnite 레벨을 테스트하는 초기 실험에서는 성능 모드에서 DLSS를 사용하여 전체 레이 트레이싱 셰이더 제품군을 초당 약 50프레임으로 실행할 수 있음이 나타났습니다! 레이 트레이싱 그림자, 앰비언트 오클루전(AO), 반사, 채광창 및 전역 조명 알고리즘은 출시 예상 예산에 가깝습니다. 처음에는 매우 흥미로웠지만 지도 제작은 여전히 훨씬 더 복잡했습니다. 특히 전역 조명의 경우 원래의 무작위 조명 선택 방법으로는 Fortnite의 수많은 활성 광원을 처리할 수 없습니다. 광원 선택에 필요한 개선이 이루어졌음에도 불구하고 상당한 샘플 노이즈(주로 내부 조명)로 인해 파이널 게더링 알고리즘을 채택하기가 어렵습니다.
프로덕션 환경에서와 마찬가지로 개발 중에 다른 기능적 목표도 변경되었습니다. 가장 주목할만한 결정 중 하나는 태양(방향성 조명)을 제외한 모든 광원에 대해 레이 트레이싱 그림자를 생략한 것입니다. 레이 트레이싱 그림자가 태양에만 포함된 경우 전역 조명에도 비슷한 제외 규칙을 적용할 수 있습니다. 물론 이는 외부 환경의 바운스 조명도 제한하지만 전역 조명을 태양으로 제한하면 광원 선택의 알고리즘 비효율성 문제를 해결할 수 있습니다. 넓은 영역 조명의 조명과 같은 기타 잠재적인 샘플링 문제도 사라집니다. 다음 이벤트 추정 샘플을 1로 조정하면 두 번째 그림자 광선을 발사하는 비용이 방지되고 추가 절감 효과를 얻을 수 있습니다. 기능 세트가 상당히 선별되었기 때문에 레이 트레이싱형 전역 조명을 사용하는 것이 실제로 실현 가능해 보입니다.
많은 알고리즘 요구 사항이 제거되었지만 Fortnite에 대한 최종 집계 알고리즘을 배포하는 것은 남아 있는 성능 및 샘플링 문제로 인해 여전히 어려운 과제입니다. 개발팀은 예산을 유지하기 위해 더 거친 해상도로 작업해야 하며 프로젝트에는 절반 해상도 또는 더 작은 작업이 필요할 것으로 예상됩니다. 불행하게도 개발팀은 더 작은 해상도에서 실행하면 소음을 줄이기 위해 더 많은 재연결 이벤트가 필요한 경우가 많다는 사실을 발견했습니다. 그렇게 하는 것은 개발팀의 임시 전략과 일치하지 않습니다. 그러나 시간 지연이 증가하면 성공적인 재연결 이벤트 가능성은 감소합니다. Fortnite만큼 빠르게 움직이는 게임의 경우 시간 기록에 대한 전반적인 의존이 어렵다는 것이 입증되었습니다.
개발팀은 집결지 재투영 및 경로 재연결을 위한 확장된 전략을 실험하기 시작했으며, 빠르게 움직이는 카메라 움직임의 안정성을 향상시키기 위해 시변 카메라 투영을 사용하여 간단한 세계 기반 수집 지점 데이터 재투영을 개선했습니다. 최종 집계 알고리즘의 첫 번째 구현에서는 집계 지점의 시간 경로 재투영을 활용했지만 픽셀당 유효 샘플을 늘리려면 공간적 및 시간적 재사용이 필요할 것으로 예상됩니다. 이 분야의 이전 실험은 Bekaert et al.이 이전에 제안한 것처럼 실패했습니다. 일반 이웃 재구성 커널을 사용하면 최종 결과에서 관찰된 오류 감소에도 불구하고 매우 불쾌한 구조적 노이즈가 나타났습니다.
일반적인 소음 감소 문제도 해결해야 했기 때문에 이 분야의 추가 실험은 중단되었습니다. 개발팀의 이전 전역 조명 노이즈 제거기는 전체 해상도에서는 잘 작동했지만 낮은 해상도에서 렌더링할 때는 성능이 빠르게 저하되었습니다. 고맙게도 NVIDIA는 전역 조명 알고리즘의 판도를 바꾸는 것으로 입증되었으며 Fortnite 개발 팀의 통합 디노이저보다 다운샘플링된 노이즈 이미지에 더 잘 견디는 시공간 분산 유도 필터링(SVGF)을 제공합니다. SVGF는 새로운 공간 경로 재연결 전략에 존재하는 구조화된 노이즈를 쉽게 수용하고 매우 만족스러운 결과를 생성하는 동시에 픽셀당 전체 유효 샘플을 증가시킵니다. 불행하게도 개발팀의 SVGF 구현에서는 필요한 해상도로 업스케일하려고 할 때 알베도 표면이 높은 색상 유출 아티팩트가 나타났습니다(아래 이미지).

레이트레이싱된 전역 조명을 적용하기 전과 후의 Fortnite의 Misty Meadows에서는 지붕에서 건물 위로 빨간색이 쏟아지는 것을 확인하세요.
이 상태는 드물지만 땀이 많이 나는 관심 있는 모래 지점(아래)에서 널리 퍼져 있으므로 해결해야 합니다. 촉박한 기한에 직면한 개발팀은 G-Buffer 상관관계가 필터를 방해하지 않도록 업샘플링 사전 통과를 구현하기로 결정했습니다. 개발팀은 SVGF가 다른 프로젝트에서 사용될 경우 향후 이 엄청난 비용을 해결할 계획입니다.

레이 트레이싱 전역 조명을 적용하기 전과 후의 Fortnite의 Sweaty Sands. 공유된 이웃 샘플은 강력한 구조적 아티팩트를 생성하지만 SVGF는 여전히 일시적인 아티팩트를 억제하면서 원활한 결과를 재구성할 수 있습니다.
최종 집계 알고리즘에 대한 반복적 개선의 최종 분석은 아래 왼쪽 표에 나와 있으며, 패스당 최종 비용 분석은 아래 오른쪽 표에 나와 있습니다. 전역 조명을 방향성 조명으로 제한하면 조명 선택을 피할 수 있어 상당한 비용 절감 효과를 얻을 수 있습니다. DLSS를 사용하면 이 방법이 분수 단위로 작동하여 또 다른 상당한 속도 향상을 적용할 수 있습니다. 조명을 다음 이벤트 추정 샘플로 제한하여 적당한 속도 이득을 얻습니다. SVGF를 사용하여 공간 재연결 전략을 적용할 때 시간적 안정성을 유지하기 위해 비용이 다시 도입됩니다. SVGF는 높은 품질과 낮은 품질 설정을 구동하는 절반 해상도와 1/4 해상도에서 만족스러운 결과를 제공합니다.

17.5.2.7 CPU 최적화
- GPU 버퍼 관리
개발팀은 Ray Tracing이 활성화된 경우 CPU 비용을 분석하면서 D3D12 데이터 버퍼 관리 코드에 개선이 필요하다는 사실을 빠르게 알아차렸습니다. 메시 데이터 스트리밍으로 인해 Fortnite는 게임 플레이 중에 가속 구조 데이터 버퍼를 생성하고 파괴하는 데 많은 시간을 소비합니다.
간단한 최적화는 커밋되거나 배치된 단일 리소스를 사용하는 대신 개인 힙에서 이러한 모든 리소스를 할당하는 것입니다. 가속 구조 버퍼는 D3D12 Ray Tracing RAYTRACING_ACCELERATION_STRUCTURE 상태로 유지되어야 하고 스크래치 버퍼는 UNORDERED_ACCESS 상태로 유지되기 때문입니다. 이는 상태 전환이 필요하지 않으며 버퍼별 상태 추적이 필요하지 않음을 의미합니다. 또한 이 조정은 정렬 오버헤드가 적기 때문에 많은 메모리를 절약합니다(D3D12에 배치된 리소스에는 64K 정렬이 필요하지만 대부분의 버퍼는 훨씬 작습니다).
개발 팀은 게임 플레이 중에 커밋된 리소스가 생성되지 않도록 해야 합니다. 이로 인해 엄청난 CPU 스파이크(때로는 100밀리초 초과)가 발생할 수 있기 때문입니다. UE4 D3D12 백엔드의 버퍼 풀 방식은 최상위 및 최하위 가속 구조 데이터에 필요한 대규모 할당을 지원하도록 조정되었습니다(최대 할당 크기 증가). 다시 읽기 버퍼는 압축 정보를 얻는 데 사용되며 개인 힙에 넣어 리소스로 풀링 및 하위 할당됩니다.
또 다른 문제는 거의 모든 BLAS(정적 기본 가속 구조) 버퍼가 일시적으로 전체 크기로 생성된 후 압축된다는 것입니다. 압축하려면 최종 BLAS 크기를 다시 읽고 이를 새로운 압축 가속 구조 버퍼에 복사해야 하므로 많은 조각화 및 메모리 낭비가 발생합니다. 시간 제약으로 인해 개발팀은 Fortnite에 대해 풀 조각 모음을 구현하지 않았지만 나중에 Unreal Engine 5에 대해 완료했습니다.
- 동적 레이 트레이싱 기하학
또 다른 CPU 병목 현상은 BLAS 데이터를 업데이트하기 전에 장면의 모든 동적 메시를 수집하고 업데이트하는 것입니다. 각 메시에 대해 컴퓨팅 셰이더를 실행하여 해당 프레임에 대한 동적 버텍스 데이터를 생성합니다. 이 임시 버텍스 데이터는 BLAS를 업데이트/조정하는 데 사용됩니다. 수백 개의 스케줄링 및 빌드 작업이 단일 프레임에서 시작될 수 있습니다. 각 일정은 서로 다른 컴퓨팅 셰이더와 출력 버텍스 버퍼를 사용할 수 있으므로 명령 목록을 생성할 때 상당한 CPU 오버헤드가 발생할 수 있습니다. 이러한 성능 오버헤드는 셰이더, 상태 전환 및 다양한 셰이더 매개변수 바인딩으로 인해 발생합니다.
개발팀은 먼저 모든 동적 지오메트리 업데이트 요청을 셰이더별로 정렬하여 이 프로세스를 최적화했지만 충분하지 않았습니다. 각 메시에 대한 버퍼를 전환하고 내부 리소스 상태 전환을 수행하는 데서 추가적인 오버헤드가 발생했습니다. 대부분의 동적 메시(예: 캐릭터 또는 변형 가능한 개체)는 프레임마다 업데이트해야 하므로 업데이트된 버텍스 데이터를 지속적으로 저장할 필요가 없습니다. 이를 통해 각 메시 내에서 간단한 선형 하위 할당으로 임시 프레임별 버퍼를 사용할 수 있어 상태 추적 비용이 최소화됩니다. 각 컴퓨팅 셰이더 일정은 버퍼의 다른 부분에 기록되므로 일정 간에 UAV(Unordered Access View) 장벽이 필요하지 않습니다. 마지막으로 BLAS 업데이트 명령 목록을 사용하여 동적 지오메트리 업데이트 명령 목록 생성을 병렬화하여 BuildRaytracingAccelerationStructure의 놀라운 CPU 비용을 숨깁니다.
- 셰이더 바인딩 테이블 구축
지금까지 가장 큰 CPU 비용은 각 장면 및 레이 트레이싱 파이프라인 상태 개체에 대한 레이 트레이싱 셰이더 바인딩 테이블(SBT)을 구축하는 것입니다. 언리얼 엔진은 영구 셰이더 리소스 설명자 테이블을 사용하지 않으므로 모든 리소스 바인딩을 수동으로 수집하여 각 프레임마다 단일 공유 설명자 힙에 복사해야 합니다.
모든 메시에 대한 모든 설명자를 복사하는 비용을 줄이기 위해 개발 팀은 여러 수준의 캐싱을 도입했습니다. 가장 큰 이점은 동일한 셰이더와 리소스를 사용하여 중복된 SBT 항목(예: 서로 다른 메쉬에 적용된 동일한 재질 또는 동일한 메쉬의 여러 인스턴스)을 제거함으로써 얻을 수 있습니다. 언리얼 엔진은 셰이더 리소스 바인딩을 상위 수준 테이블(유니폼 버퍼)로 그룹화합니다. 이 테이블 중 일반적인 셰이더는 3~4개(뷰, 버텍스 팩토리, 머티리얼 등)를 참조하고 각 균일 버퍼에는 차례로 텍스처, 버퍼, 샘플러 등에 대한 참조가 포함될 수 있습니다. 내용을 검사하지 않고 고급 통합 버퍼를 보기만 하면 SBT 레코드를 캐시할 수 있습니다.
단일 D3D12 샘플러 힙에는 2048개의 항목만 있을 수 있고 한 번에 하나의 샘플러만 바인딩될 수 있으므로 설명자 힙에서 실제 리소스 설명자 데이터의 중복을 제거하기 위해 낮은 수준 캐싱이 추가되었습니다. 주로 샘플러 설명자의 중복을 제거하기 위한 것입니다. D3D12 CopyDescriptors 호출 비용은 설명자 해싱 및 해시 테이블 조회/삽입 비용과 거의 같거나 그보다 큽니다.
마지막으로 개발팀은 SBT 레코드 빌드를 병렬화했지만 작업자 스레드를 추가하여 얻은 개선 사항이 빠르게 감소한다는 사실을 발견했습니다. 여전히 설명자 중복 제거를 사용하고 싶기 때문에 각 작업자 스레드는 동기화 오버헤드를 방지하기 위해 자체 로컬 설명자 캐시를 사용해야 합니다. 그런 다음 전역 설명자 힙 공간은 원자성을 사용하여 각 작업자에 의해 청크로 할당됩니다. 이 방법은 캐시 효율성을 감소시키고 사용되는 총 설명자 힙 슬롯 수를 늘립니다. 4~5개의 작업자 스레드가 병렬 SBT 생성에 가장 적합합니다.
- 기하학 클리핑
CPU 및 GPU 렌더링 시간을 단축하는 간단하지만 효과적인 기술은 현재 프레임과 관련이 없는 경우 인스턴스를 완전히 건너뛰는 것입니다. 레이 트레이싱 시 장면의 모든 개체는 카메라가 보는 내용에 영향을 미칩니다. 그러나 실제 생활에서는 많은 개체의 기여가 미미합니다. 처음에 개발팀은 문지방에서 더 멀리 배치된 카메라 뒤의 기하학을 컬링하려고 시도했습니다. 그러나 이 솔루션은 적용 범위가 넓은 객체가 한 프레임에서 거부되고 다음 프레임에서 허용될 때 점프 아티팩트를 생성하기 때문에 포기되었습니다. 해결책은 인스턴스 경계 구의 투영된 영역도 고려하도록 컬링 기준을 변경하고 충분히 작은 경우에만 삭제하는 것입니다. 이 간단한 변경으로 점프가 사라지고 속도가 크게 향상됩니다. 평균적으로 이득은 프레임당 2-3밀리초입니다.
- DLSS
NVIDIA DLSS 기술의 통합은 성능 향상에 도움이 되며, 레이 트레이싱 전역 조명을 활성화하고 레이 트레이싱이 활성화된 더 높은 해상도에서 게임을 실행할 수 있게 해줍니다. DLSS 통합을 위한 엔진 변경 사항은 이제 공용 UE4 코드베이스에서 사용할 수 있으며, UE4용 DLSS 플러그인은 이제 Unreal Engine Store에서 사용할 수 있습니다.
전체적으로 Ray Tracing은 Fortnite 시즌 15(2020년 9월)에 출시되어 Fortnite 팀이 원래 설정했던 것보다 더 야심찬 목표를 달성했습니다. 이 프로젝트는 여러 측면에서 도전적이었지만 궁극적으로는 성공했습니다. 레이 트레이싱을 활성화하면 게임이 더 좋아 보일 뿐만 아니라 모든 개선 사항이 이제 UE4 공개 코드베이스의 일부입니다.
실시간 Ray Tracing에는 매 프레임마다 업데이트해야 하는 대량의 동적 기하학이 있는 장면, 다중 바운스 산란이 필요한 복잡한 조명 전송, 다량의 동적 조명이 있는 장면을 포함하여 더 많은 작업이 필요한 미해결 문제가 많이 있습니다. 이러한 문제는 해결하기 어렵고 컴퓨터 그래픽 커뮤니티의 수년간의 노력이 필요합니다. Epic Games의 엔지니어링 팀은 레이 트레이싱을 모든 유형의 게임 또는 실시간 그래픽 애플리케이션에 실행 가능한 솔루션으로 만드는 것을 목표로 이 프로젝트를 위해 개발된 기술과 로드맵의 다른 새로운 방법을 지속적으로 개선할 것입니다.
17.6 UE 레이 트레이싱 소스 코드 분석
이 문서에서는 분석을 위한 소스 코드 버전으로 UE 5.0.3을 사용합니다. 소스코드를 동시에 읽어야 한다면 주의하시기 바랍니다.
17.6.1 UE 라이트 트레이싱 개요
UE의 레이 트레이싱 소스 코드를 분석하기 전에 먼저 UE4의 렌더링 파이프라인에 대한 개요를 살펴보겠습니다(UE의 공식 비디오 튜토리얼, 클릭하면 확대됨):

위 이미지에서 볼 수 있듯이 레이 트레이싱과 래스터라이제이션는 하이브리드 렌더링 파이프라인이라는 방식으로 결합됩니다. 그 중 레이 트레이싱과 관련된 모듈이나 기능에는 그림자, AO, GI, 반사, 반투명도, 체적 재질 등이 포함됩니다.
UE 5.0.3 버전에서 Ray Tracing과 관련된 [C++ 측]은 다음과 같습니다.
- 공유됨
RayTracingBuiltInResources.h
-RayTracingDefinitions.h
RayTracingTypes.h
D3D12RHI
D3D12RayTracing.h
-D3D12RayTracing.cpp
D3D12RayTracingRootSignature.h
엔진
RayTracingInstance.h
RayTracingInstance.cpp
RayTracingSkinnedGeometry.cpp
렌더코어
RayGenShaderUtils.h
-BuiltInRayTracingShaders.h
- RayTracingGeometryManager.h
-BuiltInRayTracingShaders.cpp
RayTracingGeometryManager.cpp
루멘
LumenHardwareRayTracingCommon.h
LumenHardwareRayTracingCommon.cpp
LumenHardwareRayTracingMaterials.cpp
LumenRadianceCacheHardwareRayTracing.cpp
LumenReflectionHardwareRayTracing.cpp
LumenSceneDirectLightingHardwareRayTracing.cpp
LumenScreenProbeHardwareRayTracing.cpp
LumenTranslucencyVolumeHardwareRayTracing.cpp
레이 트레이싱
RayTracingAmbientOcclusion.cpp
RayTracingBarycentrics.cpp
RayTracingDeferredMaterials.cpp
RayTracingDeferredMaterials.h
RayTracingDeferredReflections.cpp
RayTracingDynamicGeometry.cpp
RayTracingGlobalIllumination.cpp
RayTracingIESLightProfiles.cpp
RayTracingIESLightProfiles.h
RayTracingInstanceBufferUtil.cpp
RayTracingInstanceBufferUtil.h
RayTracingInstanceCulling.cpp
RayTracingInstanceCulling.h
RayTracingLighting.h
RayTracingLighting.cpp
RayTracingMaterialHitShaders.cpp
RayTracingMaterialHitShaders.h
RaytracingOptions.h
RayTracingPrimaryRays.cpp
RayTracingReflections.cpp
RayTracingReflections.h
-RayTracingScene.h
RayTracingScene.cpp
RayTracingShadows.cpp
-RayTracingSkyLight.h
RayTracingSkyLight.cpp
RayTracingTranslucency.cpp
RayTracingDynamicGeometryCollection.h
불칸RHI
VulkanRayTracing.h
- VulkanRayTracing.cpp
[셰이더 측면] 레이 트레이싱과 관련된 내용은 다음과 같습니다.
- 레이 트레이싱
생성SkyLightVisibilityRaysCS.usf
-RayGenUtils.ush
RayTracingAmbientOcclusionRGS.usf
RayTracingBarycentrics.usf
RayTracingBuiltInShaders.usf
RayTracingCalcInterpolants.ush
RayTracingCommon.ush
RayTracingCreateGatherPointsRGS.usf
RayTracingDeferredMaterials.usf
RayTracingDeferredMaterials.ush
RayTracingDeferredReflections.usf
RayTracingDeferredReflections.ush
RayTracingDeferredShadingCommon.ush
RayTracingDirectionalLight.ush
RayTracingDiskLight.ush
RayTracingDispatchDesc.usf
RayTracingDynamicMesh.usf
RayTracingFinalGatherRGS.usf
RayTracingGatherPoints.ush
RayTracingGlobalIlluminationRGS.usf
RayTracingHitGroupCommon.ush
RayTracingInstanceBufferUtil.usf
RayTracingLightCullingCommon.ush
RayTracingLightingCommon.ush
RayTracingLightingMS.usf
RayTracingMaterialDefaultHitShaders.usf
RayTracingMaterialHitShaders.usf
RayTracingOcclusionRGS.usf
RayTracingPointLight.ush
RayTracingPrimaryRays.usf
RayTracingRectLight.ush
RayTracingRectLightRGS.usf
RayTracingReflectionEnvironment.ush
RayTracingReflectionResolve.usf
RayTracingReflections.usf
RayTracingReflectionsCommon.ush
RayTracingReflectionsGenerateRaysCS.usf
RayTracingSkyLightCommon.ush
RayTracingSkyLightEvaluation.ush
RayTracingSkyLightRGS.usf
RayTracingSphereLight.ush
RayTracingSpotLight.ush
-SkyLightVisibilityRaysData.ush
TraceRayInline.ush
TraceRayInlineCommon.ush
TraceRayInlineVulkan.ush
VFXTraceRay.ush
루멘
LumenProbeHierarchyBuildProbeArray.usf
LumenRadiosityHardwareRayTracing.usf
LumenHardwareRayTracingCommon.ush
LumenHardwareRayTracingMaterials.usf
LumenHardwareRayTracingPayloadCommon.ush
LumenHardwareRayTracingPipeline.usf
LumenHardwareRayTracingPipelineCommon.ush
LumenHardwareRayTracingPlatformCommon.ush
LumenRadianceCacheHardwareRayTracing.usf
LumenReflectionHardwareRayTracing.usf
LumenSceneDirectLightingHardwareRayTracing.usf
LumenScreenProbeHardwareRayTracing.usf
LumenTranslucencyVolumeHardwareRayTracing.usf
LumenVisualizeHardwareRayTracing.usf
헤어스트랜드
HairStrandsRaytracing.ush
HairStrandsRaytracingGeometry.usf
HairStrandsVoxelPageRayMarching.usf
적용 범위가 상대적으로 넓어 모든 특성을 분석하는 것은 불가능합니다. 몇 가지 중요한 특성만 추출하고 분석할 수 있습니다.
17.6.2 UE 라이트 트레이싱 기본
UE5가 D3D12와 Vulkan이라는 두 가지 그래픽 플랫폼에서 레이 트레이싱을 지원한다는 것은 소스 코드에서 볼 수 있습니다.
17.6.2.1 RHI 레이트레이싱
RHI 레이어는 상위 레이어에 대한 통합 액세스 방법을 제공하기 위해 특정 그래픽 플랫폼과 독립적인 일부 유형과 인터페이스를 추상화합니다. Ray Tracing과 관련된 RHI 레이어의 주요 유형과 인터페이스는 다음과 같습니다.
// RHI.h
// 플랫폼는 로써 가속 구조체 을(를) 활용하여 레이 트레이싱(Ray Tracing)파이프라인 또는 레이 트레이싱(Ray Tracing)()。
inline RHI_API bool RHISupportsRayTracing(const FStaticShaderPlatform Platform);
// 플랫폼는 로써 레이 트레이싱(Ray Tracing)셰이딩 (설정(Set))。
inline RHI_API bool RHISupportsRayTracingShaders(const FStaticShaderPlatform Platform);
// 플랫폼는 로써 레이 트레이싱(Ray Tracing)의 셰이딩 。
inline RHI_API bool RHISupportsInlineRayTracing(const FStaticShaderPlatform Platform);
// RHI는 지원 현재(Current)의 레이 트레이싱(Ray Tracing)(가속 구조체 및 의 레이 트레이싱(Ray Tracing)셰이딩 타입/유형 )。
extern RHI_API bool GRHISupportsRayTracing;
// RHI는 지원 레이 트레이싱(Ray Tracing)raygen、miss 및 hit셰이딩 (즉, 의 레이 트레이싱(Ray Tracing)파이프라인 )。
extern RHI_API bool GRHISupportsRayTracingShaders;
// RHI는 지원 RT PSO추가(Add)셰이딩 。
extern RHI_API bool GRHISupportsRayTracingPSOAdditions;
// RHI는 지원 레이 트레이싱(Ray Tracing)스케줄링 。
extern RHI_API bool GRHISupportsRayTracingDispatchIndirect;
// RHI는 지원 비동기 레이 트레이싱(Ray Tracing)가속 구조체 。
extern RHI_API bool GRHISupportsRayTracingAsyncBuildAccelerationStructure;
// RHI는 지원 AMD Hit Token확장 。
extern RHI_API bool GRHISupportsRayTracingAMDHitToken;
// RHI는 지원 계산/산출(Calculate)셰이딩 내에서 의 레이 트레이싱(Ray Tracing),지원 의 레이 트레이싱(Ray Tracing)파이프라인 。
extern RHI_API bool GRHISupportsInlineRayTracing;
// 레이 트레이싱(Ray Tracing)가속 구조체 의 정렬 。
extern RHI_API uint32 GRHIRayTracingAccelerationStructureAlignment;
// 레이 트레이싱(Ray Tracing)버퍼 의 정렬 。
extern RHI_API uint32 GRHIRayTracingScratchBufferAlignment;
// 레이 트레이싱(Ray Tracing)셰이딩 바인딩(Bind)테이블 버퍼 정렬 。
extern RHI_API uint32 GRHIRayTracingShaderTableAlignment;
// 레이 트레이싱(Ray Tracing)인스턴스 버퍼 내에서 개 의 크기 。정의 인스턴스 의 구조체 버퍼 의 및 정렬 。
extern RHI_API uint32 GRHIRayTracingInstanceDescriptorSize;
// 변환(Transform/Convert)정보 .
struct FRHITransitionInfo : public FRHISubresourceRange
{
union
{
class FRHIResource* Resource = nullptr;
class FRHITexture* Texture;
class FRHIBuffer* Buffer;
class FRHIUnorderedAccessView* UAV;
// 가속 구조체 .
class FRHIRayTracingAccelerationStructure* BVH;
};
(...)
};
// RHICommandList.h
// 셰이딩 바인딩(Bind).
struct FRayTracingShaderBindings
{
FRHITexture* Textures[64] = {};
FRHIShaderResourceView* SRVs[64] = {};
FRHIUniformBuffer* UniformBuffers[16] = {};
FRHISamplerState* Samplers[16] = {};
FRHIUnorderedAccessView* UAVs[16] = {};
};
// 로컬 셰이딩 바인딩(Bind).
struct FRayTracingLocalShaderBindings
{
uint32 InstanceIndex = 0;
uint32 SegmentIndex = 0;
uint32 ShaderSlot = 0;
uint32 ShaderIndexInPipeline = 0;
uint32 UserData = 0;
uint16 NumUniformBuffers = 0;
uint16 LooseParameterDataSize = 0;
FRHIUniformBuffer** UniformBuffers = nullptr;
uint8* LooseParameterData = nullptr;
};
// RayTracingCommon.ush 내에서 선언 의 FBasicRayData의 C++.
struct FBasicRayData
{
float Origin[3];
uint32 Mask;
float Direction[3];
float TFar;
};
// RayTracingCommon.ush 내에서 선언 의 FIntersectionPayload의 C++.
struct FIntersectionPayload
{
float HitT; // 광선(Ray)로부터 광선(Ray)포인트 까지 교차점(Intersection)의 거리。만약 는 이면 테이블 히트(Hit)。
uint32 PrimitiveIndex; // 레벨 가속 구조체 인스턴스 내에서 의 。히트(Hit)이면 는 정의 상태 。
uint32 InstanceIndex; // 레벨 구조체 내에서 현재(Current)인스턴스 의 。히트(Hit)이면 는 정의 상태 。
float Barycentrics[2]; // 교차점(Intersection)의 좌표 。히트(Hit)이면 는 정의 상태 。
};
// 갱신(Update)정보 .
struct FRHIRayTracingGeometryUpdateInfo
{
FRHIRayTracingGeometry* DestGeometry;
FRHIRayTracingGeometry* SrcGeometry;
};
struct FRHIResourceUpdateInfo
{
enum EUpdateType
{
UT_Buffer,
UT_BufferSRV,
UT_BufferFormatSRV,
UT_RayTracingGeometry, // 로부터 내에서 레벨 리소스
UT_Num
};
EUpdateType Type;
union
{
FRHIBufferUpdateInfo Buffer;
FRHIShaderResourceViewUpdateInfo BufferSRV;
FRHIRayTracingGeometryUpdateInfo RayTracingGeometry; // 갱신(Update)정보 .
};
(...)
};
// 정의 의
FRHICOMMAND_MACRO(FRHICommandCopyBufferRegions)
{
(...)
};
struct FRHICommandBindAccelerationStructureMemory final : public FRHICommand<FRHICommandBindAccelerationStructureMemory>
{
(...)
};
struct FRHICommandBuildAccelerationStructure final : public FRHICommand<FRHICommandBuildAccelerationStructure>
{
(...)
};
FRHICOMMAND_MACRO(FRHICommandClearRayTracingBindings)
{
FRHIRayTracingScene* Scene;
(...)
};
struct FRHICommandBuildAccelerationStructures final : public FRHICommand<FRHICommandBuildAccelerationStructures>
{
(...)
};
FRHICOMMAND_MACRO(FRHICommandRayTraceOcclusion)
{
(...)
};
FRHICOMMAND_MACRO(FRHICommandRayTraceIntersection)
{
(...)
};
FRHICOMMAND_MACRO(FRHICommandRayTraceDispatch)
{
(...)
};
FRHICOMMAND_MACRO(FRHICommandSetRayTracingBindings)
{
(...)
};
class FRHIComputeCommandList : public FRHICommandListBase
{
public:
// 가속 구조체 및 메모리 .
void BuildAccelerationStructure(FRHIRayTracingGeometry* Geometry);
void BuildAccelerationStructures(const TArrayView<const FRayTracingGeometryBuildParams> Params);
void BuildAccelerationStructures(const TArrayView<const FRayTracingGeometryBuildParams> Params, const FRHIBufferRange& ScratchBufferRange);
void BuildAccelerationStructure(const FRayTracingSceneBuildParams& SceneBuildParams);
void BindAccelerationStructureMemory(FRHIRayTracingScene* Scene, FRHIBuffer* Buffer, uint32 BufferOffset);
(...)
};
class RHI_API FRHICommandList : public FRHIComputeCommandList
{
public:
// 스케줄링
void RayTraceDispatchIndirect(FRayTracingPipelineState* Pipeline, FRHIRayTracingShader* RayGenShader, FRHIRayTracingScene* Scene, const FRayTracingShaderBindings& GlobalResourceBindings, FRHIBuffer* ArgumentBuffer, uint32 ArgumentOffset);
void RayTraceOcclusion(FRHIRayTracingScene* Scene, ...);
void RayTraceIntersection(FRHIRayTracingScene* Scene, ...);
void SetRayTracingHitGroup(FRHIRayTracingScene* Scene, ...);
void SetRayTracingCallableShader(FRHIRayTracingScene* Scene, ...);
void SetRayTracingMissShader(FRHIRayTracingScene* Scene, ...);
void ClearRayTracingBindings(FRHIRayTracingScene* Scene);
(...)
};
template <uint32 MaxNumUpdates>
struct TRHIResourceUpdateBatcher
{
void QueueUpdateRequest(FRHIRayTracingGeometry* DestGeometry, FRHIRayTracingGeometry* SrcGeometry);
(...)
};
// DynamicRHI.h
// FDynamicRHI 및 의 인터페이스 。
class RHI_API FDynamicRHI
{
public:
virtual FRayTracingAccelerationStructureSize RHICalcRayTracingSceneSize(uint32 MaxInstances, ERayTracingAccelerationStructureFlags Flags);
virtual FRayTracingAccelerationStructureSize RHICalcRayTracingGeometrySize(const FRayTracingGeometryInitializer& Initializer);
virtual FRayTracingGeometryRHIRef RHICreateRayTracingGeometry(const FRayTracingGeometryInitializer& Initializer);
virtual FRayTracingSceneRHIRef RHICreateRayTracingScene(const FRayTracingSceneInitializer& Initializer);
virtual FRayTracingSceneRHIRef RHICreateRayTracingScene(FRayTracingSceneInitializer2 Initializer);
virtual FRayTracingShaderRHIRef RHICreateRayTracingShader(TArrayView<const uint8> Code, const FSHAHash& Hash, EShaderFrequency ShaderFrequency);
virtual FRayTracingPipelineStateRHIRef RHICreateRayTracingPipelineState(const FRayTracingPipelineStateInitializer& Initializer);
virtual void RHITransferRayTracingGeometryUnderlyingResource(FRHIRayTracingGeometry* DestGeometry, FRHIRayTracingGeometry* SrcGeometry);
(...)
};
// 및 의 글로벌 인터페이스 .
TRefCountPtr<FRHIRayTracingPipelineState> RHICreateRayTracingPipelineState(const FRayTracingPipelineStateInitializer& Initializer);
FRayTracingAccelerationStructureSize RHICalcRayTracingSceneSize(uint32 MaxInstances, ERayTracingAccelerationStructureFlags Flags);
FRayTracingAccelerationStructureSize RHICalcRayTracingGeometrySize(const FRayTracingGeometryInitializer& Initializer);
FRayTracingGeometryRHIRef RHICreateRayTracingGeometry(const FRayTracingGeometryInitializer& Initializer);
FRayTracingSceneRHIRef RHICreateRayTracingScene(FRayTracingSceneInitializer2 Initializer);
FRayTracingShaderRHIRef RHICreateRayTracingShader(TArrayView<const uint8> Code, const FSHAHash& Hash, EShaderFrequency ShaderFrequency);
// RHIResources.h
// Shader
class FRHIRayTracingShader : public FRHIShader
{
(...)
};
// Shader
class FRHIRayGenShader : public FRHIRayTracingShader
{
(...)
};
// 히트(Hit)Shader
class FRHIRayMissShader : public FRHIRayTracingShader
{
(...)
};
// 호출(Call)Shader
class FRHIRayCallableShader : public FRHIRayTracingShader
{
(...)
};
// 히트(Hit)shader
class FRHIRayHitGroupShader : public FRHIRayTracingShader
{
(...)
};
// 파이프라인 스테이트 .
class FRHIRayTracingPipelineState : public FRHIResource
{
(...)
};
// 타입/유형 정의 .
typedef TRefCountPtr<FRHIRayTracingShader> FRayTracingShaderRHIRef;
typedef TRefCountPtr<FRHIRayTracingPipelineState> FRayTracingPipelineStateRHIRef;
// 인스턴스 플래그 .
enum class ERayTracingInstanceFlags : uint8
{
None = 0,
TriangleCullDisable = 1 << 1, // 백페이스 컬링 。로부터 。
TriangleCullReverse = 1 << 2, // 만약 포인트 로부터 포인트 회전 ,이면 。
ForceOpaque = 1 << 3, // 비활성화(Disable)인스턴스 의 히트(Hit)셰이딩 호출(Call)。
ForceNonOpaque = 1 << 4, // 히트(Hit)셰이딩 호출(Call),즉, 인스턴스 내에서 의 플래그 로 반투명(Translucent)。
};
// 레이 트레이싱(Ray Tracing) 내에서 의 개 또는 개 인스턴스 의 단계 디스크립터 。디스크립터 의 인스턴스 셰이딩 바인딩(Bind),그러나 의 변환(Transform) 및 을(를) 활용하여 데이터 。
struct FRayTracingGeometryInstance
{
// 의 RHI.
TRefCountPtr<FRHIRayTracingGeometry> GeometryRHI = nullptr;
// 저장(Store)GPU변환(Transform/Convert)의 버퍼 。을(를) 활용하여 CPU변환(Transform/Convert)데이터 。
FShaderResourceViewRHIRef GPUTransformsSRV = nullptr;
// 개 로써 을(를) 활용하여 의 변환(Transform) 및 을(를) 활용하여 데이터 내에서 회 。의 셰이딩 바인딩(Bind)테이블 개 ,의 머티리얼(Material) 및 셰이딩 리소스 。
TArrayView<const FMatrix> Transforms;
// 인스턴스 데이터 의 .
TArrayView<const uint32> InstanceSceneDataOffsets;
// 의 인스턴스 。만약 을(를) 활용하여 GPU변환(Transform),이면 인스턴스 상태 。만약 을(를) 활용하여 CPU변환(Transform/Convert)데이터 ,이면 필수: 또는 변환(Transform/Convert)뷰(View) 내에서 의 개 。만약 GPUTransformsSRV로 ,이면 필수: 또는 내에서 의 개 。
uint32 NumTransforms = 0;
// 개 로써 을(를) 활용하여 의 ,을(를) 활용하여 의 셰이딩 파라미터 또는 정의 。
uint32 DefaultUserData = 0;
TArrayView<const uint32> UserData;
// 개 로써 개 ,을(를) 활용하여 (로부터 TLA 내에서 ,히트(Hit))。을(를) 활용하여 컬링 。
TArrayView<const uint32> ActivationMask;
// 에 따라 셰이딩 코드 내에서 TraceRay()의 。만약 의 인스턴스 의 및 로 ,이면 인스턴스 로 교차(Intersection)/。
uint8 Mask = 0xFF;
// 을(를) 활용하여 백페이스 컬링 、는 히트(Hit)셰이딩 의 。
ERayTracingInstanceFlags Flags = ERayTracingInstanceFlags::None;
};
// 타입/유형 .
enum ERayTracingGeometryType
{
// 함수 교차점(Intersection)의 또는 리스트 。포인트 버퍼 필수: 포인트 위치 ,VET_Float3。포인트 필수: 로 12,그러나 ,로써 지원 정의 포인트 데이터 . 로써 로 리스트 버퍼 , 이면 리스트 。
RTGT_Triangles,
// 셰이딩 의 정의 타입/유형 。의 포인트 버퍼 개 필수: 개 AABB,{float3 MinXYZ,float3 maxyz}。포인트 필수: 로 24,그러나 로써 ,로써 지원 정의 데이터 , 버퍼 을(를) 활용하여 。
RTGT_Procedural,
};
// 초기화(Initialize)타입/유형 .
enum class ERayTracingGeometryInitializerType
{
Rendering, // 초기화(Initialize)RayTracingGeometry객체/오브젝트 :생성(Create)버퍼 초기화(Initialize)셰이딩 파라미터 。
StreamingDestination, // 생성(Create)버퍼 또는 셰이딩 파라미터 , 에 의해 을(를) 활용하여 까지 의 객체/오브젝트 。
StreamingSource, // 생성(Create)버퍼 ,그러나 생성(Create)셰이딩 파라미터 , 을(를) 활용하여 내에서 의 내에서 객체/오브젝트 。
};
// (모델 ).
struct FRayTracingGeometrySegment
{
DECLARE_TYPE_LAYOUT(FRayTracingGeometrySegment, NonVirtual);
public:
// 포인트 데이터 .
LAYOUT_FIELD_INITIALIZED(FBufferRHIRef, VertexBuffer, nullptr);
LAYOUT_FIELD_INITIALIZED(EVertexElementType, VertexBufferElementType, VET_Float3);
LAYOUT_FIELD_INITIALIZED(uint32, VertexBufferOffset, 0); // 포인트 버퍼 의 ()。
LAYOUT_FIELD_INITIALIZED(uint32, VertexBufferStride, 12);
LAYOUT_FIELD_INITIALIZED(uint32, MaxVertices, 0);
// 의 범위 .
LAYOUT_FIELD_INITIALIZED(uint32, FirstPrimitive, 0);
LAYOUT_FIELD_INITIALIZED(uint32, NumPrimitives, 0);
LAYOUT_FIELD_INITIALIZED(bool, bForceOpaque, false);
LAYOUT_FIELD_INITIALIZED(bool, bAllowDuplicateAnyHitShaderInvocation, true);
LAYOUT_FIELD_INITIALIZED(bool, bEnabled, true);
};
// 레이 트레이싱(Ray Tracing)초기화(Initialize).
struct FRayTracingGeometryInitializer
{
public:
LAYOUT_FIELD_INITIALIZED(FBufferRHIRef, IndexBuffer, nullptr);
LAYOUT_FIELD_INITIALIZED(uint32, IndexBufferOffset, 0);
LAYOUT_FIELD_INITIALIZED(ERayTracingGeometryType, GeometryType, RTGT_Triangles);
LAYOUT_FIELD_INITIALIZED(uint32, TotalPrimitiveCount, 0);
LAYOUT_FIELD(TMemoryImageArray<FRayTracingGeometrySegment>, Segments);
LAYOUT_FIELD_INITIALIZED(FResourceArrayInterface*, OfflineData, nullptr);
LAYOUT_FIELD_INITIALIZED(FRHIRayTracingGeometry*, SourceGeometry, nullptr);
LAYOUT_FIELD_INITIALIZED(bool, bFastBuild, false);
LAYOUT_FIELD_INITIALIZED(bool, bAllowUpdate, false);
LAYOUT_FIELD_INITIALIZED(bool, bAllowCompaction, true);
LAYOUT_FIELD_INITIALIZED(ERayTracingGeometryInitializerType, Type, ERayTracingGeometryInitializerType::Rendering);
};
// .
enum ERayTracingSceneLifetime
{
RTSL_SingleFrame, // 오직 생성(Create)의 내에서 을(를) 활용하여 。
// RTSL_MultiFrame, // 로써 회 ,개수 의 내에서 을(를) 활용하여 (현재(Current)구현 )。
};
// 가속 구조체 플래그 .
enum class ERayTracingAccelerationStructureFlags
{
None = 0,
AllowUpdate = 1 << 0,
AllowCompaction = 1 << 1,
FastTrace = 1 << 2,
FastBuild = 1 << 3,
MinimizeMemory = 1 << 4,
};
// 초기화(Initialize).
struct FRayTracingSceneInitializer
{
TArrayView<FRayTracingGeometryInstance> Instances;
uint32 ShaderSlotsPerGeometrySegment = 1;
uint32 NumCallableShaderSlots = 0;
uint32 NumMissShaderSlots = 1;
ERayTracingSceneLifetime Lifetime = RTSL_SingleFrame;
};
// 초기화(Initialize)2.
struct FRayTracingSceneInitializer2
{
TArray<TRefCountPtr<FRHIRayTracingGeometry>> ReferencedGeometries;
TArray<FRHIRayTracingGeometry*> PerInstanceGeometries;
TArray<uint32> BaseInstancePrefixSum;
TArray<uint32> SegmentPrefixSum;
uint32 NumNativeInstances = 0;
uint32 NumTotalSegments = 0;
uint32 ShaderSlotsPerGeometrySegment = 1;
uint32 NumCallableShaderSlots = 0;
uint32 NumMissShaderSlots = 1;
ERayTracingSceneLifetime Lifetime = RTSL_SingleFrame;
};
// 가속 구조체 해상도/크기 .
struct FRayTracingAccelerationStructureSize
{
uint64 ResultSize = 0;
uint64 BuildScratchSize = 0;
uint64 UpdateScratchSize = 0;
};
// 가속 구조체
class FRHIRayTracingAccelerationStructure : public FRHIResource
{
public:
FRHIRayTracingAccelerationStructure() : FRHIResource(RRT_RayTracingAccelerationStructure) {}
};
// 레벨 가속 구조체 ()。
class FRHIRayTracingGeometry : public FRHIRayTracingAccelerationStructure
{
public:
virtual FRayTracingAccelerationStructureAddress GetAccelerationStructureAddress(uint64 GPUIndex) const = 0;
virtual void SetInitializer(const FRayTracingGeometryInitializer& Initializer) = 0;
(...)
protected:
FRayTracingAccelerationStructureSize SizeInfo = {};
FRayTracingGeometryInitializer Initializer = {};
ERayTracingGeometryInitializerType InitializedType = ERayTracingGeometryInitializerType::Rendering;
};
typedef TRefCountPtr<FRHIRayTracingGeometry> FRayTracingGeometryRHIRef;
// 레벨 레이 트레이싱(Ray Tracing)가속 구조체 (인스턴스 ).
class FRHIRayTracingScene : public FRHIRayTracingAccelerationStructure
{
public:
virtual const FRayTracingSceneInitializer2& GetInitializer() const = 0;
// 반환(Return) 및 의 RHI파라미터 의 버퍼 뷰(View), 을(를) 활용하여 의 셰이딩 내에서 의 레이 트레이싱(Ray Tracing)데이터 。만약 현재(Current)RHI버퍼 ,이면 반환(Return)NULL。
virtual FRHIShaderResourceView* GetMetadataBufferSRV() const;
};
typedef TRefCountPtr<FRHIRayTracingScene> FRayTracingSceneRHIRef;
// 파이프라인 스테이트
class FRayTracingPipelineStateSignature
{
public:
uint32 MaxPayloadSizeInBytes = 24; // sizeof FDefaultPayload declared in RayTracingCommon.ush
bool bAllowHitGroupIndexing = true;
bool operator==(const FRayTracingPipelineStateSignature& rhs) const;
friend uint32 GetTypeHash(const FRayTracingPipelineStateSignature& Initializer);
uint64 GetHitGroupHash();
uint64 GetRayGenHash();
uint64 GetRayMissHash();
uint64 GetCallableHash();
(...)
};
// 파이프라인 스테이트 .
class FRayTracingPipelineStateInitializer : public FRayTracingPipelineStateSignature
{
public:
// 레이 트레이싱(Ray Tracing)파이프라인 을(를) 활용하여 비동기 셰이딩 ,그러나 을(를) 활용하여 렌더링(Render)。생성(Create)파이프라인 ,로써 로 스테이지 개수 의 셰이딩 ,그러나 필수: 개 셰이딩 (의 파이프라인 )。
bool bPartial = false;
// 레이 트레이싱(Ray Tracing)파이프라인 로써 로부터 추출/내보내기(Export)생성(Create)。파이프라인 내에서 추가(Add)의 셰이딩 확장 ,CPU。GRHISupportsRayTracingPSOAdditions지원 (만약 지원 파이프라인 ,이면 무시/스킵(Ignore))。
FRayTracingPipelineStateRHIRef BasePipeline;
(...)
private:
uint64 ComputeShaderTableHash(const TArrayView<FRHIRayTracingShader*>& ShaderTable, uint64 InitialHash = 5699878132332235837ull);
// 셰이딩 테이블 .
TArrayView<FRHIRayTracingShader*> RayGenTable;
TArrayView<FRHIRayTracingShader*> MissTable;
TArrayView<FRHIRayTracingShader*> HitGroupTable;
TArrayView<FRHIRayTracingShader*> CallableTable;
};17.6.2.2 D3D12 레이트레이싱
D3D12 라이트 트레이싱과 관련된 핵심 유형 및 설명은 다음과 같습니다.
// D3D12RHIPrivate.h
// 테이블 RTPSO의 구조체 (만약 ,이면 로 0),을(를) 활용하여 성능 、에 따라 을(를) 활용하여 셰이딩 정렬 。
struct FD3D12RayTracingPipelineInfo
{
// 을(를) 활용하여 또는 플랫폼의 메서드 RTPSO。0테이블 ,9테이블 。
uint32 PerformanceGroup = 0;
uint32 NumVGPR = 0;
uint32 NumSGPR = 0;
uint32 StackSize = 0;
uint32 ScratchSize = 0;
};
// FD3D12DynamicRHI 및 레이 트레이싱(Ray Tracing)의 인터페이스 .
class FD3D12DynamicRHI : public FDynamicRHI
{
(...)
// 계산/산출(Calculate)해상도/크기 .
virtual FRayTracingAccelerationStructureSize RHICalcRayTracingSceneSize(uint32 MaxInstances, ERayTracingAccelerationStructureFlags Flags) final override;
// 계산/산출(Calculate)크기 .
virtual FRayTracingAccelerationStructureSize RHICalcRayTracingGeometrySize(const FRayTracingGeometryInitializer& Initializer) final override;
// 생성(Create).
virtual FRayTracingGeometryRHIRef RHICreateRayTracingGeometry(const FRayTracingGeometryInitializer& Initializer) final override;
// 생성(Create).
virtual FRayTracingSceneRHIRef RHICreateRayTracingScene(const FRayTracingSceneInitializer& Initializer) final override;
virtual FRayTracingSceneRHIRef RHICreateRayTracingScene(FRayTracingSceneInitializer2 Initializer) final override;
// 생성(Create)shader.
virtual FRayTracingShaderRHIRef RHICreateRayTracingShader(TArrayView<const uint8> Code, const FSHAHash& Hash, EShaderFrequency ShaderFrequency) final override;
// 생성(Create)파이프라인 스테이트 .
virtual FRayTracingPipelineStateRHIRef RHICreateRayTracingPipelineState(const FRayTracingPipelineStateInitializer& Initializer) final override;
// 생성(Create)레벨 리소스 .
virtual void RHITransferRayTracingGeometryUnderlyingResource(FRHIRayTracingGeometry* DestGeometry, FRHIRayTracingGeometry* SrcGeometry) final override;
(...)
};
// RayTracingBuiltInResources.h
struct FHitGroupSystemRootConstants
{
// 구성(Configuration)에 의해 :
// uint IndexStride : 8; // 로써 오직 1로써 는 16는 32.
// uint VertexStride : 8; // 로써 오직 2로써 는 float3는 half2.
// uint Unused : 16;
UINT_TYPE Config;
UINT_TYPE IndexBufferOffsetInBytes; // HitGroupSystemIndexBuffer의 .
UINT_TYPE UserData; // 할당(Allocate)hit의 을(를) 활용하여 상수(Constant)
UINT_TYPE BaseInstanceIndex; // 현재(Current)회 의 개 인스턴스 의 。을(를) 활용하여 레이 트레이싱(Ray Tracing)셰이딩 내에서 SV_InstanceID.
(...)
};
// D3D12RayTracing.h
// 바인딩(Bind)까지 히트(Hit)셰이딩 의 파라미터 .
struct FHitGroupSystemParameters
{
D3D12_GPU_VIRTUAL_ADDRESS IndexBuffer;
D3D12_GPU_VIRTUAL_ADDRESS VertexBuffer;
FHitGroupSystemRootConstants RootConstants;
};
// D3D12
class FD3D12RayTracingGeometry : public FRHIRayTracingGeometry, public FD3D12AdapterChild, public FD3D12ShaderResourceRenameListener, public FNoncopyable
{
public:
// 설정(Set)FHitGroupSystemParameters.
void SetupHitGroupSystemParameters(uint32 InGPUIndex);
// 변환(Transform/Convert)버퍼 .
void TransitionBuffers(FD3D12CommandContext& CommandContext);
// 갱신(Update)데이터 .
void UpdateResidency(FD3D12CommandContext& CommandContext);
// 압축/패킹(Packing)가속 구조체 .
void CompactAccelerationStructure(FD3D12CommandContext& CommandContext, uint32 InGPUIndex, uint64 InSizeAfterCompaction);
// 생성(Create)가속 구조체 .
void CreateAccelerationStructureBuildDesc(FD3D12CommandContext& CommandContext, EAccelerationStructureBuildMode BuildMode, D3D12_GPU_VIRTUAL_ADDRESS ScratchBufferAddress, D3D12_BUILD_RAYTRACING_ACCELERATION_STRUCTURE_DESC& OutDesc, TArrayView<D3D12_RAYTRACING_GEOMETRY_DESC>& OutGeometryDescs) const;
// 해제(Release)레벨 리소스 .
void ReleaseUnderlyingResource();
(...)
// 플래그 가속 구조체 는 수정
bool bIsAccelerationStructureDirty[MAX_NUM_GPUS] = {};
// 가속 구조체 버퍼 .
TRefCountPtr<FD3D12Buffer> AccelerationStructureBuffers[MAX_NUM_GPUS];
bool bRegisteredAsRenameListener[MAX_NUM_GPUS];
bool bHasPendingCompactionRequests[MAX_NUM_GPUS];
// 히트(Hit)셰이딩 파라미터
TArray<FHitGroupSystemParameters> HitGroupSystemParameters[MAX_NUM_GPUS];
// 배열 ,개 (는 ). 오직 을(를) 활용하여 CPU의 구조체 (GPU리소스 ), 을(를) 활용하여 BuildAccelerationStructure()의 스텐실 。
TArray<D3D12_RAYTRACING_GEOMETRY_DESC, TInlineAllocator<1>> GeometryDescs;
uint64 AccelerationStructureCompactedSize = 0;
(...)
};
// D3D12
class FD3D12RayTracingScene : public FRHIRayTracingScene, public FD3D12AdapterChild, public FNoncopyable
{
public:
// 레이 트레이싱(Ray Tracing)셰이딩 바인딩(Bind)로써 병렬 처리(Process)。개 동시성 스레드(Thread)의 을(를) 활용하여 디스크립터 캐싱(Cache)인스턴스 ,로써 을(를) 활용하여 또는 。(Fortnite),확장 5개 스레드(Thread)가속 .
static constexpr uint32 MaxBindingWorkers = 5; // RHI thread + 4 parallel workers.
// 버퍼 .
void BindBuffer(FRHIBuffer* Buffer, uint32 BufferOffset);
void ReleaseBuffer();
// 가속 구조체 .
void BuildAccelerationStructure(FD3D12CommandContext& CommandContext, FD3D12Buffer* ScratchBuffer, uint32 ScratchBufferOffset, FD3D12Buffer* InstanceBuffer, uint32 InstanceBufferOffset);
// SRV
FShaderResourceViewRHIRef ShaderResourceView;
// 가속 구조체 데이터 .
TRefCountPtr<FD3D12Buffer> AccelerationStructureBuffers[MAX_NUM_GPUS];
uint32 BufferOffset = 0;
D3D12_BUILD_RAYTRACING_ACCELERATION_STRUCTURE_INPUTS BuildInputs = {};
FRayTracingAccelerationStructureSize SizeInfo = {};
// 초기화(Initialize)데이터 .
const FRayTracingSceneInitializer2 Initializer;
// 인스턴스 데이터 .
TResourceArray<D3D12_RAYTRACING_INSTANCE_DESC, 16> Instances;
TArray<uint32> PerInstanceNumTransforms;
// Scene keeps track of child acceleration structure buffers to ensure
// they are resident when any ray tracing work is dispatched.
TArray<FD3D12ResidencyHandle*> GeometryResidencyHandles[MAX_NUM_GPUS];
// 갱신(Update)데이터 .
void UpdateResidency(FD3D12CommandContext& CommandContext);
// 인스턴스 내에서 개 의 히트(Hit)파라미터 배열 。로써 HitGroupSystemParametersCache[SegmentPrefixSum[InstanceIndex]+SegmentIndex]。을(를) 활용하여 GPU 0(GPU을(를) 활용하여 )。
TArray<FHitGroupSystemParameters> HitGroupSystemParametersCache;
// 탐색/조회(Find/Lookup)셰이딩 테이블 .
FD3D12RayTracingShaderTable* FindOrCreateShaderTable(const FD3D12RayTracingPipelineState* Pipeline, FD3D12Device* Device);
FD3D12RayTracingShaderTable* FindExistingShaderTable(const FD3D12RayTracingPipelineState* Pipeline, FD3D12Device* Device) const;
// 셰이딩 테이블 .
TMap<const FD3D12RayTracingPipelineState*, FD3D12RayTracingShaderTable*> ShaderTables[MAX_NUM_GPUS];
(...)
};
// 일시 중지 의 BLAS압축/패킹(Packing).
class FD3D12RayTracingCompactionRequestHandler : FD3D12DeviceChild
{
public:
void RequestCompact(FD3D12RayTracingGeometry* InRTGeometry);
bool ReleaseRequest(FD3D12RayTracingGeometry* InRTGeometry);
void Update(FD3D12CommandContext& InCommandContext);
(...)
};17.6.2.3 Vulkan 레이트레이싱
Vulkan 라이트 트레이싱과 관련된 핵심 유형 및 설명은 다음과 같습니다.
// VulkanRaytracing.h
// 선언 레이 트레이싱(Ray Tracing)포인트
namespace VulkanDynamicAPI
{
ENUM_VK_ENTRYPOINTS_RAYTRACING(DECLARE_VK_ENTRYPOINTS);
}
// Vulkan플랫폼인터페이스 。
class FVulkanRayTracingPlatform
{
public:
static void GetDeviceExtensions(EGpuVendorId VendorId, TArray<const ANSICHAR*>& OutExtensions);
static void EnablePhysicalDeviceFeatureExtensions(VkDeviceCreateInfo& DeviceInfo, FVulkanDevice& Device);
static bool LoadVulkanInstanceFunctions(VkInstance inInstance);
};
// Vulkan할당(Allocate)
struct FVkRtAllocation
{
VkDevice Device = VK_NULL_HANDLE;
VkDeviceMemory Memory = VK_NULL_HANDLE;
VkBuffer Buffer = VK_NULL_HANDLE;
};
// Vulkan할당(Allocate)
class FVulkanRayTracingAllocator
{
public:
static void Allocate(FVulkanDevice* Device, VkDeviceSize Size, VkBufferUsageFlags UsageFlags, VkMemoryPropertyFlags MemoryFlags, FVkRtAllocation& Result);
static void Free(FVkRtAllocation& Allocation);
};
// VulkanTLAS.
struct FVkRtTLASBuildData
{
VkAccelerationStructureGeometryKHR Geometry;
VkAccelerationStructureBuildGeometryInfoKHR GeometryInfo;
VkAccelerationStructureBuildSizesInfoKHR SizesInfo;
};
// VulkanBLAS.
struct FVkRtBLASBuildData
{
TArray<VkAccelerationStructureGeometryKHR, TInlineAllocator<1>> Segments;
TArray<VkAccelerationStructureBuildRangeInfoKHR, TInlineAllocator<1>> Ranges;
VkAccelerationStructureBuildGeometryInfoKHR GeometryInfo;
VkAccelerationStructureBuildSizesInfoKHR SizesInfo;
};
// Vulkan.
class FVulkanRayTracingGeometry : public FRHIRayTracingGeometry
{
public:
virtual FRayTracingAccelerationStructureAddress GetAccelerationStructureAddress(uint64 GPUIndex) const final override;
virtual void SetInitializer(const FRayTracingGeometryInitializer& Initializer) final override;
void Swap(FVulkanRayTracingGeometry& Other);
void BuildAccelerationStructure(FVulkanCommandListContext& CommandContext, EAccelerationStructureBuildMode BuildMode);
private:
FVulkanDevice* const Device = nullptr;
VkAccelerationStructureKHR Handle = VK_NULL_HANDLE;
VkDeviceAddress Address = 0;
TRefCountPtr<FVulkanResourceMultiBuffer> AccelerationStructureBuffer;
TRefCountPtr<FVulkanResourceMultiBuffer> ScratchBuffer;
};
// Vulkan.
class FVulkanRayTracingScene : public FRHIRayTracingScene
{
public:
void BindBuffer(FRHIBuffer* InBuffer, uint32 InBufferOffset);
void BuildAccelerationStructure(FVulkanCommandListContext& CommandContext, FVulkanResourceMultiBuffer* ScratchBuffer, uint32 ScratchOffset, FVulkanResourceMultiBuffer* InstanceBuffer, uint32 InstanceOffset);
FRayTracingAccelerationStructureSize SizeInfo;
private:
FVulkanDevice* const Device = nullptr;
const FRayTracingSceneInitializer2 Initializer;
TRefCountPtr<FVulkanResourceMultiBuffer> InstanceBuffer;
// TLAS에 의해 Vulkan RHI 내에서 의 SRV객체/오브젝트 。D3D12 및 RHI로부터 GPU생성(Create)TLAS SRV, 또는 갱신(Update)。FVulkanRayTracingSceneVkaAccelerationStructureKHR,이므로 을(를) 활용하여 리소스 할당(Allocate)할당(Allocate)TLAS메모리 ,객체/오브젝트 의 및 버퍼 의 。로써 생성(Create)개 VkaAccelerationStructureKHR,버퍼 。
TRefCountPtr<FVulkanShaderResourceView> AccelerationStructureView;
TRefCountPtr<FVulkanResourceMultiBuffer> AccelerationStructureBuffer;
// 개 인스턴스 및 포인트 버퍼 바인딩(Bind)데이터 의 버퍼 .
TRefCountPtr<FVulkanResourceMultiBuffer> PerInstanceGeometryParameterBuffer;
TRefCountPtr<FVulkanShaderResourceView> PerInstanceGeometryParameterSRV;
void BuildPerInstanceGeometryParameterBuffer();
};
// Vulkan파이프라인 스테이트 .
class FVulkanRayTracingPipelineState : public FRHIRayTracingPipelineState
{
private:
FVulkanRayTracingLayout* Layout = nullptr;
VkPipeline Pipeline = VK_NULL_HANDLE;
FVkRtAllocation RayGenShaderBindingTable;
FVkRtAllocation MissShaderBindingTable;
FVkRtAllocation HitShaderBindingTable;
};
// Vulkan파이프라인 . 을(를) 활용하여 처리(Process)차폐/오클루전 .
class FVulkanBasicRaytracingPipeline
{
private:
FVulkanRayTracingPipelineState* Occlusion = nullptr;
};17.6.3 UE 라이트 트레이싱 렌더링 프로세스
UE의 레이 트레이싱 렌더링 프로세스를 분석하려면 우리에게 가장 친숙한 FDeferredShadingSceneRenderer::Render를 옮겨야 합니다. 주요 프로세스의 Ray Tracing과 관련된 단계만 아래에 나열되어 있습니다.
// DeferredShadingRenderer.cpp
void FDeferredShadingSceneRenderer::Render(FRDGBuilder& GraphBuilder)
{
(...)
// 갱신(Update)의 GPU정보 .
Scene->UpdateAllPrimitiveSceneInfos(GraphBuilder, true);
#if RHI_RAYTRACING
(...)
if (CurrentMode != Scene->CachedRayTracingMeshCommandsMode || bNaniteCoarseMeshStreamingModeChanged)
{
// 만약 로 렌더링(Render) 또는 로부터 렌더링(Render),이면 캐싱(Cache)의 레이 트레이싱(Ray Tracing),이므로 현재(Current)바인딩(Bind)셰이딩 의 데이터 。포인트 ,그러나 오직 변환(Transform/Convert)회 ,는 의 。
Scene->CachedRayTracingMeshCommandsMode = CurrentMode;
Scene->RefreshRayTracingMeshCommandCache();
}
(...)
#endif
(...)
#if RHI_RAYTRACING
FRayTracingScene& RayTracingScene = Scene->RayTracingScene;
// 리셋/초기화(Reset)배열 ,그러나 해제(Release)리소스 。
RayTracingScene.Reset();
(...)
// 로 렌더링(Render)。인스턴스 、셰이딩 、리소스 、파라미터 ,레이 트레이싱(Ray Tracing)가속 구조체 .
GatherRayTracingWorldInstancesForView(GraphBuilder, ReferenceView, RayTracingScene);
#endif
(...)
RenderPrePass(GraphBuilder, ...);
(...)
#if RHI_RAYTRACING
bool bRayTracingSceneReady = false;
#endif
(...)
#if RHI_RAYTRACING
// 비동기 가속 구조체 및 BasePass오버랩 .
FRDGBufferRef DynamicGeometryScratchBuffer;
DispatchRayTracingWorldUpdates(GraphBuilder, DynamicGeometryScratchBuffer);
#endif
RenderBasePass(GraphBuilder, ...);
(...)
// Shadows, lumen and fog after base pass
if (!bHasRayTracedOverlay)
{
(...)
RenderShadowDepthMaps(GraphBuilder, ...);
(...)
#if RHI_RAYTRACING
// 만약 그림자(Shadow),Lumen라이팅 레이 트레이싱(Ray Tracing)
if (Lumen::UseHardwareRayTracedSceneLighting(ViewFamily))
{
WaitForRayTracingScene(GraphBuilder, DynamicGeometryScratchBuffer);
bRayTracingSceneReady = true;
}
#endif
(...)
}
(...)
#if RHI_RAYTRACING
// 만약 Lumen의 레이 트레이싱(Ray Tracing)동기화 ,이면 필수: 。
if (!bRayTracingSceneReady)
{
WaitForRayTracingScene(GraphBuilder, DynamicGeometryScratchBuffer);
bRayTracingSceneReady = true;
}
#endif
if (bRenderDeferredLighting)
{
(...)
#if RHI_RAYTRACING
// 렌더링(Render)지터링 의 LOD.
RenderDitheredLODFadingOutMask(GraphBuilder, Views[0], SceneTextures.Depth.Target);
#endif
(...)
// 렌더링(Render)라이트(Light).
RenderLights(GraphBuilder, ...);
(...)
#if RHI_RAYTRACING
// 렌더링(Render)의 스카이라이트(Skylight).
RenderRayTracingSkyLight(GraphBuilder, ...);
CompositeRayTracingSkyLight(GraphBuilder, ...);
#endif
}
(...)
// 반투명(Translucent)
if (!bHasRayTracedOverlay && TranslucencyViewsToRender != ETranslucencyView::None)
{
(...)
#if RHI_RAYTRACING
// 렌더링(Render)반투명(Translucent).
RenderRayTracingTranslucency(GraphBuilder, SceneTextures.Color);
EnumRemoveFlags(TranslucencyViewsToRender, ETranslucencyView::RayTracing);
#endif
(...)
// 렌더링(Render)반투명(Translucent).
RenderTranslucency(GraphBuilder, ...);
(...)
}
(...)
#if RHI_RAYTRACING
//
RenderPathTracing(GraphBuilder, View, ...);
#endif
(...)
// 포스트 프로세스.
AddPostProcessingPasses(GraphBuilder, View, ...);
(...)
#if RHI_RAYTRACING
// 해제(Release)리소스 .
ReleaseRaytracingResources(GraphBuilder, Views, Scene->RayTracingScene);
#endif
(...)
}전통적인 관행에 따르면 이제 순서도를 발행할 차례입니다.
그래프 TD A(장면->UpdateAllPrimitiveSceneInfos) --> B(장면->RefreshRayTracingMeshCommandCache) B --> C(GatherRayTracingWorldInstancesForView) C --> D(RenderPrePass) D --> E(DispatchRayTracingWorldUpdates) E --> F(RenderBasePass) F --> F1(RenderShadowDepthMaps) F1 --> G(RayTracingScene 대기) G --> H(RenderDitheredLODFadingOutMask) H --> I(RenderLights) I --> J(RenderRayTracingSkyLight) J --> K(CompositeRayTracingSkyLight) K --> L(RenderRayTracingTranslucency) L --> M(렌더 반투명도) M --> N(RenderPathTracing) N --> O(AddPostProcessingPasses) O --> P(ReleaseRaytracingResources)
RenderDoc에 해당하는 프레임 캡처는 다음과 같습니다.

RenderDoc은 하드웨어 레이 트레이싱의 세부 사항을 가로챌 수 없기 때문에 블로거는 Windows PIX를 통해 프레임을 캡처하고 싶었지만 PIX에 버그(드라이버나 UE)가 있어서 UE5.0.3을 정상적으로 가로챌 수 없었고 프레임 캡처 데이터가 매우 불완전하다는 사실을 발견했습니다.

UE5에서 PIX 프레임 캡처 디버깅을 활성화하려면 다음을 참조하세요:
-PIX GPU 캡처
윈도우용 PIX
NVIDIA 개발 도구 솔루션 - ERR_NVGPUCTRPERM: 성능 카운터 관련 권한 문제
팁과 요령: 전문가들이 PIX를 사용하여 Xbox 및 Windows에서 게임을 개선하는 방법
광추적과 관련된 단계는 다음과 같이 간략하게 설명됩니다.
- GatherRayTracingWorldInstancesForView:
RayTracingCollector를 사용하여 라이트 추적에 사용할 수 있는 뷰의 객체를 수집하고 동적 객체와 정적 객체를 구별합니다.
- DispatchRayTracingWorldUpdates:
장면의 RayTracingSkinnedGeometryUpdateQueue를 가져와 GraphBuilder를 통해 제출합니다.
GRayTracingGeometryManager는 광 추적 개체에 대한 가속 구조 구성 요청을 처리합니다.
GRayTracingGeometryManager는 RayTracingScene.GeometriesToBuild를 강제로 생성합니다.
동적 기하학 버퍼를 생성합니다.
GraphBuilder에 RayTracingScene 패스를 추가했습니다.
RayTracingScene 대기:
레이 트레이싱 파이프라인 상태를 설정하려면 SetupRayTracingPipelineStates를 호출하세요.
장면에 인라인 레이 트레이싱 개체가 있는 경우 SetupLumenHardwareRayTracingHitGroupBuffer를 호출하여 Lumen 하드웨어 레이 트레이싱 가속을 지원합니다.
GraphBuilder에 WaitForRayTracingScene 패스 추가:
TaskGraph를 사용하여 ReferenceView.RayTracingMaterialBindingsTask 처리가 완료될 때까지 기다립니다. 로컬 렌더링 스레드에서 실행됩니다.
ReferenceView.RayTracingMaterialBindings를 처리하고 병합해 보세요.
RHICmdList.SetRayTracingHitGroups를 호출하여 레이 트레이싱 히트 그룹을 설정합니다.
루멘 광 추적 하드웨어 가속을 처리합니다.
미스 셰이더를 설정하려면 SetupRayTracingLightingMissShader를 호출하세요.
GPU 리소스 변환을 처리합니다.
렌더라이트:
광원이 레이 트레이싱 모드이고 레이 트레이싱 그림자가 활성화된 경우 RenderRayTracingShadows가 호출되어 레이 트레이싱 그림자 계산을 수행합니다.
레이 트레이싱 그림자에 대한 노이즈 제거를 수행합니다(활성화된 경우).
RenderRayTracingSkyLight:
generateSkyLightVisibilityRays는 하늘빛 가시광선을 생성합니다.
이전 단계에서 생성된 채광창 가시광선을 기반으로 가속 구조를 구축합니다.
GraphBuilder에 SkyLightRayTracing 패스를 추가합니다.
스카이라이트 추적 결과에 대한 노이즈 감소를 수행합니다.
CompositeRayTracingSkyLight:
스카이 레이 트레이싱의 노이즈 제거된 결과를 장면 색상으로 결합합니다.
- RenderRayTracingTranslucency:
각 뷰를 탐색하고 반투명 레이 트레이싱 객체가 있는 뷰에서 RenderRayTracingPrimaryRaysView를 호출하여 반투명 객체의 조명 결과를 그립니다.
반투명 텍스처를 장면 색상에 통합합니다.
렌더 경로 추적:
뷰 상태가 변경되었는지 감지합니다. 그렇다면 이전 추적 결과를 무효화하고 경로 추적을 다시 시작하세요.
- GraphBuilder에 경로 추적을 위한 패스를 추가합니다.
여기서 바운스가 0보다 큰 경우 RHICmdList.RayTraceDispatchIndirect를 호출합니다.
그렇지 않으면 RHICmdList.RayTraceDispatch를 호출합니다.
노이즈 감소가 필요한 경우 경로 추적 결과에 대해 노이즈 감소를 수행합니다.
경로 추적 결과를 표시하기 위해 전체 화면 그리기를 수행합니다.
Raytracing리소스 릴리스:
GraphBuilder에 패스를 추가하여 광 추적 리소스를 해제하세요. 공개된 리소스에는 조명 추적 장면, 지하, 조명 데이터 등이 포함됩니다.
주요 프로세스와 단계를 설명했습니다. 다음 섹션에서는 몇 가지 중요한 기능을 분석합니다.
17.6.4 빛과 그림자를 쫓는 UE 라이트
17.6.4.1 렌더라이트
라이트 트레이싱 라이트 및 그림자의 렌더링 프로세스는 FDeferredShadingSceneRenderer::RenderLights에 통합되어 있습니다.
// LightRendering.cpp
void FDeferredShadingSceneRenderer::RenderLights(FRDGBuilder& GraphBuilder, ...)
{
(...)
// 라이트(Light),처리(Process)RHI처리(Process)그림자(Shadow)텍스처(Texture)PreprocessedShadowMaskTextures。
if (RHI_RAYTRACING && bDoShadowBatching)
{
(...)
// 할당(Allocate)PreprocessedShadowMaskTextures
if (!View.bStatePrevViewInfoIsReadOnly)
{
View.ViewState->PrevFrameViewInfo.ShadowHistories.Empty();
View.ViewState->PrevFrameViewInfo.ShadowHistories.Reserve(SortedLights.Num());
}
PreprocessedShadowMaskTextures.SetNum(SortedLights.Num());
(...)
}
(...)
for (int32 LightIndex = UnbatchedLightStart; LightIndex < SortedLights.Num(); LightIndex++)
{
(...)
// 는 처리(Process)그림자(Shadow),만약 ,실행(Execute)처리(Process)로써 .
if (RHI_RAYTRACING && bWantsBatchedShadow && (PreprocessedShadowMaskTextures.Num() == 0 || !PreprocessedShadowMaskTextures[LightIndex - UnbatchedLightStart]))
{
(...)
// 처리(Process)디노이징(Denoising)회 .
const auto QuickOffDenoisingBatch = [&]
{
(...)
TStaticArray<IScreenSpaceDenoiser::FShadowVisibilityOutputs, IScreenSpaceDenoiser::kMaxBatchSize> Outputs;
// 디노이징(Denoising)그림자(Shadow)텍스처(Texture).
DenoiserToUse->DenoiseShadowVisibilityMasks(GraphBuilder, View, ...);
for (int32 i = 0; i < InputParameterCount; i++)
{
const FLightSceneInfo* LocalLightSceneInfo = DenoisingQueue[i].LightSceneInfo;
int32 LocalLightIndex = LightIndices[i];
FRDGTextureRef& RefDestination = PreprocessedShadowMaskTextures[LocalLightIndex - UnbatchedLightStart];
check(RefDestination == nullptr);
RefDestination = Outputs[i].Mask;
DenoisingQueue[i].LightSceneInfo = nullptr;
}
};
// 레이 트레이싱(Ray Tracing)의 그림자(Shadow),비활성화(Disable)디노이징(Denoising)회 。
for (int32 LightBatchIndex = LightIndex; LightBatchIndex < SortedLights.Num(); LightBatchIndex++)
{
const FSortedLightSceneInfo& BatchSortedLightInfo = SortedLights[LightBatchIndex];
const FLightSceneInfo& BatchLightSceneInfo = *BatchSortedLightInfo.LightSceneInfo;
// 디노이징(Denoising)지원 텍스처(Texture)모멘트(Moment)라이트(Light)의 샘플링。
const bool bBatchDrawShadows = BatchSortedLightInfo.SortKey.Fields.bShadowed;
(...)
// 만약 디노이징(Denoising)지원 레이 트레이싱(Ray Tracing)구성(Configuration),이면 처리(Process)추가(Add)메모리 。
if (bRequiresDenoiser && DenoiserRequirements != IScreenSpaceDenoiser::EShadowRequirements::PenumbraAndClosestOccluder)
{
continue;
}
(...)
// 실행(Execute)레이 트레이싱(Ray Tracing)그림자(Shadow).
FRDGTextureUAV* RayHitDistanceUAV = GraphBuilder.CreateUAV(FRDGTextureUAVDesc(RayDistanceTexture));
{
// 레이 트레이싱(Ray Tracing)반투명(Translucent)까지 의 그림자(Shadow). ※ 주의: :출력디노이징(Denoising),이므로 노이즈 ,디노이징(Denoising).
RenderRayTracingShadows(GraphBuilder, SceneTextureParameters, View, BatchLightSceneInfo, BatchRayTracingConfig, DenoiserRequirements, LightingChannelsTexture, RayTracingShadowMaskUAV, RayHitDistanceUAV, SubPixelRayTracingShadowMaskUAV);
(...)
}
bool bBatchFull = false;
// 레이 트레이싱(Ray Tracing)로부터 로써 그림자(Shadow)디노이징(Denoising)。
if (bRequiresDenoiser)
{
for (int32 i = 0; i < IScreenSpaceDenoiser::kMaxBatchSize; i++)
{
if (DenoisingQueue[i].LightSceneInfo == nullptr)
{
DenoisingQueue[i].LightSceneInfo = &BatchLightSceneInfo;
DenoisingQueue[i].RayTracingConfig = RayTracingConfig;
DenoisingQueue[i].InputTextures.Mask = RayTracingShadowMaskTexture;
DenoisingQueue[i].InputTextures.ClosestOccluder = RayDistanceTexture;
LightIndices[i] = LightBatchIndex;
// 만약 타입/유형 의 ,이면 처리(Process)。
if ((i + 1) == MaxDenoisingBatchSize)
{
QuickOffDenoisingBatch();
bBatchFull = true;
}
break;
}
else
{
check((i - 1) < IScreenSpaceDenoiser::kMaxBatchSize);
}
}
}
else // 디노이징(Denoising), 까지 처리(Process)처리(Process)그림자(Shadow)텍스처(Texture)배열 내에서 .
{
PreprocessedShadowMaskTextures[LightBatchIndex - UnbatchedLightStart] = RayTracingShadowMaskTexture;
}
// 만약 패딩/채우기 의 디노이징(Denoising)회 또는 까지 회 ,이면 회 .
ProcessShadows++;
if (bBatchFull || ProcessShadows == MaxRTShadowBatchSize)
{
break;
}
}
// 처리(Process)디노이징(Denoising)。
if (DenoisingQueue[0].LightSceneInfo)
{
QuickOffDenoisingBatch();
}
}
(...)
}
(...)
}빛 추적 그림자에 다음 참고 사항을 추가합니다.
- 광원 유형에 따라 라이트 추적 그림자를 켜는 조건이 다릅니다.
// LightRendering.cpp
// 에 따라 타입/유형 의 라이트(Light)판정/확인 는 로써 활성화(Enable)그림자(Shadow)。
static bool ShouldRenderRayTracingShadowsForLightType(ELightComponentType LightType)
{
switch(LightType)
{
case LightType_Directional:
return !!CVarRayTracingShadowsDirectionalLight.GetValueOnRenderThread();
case LightType_Point:
return !!CVarRayTracingShadowsPointLight.GetValueOnRenderThread();
case LightType_Spot:
return !!CVarRayTracingShadowsSpotLight.GetValueOnRenderThread();
case LightType_Rect:
return !!CVarRayTracingShadowsRectLight.GetValueOnRenderThread();
default:
return true;
}
}
// 판정/확인 는 로써 렌더링(Render)그림자(Shadow)。
bool ShouldRenderRayTracingShadows()
{
const bool bIsStereo = GEngine->StereoRenderingDevice.IsValid() && GEngine->StereoRenderingDevice->IsStereoEnabled();
const bool bHairStrands = IsHairStrandsEnabled(EHairStrandsShaderType::Strands);
return ShouldRenderRayTracingEffect((CVarRayTracingOcclusion.GetValueOnRenderThread() > 0) && !(bIsStereo && bHairStrands), ERayTracingPipelineCompatibilityFlags::FullPipeline, nullptr);
}
// 판정/확인 라이트(Light)테이블 는 활성화(Enable)그림자(Shadow)。
bool ShouldRenderRayTracingShadowsForLight(const FLightSceneProxy& LightProxy)
{
const bool bShadowRayTracingAllowed = ShouldRenderRayTracingEffect(true, ERayTracingPipelineCompatibilityFlags::FullPipeline, nullptr);
return (LightProxy.CastsRaytracedShadow() == ECastRayTracedShadow::Enabled || (ShouldRenderRayTracingShadows() && LightProxy.CastsRaytracedShadow() == ECastRayTracedShadow::UseProjectSetting))
&& ShouldRenderRayTracingShadowsForLightType((ELightComponentType)LightProxy.GetLightType())
&& bShadowRayTracingAllowed;
}
// 판정/확인 라이트(Light)정보 는 활성화(Enable)그림자(Shadow)。
bool ShouldRenderRayTracingShadowsForLight(const FLightSceneInfoCompact& LightInfo)
{
const bool bShadowRayTracingAllowed = ShouldRenderRayTracingEffect(true, ERayTracingPipelineCompatibilityFlags::FullPipeline, nullptr);
return (LightInfo.CastRaytracedShadow == ECastRayTracedShadow::Enabled || (ShouldRenderRayTracingShadows() && LightInfo.CastRaytracedShadow == ECastRayTracedShadow::UseProjectSetting))
&& ShouldRenderRayTracingShadowsForLightType((ELightComponentType)LightInfo.LightType)
&& bShadowRayTracingAllowed;
}Shadow denoising은 배치로 결합되어 배치 및 상태 전환을 줄이고 효율성을 향상시킵니다.
모든 그림자에 노이즈 감소가 필요한 것은 아닙니다. 그림자 노이즈 감소 조건은 다음과 같습니다.
광원 유형은 직사각형 광원, 각도가 0보다 큰 방향성 광원, 반경이 0보다 큰 점 광원 또는 스포트라이트와 같은 특정 조건을 충족합니다.
static bool LightRequiresDenosier(const FLightSceneInfo& LightSceneInfo)
{
ELightComponentType LightType = ELightComponentType(LightSceneInfo.Proxy->GetLightType());
if (LightType == LightType_Directional)
{
return LightSceneInfo.Proxy->GetLightSourceAngle() > 0;
}
else if (LightType == LightType_Point || LightType == LightType_Spot)
{
return LightSceneInfo.Proxy->GetSourceRadius() > 0;
}
else if (LightType == LightType_Rect)
{
return true;
}
return false;
}그림자 요구 사항 유형은 IScreenSpaceDenoiser::EShadowRequirements::PenumbraAndClosestOccluder입니다.
중요도 샘플링을 사용하는 질감이 있는 직사각형 조명 유형이 아닙니다.
17.6.4.2 RenderRayTracingShadows
이 섹션에서는 그림자 추적의 특정 프로세스를 설명합니다.
// RayTracingShadows.cpp
void FDeferredShadingSceneRenderer::RenderRayTracingShadows(FRDGBuilder& GraphBuilder, ...)
#if RHI_RAYTRACING
{
FLightSceneProxy* LightSceneProxy = LightSceneInfo.Proxy;
(...)
// 그림자(Shadow)차폐/오클루전 의 Pass。
{
(...)
// 패딩/채우기 FOcclusionRGS파라미터 。
FOcclusionRGS::FParameters* PassParameters = GraphBuilder.AllocParameters<FOcclusionRGS::FParameters>();
PassParameters->RWOcclusionMaskUAV = OutShadowMaskUAV;
PassParameters->RWRayDistanceUAV = OutRayHitDistanceUAV;
PassParameters->RWSubPixelOcclusionMaskUAV = SubPixelRayTracingShadowMaskUAV;
PassParameters->SamplesPerPixel = RayTracingConfig.RayCountPerPixel;
PassParameters->NormalBias = GetRaytracingMaxNormalBias();
PassParameters->LightingChannelMask = LightSceneProxy->GetLightingChannelMask();
(...)
// FOcclusionRGS의 shader。
TShaderMapRef<FOcclusionRGS> RayGenerationShader(GetGlobalShaderMap(FeatureLevel), PermutationVector);
// 을(를) 활용하여 의 RDG리소스 。
ClearUnusedGraphResources(RayGenerationShader, PassParameters);
(...)
// 추가(Add)RayTracedShadow의 채널/패스(Pass)。
GraphBuilder.AddPass(
RDG_EVENT_NAME("RayTracedShadow (spp=%d) %dx%d", RayTracingConfig.RayCountPerPixel, Resolution.X, Resolution.Y),
PassParameters,
// Pass플래그 는 Compute。
ERDGPassFlags::Compute,
[this, &View, RayGenerationShader, PassParameters, Resolution](FRHIRayTracingCommandList& RHICmdList)
{
FRayTracingShaderBindingsWriter GlobalResources;
SetShaderParameters(GlobalResources, RayGenerationShader, *PassParameters);
FRHIRayTracingScene* RayTracingSceneRHI = View.GetRayTracingSceneChecked();
// 활성화(Enable)머티리얼(Material).
if (GRayTracingShadowsEnableMaterials)
{
// RHI발광(Emissive) .
RHICmdList.RayTraceDispatch(View.RayTracingMaterialPipeline, RayGenerationShader.GetRayTracingShader(), RayTracingSceneRHI, GlobalResources, Resolution.X, Resolution.Y);
}
// 활성화(Enable)머티리얼(Material).
else
{
// 초기화(Initialize)파이프라인 스테이트 .
FRayTracingPipelineStateInitializer Initializer;
Initializer.MaxPayloadSizeInBytes = RAY_TRACING_MAX_ALLOWED_PAYLOAD_SIZE;
FRHIRayTracingShader* RayGenShaderTable[] = { RayGenerationShader.GetRayTracingShader() };
Initializer.SetRayGenShaderTable(RayGenShaderTable);
FRHIRayTracingShader* HitGroupTable[] = { View.ShaderMap->GetShader<FOpaqueShadowHitGroup>().GetRayTracingShader() };
Initializer.SetHitGroupTable(HitGroupTable);
// 비활성화(Disable)SBT,로써 내에서 의 을(를) 활용하여 의 히트(Hit)셰이딩 。
Initializer.bAllowHitGroupIndexing = false;
FRayTracingPipelineState* Pipeline = PipelineStateCache::GetAndOrCreateRayTracingPipelineState(RHICmdList, Initializer);
// RHI발광(Emissive) .
RHICmdList.RayTraceDispatch(Pipeline, RayGenerationShader.GetRayTracingShader(), RayTracingSceneRHI, GlobalResources, Resolution.X, Resolution.Y);
}
});
}
}레이 트레이싱 그림자의 셰이더는 FOcclusionRGS이고 해당 셰이더 파일은 RayTracingOcclusionRGS.usf입니다. 아래에서 분석해 보겠습니다.
// RayTracingOcclusionRGS.usf
RAY_TRACING_ENTRY_RAYGEN(OcclusionRGS)
{
uint2 PixelCoord = DispatchRaysIndex().xy + View.ViewRectMin.xy + PixelOffset;
FOcclusionResult Occlusion = InitOcclusionResult();
FOcclusionResult HairOcclusion = InitOcclusionResult();
const uint RequestedSamplePerPixel = ENABLE_MULTIPLE_SAMPLES_PER_PIXEL ? SamplesPerPixel : 1;
uint LocalSamplesPerPixel = RequestedSamplePerPixel;
if (all(PixelCoord >= LightScissor.xy) && all(PixelCoord <= LightScissor.zw)) //
{
// 랜덤/난수 시퀀스 .
RandomSequence RandSequence;
uint LinearIndex = CalcLinearIndex(PixelCoord);
RandomSequence_Initialize(RandSequence, LinearIndex, View.StateFrameIndex);
FLightShaderParameters LightParameters = GetRootLightShaderParameters(PrimaryView.PreViewTranslation);
// 페치/가져오기(Fetch)GBuffer데이터 .
float2 InvBufferSize = View.BufferSizeAndInvSize.zw;
float2 BufferUV = (float2(PixelCoord) + 0.5) * InvBufferSize;
float3 WorldNormal = 0;
uint ShadingModelID = SHADINGMODELID_UNLIT;
(...)
// 의 뎁스 .
float DeviceZ = SceneDepthTexture.Load(int3(PixelCoord, 0)).r;
const bool bIsDepthValid = SceneDepthTexture.Load(int3(PixelCoord, 0)).r > 0.0;
const bool bIsValidPixel = ShadingModelID != SHADINGMODELID_UNLIT && bIsDepthValid;
const uint LightChannel = GetSceneLightingChannel(PixelCoord);
const bool bTraceRay = bIsValidPixel && (LightChannel & LightingChannelMask) != 0;
if (!bTraceRay)
{
LocalSamplesPerPixel = 0;
}
(...)
// 계산/산출(Calculate)차폐/오클루전 .
Occlusion = ComputeOcclusion(PixelCoord, ShadingModelID, RAY_TRACING_MASK_SHADOW | RAY_TRACING_MASK_THIN_SHADOW, DeviceZ, WorldNormal, LightParameters, TransmissionProfileParams, LocalSamplesPerPixel);
(...)
}
(...)
// 계산/산출(Calculate)차폐/오클루전 까지 그림자(Shadow).
const float Shadow = OcclusionToShadow(Occlusion, LocalSamplesPerPixel);
// 에 따라 의 디노이징(Denoising)출력, 저장(Save)의 결과 .
if (DIM_DENOISER_OUTPUT == 2)
{
RWOcclusionMaskUAV[PixelCoord] = float4(Shadow, Occlusion.ClosestRayDistance, 0, Occlusion.TransmissionDistance);
}
else if (DIM_DENOISER_OUTPUT == 1)
{
float AvgHitDistance = -1.0;
if (Occlusion.HitCount > 0.0)
{
AvgHitDistance = Occlusion.SumRayDistance / Occlusion.HitCount;
}
else if (Occlusion.RayCount > 0.0)
{
AvgHitDistance = 1.0e27;
}
RWOcclusionMaskUAV[PixelCoord] = float4(Shadow, Occlusion.TransmissionDistance, Shadow, Occlusion.TransmissionDistance);
RWRayDistanceUAV[PixelCoord] = AvgHitDistance;
}
else
{
const float ShadowFadeFraction = 1;
float SSSTransmission = Occlusion.TransmissionDistance;
// 0로 그림자(Shadow),1로 그림자(Shadow),기록/쓰기(Write)SceneColor,이면 RETURN_COLOR.
float FadedShadow = lerp(1.0f, Square(Shadow), ShadowFadeFraction);
float FadedSSSShadow = lerp(1.0f, Square(SSSTransmission), ShadowFadeFraction);
// 채널/패스(Pass)ShadowRendering.cpp(감쇠(Falloff/Attenuation)할당(Allocate)).
float4 OutColor;
if (LIGHT_TYPE == LIGHT_TYPE_DIRECTIONAL)
{
OutColor = EncodeLightAttenuation(half4(FadedShadow, FadedSSSShadow, 1.0, FadedSSSShadow));
}
else
{
OutColor = EncodeLightAttenuation(half4(FadedShadow, FadedSSSShadow, FadedShadow, FadedSSSShadow));
}
RWOcclusionMaskUAV[PixelCoord] = OutColor;
}
}위에는 몇 가지 중요한 기능이 포함되어 있습니다. 계속해서 분석해 보겠습니다.
// 차폐/오클루전 그림자(Shadow),는 /。
float OcclusionToShadow(FOcclusionResult In, uint LocalSamplesPerPixel)
{
return (LocalSamplesPerPixel > 0) ? In.Visibility / LocalSamplesPerPixel : In.Visibility;
}
// 계산/산출(Calculate)차폐/오클루전 .
FOcclusionResult ComputeOcclusion(...)
{
FOcclusionResult Out = InitOcclusionResult();
const float3 WorldPosition = ReconstructWorldPositionFromDeviceZ(PixelCoord, DeviceZ);
(...)
uint TimeSeed = View.StateFrameIndex;
// 에 따라 의 개수 .
#if ENABLE_MULTIPLE_SAMPLES_PER_PIXEL
LOOP for (uint SampleIndex = 0; SampleIndex < LocalSamplesPerPixel; ++SampleIndex)
#else
do if (LocalSamplesPerPixel > 0)
#endif
{
// 처리(Process)랜덤/난수 시퀀스 .
RandomSequence RandSequence;
#if ENABLE_MULTIPLE_SAMPLES_PER_PIXEL
RandomSequence_Initialize(RandSequence, PixelCoord, SampleIndex, TimeSeed, LocalSamplesPerPixel);
#else
RandomSequence_Initialize(RandSequence, PixelCoord, 0, TimeSeed, 1);
#endif
float2 RandSample = RandomSequence_GenerateSample2D(RandSequence);
// .
RayDesc Ray;
bool bIsValidRay = GenerateOcclusionRay(LightParameters, ...);
uint Stencil = SceneStencilTexture.Load(int3(PixelCoord, 0)) STENCIL_COMPONENT_SWIZZLE;
bool bDitheredLODFadingOut = Stencil & 1;
(...)
BRANCH
if (!bIsValidRay && (DIM_DENOISER_OUTPUT == 0))
{
// 디노이징(Denoising)필수: 무효(Invalid)의 ,로써 의 히트(Hit)거리.
continue;
}
else if (bApplyNormalCulling && dot(WorldNormal, Ray.Direction) <= 0.0)
{
continue;
}
// 감쇠(Falloff/Attenuation)검사/감지(Detect).
if (LightParameters.InvRadius > 0.0)
{
const float MaxAttenuationDistance = 1.0 / LightParameters.InvRadius;
if (Ray.TMax > MaxAttenuationDistance)
{
continue;
}
}
uint RayFlags = 0;
// 만약 그림자(Shadow) 내에서 을(를) 활용하여 ,이면 활성화(Enable)백페이스 컬링 。
if (bTwoSidedGeometry != 1)
{
RayFlags |= RAY_FLAG_CULL_BACK_FACING_TRIANGLES;
}
(...)
uint RayFlagsForOpaque = bAcceptFirstHit != 0 ? RAY_FLAG_ACCEPT_FIRST_HIT_AND_END_SEARCH : 0;
// .
FMinimalPayload MinimalPayload = TraceVisibilityRay(TLAS, RayFlags | RayFlagsForOpaque, RaytracingMask, PixelCoord, Ray);
(...)
Out.RayCount += 1.0;
// 히트(Hit).
if (MinimalPayload.IsHit())
{
float HitT = MinimalPayload.HitT;
Out.ClosestRayDistance = (Out.ClosestRayDistance == DENOISER_INVALID_HIT_DISTANCE) || (HitT < Out.ClosestRayDistance) ? HitT : Out.ClosestRayDistance;
Out.SumRayDistance += HitT;
Out.HitCount += 1.0;
if (ShadingModelID == SHADINGMODELID_SUBSURFACE || ShadingModelID == SHADINGMODELID_HAIR)
{
(...)
}
}
// 히트(Hit).
else
{
Out.ClosestRayDistance = (Out.ClosestRayDistance == DENOISER_INVALID_HIT_DISTANCE) ? DENOISER_MISS_HIT_DISTANCE : Out.ClosestRayDistance;
Out.TransmissionDistance += 1.0;
Out.Visibility += 1.0;
}
}
(...)
// 출력결과 .
if (ENABLE_TRANSMISSION && LocalSamplesPerPixel > 0 && ShadingModelID == SHADINGMODELID_SUBSURFACE_PROFILE)
{
(...)
}
else if (ShadingModelID == SHADINGMODELID_SUBSURFACE || ShadingModelID == SHADINGMODELID_HAIR)
{
(...)
}
else
{
Out.TransmissionDistance = (LocalSamplesPerPixel > 0) ? Out.Visibility / LocalSamplesPerPixel : Out.Visibility;
}
return Out;
}랜덤 시퀀스 생성, 광선 생성, 가시레이 트레이싱을 분석하면 다음과 같습니다.
// PathTracingRandomSequence.ush
// 랜덤/난수 시퀀스 。
float2 RandomSequence_GenerateSample2D(inout RandomSequence RandSequence)
{
float2 Result;
// 랜덤/난수 .
#if RANDSEQ == RANDSEQ_PURERANDOM
Result.x = Rand(RandSequence.SampleSeed);
Result.y = Rand(RandSequence.SampleSeed);
// Halton랜덤/난수 시퀀스 .
#elif RANDSEQ == RANDSEQ_HALTON
Result.x = Halton(RandSequence.SampleIndex, Prime512(RandSequence.SampleSeed + 0));
Result.y = Halton(RandSequence.SampleIndex, Prime512(RandSequence.SampleSeed + 1));
RandSequence.SampleSeed += 2;
// Sobol랜덤/난수 시퀀스 .
#elif RANDSEQ == RANDSEQ_OWENSOBOL
Result = SobolSampler(RandSequence.SampleIndex, RandSequence.SampleSeed).xy;
#endif
return Result;
}
// RayTracingDirectionalLight.ush
// 차폐/오클루전 .
void GenerateDirectionalLightOcclusionRay(...)
{
// 드로우/렌더(Draw)랜덤/난수 변수개 포인트 .
float2 BufferSize = View.BufferSizeAndInvSize.xy;
float2 DiskUV = UniformSampleDiskConcentric(RandSample) * LightParameters.SourceRadius;
// 에 따라 을(를) 활용하여 정의 의 .
float3 LightDirection = LightParameters.Direction;
float3 N = LightDirection;
float3 dPdu = float3(1, 0, 0);
if (dot(N, dPdu) != 0)
{
dPdu = cross(N, dPdu);
}
else
{
dPdu = cross(N, float3(0, 1, 0));
}
float3 dPdv = cross(dPdu, N);
LightDirection += dPdu * DiskUV.x + dPdv * DiskUV.y;
RayOrigin = WorldPosition;
RayDirection = normalize(LightDirection);
RayTMin = 0.0;
RayTMax = 1.0e27;
}
// RayTracingPointLight.ush
// 포인트 라이트(Light)차폐/오클루전 .
bool GeneratePointLightOcclusionRay(...)
{
float3 LightDirection = LightParameters.Position - WorldPosition;
float RayLength = length(LightDirection);
LightDirection /= RayLength;
// 정의 적용 노멀(Normal).
RayOrigin = WorldPosition;
RayDirection = LightDirection;
RayTMin = 0.0;
RayTMax = RayLength;
return true;
}
// RayTracingSphereLight.ush
// 을(를) 활용하여 샘플링라이트(Light)차폐/오클루전 .
bool GenerateSphereLightOcclusionRayWithAreaSampling(...)
{
float4 Result = UniformSampleSphere(RandSample);
float3 LightNormal = Result.xyz;
float3 LightPosition = LightParameters.Position + LightNormal * LightParameters.SourceRadius;
float3 LightDirection = LightPosition - WorldPosition;
float RayLength = length(LightDirection);
LightDirection /= RayLength;
RayOrigin = WorldPosition;
RayDirection = LightDirection;
RayTMin = 0.0;
RayTMax = RayLength;
float SolidAnglePdf = Result.w * saturate(dot(LightNormal, -LightDirection)) / (RayLength * RayLength);
RayPdf = SolidAnglePdf;
return true;
}
// 을(를) 활용하여 샘플링라이트(Light)차폐/오클루전 .
bool GenerateSphereLightOcclusionRayWithSolidAngleSampling(...)
{
(...)
// 셰이딩 포인트 는 내에서 .
float3 LightDirection = LightParameters.Position - WorldPosition;
float RayLength2 = dot(LightDirection, LightDirection);
float Radius2 = LightParameters.SourceRadius * LightParameters.SourceRadius;
BRANCH
if (RayLength2 <= Radius2)
{
return GenerateSphereLightOcclusionRayWithAreaSampling(...);
}
// 및 z정렬 의 샘플링.
float SinThetaMax2 = Radius2 / RayLength2;
float4 DirAndPdf = UniformSampleConeConcentricRobust(RandSample, SinThetaMax2);
float CosTheta = DirAndPdf.z;
float SinTheta2 = 1.0 - CosTheta * CosTheta;
RayOrigin = WorldPosition;
// 투영/프로젝션 까지 ,z 및 라이팅 정렬 .
float RayLength = sqrt(RayLength2);
LightDirection *= rcp(RayLength + 1e-4);
RayDirection = TangentToWorld(DirAndPdf.xyz, LightDirection);
RayTMin = 0.0;
// 클리핑/클램핑(Clipping)까지 및 교차점(Intersection)의 .
RayTMax = RayLength * (CosTheta - sqrt(max(SinThetaMax2 - SinTheta2, 0.0)));
RayPdf = DirAndPdf.w;
return true;
}
// RayTracingOcclusionRGS.usf
// 차폐/오클루전 .
bool GenerateOcclusionRay(...)
{
// 에 따라 라이트(Light)타입/유형 .
#if LIGHT_TYPE == LIGHT_TYPE_DIRECTIONAL
{
GenerateDirectionalLightOcclusionRay(...);
}
#elif LIGHT_TYPE == LIGHT_TYPE_POINT
{
if (LightParameters.SourceRadius == 0)
{
return GeneratePointLightOcclusionRay(...);
}
else
{
float RayPdf;
return GenerateSphereLightOcclusionRayWithSolidAngleSampling(...);
}
}
#elif LIGHT_TYPE == LIGHT_TYPE_SPOT
{
return GenerateSpotLightOcclusionRay(...);
}
#elif LIGHT_TYPE == LIGHT_TYPE_RECT
{
float RayPdf = 0.0;
return GenerateRectLightOcclusionRay(..);
}
#endif
return true;
}
// RayTracingCommon.ush
void TraceVisibilityRayPacked(inout FPackedMaterialClosestHitPayload PackedPayload, ...)
{
const uint RayContributionToHitGroupIndex = RAY_TRACING_SHADER_SLOT_SHADOW;
const uint MultiplierForGeometryContributionToShaderIndex = RAY_TRACING_NUM_SHADER_SLOTS;
const uint MissShaderIndex = 0;
// 활성화(Enable)유효(Valid),무시/스킵(Ignore)유효(Valid)정보 ,유효(Valid)입력.
PackedPayload.SetMinimalPayloadMode();
PackedPayload.HitT = 0;
PackedPayload.SetPixelCoord(PixelCoord);
// (API함수 ).
TraceRay(TLAS, RayFlags, InstanceInclusionMask, RayContributionToHitGroupIndex, MultiplierForGeometryContributionToShaderIndex, MissShaderIndex, Ray, PackedPayload);
}
FMinimalPayload TraceVisibilityRay(in RaytracingAccelerationStructure TLAS, ...)
{
FPackedMaterialClosestHitPayload PackedPayload = (FPackedMaterialClosestHitPayload)0;
if ((PayloadFlags & RAY_TRACING_PAYLOAD_INPUT_FLAG_IGNORE_TRANSLUCENT) != 0)
{
PackedPayload.SetIgnoreTranslucentMaterials();
}
// .
TraceVisibilityRayPacked(PackedPayload, TLAS, RayFlags, InstanceInclusionMask, PixelCoord, Ray);
// 압축해제/언패킹(Unpacking).
FMinimalPayload MinimalPayload = (FMinimalPayload)0;
// ,에 의해 FPackedMaterialClosestHitPayloadFminiMallPayLoad,setp,그러나 의 변환(Transform/Convert)。,만약 HitT로써 의 ,FMinimalPayload는 로부터 내에서 의 ,이면 。
MinimalPayload.HitT = PackedPayload.HitT;
return MinimalPayload;
}위에서 볼 수 있듯이 그림자를 추적하는 과정은 상대적으로 복잡합니다. 좀 더 명확하게 하기 위해 순서도를 직접 그려보겠습니다.
그래프 TD A(OcclusionRGS) --> B(ComputeOcclusion) B --> B1(RandomSequence_GenerateSample2D) B1 --> |RANDSEQ_PURERANDOM| B1_1(랜드) B1_1 --> B2(OcclusionRay 생성) B1 --> |RANDSEQ_HALTON| B1_2(할튼) B1_2 --> B2(OcclusionRay 생성) B1 --> |RANDSEQ_OWENSOBOL| B1_3(소볼샘플러) B1_3 --> B2(OcclusionRay 생성)
B2 --> |LIGHT_TYPE_DIRECTIONAL| B2_1(방향광 차단레이 생성) B2_1 --> B3(TraceVisibilityRay) B2 --> |LIGHT_TYPE_POINT| B2_2(GeneratePointLightOcclusionRay) B2_2 --> B3(TraceVisibilityRay) B2 --> |LIGHT_TYPE_SPOT| B2_3(SpotLightOcclusionRay 생성) B2_3 --> B3(TraceVisibilityRay) B2 --> |LIGHT_TYPE_RECT| B2_4(SpotLightOcclusionRay 생성) B2_4 --> B3(TraceVisibilityRay)
B3 --> B3_1(TraceVisibilityRayPacked) B3_1 --> B3_2(TraceRay)
B3_2 --> C(OcclusionToShadow) C --> D(EncodeLightAttenuation)
17.6.4.3 빛 추적 그림자 노이즈 감소
레이 트레이싱형 그림자의 노이즈 제거기는 다양한 노이즈 제거 유형에 따라 다릅니다.
// LightRendering.cpp
(...)
const int32 DenoiserMode = CVarShadowUseDenoiser.GetValueOnRenderThread();
const IScreenSpaceDenoiser* DefaultDenoiser = IScreenSpaceDenoiser::GetDefaultDenoiser();
const IScreenSpaceDenoiser* DenoiserToUse = DenoiserMode == 1 ? DefaultDenoiser : GScreenSpaceDenoiser;
(...)
const auto QuickOffDenoisingBatch = [&]
{
(...)
// 실행(Execute)디노이징(Denoising)처리(Process)。
DenoiserToUse->DenoiseShadowVisibilityMasks(GraphBuilder, View, &View.PrevViewInfo, SceneTextureParameters, DenoisingQueue, InputParameterCount, Outputs);
(...)
};
(...)IScreenSpaceDenoiser::GetDefaultDenoiser()와 GScreenSpaceDenoiser라는 두 가지 그림자 제거기가 있음을 알 수 있습니다. 그러나 해당 블로거는 전체 UE 프로젝트를 검색한 결과 실제로 동일한 유형인 IScreenSpaceDenoiser를 발견했습니다. 아래에서 분석해 보겠습니다.
// ScreenSpaceDenoise.cpp
class FDefaultScreenSpaceDenoiser : public IScreenSpaceDenoiser
{
public:
virtual void DenoiseShadowVisibilityMasks(FRDGBuilder& GraphBuilder, const FViewInfo& View, ...) const
{
// 설정(Set)렌더링(Render)텍스처(Texture).
FViewInfoPooledRenderTargets ViewInfoPooledRenderTargets;
SetupSceneViewInfoPooledRenderTargets(View, &ViewInfoPooledRenderTargets);
FSSDSignalTextures InputSignal;
// 설정(Set)디노이징(Denoising)데이터 .
DECLARE_FSSD_CONSTANT_PIXEL_DENSITY_SETTINGS(SSDShadowVisibilityMasksEffectName);
Settings.SignalProcessing = ESignalProcessing::ShadowVisibilityMask;
(...)
// 처리(Process)ID.
for (int32 BatchedSignalId = 0; BatchedSignalId < InputParameterCount; BatchedSignalId++)
{
Settings.MaxInputSPP = FMath::Max(Settings.MaxInputSPP, InputParameters[BatchedSignalId].RayTracingConfig.RayCountPerPixel);
}
// 디노이징(Denoising)히스토리 데이터(History Data).
TStaticArray<FScreenSpaceDenoiserHistory*, IScreenSpaceDenoiser::kMaxBatchSize> PrevHistories;
TStaticArray<FScreenSpaceDenoiserHistory*, IScreenSpaceDenoiser::kMaxBatchSize> NewHistories;
for (int32 BatchedSignalId = 0; BatchedSignalId < InputParameterCount; BatchedSignalId++)
{
(...)
}
(...)
FSSDSignalTextures SignalOutput;
// 픽셀(Pixel)의 신호(Signal)디노이징(Denoising).
DenoiseSignalAtConstantPixelDensity(GraphBuilder, View, SceneTextures, ViewInfoPooledRenderTargets, InputSignal, Settings, PrevHistories, NewHistories, &SignalOutput);
// 저장(Save)출력데이터 .
for (int32 BatchedSignalId = 0; BatchedSignalId < InputParameterCount; BatchedSignalId++)
{
Outputs[BatchedSignalId].Mask = SignalOutput.Textures[BatchedSignalId];
}
}
};위 코드에 포함된 DenoiseSignalAtConstantPixelDensity는 매우 복잡합니다. 주요 단계는 아래에 간략하게 설명되어 있습니다.
static void DenoiseSignalAtConstantPixelDensity(FRDGBuilder& GraphBuilder, const FViewInfo& View, ...)
{
(...)
// 생성(Create)디노이징(Denoising)버퍼 의 디스크립터 및 버퍼 .
bool bHasReconstructionLayoutDifferentFromHistory = false;
TStaticArray<FRDGTextureDesc, kMaxBufferProcessingCount> InjestDescs;
TStaticArray<FRDGTextureDesc, kMaxBufferProcessingCount> ReconstructionDescs;
TStaticArray<FRDGTextureDesc, kMaxBufferProcessingCount> HistoryDescs;
(...)
// 설정(Set)셰이딩 파라미터 .
FSSDCommonParameters CommonParameters;
{
Denoiser::SetupCommonShaderParameters(View, SceneTextures, ...);
(...)
}
// 설정(Set)데이터 로써 。
FSSDConvolutionMetaData ConvolutionMetaData;
if (Settings.SignalProcessing == ESignalProcessing::ShadowVisibilityMask)
{
for (int32 BatchedSignalId = 0; BatchedSignalId < Settings.SignalBatchSize; BatchedSignalId++)
{
FLightSceneProxy* LightSceneProxy = Settings.LightSceneInfo[BatchedSignalId]->Proxy;
(...)
ConvolutionMetaData.LightPositionAndRadius[BatchedSignalId] = FVector4f(TranslatedWorldPosition, Parameters.SourceRadius);
ConvolutionMetaData.LightDirectionAndLength[BatchedSignalId] = FVector4f(Parameters.Direction, Parameters.SourceLength);
GET_SCALAR_ARRAY_ELEMENT(ConvolutionMetaData.HitDistanceToWorldBluringRadius, BatchedSignalId) =
FMath::Tan(0.5 * FMath::DegreesToRadians(LightSceneProxy->GetLightSourceAngle()) * LightSceneProxy->GetShadowSourceAngleFactor());
GET_SCALAR_ARRAY_ELEMENT(ConvolutionMetaData.LightType, BatchedSignalId) = LightSceneProxy->GetLightType();
}
}
// 압축/패킹(Packing)데이터 로써 구현 의 메모리 대역폭 、해상도 의 메모리 및 의 VGPR을(를) 활용하여 .
ECompressedMetadataLayout CompressedMetadataLayout = GetSignalCompressedMetadata(Settings.SignalProcessing);
if (CompressedMetadataLayout == ECompressedMetadataLayout::FedDepthAndShadingModelID)
{
CommonParameters.CompressedMetadata[0] = Settings.CompressedDepthTexture;
CommonParameters.CompressedMetadata[1] = Settings.CompressedShadingModelTexture;
}
else if (CompressedMetadataLayout != ECompressedMetadataLayout::Disabled)
{
(...)
FComputeShaderUtils::AddPass(GraphBuilder,RDG_EVENT_NAME("SSD CompressMetadata %dx%d", ...);
}
FSSDSignalTextures SignalHistory = InputSignal;
// 내에서 계산/산출(Calculate)의 .
if (SignalUsesInjestion(Settings.SignalProcessing))
{
(...)
FComputeShaderUtils::AddPass(GraphBuilder,RDG_EVENT_NAME("SSD Injest(MultiSPP=%i)", ...);
SignalHistory = NewSignalOutput;
}
// 을(를) 활용하여 ,로써 컬링 내에서 .
if (Settings.bEnableReconstruction)
{
(...)
TShaderMapRef<FSSDSpatialAccumulationCS> ComputeShader(View.ShaderMap, PermutationVector);
FComputeShaderUtils::AddPass(GraphBuilder,RDG_EVENT_NAME("SSD Reconstruction(MaxSamples=%i Scissor=%ix%i%s%s)", ...);
SignalHistory = NewSignalOutput;
}
// .
for (int32 PreConvolutionId = 0; PreConvolutionId < Settings.PreConvolutionCount; PreConvolutionId++)
{
(...)
TShaderMapRef<FSSDSpatialAccumulationCS> ComputeShader(View.ShaderMap, PermutationVector);
FComputeShaderUtils::AddPass(GraphBuilder,RDG_EVENT_NAME("SSD PreConvolution(MaxSamples=%d Spread=%f)", ...);
SignalHistory = NewSignalOutput;
}
(...)
// Pass.
// ※ 주의: :즉, ViewState,는 ,이므로 는 디노이징(Denoising)품질 의 ,성능 , 및 템포럴 어큐뮬레이션(시간축 누적)출력의 。
if (bHasReconstructionLayoutDifferentFromHistory || Settings.bUseTemporalAccumulation)
{
FSSDSignalTextures RejectionPreConvolutionSignal;
// 기각(Rejection)을(를) 활용하여 분리형(Separable).
if (SignalUsesRejectionPreConvolution(Settings.SignalProcessing))
{
(...)
TShaderMapRef<FSSDSpatialAccumulationCS> ComputeShader(View.ShaderMap, PermutationVector);
FComputeShaderUtils::AddPass(GraphBuilder,RDG_EVENT_NAME("SSD RejectionPreConvolution(MaxSamples=5)"), ...);
}
(...)
TShaderMapRef<FSSDTemporalAccumulationCS> ComputeShader(View.ShaderMap, PermutationVector);
(...)
// 설정(Set)신호(Signal)의 버퍼 .
for (int32 BatchedSignalId = 0; BatchedSignalId < Settings.SignalBatchSize; BatchedSignalId++)
{
FScreenSpaceDenoiserHistory* PrevFrameHistory = PrevFilteringHistory[BatchedSignalId] ? PrevFilteringHistory[BatchedSignalId] : &DummyPrevFrameHistory;
(...)
PassParameters->HistoryBufferScissorUVMinMax[BatchedSignalId] = FVector4f(
float(PrevFrameHistory->Scissor.Min.X + 0.5f) / float(PrevFrameBufferExtent.X),
float(PrevFrameHistory->Scissor.Min.Y + 0.5f) / float(PrevFrameBufferExtent.Y),
float(PrevFrameHistory->Scissor.Max.X - 0.5f) / float(PrevFrameBufferExtent.X),
float(PrevFrameHistory->Scissor.Max.Y - 0.5f) / float(PrevFrameBufferExtent.Y));
PrevFrameHistory->SafeRelease();
}
// 클리어 을(를) 활용하여 의 리소스 ,로써 셰이딩 다음 프레임(Next Frame) 내에서 .
{
ClearUnusedGraphResources(ComputeShader, PassParameters);
(...)
for (int32 i = 0; i < kCompressedMetadataTextures; i++)
bExtractCompressedMetadata[i] = PassParameters->PrevCompressedMetadata[i] != nullptr;
}
// 추가(Add)템포럴 어큐뮬레이션(시간축 누적)채널/패스(Pass).
FComputeShaderUtils::AddPass(GraphBuilder, RDG_EVENT_NAME("SSD TemporalAccumulation%s", ...);
SignalHistory = SignalOutput;
}
// 필터링 ,.
int32 MaxPostFilterSampleCount = FMath::Clamp(Settings.HistoryConvolutionSampleCount, 1, kStackowiakMaxSampleCountPerSet);
if (MaxPostFilterSampleCount > 1)
{
(...)
TShaderMapRef<FSSDSpatialAccumulationCS> ComputeShader(View.ShaderMap, PermutationVector);
FComputeShaderUtils::AddPass(GraphBuilder,RDG_EVENT_NAME("SSD HistoryConvolution(MaxSamples=%i)", ...);
SignalHistory = SignalOutput;
}
(...)
// /출력보정(Correct)
if (SignalUsesFinalConvolution(Settings.SignalProcessing))
{
(...)
TShaderMapRef<FSSDSpatialAccumulationCS> ComputeShader(View.ShaderMap, PermutationVector);
FComputeShaderUtils::AddPass(GraphBuilder,RDG_EVENT_NAME("SSD SpatialAccumulation(Final)"), ...);
}
else
{
*OutputSignal = SignalHistory;
}
}위에서 볼 수 있듯이 SSD(화면 공간 노이즈 제거) 프로세스는 매우 복잡하며 압축 메타데이터, 주입, 재구성, 사전 컨볼루션, 사전 컨볼루션 거부, 시간 누적, 기록 컨볼루션, 공간 누적 등 많은 단계를 포함합니다. 공간 제한으로 인해 아래 분석을 위해 시간 누적이 선택됩니다.
// SSDTemporalAccumulation.usf
void TemporallyAccumulate(...)
{
(...)
// 샘플링현재(Current)데이터 .
FSSDCompressedSceneInfos CompressedRefSceneMetadata = SampleCompressedSceneMetadata(SceneBufferUV, BufferUVToBufferPixelCoord(SceneBufferUV));
(...)
// 투영/프로젝션 까지 이전 프레임(Previous Frame).
float3 HistoryScreenPosition = float3(DenoiserBufferUVToScreenPosition(SceneBufferUV), DeviceZ);
bool bIsDynamicPixel = false;
float4 ThisClip = float4(HistoryScreenPosition, 1);
float4 PrevClip = mul(ThisClip, View.ClipToPrevClip);
float3 PrevScreen = PrevClip.xyz * rcp(PrevClip.w);
float3 Velocity = HistoryScreenPosition - PrevScreen;
float4 EncodedVelocity = GBufferVelocityTexture.SampleLevel(GlobalPointClampedSampler, SceneBufferUV, 0);
bIsDynamicPixel = EncodedVelocity.x > 0.0;
if (bIsDynamicPixel)
{
Velocity = DecodeVelocityFromTexture(EncodedVelocity);
}
HistoryScreenPosition -= Velocity;
// 샘플링멀티플렉스(Multiplex)신호(Signal).
FSSDSignalArray CurrentFrameSamples;
FSSDSignalFrequencyArray CurrentFrameFrequencies;
SampleMultiplexedSignals(SignalInput_Textures_0, SignalInput_Textures_1, ...);
// 샘플링버퍼 .
FSSDSignalArray HistorySamples = CreateSignalArrayFromScalarValue(0.0);
{
float2 HistoryBufferUV = HistoryScreenPosition.xy * ScreenPosToHistoryBufferUV.xy + ScreenPosToHistoryBufferUV.zw;
float2 ClampedHistoryBufferUV = clamp(HistoryBufferUV, HistoryBufferUVMinMax.xy, HistoryBufferUVMinMax.zw);
bool bIsPreviousFrameOffscreen = any(HistoryBufferUV != ClampedHistoryBufferUV);
BRANCH
if (!bIsPreviousFrameOffscreen)
{
FSSDKernelConfig KernelConfig = CreateKernelConfig();
// 의 구성(Configuration).
KernelConfig.SampleSet = CONFIG_HISTORY_KERNEL;
KernelConfig.bSampleKernelCenter = true;
(...)
// 히스토리 버퍼의 양방향(Bilateral)기각(Rejection)포인트 ,로써 TAA지터링 .
KernelConfig.WorldBluringDistanceMultiplier = max(CONFIG_BILATERAL_DISTANCE_MULTIPLIER, 3.0);
// 설정(Set)양방향(Bilateral).
SetBilateralPreset(CONFIG_HISTORY_BILATERAL_PRESET, KernelConfig);
// 의 SGPR구성(Configuration).
KernelConfig.BufferSizeAndInvSize = HistoryBufferSizeAndInvSize;
KernelConfig.BufferBilinearUVMinMax = HistoryBufferUVMinMax;
(...)
// 의 VGPR구성(Configuration).
KernelConfig.BufferUV = HistoryBufferUV + BufferUVBilinearCorrection;
KernelConfig.bIsDynamicPixel = bIsDynamicPixel;
(...)
// 계산/산출(Calculate)랜덤/난수 신호(Signal).
KernelConfig.Randoms[0] = InterleavedGradientNoise(SceneBufferUV * BufferUVToOutputPixelPosition, View.StateFrameIndexMod8);
FSSDSignalAccumulatorArray SignalAccumulators = CreateSignalAccumulatorArray();
FSSDCompressedSignalAccumulatorArray UnusedCompressedAccumulators = CreateUninitialisedCompressedAccumulatorArray();
// 누적(Accumulate).
AccumulateKernel(KernelConfig, PrevHistory_Textures_0, ...);
// 로부터 누적 합산(Accumulate)추출/내보내기(Export)히스토리 샘플(History Sample).
for (uint BatchedSignalId = 0; BatchedSignalId < CONFIG_SIGNAL_BATCH_SIZE; BatchedSignalId++)
{
(...)
}
(...)
// 기각(Rejection). (, 무시/스킵(Ignore))
#if (CONFIG_HISTORY_REJECTION == HISTORY_REJECTION_MINMAX_BOUNDARIES || CONFIG_HISTORY_REJECTION == HISTORY_REJECTION_VAR_BOUNDARIES)
{
(...)
}
// 출력의 ,로써 의 。
uint MultiplexCount = 1;
FSSDSignalArray OutputSamples = CreateSignalArrayFromScalarValue(0.0);
FSSDSignalFrequencyArray OutputFrequencies = CreateInvalidSignalFrequencyArray();
{
MultiplexCount = CONFIG_SIGNAL_BATCH_SIZE;
for (uint BatchedSignalId = 0; BatchedSignalId < MultiplexCount; BatchedSignalId++)
{
OutputSamples.Array[BatchedSignalId] = HistorySamples.Array[BatchedSignalId];
OutputFrequencies.Array[BatchedSignalId] = CurrentFrameFrequencies.Array[BatchedSignalId];
}
}
// DispatchThreadId,SceneBufferUVVGPR,이므로 의 내에서 。
uint2 OutputPixelPostion = BufferUVToBufferPixelCoord(SceneBufferUV);
if (all(OutputPixelPostion < ViewportMax))
{
OutputMultiplexedSignal(SignalHistoryOutput_UAVs_0, ...);
}
}위에서 언급한 노이즈 감소 프로세스는 6.6.1 임시 초해상도 및 7.4.8.2 SSGI 노이즈 감소와 유사합니다. 이는 필터링 및 샘플링의 여러 기술(양방향 필터링, 공간 컨볼루션, 시간 컨볼루션, 무작위 샘플링, 신호 및 주파수 등)을 사용합니다.
17.6.5 스카이라이트를 추적하는 UE 라이트
Cast Ray Traced Shadow가 활성화되고 Source Type이 지정된 경우 하늘 조명은 부드러운 주변 그림자를 지원합니다. 스카이라이트는 레벨의 거리 부분을 캡처하여 장면에 광원으로 적용합니다.

17.6.5.1 RenderRayTracingSkyLight
RenderRayTracingSkyLight는 스카이라이트를 렌더링하는 주요 논리입니다. C++ 측 논리는 다음과 같습니다.
// RaytracingSkylight.cpp
void FDeferredShadingSceneRenderer::RenderRayTracingSkyLight(FRDGBuilder& GraphBuilder, ...)
{
(...)
// 패딩/채우기 스카이라이트(Skylight)파라미터
if (!SetupSkyLightParameters(GraphBuilder, Scene, Views[0], bShouldRenderRayTracingSkyLight, &SkylightParameters, &SkyLightData))
{
(...)
return;
}
(...)
// 만약 샘플링, 이면 스카이라이트(Skylight).
if (CVarRayTracingSkyLightDecoupleSampleGeneration.GetValueOnRenderThread() == 1)
{
GenerateSkyLightVisibilityRays(GraphBuilder, Views[0], SkylightParameters, SkyLightData, SkyLightVisibilityRaysBuffer, SkyLightVisibilityRaysDimensions);
}
(...)
for (FViewInfo& View : Views)
{
(...)
TShaderMapRef<FRayTracingSkyLightRGS> RayGenerationShader(GetGlobalShaderMap(FeatureLevel), PermutationVector);
(...)
GraphBuilder.AddPass(RDG_EVENT_NAME("SkyLightRayTracing %dx%d", ...)
{
FRayTracingShaderBindingsWriter GlobalResources;
SetShaderParameters(GlobalResources, RayGenerationShader, *PassParameters);
FRayTracingPipelineState* Pipeline = View.RayTracingMaterialPipeline;
if (CVarRayTracingSkyLightEnableMaterials.GetValueOnRenderThread() == 0)
{
FRayTracingPipelineStateInitializer Initializer;
Initializer.MaxPayloadSizeInBytes = RAY_TRACING_MAX_ALLOWED_PAYLOAD_SIZE;
// 셰이딩 테이블 .
FRHIRayTracingShader* RayGenShaderTable[] = { RayGenerationShader.GetRayTracingShader() };
Initializer.SetRayGenShaderTable(RayGenShaderTable);
// 히트(Hit).
FRHIRayTracingShader* HitGroupTable[] = { View.ShaderMap->GetShader<FOpaqueShadowHitGroup>().GetRayTracingShader() };
Initializer.SetHitGroupTable(HitGroupTable);
Initializer.bAllowHitGroupIndexing = false;
Pipeline = PipelineStateCache::GetAndOrCreateRayTracingPipelineState(RHICmdList, Initializer);
}
FRHIRayTracingScene* RayTracingSceneRHI = View.GetRayTracingSceneChecked();
// 발광(Emissive) .
RHICmdList.RayTraceDispatch(Pipeline, RayGenerationShader.GetRayTracingShader(), RayTracingSceneRHI, GlobalResources, RayTracingResolution.X, RayTracingResolution.Y);
});
// 디노이징(Denoising).
if (GRayTracingSkyLightDenoiser != 0)
{
// 을(를) 활용하여 기본값(Default)디노이징(Denoising)(즉, 스크린 스페이스디노이징(Denoising))
const IScreenSpaceDenoiser* DefaultDenoiser = IScreenSpaceDenoiser::GetDefaultDenoiser();
const IScreenSpaceDenoiser* DenoiserToUse = DefaultDenoiser;
(...)
IScreenSpaceDenoiser::FDiffuseIndirectOutputs DenoiserOutputs = DenoiserToUse->DenoiseSkyLight(GraphBuilder, ...);
}
(...)
}
}노이즈 감소 프로세스는 그림자와 동일하므로 나중에 설명하지 않습니다. 사용된 셰이더 코드를 분석해 보겠습니다.
// RayTracing\RayTracingSkyLightRGS.usf
RAY_TRACING_ENTRY_RAYGEN(SkyLightRGS)
{
(...)
// 페치/가져오기(Fetch)GBuffer데이터 .
FScreenSpaceData ScreenSpaceData = GetScreenSpaceData(UV);
FGBufferData GBufferData = GetGBufferDataFromSceneTexturesLoad(PixelCoord);
float DeviceZ = SceneDepthTexture.Load(int3(PixelCoord, 0)).r;
float3 WorldPosition;
float3 CameraDirection;
ReconstructWorldPositionAndCameraDirectionFromDeviceZ(PixelCoord, DeviceZ, WorldPosition, CameraDirection);
float3 WorldNormal = GBufferData.WorldNormal;
float3 Albedo = GBufferData.DiffuseColor;
(...)
// 의 뎁스
bool IsFiniteDepth = DeviceZ > 0.0;
bool bTraceRay = (IsFiniteDepth && GBufferData.ShadingModelID != SHADINGMODELID_UNLIT);
uint SamplesPerPixel = SkyLight.SamplesPerPixel;
if (!bTraceRay)
{
SamplesPerPixel = 0;
}
// 테이블 포인트 의 스카이라이트(Skylight)
const bool bGBufferSampleOrigin = true;
const bool bDecoupleSampleGeneration = DECOUPLE_SAMPLE_GENERATION != 0;
float3 ExitantRadiance;
float3 DiffuseExitantRadiance;
float AmbientOcclusion;
float HitDistance;
// 스카이라이트(Skylight).
SkyLightEvaluate(DispatchThreadId, ...);
// 로써 , 내에서 복구 .
DiffuseExitantRadiance.r = Albedo.r > 0.0 ? DiffuseExitantRadiance.r / Albedo.r : DiffuseExitantRadiance.r;
DiffuseExitantRadiance.g = Albedo.g > 0.0 ? DiffuseExitantRadiance.g / Albedo.g : DiffuseExitantRadiance.g;
DiffuseExitantRadiance.b = Albedo.b > 0.0 ? DiffuseExitantRadiance.b / Albedo.b : DiffuseExitantRadiance.b;
DiffuseExitantRadiance.rgb *= View.PreExposure;
RWSkyOcclusionMaskUAV[DispatchThreadId] = float4(ClampToHalfFloatRange(DiffuseExitantRadiance.rgb), AmbientOcclusion);
RWSkyOcclusionRayDistanceUAV[DispatchThreadId] = float2(HitDistance, SamplesPerPixel);
}SkyLightEvaluate를 분석해 보겠습니다.
// RayTracingSkyLightEvaluation.ush
void SkyLightEvaluate(...)
{
// 초기화(Initialize)데이터 .
float3 CurrentWorldNormal = WorldNormal;
(...)
// ,스카이라이트(Skylight)pdf에 의해 MIS(샘플링)로 0().
const float SkyLightSamplingStrategyPdf = SkyLight_Estimate() > 0 ? 0.5 : 0.0;
// 까지 의 .
for (uint SampleIndex = 0; SampleIndex < SamplesPerPixel; ++SampleIndex)
{
RayDesc Ray;
float RayWeight;
if (bDecoupleSampleGeneration)
{
// 로부터 계산/산출(Calculate)의 버퍼 내에서 페치/가져오기(Fetch)현재(Current)샘플링의
const uint SkyLightVisibilityRayIndex = GetSkyLightVisibilityRayTiledIndex(SampleCoord, SampleIndex, SkyLightVisibilityRaysDimensions.xy);
FSkyLightVisibilityRays SkyLightVisibilityRay = SkyLightVisibilityRays[SkyLightVisibilityRayIndex];
Ray.Origin = WorldPosition;
Ray.Direction = SkyLightVisibilityRay.DirectionAndPdf.xyz;
Ray.TMin = 0.0;
Ray.TMax = SkyLight.MaxRayDistance;
RayWeight = SkyLightVisibilityRay.DirectionAndPdf.w;
}
else // .
{
RandomSequence RandSequence;
RandomSequence_Initialize(RandSequence, PixelCoord, SampleIndex, View.StateFrameIndex, SamplesPerPixel);
// 또는 .
float2 RandSample = RandomSequence_GenerateSample2D(RandSequence);
// 로 현재(Current)샘플링.
float SkyLightPdf = 0;
float CosinePdf = 0;
BRANCH
if (RandSample.x < SkyLightSamplingStrategyPdf)
{
RandSample.x /= SkyLightSamplingStrategyPdf;
// 샘플링라이트(Light).
FSkyLightSample SkySample = SkyLight_SampleLight(RandSample);
Ray.Direction = SkySample.Direction;
SkyLightPdf = SkySample.Pdf;
CosinePdf = saturate(dot(CurrentWorldNormal, Ray.Direction)) / PI;
}
else
{
RandSample.x = (RandSample.x - SkyLightSamplingStrategyPdf) / (1.0 - SkyLightSamplingStrategyPdf);
// 샘플링.
float4 CosSample = CosineSampleHemisphere(RandSample, CurrentWorldNormal);
Ray.Direction = CosSample.xyz;
CosinePdf = CosSample.w;
// 계산/산출(Calculate)pdf.
SkyLightPdf = SkyLight_EvalLight(Ray.Direction).w;
}
Ray.Origin = WorldPosition;
Ray.TMin = 0.0;
Ray.TMax = SkyLight.MaxRayDistance;
// MIS / pdf
RayWeight = 1.0 / lerp(CosinePdf, SkyLightPdf, SkyLightSamplingStrategyPdf);
}
(...)
// 샘플링위치 는 GBuffer,적용 뎁스 .
float NoL = dot(CurrentWorldNormal, Ray.Direction);
if (NoL > 0.0)
{
if (bGBufferSampleOrigin)
{
ApplyCameraRelativeDepthBias(Ray, PixelCoord, DeviceZ, CurrentWorldNormal, SkyLight.MaxNormalBias);
}
else
{
ApplyPositionBias(Ray, CurrentWorldNormal, SkyLight.MaxNormalBias);
}
}
else
{
ApplyPositionBias(Ray, -CurrentWorldNormal, SkyLight.MaxNormalBias);
}
NoL = saturate(NoL);
(...)
// 개 .
FMinimalPayload MinimalPayload = TraceVisibilityRay(TLAS, RayFlags, InstanceInclusionMask, PixelCoord, Ray);
(...)
if (MinimalPayload.IsHit()) // 만약 히트(Hit), 까지 .
{
RayDistance += MinimalPayload.HitT;
HitCount += 1.0;
}
else // 히트(Hit), 이면 히트(Hit).
{
BentNormal += Ray.Direction;
// 머티리얼(Material).
const half3 N = WorldNormal;
const half3 V = -ViewDirection;
const half3 L = Ray.Direction;
FDirectLighting LightingSample;
if (GBufferData.ShadingModelID == SHADINGMODELID_HAIR)
{
(...)
}
else
{
FShadowTerms ShadowTerms = { 0.0, 0.0, 0.0, InitHairTransmittanceData() };
// 계산/산출(Calculate)BxDF
LightingSample = EvaluateBxDF(GBufferData, N, V, L, NoL, ShadowTerms);
}
float3 Brdf = LightingSample.Diffuse + LightingSample.Transmission + LightingSample.Specular;
// 계산/산출(Calculate)스카이라이트(Skylight).
float3 IncomingRadiance = SkyLight_EvalLight(Ray.Direction).xyz;
ExitantRadiance += IncomingRadiance * Brdf * RayWeight;
float3 DiffuseThroughput = LightingSample.Diffuse;
if (SkyLight.bTransmission)
{
DiffuseThroughput += LightingSample.Transmission;
}
DiffuseExitantRadiance += IncomingRadiance * DiffuseThroughput * RayWeight;
}
} // for
// 의 평균값(Mean)
if (SamplesPerPixel > 0)
{
const float SamplesPerPixelInv = rcp(SamplesPerPixel);
ExitantRadiance *= SamplesPerPixelInv;
DiffuseExitantRadiance *= SamplesPerPixelInv;
AmbientOcclusion = HitCount * SamplesPerPixelInv;
}
(...)
// 만약 까지 차폐/오클루전 ,이면 계산/산출(Calculate)거리.
if (HitCount > 0.0)
{
HitDistance = RayDistance / HitCount;
}
(...)
}위 코드는 SkyLight_EvalLight를 두 번 호출합니다. 첫 번째는 스카이라이트의 pdf를 계산하고, 두 번째는 휘도를 계산합니다. SkyLight_EvalLight의 분석은 다음과 같습니다.
// MonteCarlo.ush
// 의 매핑 .
// Based on: [Clarberg 2008, "Fast Equal-Area Mapping of the (Hemi)Sphere using SIMD"]
float2 InverseEquiAreaSphericalMapping(float3 Direction)
{
float3 AbsDir = abs(Direction);
float R = sqrt(1 - AbsDir.z);
float Epsilon = 5.42101086243e-20;
float x = min(AbsDir.x, AbsDir.y) / (max(AbsDir.x, AbsDir.y) + Epsilon);
// Coefficients for 6th degree minimax approximation of atan(x)*2/pi, x=[0,1].
const float t1 = 0.406758566246788489601959989e-5f;
const float t2 = 0.636226545274016134946890922156f;
const float t3 = 0.61572017898280213493197203466e-2f;
const float t4 = -0.247333733281268944196501420480f;
const float t5 = 0.881770664775316294736387951347e-1f;
const float t6 = 0.419038818029165735901852432784e-1f;
const float t7 = -0.251390972343483509333252996350e-1f;
// Polynomial approximation of atan(x)*2/pi
float Phi = t6 + t7 * x;
Phi = t5 + Phi * x;
Phi = t4 + Phi * x;
Phi = t3 + Phi * x;
Phi = t2 + Phi * x;
Phi = t1 + Phi * x;
Phi = (AbsDir.x < AbsDir.y) ? 1 - Phi : Phi;
float2 UV = float2(R - Phi * R, Phi * R);
UV = (Direction.z < 0) ? 1 - UV.yx : UV;
UV = asfloat(asuint(UV) ^ (asuint(Direction.xy) & 0x80000000u));
return UV * 0.5 + 0.5;
}
// RayTracingSkyLightCommon.ush
float4 SkyLight_EvalLight(float3 Dir)
{
// 을(를) 활용하여 의 매핑 스카이라이트(Skylight)의 UV,샘플링스카이라이트(Skylight)텍스처(Texture)의 컬러 .
float2 UV = InverseEquiAreaSphericalMapping(Dir.yzx);
float4 Result = SkylightTexture.SampleLevel(SkylightTextureSampler, UV, 0);
float3 Radiance = Result.xyz;
// 계산/산출(Calculate)pdf.
#if USE_HIERARCHICAL_IMPORTANCE_SAMPLING
float Pdf = Result.w > 0 ? Result.w / (4 * PI * SkylightPdf.Load(int3(0, 0, SkylightMipCount - 1))) : 0.0;
#else
float Pdf = 1.0 / (4.0 * PI);
#endif
return float4(Radiance, Pdf);
}17.6.5.2 CompositeRayTracingSkyLight
CompositeRayTracingSkyLight는 RenderRayTracingSkyLight로 계산된 결과를 장면 색상으로 결합합니다. C++ 측 논리는 다음과 같습니다.
// RaytracingSkylight.cpp
void FDeferredShadingSceneRenderer::CompositeRayTracingSkyLight(FRDGBuilder& GraphBuilder, ...)
{
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
{
const FViewInfo& View = Views[ViewIndex];
(...)
GraphBuilder.AddPass(RDG_EVENT_NAME("GlobalIlluminationComposite"), ...)
{
// VS 및 PS인스턴스 .
TShaderMapRef<FPostProcessVS> VertexShader(View.ShaderMap);
TShaderMapRef<FCompositeSkyLightPS> PixelShader(View.ShaderMap);
(...)
// (Additive)블렌딩(Blending).
GraphicsPSOInit.BlendState = TStaticBlendState<CW_RGB, BO_Add, BF_One, BF_One>::GetRHI();
(...)
DrawRectangle(RHICmdList, ...);
});
}
}PS에서 사용하는 셰이더 코드로 직접 이동해 보겠습니다.
// CompositeSkyLightPS.usf
void CompositeSkyLightPS(in noperspective float2 UV : TEXCOORD0, out float4 OutColor : SV_Target0)
{
// 페치/가져오기(Fetch)GBuffer데이터 .
FGBufferData GBufferData = GetGBufferDataFromSceneTextures(UV);
float3 Albedo = GBufferData.StoredBaseColor - GBufferData.StoredBaseColor * GBufferData.Metallic;
// 로부터 스카이라이트(Skylight)텍스처(Texture)샘플링데이터 .
float4 SkyLight = SkyLightTexture.Sample(SkyLightTextureSampler, UV);
// 디노이징(Denoising)적용
SkyLight.rgb *= Albedo;
OutColor = SkyLight;
}17.6.6 UE 라이트 체이싱 GI
17.6.6.1 UE 광 추적 GI 열기 조건
UE 5.0.3의 표준 레이 트레이싱 GI는 Lumen 하드웨어 레이 트레이싱(아래 그림)으로 대체되었으며 Lumen의 전역 조명은 두 가지 레이 트레이싱 모드, 즉 소프트웨어 레이 트레이싱(메시 디스턴스 필드 생성을 프로젝트 설정에서 켜야 함)과 하드웨어 레이 트레이싱(지원 하드웨어 레이 트레이싱을 프로젝트 설정에서 켜야 함)을 지원합니다. 나중에 Lumen 하드웨어 레이 트레이싱만 분석됩니다.

Lumen GI 사용 여부를 결정하는 코드는 다음과 같습니다.
void FDeferredShadingSceneRenderer::Render(FRDGBuilder& GraphBuilder)
{
(...)
InitViews(...);
// 계산/산출(Calculate)렌더링(Render)의 개 의 상태 。
CommitFinalPipelineState();
(...)
}
void FDeferredShadingSceneRenderer::CommitFinalPipelineState()
{
(...)
CommitIndirectLightingState();
(...)
}
// IndirectLightRendering.cpp
bool ShouldRenderLumenDiffuseGI(const FScene* Scene, const FSceneView& View, bool bSkipTracingDataCheck, bool bSkipProjectCheck)
{
// 는 활성화(Enable)Lumen.
return Lumen::IsLumenFeatureAllowedForView(Scene, View, bSkipTracingDataCheck, bSkipProjectCheck)
// 동적 글로벌 라이팅 메서드 는 Lumen
&& View.FinalPostProcessSettings.DynamicGlobalIlluminationMethod == EDynamicGlobalIlluminationMethod::Lumen
// 변수는 활성화(Enable).
&& CVarLumenGlobalIllumination.GetValueOnAnyThread()
// 뷰(View)의 GI플래그 는 활성화(Enable).
&& View.Family->EngineShowFlags.GlobalIllumination
&& View.Family->EngineShowFlags.LumenGlobalIllumination
// 는 을(를) 활용하여 또는 지원 .
&& (bSkipTracingDataCheck || Lumen::UseHardwareRayTracedScreenProbeGather() || Lumen::IsSoftwareRayTracingSupported());
}
void FDeferredShadingSceneRenderer::CommitIndirectLightingState()
{
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
{
const FViewInfo& View = Views[ViewIndex];
TPipelineState<FPerViewPipelineState>& ViewPipelineState = ViewPipelineStates[ViewIndex];
EDiffuseIndirectMethod DiffuseIndirectMethod = EDiffuseIndirectMethod::Disabled;
EAmbientOcclusionMethod AmbientOcclusionMethod = EAmbientOcclusionMethod::Disabled;
EReflectionsMethod ReflectionsMethod = EReflectionsMethod::Disabled;
IScreenSpaceDenoiser::EMode DiffuseIndirectDenoiser = IScreenSpaceDenoiser::EMode::Disabled;
bool bUseLumenProbeHierarchy = false;
// 검사/감지(Detect)는 을(를) 활용하여 Lumen GI.
if (ShouldRenderLumenDiffuseGI(Scene, View))
{
DiffuseIndirectMethod = EDiffuseIndirectMethod::Lumen;
bUseLumenProbeHierarchy = CVarLumenProbeHierarchy.GetValueOnRenderThread() != 0;
}
else if (ScreenSpaceRayTracing::IsScreenSpaceDiffuseIndirectSupported(View))
(...)
}
}UseHardwareRayTracedScreenProbeGather 코드는 다음과 같습니다.
// LumenScreenProbeHardwareRayTracing.cpp
bool UseHardwareRayTracedScreenProbeGather()
{
#if RHI_RAYTRACING
// 레이 트레이싱(Ray Tracing)는 활성화(Enable).
return IsRayTracingEnabled()
// 는 을(를) 활용하여 레이 트레이싱(Ray Tracing).
&& Lumen::UseHardwareRayTracing()
// Lumen의 의 변수로 0
&& (CVarLumenScreenProbeGatherHardwareRayTracing.GetValueOnAnyThread() != 0);
#else
return false;
#endif
}17.6.6.2 RenderDiffuseIndirectAndAmbientOcclusion
모든 조건이 충족되면 Lumen의 하드웨어 조명 추적 GI는 RenderBasePass와 RenderLights 사이에서 RenderDiffuseIndirectAndAmbientOcclusion 렌더링 관련 GI를 호출합니다.
void FDeferredShadingSceneRenderer::Render(FRDGBuilder& GraphBuilder)
{
(...)
RenderBasePass(...);
(...)
RenderDiffuseIndirectAndAmbientOcclusion(GraphBuilder, ...);
(...)
RenderLights(...);
(...)
}RenderDiffuseIndirectAndAmbientOcclusion 분석 및 Lumen GI와 관련된 논리를 입력해 보겠습니다.
// IndirectLightRendering.cpp
void FDeferredShadingSceneRenderer::RenderDiffuseIndirectAndAmbientOcclusion(FRDGBuilder& GraphBuilder, ...)
{
(...)
for (FViewInfo& View : Views)
{
const FPerViewPipelineState& ViewPipelineState = GetViewPipelineState(View);
(...)
else if (ViewPipelineState.DiffuseIndirectMethod == EDiffuseIndirectMethod::Lumen)
{
FLumenMeshSDFGridParameters MeshSDFGridParameters;
LumenRadianceCache::FRadianceCacheInterpolationParameters RadianceCacheParameters;
// 렌더링(Render)Lumen.
DenoiserOutputs = RenderLumenScreenProbeGather(GraphBuilder, ...);
if (ViewPipelineState.ReflectionsMethod == EReflectionsMethod::Lumen)
{
DenoiserOutputs.Textures[2] = RenderLumenReflections(GraphBuilder, View, ...);
}
// Lumen의 뎁스 ,이므로 반투명(Translucent)의 기록/쓰기(Write)뎁스 .
StoreLumenDepthHistory(GraphBuilder, SceneTextures, View);
if (!DenoiserOutputs.Textures[2])
{
DenoiserOutputs.Textures[2] = DenoiserOutputs.Textures[1];
}
}
(...)
// 디퓨즈(Diffuse) 및 차폐/오클루전 적용 씬 컬러(SceneColor)。
if (... ViewPipelineState.DiffuseIndirectMethod == EDiffuseIndirectMethod::Lumen ...)
{
FDiffuseIndirectCompositePS::FParameters* PassParameters = GraphBuilder.AllocParameters<FDiffuseIndirectCompositePS::FParameters>();
(...)
else if (ViewPipelineState.DiffuseIndirectMethod == EDiffuseIndirectMethod::Lumen)
{
PermutationVector.Set<FDiffuseIndirectCompositePS::FApplyDiffuseIndirectDim>(4);
PermutationVector.Set<FDiffuseIndirectCompositePS::FScreenBentNormal>(ScreenBentNormalParameters.UseScreenBentNormal != 0);
DiffuseIndirectSampling = TEXT("ScreenProbeGather");
}
(...)
FPixelShaderUtils::AddFullscreenPass(GraphBuilder, View.ShaderMap,RDG_EVENT_NAME("DiffuseIndirectComposite(DiffuseIndirect=%s%s%s%s) %dx%d", ...);
}
(...)
} // for
}17.6.6.3 RenderLumenScreenProbeGather
다음은 RenderLumenScreenProbeGather의 하드웨어 광 추적 부분에 대한 분석입니다.
// LumenScreenProbeGather.cpp
FSSDSignalTextures FDeferredShadingSceneRenderer::RenderLumenScreenProbeGather(FRDGBuilder& GraphBuilder, ...)
{
(...)
if (GLumenIrradianceFieldGather != 0)
{
return RenderLumenIrradianceFieldGather(GraphBuilder, SceneTextures, FrameTemporaries, View);
}
(...)
auto ComputeShader = View.ShaderMap->GetShader<FScreenProbeDownsampleDepthUniformCS>(0);
// 추가(Add)글로벌 다운샘플링(Downsampling)의 Pass.
FComputeShaderUtils::AddPass(GraphBuilder,RDG_EVENT_NAME("UniformPlacement DownsampleFactor=%u", ScreenProbeParameters.ScreenProbeDownsampleFactor), ...);
(...)
if (ScreenProbeParameters.MaxNumAdaptiveProbes > 0 && AdaptiveProbeMinDownsampleFactor < ScreenProbeParameters.ScreenProbeDownsampleFactor)
{
uint32 PlacementDownsampleFactor = ScreenProbeParameters.ScreenProbeDownsampleFactor;
do
{
PlacementDownsampleFactor /= 2;
FScreenProbeAdaptivePlacementCS::FParameters* PassParameters = GraphBuilder.AllocParameters<FScreenProbeAdaptivePlacementCS::FParameters>();
(...)
auto ComputeShader = View.ShaderMap->GetShader<FScreenProbeAdaptivePlacementCS>(0);
// 추가(Add)의 Pass.
FComputeShaderUtils::AddPass(GraphBuilder,RDG_EVENT_NAME("AdaptivePlacement DownsampleFactor=%u", PlacementDownsampleFactor), ...);
}
while (PlacementDownsampleFactor > AdaptiveProbeMinDownsampleFactor);
}
(...)
auto ComputeShader = View.ShaderMap->GetShader<FSetupAdaptiveProbeIndirectArgsCS>(0);
// 설정(Set)파라미터 의 Pass.
FComputeShaderUtils::AddPass(GraphBuilder, RDG_EVENT_NAME("SetupAdaptiveProbeIndirectArgs"), ...);
(...)
// BRDF의 pdf.
GenerateBRDF_PDF(GraphBuilder, View, SceneTextures, BRDFProbabilityDensityFunction, BRDFProbabilityDensityFunctionSH, ScreenProbeParameters);
(...)
if (LumenScreenProbeGather::UseRadianceCache(View))
{
(...)
// 렌더링(Render)캐싱(Cache).
RenderRadianceCache(GraphBuilder, ...);
(...)
}
// 샘플링의 .
if (LumenScreenProbeGather::UseImportanceSampling(View))
{
GenerateImportanceSamplingRays(GraphBuilder, View, ...);
}
(...)
// .
TraceScreenProbes(GraphBuilder, Scene, ...);
FScreenProbeGatherParameters GatherParameters;
// 필터링 .
FilterScreenProbes(GraphBuilder, View, SceneTextures, ScreenProbeParameters, GatherParameters);
(...)
// 스크린 스페이스 내에서 보간(Interpolation)통합 .
InterpolateAndIntegrate(GraphBuilder, ...);
(...)
// 디노이징(Denoising).
if (GLumenScreenProbeTemporalFilter)
{
if (GLumenScreenProbeUseHistoryNeighborhoodClamp)
{
(...)
auto ComputeShader = View.ShaderMap->GetShader<FGenerateCompressedGBuffer>(0);
// 압축/패킹(Packing)의 GBuffer데이터 .
FComputeShaderUtils::AddPass(GraphBuilder, RDG_EVENT_NAME("GenerateCompressedGBuffer"), ...);
(...)
// 레벨 단계 디노이징(Denoising).
DenoiserOutputs = IScreenSpaceDenoiser::DenoiseIndirectProbeHierarchy(GraphBuilder, View, ...);
bLumenUseDenoiserComposite = true;
}
else
{
// 갱신(Update).
UpdateHistoryScreenProbeGather(GraphBuilder, View, ...);
DenoiserOutputs.Textures[0] = DiffuseIndirect;
DenoiserOutputs.Textures[1] = RoughSpecularIndirect;
}
}
(...)
return DenoiserOutputs;
}17.6.6.4 TraceScreenProbes
위에서 볼 수 있듯이 Lumen의 GI는 화면 공간 조명 프로브를 사용합니다. 그 중 TraceScreenProbes는 하드웨어 레이 트레이싱과 관련이 있고 나머지는 소프트웨어 레이 트레이싱과 동일해야 합니다. 아래에서는 TraceScreenProbes만 분석합니다.
// LumenScreenProbeTracing.cpp
void TraceScreenProbes(FRDGBuilder& GraphBuilder, const FScene* Scene, ...)
{
(...)
// 결과 .
auto ComputeShader = View.ShaderMap->GetShader<FClearTracesCS>(0);
FComputeShaderUtils::AddPass(GraphBuilder, RDG_EVENT_NAME("ClearTraces %ux%u", ...);
(...)
// 스크린 스페이스의 .
auto ComputeShader = View.ShaderMap->GetShader<FScreenProbeTraceScreenTexturesCS>(PermutationVector);
FComputeShaderUtils::AddPass(GraphBuilder, RDG_EVENT_NAME("TraceScreen(%s)", ...);
(...)
// 는 을(를) 활용하여 레이 트레이싱(Ray Tracing).
const bool bUseHardwareRayTracing = Lumen::UseHardwareRayTracedScreenProbeGather();
if (bUseHardwareRayTracing)
{
FCompactedTraceParameters CompactedTraceParameters = CompactTraces(GraphBuilder, View, ...);
// .
RenderHardwareRayTracingScreenProbe(GraphBuilder, Scene, ...);
}
else
{
// .
(...)
}
(...)
// 스크린 스페이스, 및 .
PermutationVector.Set< FScreenProbeTraceVoxelsCS::FTraceVoxels>(!bUseHardwareRayTracing && Lumen::UseGlobalSDFTracing(*View.Family));
auto ComputeShader = View.ShaderMap->GetShader<FScreenProbeTraceVoxelsCS>(PermutationVector);
FComputeShaderUtils::AddPass(GraphBuilder, RDG_EVENT_NAME("%s%s", ...);
}17.6.6.5 RenderHardwareRayTracingScreenProbe
위에서 우리는 하드웨어 레이 트레이싱 모드인 경우 RenderHardwareRayTracingScreenProbe로 진입한다는 것을 알고 있습니다.
// LumenScreenProbeHardwareRayTracing.cpp
void RenderHardwareRayTracingScreenProbe(FRDGBuilder& GraphBuilder, const FScene* Scene, ...)
{
(...)
// 변환(Transform/Convert)할당(Allocate)
TShaderRef<FConvertRayAllocatorCS> ComputeShader = View.ShaderMap->GetShader<FConvertRayAllocatorCS>();
FComputeShaderUtils::AddPass(GraphBuilder,RDG_EVENT_NAME("FConvertRayAllocatorCS"), ...);
(...)
// 【(near-field)】、테이블 캐싱(Cache) 및 머티리얼(Material)id의 기본값(Default).
PermutationVector.Set<FLumenScreenProbeGatherHardwareRayTracingRGS::FEnableNearFieldTracing>(true);
PermutationVector.Set<FLumenScreenProbeGatherHardwareRayTracingRGS::FEnableFarFieldTracing>(false);
if (bInlineRayTracing)
{
DispatchComputeShader(GraphBuilder, Scene, ...);
}
else
{
DispatchRayGenShader(GraphBuilder, Scene, ...);
}
(...)
// 을(를) 활용하여 【】
if (bUseFarFieldForScreenProbeGather)
{
// 압축/패킹(Packing), 로써 캐싱(Cache) 및 히트(Hit), 효율 .
LumenHWRTCompactRays(GraphBuilder, Scene, ...);
(...)
PermutationVector.Set<FLumenScreenProbeGatherHardwareRayTracingRGS::FEnableNearFieldTracing>(false);
PermutationVector.Set<FLumenScreenProbeGatherHardwareRayTracingRGS::FEnableFarFieldTracing>(true);
if (bInlineRayTracing)
{
DispatchComputeShader(GraphBuilder, Scene, ...);
}
else
{
DispatchRayGenShader(GraphBuilder, Scene, ...);
}
}
}위의 경우 Ray Tracing을 두 번 수행해야 하는데, 첫 번째는 근거리장(Near Field)을 추적하는 것이고, 두 번째는 원거리장(Far Field)을 추적하는 것입니다. 추적 시 두 가지 모드, 즉 Compute Shader를 사용하는 인라인 모드와 Ray Generator를 사용하는 하드웨어 모드가 지원됩니다. 먼저 Compute Shader 모드를 분석하여 차이점을 분석해 보겠습니다.
// LumenScreenProbeHardwareRayTracing.cpp
void DispatchComputeShader(FRDGBuilder& GraphBuilder, const FScene* Scene, ...)
{
(...)
TShaderRef<FLumenScreenProbeGatherHardwareRayTracingCS> ComputeShader = ...;
(...)
GraphBuilder.AddPass(RDG_EVENT_NAME("HardwareInlineRayTracing %s %s", ..., ERDGPassFlags::Compute,
[PassParameters, &View, ComputeShader, DispatchResolution](FRHIRayTracingCommandList& RHICmdList)
{
(...)
if (IsHardwareRayTracingScreenProbeGatherIndirectDispatch())
{
// ,※ 주의: 파라미터 는 PassParameters->CommonParameters.HardwareRayTracingIndirectArgs
DispatchIndirectComputeShader(RHICmdList, ComputeShader.GetShader(), PassParameters->CommonParameters.HardwareRayTracingIndirectArgs->GetIndirectRHICallBuffer(), 0);
}
else
{
(...)
// .
DispatchComputeShader(RHICmdList, ComputeShader.GetShader(), GroupCount.X, GroupCount.Y, 1);
}
(...)
}
);
}위에서 볼 수 있듯이 CS 모드는 간접 및 직접을 지원합니다. 동일한 셰이더를 사용하더라도 PassParameters 매개변수는 다릅니다! 간접 개방 조건은 다음과 같습니다.
// LumenScreenProbeHardwareRayTracing.cpp
bool IsHardwareRayTracingReflectionsIndirectDispatch()
{
return GRHISupportsRayTracingDispatchIndirect && (CVarLumenReflectionsHardwareRayTracingIndirect.GetValueOnRenderThread() == 1);
}
// WindowsD3D12Device.cpp
if (D3D12Caps5.RaytracingTier >= D3D12_RAYTRACING_TIER_1_1)
{
GRHISupportsRayTracingDispatchIndirect = true;
}즉, D3D12 레이 트레이싱 Tier 1.1 이상이 필요하며(다른 그래픽 API는 아직 지원되지 않음) 이를 활성화하려면 관련 콘솔 변수가 1입니다.
관련 지침은 DX 12 레이 트레이싱 문서인 DispatchRays 및 ExecuteIndirect에서 찾을 수 있습니다.
간접 모드는 비동기 모드와 동일하며 GPU의 병렬성을 향상할 수 있으며 일반적으로 더 효율적입니다.
17.6.6.6 LumenScreenProbeGatherHardwareRayTracing
Ray Generation을 사용하여 하드웨어 모드를 계속 분석해 보겠습니다.
void DispatchRayGenShader(FRDGBuilder& GraphBuilder, const FScene* Scene, ...)
{
(...)
// 파라미터 .
DispatchLumenScreenProbeGatherHardwareRayTracingIndirectArgs(...);
// 설정(Set)파라미터 .
SetLumenHardwareRayTracingScreenProbeParameters(...);
(...)
TShaderRef<FLumenScreenProbeGatherHardwareRayTracingRGS> RayGenerationShader = ...;
(...)
GraphBuilder.AddPass(RDG_EVENT_NAME("HardwareRayTracing %s %s", ...
{
(...)
//
if (IsHardwareRayTracingScreenProbeGatherIndirectDispatch())
{
RHICmdList.RayTraceDispatchIndirect(Pipeline, ...);
}
// .
else
{
RHICmdList.RayTraceDispatch(Pipeline, ...);
}
}
);
}위에서 볼 수 있듯이 하드웨어 Ray Tracing은 간접 모드와 직접 모드도 지원합니다. 간접 모드가 지원되면 먼저 사용됩니다. FLumenScreenProbeGatherHardwareRayTracingRGS의 셰이더를 분석해 보겠습니다.
// LumenScreenProbeHardwareRayTracing.usf
LUMEN_HARDWARE_RAY_TRACING_ENTRY(LumenScreenProbeGatherHardwareRayTracing)
{
// 계산/산출(Calculate)스레드 그룹(Thread Group) 및 스레드(Thread)id.
uint ThreadIndex = DispatchThreadIndex.x;
uint GroupIndex = DispatchThreadIndex.y;
#if DIM_INDIRECT_DISPATCH
uint Iteration = 0;
uint DispatchedThreads = RayAllocator[0];
#else
uint DispatchedThreads = ThreadCount * GroupCount;
uint IterationCount = (RayAllocator[0] + DispatchedThreads - 1) / DispatchedThreads;
// 이면 을(를) 활용하여 for루프 구현 개 .
for (uint Iteration = 0; Iteration < IterationCount; ++Iteration)
#endif
{
uint RayIndex = Iteration * DispatchedThreads + GroupIndex * ThreadCount + ThreadIndex;
if (RayIndex >= RayAllocator[0])
{
return;
}
// 페치/가져오기(Fetch)데이터 .
#if (DIM_LIGHTING_MODE == LIGHTING_MODE_HIT_LIGHTING) || ENABLE_FAR_FIELD_TRACING
FTraceData TraceData = UnpackTraceData(RWRetraceDataPackedBuffer[RayIndex]);
uint RayId = TraceData.RayId;
#else
uint RayId = RayIndex;
#endif
(...)
// 생성(Create)라이팅 .
FRayTracedLightingContext Context = CreateRayTracedLightingContext(TLAS, ...);
(...)
// 실행(Execute).
FRayTracedLightingResult Result = EpsilonTrace(Ray, Context);
// 만약 히트(Hit)
if (!Result.bIsHit)
{
Ray.TMin = max(Ray.TMin, AvoidSelfIntersectionTraceDistance);
Ray.TMax = Ray.TMin;
// 의 바운딩 박스(AABB)클리핑/클램핑(Clipping)TMax
if (length(Ray.Origin - LWCHackToFloat(PrimaryView.WorldCameraOrigin)) < MaxTraceDistance)
{
float2 Hit = RayIntersectSphere(Ray.Origin, Ray.Direction, float4(LWCHackToFloat(PrimaryView.WorldCameraOrigin), MaxTraceDistance));
Ray.TMax = (Hit.x > 0) ? Hit.x : ((Hit.y > 0) ? Hit.y : Ray.TMin);
}
// 처리(Process)캐싱(Cache)히트(Hit).
bool bIsRadianceCacheHit = false;
#if DIM_RADIANCE_CACHE
{
float ClipmapDitherRandom = InterleavedGradientNoise(ScreenTileCoord, View.StateFrameIndexMod8);
FRadianceCacheCoverage Coverage = GetRadianceCacheCoverage(Ray.Origin, Ray.Direction, ClipmapDitherRandom);
if (Coverage.bValid)
{
Ray.TMax = min(Ray.TMax, Coverage.MinTraceDistanceBeforeInterpolation);
bIsRadianceCacheHit = true;
}
}
#endif
// 설정(Set).
Context.FarFieldMaxTraceDistance = FarFieldMaxTraceDistance;
Context.FarFieldReferencePos = FarFieldReferencePos;
#if DIM_LIGHTING_MODE == LIGHTING_MODE_SURFACE_CACHE
Result = TraceAndCalculateRayTracedLightingFromSurfaceCache(Ray, Context);
#if DIM_PACK_TRACE_DATA
RWRetraceDataPackedBuffer[RayIndex] = PackTraceData(CreateTraceData(RayId, ...));
#endif
#endif
}
// 기록/쓰기(Write)라이팅 결과 .
#if DIM_WRITE_FINAL_LIGHTING
bool bMoving = false;
if (Result.bIsHit)
{
float3 HitWorldPosition = Ray.Origin + Ray.Direction * Result.TraceHitDistance;
bMoving = IsTraceMoving(...);
}
RWTraceRadiance[ScreenProbeTraceCoord] = Result.Radiance * View.PreExposure;
RWTraceHit[ScreenProbeTraceCoord] = EncodeProbeRayDistance(...);
#endif
}
}아래에 레이 트레이싱 호출 스택을 입력하세요.
// LumenScreenProbeHardwareRayTracing.usf
FRayTracedLightingResult EpsilonTrace(RayDesc Ray, inout FRayTracedLightingContext Context)
{
FRayTracedLightingResult Result = CreateRayTracedLightingResult();
#if ENABLE_NEAR_FIELD_TRACING
uint OriginalCullingMode = Context.CullingMode;
Context.CullingMode = RAY_FLAG_CULL_BACK_FACING_TRIANGLES;
Ray.TMax = AvoidSelfIntersectionTraceDistance;
if (Ray.TMax > Ray.TMin)
{
// 회 : 활성화(Enable)백페이스 컬링 의 거리,로써 및 GBuffer 내에서 의 매칭 의 자가 교차(Self-intersection)(Nanite、레이 트레이싱(Ray Tracing)LOD).
#if DIM_LIGHTING_MODE == LIGHTING_FROM_SURFACE_CACHE
{
Result = TraceAndCalculateRayTracedLightingFromSurfaceCache(Ray, Context);
}
#else
{
Result = TraceAndCalculateRayTracedLighting(Ray, Context, DIM_LIGHTING_MODE);
}
#endif
}
Context.CullingMode = OriginalCullingMode;
#endif
return Result;
}위의 TraceAndCalculateRayTracedLighting 및 TraceAndCalculateRayTracedLighting은 복잡한 Luman 카드 추적 및 샘플링 논리를 입력합니다. 이 기사에서는 분석을 계속하지 않습니다. 6.5.6 루멘 장면 조명 및 6.5.7 루멘 간접 조명을 참조할 수 있습니다.
또한 UE 하드웨어 라이트 트레이싱의 반사, AO, 반투명도 및 기타 특성도 Lumen에 혼합되어 보완적이고 결합도가 높으며 매우 복잡한 렌더링 시스템을 형성하여 뛰어난 영화 수준의 실시간 렌더링 품질을 제공합니다.
17.7 이 기사 요약
이 글에서는 주로 UE 하드웨어 레이 트레이싱의 렌더링 프로세스와 주요 알고리즘을 설명하므로 독자들이 이 모듈에 대한 전반적인 이해를 가질 수 있습니다. 보다 기술적인 세부 사항과 원리에 관해서는 독자들이 UE 소스 코드를 직접 연구해야 합니다.
마오싱윤(Remember, Remember, RIP)처럼 실시간 레이 트레이싱 기술에서 해결되지 않은 문제는 무엇인가요? 에서 언급했듯이 실시간 레이 트레이싱 렌더링 분야에는 아직 해결되지 않은 문제가 많이 있습니다.
렌더링 문제. 투명성, 부분 적용 범위, 입자, 전역 조명 등
성능 문제. 일관성, 스케줄링, 디커플링, 샘플링, 노이즈 감소 등을 포함합니다.
시스템 문제. 드라이버, 하드웨어, OS, 그래픽 API, 애플리케이션 등
그러나 그럼에도 불구하고 하드웨어 레이 트레이싱을 기반으로 한 렌더링 시스템 기술은 가까운 미래에 확실히 주류가 될 것이며 우리의 심층적인 탐구와 탐색의 가치가 있습니다.
어린이 신발이 그래픽 렌더링 기술에 뿌리를 내리고, 객관적이고 공정하게 노력하고, 사실에서 진실을 추구하고, 덕으로 사람들을 설득하고, 기술로 사람들을 설득할 수 있기를 바랍니다. 상호 격려.
특별 지침
모든 참고문헌의 저자에게 감사드립니다. 일부 사진은 참고 자료와 인터넷에서 가져온 것이므로 삭제되었습니다.
이 시리즈의 기사는 저자가 직접 작성한 것이며 블로그에만 게시됩니다. 이 글의 링크를 공유하셔도 좋지만 무단 전재는 허용되지 않습니다!
계속되는 일련의 기사. 전체 목록을 보려면 콘텐츠 개요를 클릭하세요.
계속되는 일련의 기사. 전체 목록을 보려면 콘텐츠 개요를 클릭하세요.
계속되는 일련의 기사. 전체 목록을 보려면 콘텐츠 개요를 클릭하세요.
📚 참고 자료 및 공식 링크 (References)
- Unreal Engine Source
- Rendering and Graphics
- Materials
- Graphics Programming
- 📖 레이 트레이싱 기술과 UE4 구현 살펴보기
- 📖 얕은 것부터 깊은 것까지 PBR의 원리와 구현을 배워보세요.
- Numerical Robustness for Geometric Calculations
- 6 Years of Optimizing World of Tanks: Making the Game a Great Experience on All Systems from Laptops to High End PCs
- The Latest Graphics Technology in Remedy's Northlight Engine
- Advanced Graphics Techniques Tutorial: GPU-Based Clay Simulation and Ray-Tracing Tech in 'Claybook'
- Interactive Relighting of Dynamic Refractive Objects
- Math for Game Programmers: Voxel Surfing
- Leveraging Real-Time Ray Tracing to build a Hybrid Game Engine
- Implicit Function Ray Tracing
- High Quality Rendering using Ray Tracing and Photon Mapping
- Stochastic ray tracing
- General-Purpose Computation on Graphics Hardware
- Introduction to PowerVR Ray Tracing
- PowerVR Graphics - Latest Developments and Future Plans
- It Just Works: Ray-Traced Reflections in "Battlefield V"
- Highly Parallel Fast KD-tree Construction for Interactive Ray Tracing of Dynamic Scenes
- Embree
- Intel® oneAPI Rendering Toolkit
- Rendering Technology in 'Agents of Mayhem'
- Practical Techniques for Ray Tracing in Games
- Imagination-PowerVR Photon Architecture
- Photon Architecture White Paper
- Radiance Caching for real-time Global Illumination
- Real-Time Ray Tracing of Correct Soft Shadows
- http://advances.realtimerendering.com/s2018/Pharr
- http://advances.realtimerendering.com/s2021/SIGGRAPH
- Hybrid Ray-Traced Shadows
- http://advances.realtimerendering.com/s2020/Turquin
- T-ReX: Interactive Global Illumination ofMassive Models on HeterogeneousComputing Resources
- Unbiased Photon Gathering for Light Transport Simulation
- Scalable Real time Global Illumination for Large Scenes
- Announcing Microsoft DirectX Raytracing!
- Ray Tracing Denoising
- DirectML's SuperResolution Sample
- Spatiotemporal Variance-Guided Filtering
- TURING RTX RAY TRACING 및 DLSS
- DLSS 2.0 – IMAGE RECONSTRUCTION FOR REAL-TIME RENDERING WITH DEEP LEARNING
- DLSS 2.0 - AI렌더링
- Spatiotemporal Reservoir Resampling (ReSTIR) - Theory and Basic Implementation
- Ray Tracing in Games with NVIDIA RTX (Presented by NVIDIA)
- Introduction to NVIDIA RTX and DirectX Ray Tracing
- Real-Time_Rendering_4th-Real-Time_Ray_Tracing
- RTX Technology
- NVIDIA Vulkan Ray Tracing Tutorial
- Ray Tracing In Vulkan
- Ray Tracing with Metal
- Metal for Accelerating Ray Tracing
- Accelerating ray tracing using Metal
- AMD Radeon™ Rays
- AMD Radeon ProRender
- Radeon ProRender and Radeon Rays in a Gaming Rendering Workflow
- AMD RYZEN™ PROCESSOR SOFTWARE OPTIMIZATION
- AMD CDNA™ 2 ARCHITECTURE
- Hardware-Accelerated Ray Tracing in AMD Radeon™ ProRender 2.0
- Ray Tracing Resources Page
- Ray Tracing Essentials
- Implementing GGX BRDF in Arnold with Multiple Importance Sampling
- NVIDIA RTX: Enabling Ray Tracing in Vulkan
- The RTX Shader Binding Table Three Ways
- SHINING A LIGHT ON RAY TRACING
- Parallel Architectures
- PBRT: Photorealistic Rendering and the Ray-Tracing Algorithm
- Ray Tracing Gems
- Ray Tracing Gems II
- Cinematic Rendering in UE4 with Real-Time Ray Tracing and Denoising
- NVIDIA DLSS Plugin For Unreal Engine
- Practical Solutions for Ray Tracing Content Compatibility in Unreal Engine 4
- 레이 트레이싱(Ray Tracing)(real-time ray tracing)기술 ?
- PIX GPU Captures
- PIX for Windows
- NVIDIA Development Tools Solutions - ERR_NVGPUCTRPERM: Permission issue with Performance Counters
- Tips & Tricks: How the Pros Use PIX to Make Their Games Better on Xbox and Windows
- Hardware Ray Tracing
- Hardware Ray Tracing Tips and Tricks
- Hardware Ray Tracing and Path Tracer Features Properties
- DirectX Raytracing (DXR) Functional Spec
- Lumen Technical Details