Skip to content

레이 트레이싱 기술과 UE4 구현 살펴보기

🌐 원문 링크: 探究光线追踪技术及UE4的实现 (cnblogs.com/timlly)
📅 원문 발행일: 2019-08-16 | ✍️ 저자: Timlly (00 / )

💡 시리즈 분류: Unreal Engine Rendering국내 업계 표준 용어 감수 및 수식/도해 복원 적용 완료


기존의 스캔 라인이나 래스터 렌더링 방법과 달리 레이 트레이싱은 광원에서 방출되는 광선이 아닌 카메라에서 방출되는 광선을 추적하는 3D 컴퓨터 그래픽의 특수 렌더링 알고리즘입니다. 이러한 기술을 통해 안무 장면의 수학적 모델이 생성되어 표시됩니다.

레이 트레이싱 기술을 사용하여 렌더링된 사실적인 이미지.

이 방법은 기존 주사선 기술과 비교하여 반사 및 굴절에 대한 보다 정확한 시뮬레이션 효과 등 광학 효과가 더 우수하고 효율성이 매우 높기 때문에 고품질 효과를 추구할 때 자주 사용됩니다.

물리학에서는 레이 트레이싱을 사용하여 매체를 통한 광선의 전파를 계산할 수 있습니다. 매질 내에서 전파될 때 광선은 매질에 의해 흡수되거나, 전파 방향을 바꾸거나, 매질 표면에서 방출될 수 있습니다. 우리는 매체를 통과하는 이상적인 좁은 빔(빛)에 대한 상황을 계산하여 이 복잡한 상황을 해결합니다.

실제 응용에서는 다양한 전자기파나 작은 입자가 이상적인 좁은 광선(즉, 빛)으로 간주될 수 있습니다. 이 가정을 바탕으로 사람들은 레이 트레이싱을 사용하여 매체 내 빛의 전파를 계산합니다. 레이 트레이싱 방법은 먼저 광선이 매체에 흡수되거나 방향이 변경되기 전에 광선이 이동하는 거리, 방향 및 새로운 위치를 계산합니다. 그런 다음 이 새로운 위치에서 새로운 광선을 생성합니다. 동일한 처리 방법을 사용하여 최종적으로 매질에서 전파되는 빛의 전체 경로를 계산합니다.

레이 트레이싱과 래스터라이제이션

래스터 파이프라인은 전통적인 렌더링 파이프라인 프로세스입니다. 삼각형을 단위로 사용하여 삼각형을 픽셀로 변환하는 과정입니다. 이는 현재 이미지 API 및 그래픽 카드 하드웨어에 대한 광범위한 지원 및 적용을 제공합니다.

레이 트레이싱 파이프라인은 광선을 단위로 사용하여 광선과 객체의 교차점과 교차점 이후의 계산 프로세스를 설명합니다. 래스터라이제이션된 선형 파이프라인과 달리 레이 트레이싱 파이프라인은 재귀 호출을 통해 다른 광선을 파생하고 다른 파이프라인 인스턴스를 실행할 수 있습니다.

1.2 레이 트레이싱의 특징

레이 트레이싱 기술을 사용하여 다음과 같은 렌더링 기능을 제공합니다.

  • 보다 정확한 반사, 굴절 및 전송.

  • 더 정확한 그림자. 셀프 섀도잉, 소프트 섀도잉, 영역 섀도잉, 멀티 라이트 섀도잉 등이 포함됩니다.

  • 전역 조명이 더욱 정확해졌습니다.

  • 더욱 사실적인 앰비언트 오클루전(AO)(AO).

레이 트레이싱 기술은 복잡한 반사, 굴절, 투과, 그림자, 전역 조명 및 기타 물리적 특성을 정확하게 반영할 수 있습니다.

물론 레이 트레이싱이 완전한 렌더링 기술은 아닙니다. 가혹한 하드웨어 요구 사항, 제한된 렌더링 기능 지원, 소음 간섭과 같은 부정적인 특성이 있습니다. 이에 대해서는 이후 장에서 자세히 설명합니다.

1.3 레이 트레이싱의 역사

레이 트레이싱 렌더링 기술은 빛 단순화, 레이 캐스팅 알고리즘, 자연계의 레이 트레이싱 알고리즘을 거쳐 단계적으로 발전해 왔습니다.

  • 레이 캐스팅 알고리즘(1968)

Arthur Appel이 제안한 렌더링용 레이 캐스팅 알고리즘. 레이 캐스팅의 기본은 눈에서 나온 빛을 물체의 각 지점에 투사하고 빛을 차단하는 가장 가까운 물체를 찾는 것, 즉 이미지를 화면으로 취급하고 각 지점은 화면의 사각형입니다.

이 알고리즘은 재료의 속성과 장면의 조명 효과를 기반으로 개체의 음영을 결정합니다. 한 가지 간단한 가정은 표면이 빛을 향하면 표면이 빛나고 그림자가 생기지 않는다는 것입니다.

스캔라인 렌더링에 비해 레이캐스팅의 중요한 장점은 비평면 표면은 물론 원뿔 및 구와 같은 솔리드 엔터티를 쉽게 처리할 수 있다는 것입니다. 수학적 표면이 광선과 교차하는 경우 레이 캐스팅을 사용하여 렌더링할 수 있습니다. 복잡한 객체는 솔리드 모델링 기술을 사용하여 구성하고 쉽게 렌더링할 수 있습니다.

  • 레이 트레이싱 알고리즘(1979)

첫 번째 획기적인 시도는 1979년 Turner Whitted에 의해 이루어졌습니다. 이전 알고리즘은 눈에서 장면으로 광선을 투사했지만 이러한 광선을 추적하지 않았습니다. 레이 트레이싱 알고리즘은 이러한 광선을 추적하고 물체 표면과 교차할 때마다 모든 빛과 그림자의 기여도를 계산합니다.

  • Ray Tracing API 및 하드웨어 통합(2018)

초기에 NV는 Microsoft와 협력하여 차세대 하드웨어 기반 레이 트레이싱 렌더링 API 및 하드웨어를 공동으로 만들었습니다. 2018년에는 RTX(Ray Tracing X) 표준을 공동 발표했습니다. Direct X 12는 RTX를 지원하고, NV의 RTX 시리즈 그래픽 카드는 RTX 기술을 지원하여 실시간 레이 트레이싱의 등장을 알립니다.

NV RTX 시연 영상 스크린샷.

  • UE 통합 레이 트레이싱(2019)

UE는 2019년 4월에 버전 4.22를 출시했습니다. 이 버전의 가장 눈부신 새로운 기능은 의심할 여지 없이 레이 트레이싱 기술에 대한 지원입니다. 이는 UE를 사용하는 대다수의 개인이나 팀이 사실적인 이미지를 보다 효과적으로 렌더링하는 데 도움이 될 것입니다.

UE의 레이 트레이싱 기술을 사용하여 사실적인 이미지를 렌더링합니다.

1.4 레이 트레이싱 적용

이미 1960년대부터 미국 과학자들은 레이 캐스팅을 군사 분야의 컴퓨터 그래픽 생성에 적용하려는 시도를 했었다. 기술이 성숙해지면서 할리우드 영화와 애니메이션 제작에 빠르게 사용되었습니다. 현재 양식화된 형식을 제외하고 후반 작업에 특수 효과가 필요한 대부분의 할리우드 영화는 기본적으로 레이 트레이싱 기술을 사용합니다.

"라이온 킹"은 레이 트레이싱 기술을 사용하여 렌더링되었습니다.

최근에는 RTX 표준이 출시되고 그래픽 카드가 지원되면서 레이 트레이싱 기술이 실시간 렌더링 분야에 진출했습니다. 최근에 출시된 많은 3A 게임 걸작은 레이 트레이싱 렌더링을 지원했습니다.

RTX를 켜고 끈 독립형 게임 "Bright Memory"의 비교 차트입니다.

레이 트레이싱 기술은 영화, 애니메이션, 게임 분야 외에도 교육, 디자인, 의학, 과학, AR 등 다양한 분야에 적용되어 가상 세계에서 사실적인 이미지를 렌더링할 수 있습니다.

레이 트레이싱을 사용하여 렌더링된 인테리어 디자인 도면입니다.

2. 레이 트레이싱의 원리

2.1 레이 트레이싱의 물리적 원리

기하광학에서는 빛의 파동성을 무시하고 직선으로 직접 단순화하여 빛의 물리적 특성을 연구할 수 있습니다. 마찬가지로 컴퓨터 그래픽에서도 이 기능을 활용하여 조명 셰이딩 프로세스를 단순화할 수 있습니다.

또한, 인간의 눈이 받아들이는 조명 정보는 픽셀로 제한되어 있으며, 대부분의 사람의 눈은 약 5억 픽셀에 이릅니다. 인간이 수신하는 이미지 정보는 5억 픽셀, 즉 5억 개의 아주 작은 광선으로 분할될 수 있습니다. 이러한 광선을 반대 방향으로 추적하면 해당 광선에 해당하는 장면 객체의 정보(위치, 방향, 재질, 조명 색상 및 밝기 등)를 감지할 수 있습니다.

레이 트레이싱 기술은 위의 물리적 원리에서 파생됩니다. 눈을 카메라로 추상화하고, 망막을 디스플레이 화면으로 추상화하고, 5억 픽셀을 화면 픽셀로 단순화하고, 카메라 위치의 광선을 화면의 각 픽셀에 연결하고, 이러한 광선과 장면 객체의 교차점에서 조명 정보를 추적합니다.

물론 실제 레이 트레이싱 알고리즘은 다음 섹션에서 자세히 설명하는 것처럼 더 복잡할 것입니다.

2.2 레이 트레이싱 알고리즘

기존 래스터 렌더링 기술과 비교할 때 레이 트레이싱의 알고리즘 프로세스는 비교적 명확합니다.

시점을 시작점으로 N개의 광선을 장면으로 방출한 후 충돌 지점의 재질에 따라 BXDF, BRDF를 계산한 후 디퓨즈, 스페큘러 반사 또는 굴절을 수행합니다. 이 재귀 루프는 광선이 장면을 벗어나거나 최대 반사 수에 도달할 때까지 계속됩니다. 마지막으로 N개 광선에 대해 몬테카를로 통합을 수행하여 결과를 얻습니다.

위의 그림과 결합하여 레이 트레이싱의 알고리즘 프로세스는 다음 의사 코드로 추상화될 수 있습니다.

cpp
遍历屏幕的每个像素 {
  创建从视点通过该像素的光线
  初始化 最近T 为 无限大,最近物体 为 空值

  遍历場景中的每个物体 {
     如果光线与物体相交 {
        如果交点处的 t 比 最近T 小 {
           设置 最近T 为交点的 t 值
           设置 最近物体 为该物体
        }
     }
  }

  如果 最近物体 为 空值{
     用背景色填充该像素
  } 否则 {
     对每個光源射出一条光线来检测是否处在阴影中
     如果表面是反射面,生成反射光,并递归
     如果表面透明,生成折射光,并递归
     使用 最近物体 和 最近T 来计算着色函数
     以着色函数的结果填充该像素
  }
}

위 의사코드와 관련된 셰이딩 함수는 Lambert, Phong, Blinn-Phong, BRDF, BTDF, BSDF, BSSRDF 등의 조명 모델을 사용할 수 있습니다.

한 단계 더 나아가 이를 컴퓨터 언어 형태의 의사코드(pseudocode)로 기술하면 레이 트레이싱의 계산 과정은 다음과 같다.

cpp
-- 遍历图像的所有像素
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 통합 및 중요도 샘플링(중요도 샘플링) 참조) 로컬 샘플링과 함께 전체 조명 적분을 추정할 수 있습니다.

균일 샘플링은 광원의 중요도를 구분하지 않는 평균 샘플링입니다. 생성된 빛 샘플은 모든 방향에서 동일한 확률을 갖습니다. 빛은 특별히 처리되지 않으며 실제 값과의 편차는 일반적으로 매우 큽니다.

몬테카를로 샘플링은 광원 방향의 샘플링에 중점을 두는데, 이는 픽셀에 대한 광원의 기여도를 강조할 수 있지만 과도한 광원 기여도를 유발할 수 있습니다.

중요도 샘플링은 샘플링 결과를 줄여 광원의 기여도가 너무 커지는 것을 방지하기 위해 확률 밀도 함수pdf를 추가합니다.

물론, 이 방법을 도입하더라도 샘플 수가 충분히 크지 않으면 조명 기여도와 실제 값 간의 편차가 여전히 커서 노이즈가 발생하게 됩니다. 샘플 수가 증가할수록 로컬 추정값은 실제 조명 적분값에 점점 가까워지고 노이즈는 점차 사라집니다(아래).

왼쪽에서 오른쪽으로 해당 픽셀 샘플은 각각 1, 16, 256, 4096 및 65536입니다.

몬테카를로 통합과 중요도 샘플링을 결합한 레이 트레이싱 기술을 **패스 트레이싱(Path tracing)**이라고도 합니다.

2.3 RTX 및 DXR

2.3.1 RTX(NV)

그래픽 커뮤니티의 세계적 수준의 탐험 선구자로서 NV는 레이 트레이싱에 대한 심층적인 연구 개발을 수행했으며 마침내 이를 기술 표준 RTX 플랫폼으로 추상화했습니다.

DirectX 12의 DXR 및 Vulkan의 지원으로 하드웨어 수준의 레이 트레이싱 기술이 점차 대중화되고 있습니다. NV는 Turing 아키텍처 GPU에서 RTX 기술을 최초로 지원합니다.

위 그림에서 볼 수 있듯이 최상위 레이어는 딥러닝과 일반 애플리케이션 개발을 포함하는 사용자 레이어(MDL 및 USD)입니다. 중간 레이어는 OptiX, DXR 및 Vulkan을 포함한 RTX를 지원하는 그래픽 API 레이어입니다. OpenGL은 RTX를 지원하지 않습니다; 맨 아래 레이어는 RTX 플랫폼으로, 기존 래스터라이저, 레이 트레이싱(RT 코어), CUDA 계산기, AI 코어의 4개 부분으로 구성됩니다.

물론 Turing 아키텍처 GPU 외에도 PASCAL, VOLTA, TURING RTX 및 RTX 기술을 지원하는 기타 아키텍처를 갖춘 GPU도 많이 있습니다. (아래 사진)

다음 그림은 동일한 데모(Battlefield)를 실행하는 RTX 기술을 지원하는 여러 GPU의 성능 비교입니다.

또한 레이 트레이싱을 사용하면 각 레이 트레이싱 기능의 로드가 다릅니다.

위 그림에 포함된 BVH(Bounding Volume Hierarchy)는 계층적 경계 상자(Hierarchical Bounding Box)로, 장면 객체 검색을 가속화하는 알고리즘이자 구조입니다.

개발자의 경우 다양한 화질 수준의 장치에서 프로그램이 잘 실행될 수 있도록 품질 수준에 따라 다양한 표시기를 사전 선택해야 합니다.

2.3.2 DXR(마이크로소프트)

DX12의 새로운 그래픽 API에는 DXR이라고 하는 프로그래밍 가능한 레이 트레이싱 렌더링 파이프라인(위)이 추가되었습니다. 기존 래스터라이제이션 파이프라인과 마찬가지로 레이 트레이싱 파이프라인에는 고정된 논리와 프로그래밍 가능한 부분이 있습니다. 5개의 새로운 셰이더(Shader)가 새 파이프라인에 추가되었습니다.

  • Ray Generation: 광선을 생성하는 데 사용됩니다. 이 셰이더에서 TraceRay()를 호출하여 광선을 재귀적으로 추적할 수 있습니다.

  • 교차점모든 적중: TraceRay()가 빛이 객체와 교차하는 것을 감지하면 이 셰이더가 호출되어 사용자는 교차하는 객체가 특수 프리미티브(구, 세분화 표면 또는 기타 프리미티브 유형)인지 감지할 수 있습니다.

  • Closest HitMiss: TraceRay()가 전체 장면을 횡단할 때 이 두 셰이더는 광선의 교차 여부에 따라 호출됩니다. Cloesit Hit는 재질, 텍스처 조회, 조명 계산 등과 같은 픽셀 셰이딩 처리를 수행할 수 있습니다. Cloesit Hit와 Miss 모두 계속해서 TraceRay()를 재귀적으로 호출할 수 있습니다.

다음은 위 셰이더 중 일부의 사용법을 더 잘 설명하기 위한 적용 예입니다.

cpp
// An example payload struct. We can define and use as many different ones as we like.
struct Payload
{
    float4 color;
    float  hitDistance;
};

// The acceleration structure we'll trace against.
// This represents the geometry of our scene.
RaytracingAccelerationStructure scene : register(t5);

[shader("raygeneration")]
void RayGenMain()
{
    // Get the location within the dispatched 2D grid of work items
    // (often maps to pixels, so this could represent a pixel coordinate).
    uint2 launchIndex = DispatchRaysIndex();

    // Define a ray, consisting of origin, direction, and the t-interval
    // we're interested in.
    RayDesc ray;
    ray.Origin = SceneConstants.cameraPosition.
    ray.Direction = computeRayDirection( launchIndex ); // assume this function exists
    ray.TMin = 0;
    ray.TMax = 100000;

    Payload payload;

    // Trace the ray using the payload type we've defined.
    // Shaders that are triggered by this must operate on the same payload type.
    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;
}

// Attributes contain hit information and are filled in by the intersection shader.
// For the built-in triangle intersection shader, the attributes always consist of
// the barycentric coordinates of the hit point.
struct Attributes
{
    float2 barys;
};

[shader("closesthit")]
void ClosestHitMain( inout Payload payload, in Attributes attr )
{
    // Read the intersection attributes and write a result into the payload.
    payload.color = float4( attr.barys.x, attr.barys.y,
                            1 - attr.barys.x - attr.barys.y, 1 );

    // Demonstrate one of the new HLSL intrinsics: query distance along current ray
    payload.hitDistance = RayTCurrent();
}

레이 트레이싱 렌더링 파이프라인에는 가속 구조도 포함됩니다. 그 기능은 장면의 모든 기하학적 개체 정보를 저장하고 GPU에서 개체 탐색, 교차점 테스트, 조명 구성 등에 대한 극한 가속 알고리즘을 제공하여 레이 트레이싱이 실시간 렌더링 수준에 도달할 수 있도록 하는 것입니다. BuildRaytracingAccelerationStructure() 인터페이스를 통해 애플리케이션에서 빌드할 수 있습니다.

위 그림에 표시된 것처럼 장면의 각 형상에 대해 GPU 내부에는 두 가지 수준의 가속 구조가 있습니다. **기본 가속 구조(Bottom-level AS)**는 삼각형, 사각형 등의 입력 기본 정보로 구성됩니다. **최상위 AS)**는 기본 가속 구조에서 생성됩니다. 이는 기본 가속 구조의 인스턴스와 동일하며 기본 구조의 변환 행렬과 셰이더 오프셋을 저장합니다.

셰이더 테이블은 셰이더가 연결된 장면의 개체를 설명하고 셰이더와 관련된 모든 리소스(텍스처, 버퍼, 상수 등)도 포함합니다.

GPU 하단에 있는 셰이더 매핑 테이블은 동일한 크기의 레코드이며, 각 레코드는 리소스 세트가 있는 셰이더(또는 히트 그룹)와 연결됩니다. 일반적으로 기하학당 하나의 레코드 볼륨이 있습니다.

위 그림에서 볼 수 있듯이 각 레코드는 셰이더 번호로 시작하고 CBV, UAV, 상수, 설명 테이블과 같은 셰이더 리소스를 포함합니다.

이 2계층 아키텍처의 장점은 리소스와 인스턴스화를 분리하고, 인스턴스 생성 및 초기화를 가속화하며, 대역폭과 비디오 메모리 사용량을 줄이는 것입니다.

Microsoft의 오래되고 강력한 그래픽 디버깅 소프트웨어인 PIX는 출시 이후 DXR 디버깅을 지원해 왔습니다. PIX는 다양한 호출 스택, 렌더링 상태, 리소스 및 기타 정보를 쉽게 디버깅하는 데 사용할 수 있습니다.

3. UE4의 레이 트레이싱

3.1 UE4 레이 트레이싱 켜기

UE에 대해 Ray Tracing을 활성화하려면 다음 조건을 충족해야 합니다.

  • 운영 체제: Windows 10 RS5(빌드 1809) 이상 버전. Windows 버전 업그레이드 방법은 Microsoft 공식 문서인 Windows 10 2019년 5월 업데이트 다운로드를 참조하세요.

  • 그래픽 카드: DXR을 지원하는 NVIDIA RTX 및 GTX 시리즈.

  • UE 버전: 4.22 이상 버전.

위의 모든 조건이 충족된 경우에만 다음 단계에 따라 UE의 레이 트레이싱을 켤 수 있습니다:

  1. 프로젝트 설정(파일-프로젝트 설정) 인터페이스를 엽니다.

  2. 프로젝트 설정의 플랫폼 창 페이지를 찾아 기본 RHI로 DirectX 12를 선택합니다.

  1. 렌더링 페이지를 찾아 Ray Tracing을 확인합니다.

레이 트레이싱을 확인한 후 편집기에서 다시 시작할지 묻는 메시지를 표시합니다. 예를 클릭하면 됩니다.

엔진 구성 파일 및 명령줄 시작에 익숙하다면 ConsoleVariables.ini를 직접 수정할 수 있습니다.

cpp
r.RayTracing=1
r.SkinCache.CompileShaders=1

그런 다음 UE 프로젝트를 시작할 때 -d3d12 플래그를 추가하여 DX12 모드 렌더링을 직접 활성화합니다.

  1. 포스트 프로세싱 컨볼루션(포스트 프로세스 볼륨)을 추가합니다.

편집기를 다시 시작하고 모든 셰이더 컴파일이 완료될 때까지 기다린 후 레벨에 포스트 프로세싱 볼륨을 추가하여 레이 트레이싱 관련 기능을 활성화하고 다양한 매개변수를 조정할 수 있습니다.

포스트 프로세싱 볼륨을 선택하고 세부 정보 패널에서 영향 범위를 조정하고 다양한 기능의 매개변수를 개별적으로 활성화 및 설정할 수 있습니다.

3.2 UE4 레이 트레이싱의 기능

UE4의 현재 버전은 다음 기능으로 레이 트레이싱을 지원합니다:

3.2.1 레이 트레이싱 그림자

여러 광원으로부터 발생하는 과도기적인 부드러운 그림자, 영역 그림자, 모델 자체 그림자 및 기타 복잡한 폐색 그림자를 시뮬레이션할 수 있으며 명백한 결함 없이 장면 객체와 밀접하게 통합될 수 있습니다.

상단: 래스터라이제이션된 그림자; 하단: 레이 트레이싱 그림자.

3.2.2 레이 트레이싱 반사

레이 트레이싱형 반사는 장면의 모든 객체를 실시간으로 동적으로 반사할 수 있습니다. 기존 SSR, 평면 반사, 큐브 맵 등에 제한을 받지 않습니다. 렌더링된 이미지가 더욱 사실적이고 장면에 통합됩니다.

상단: SSR 효과; 하단: 레이 트레이싱 반사.

또한, 레이 트레이싱 반사는 비스듬한 각도에서 반사된 물체의 늘어나는 효과를 정확하게 나타낼 수 있습니다.

상단: 래스터라이제이션된 반사; 하단: 레이 트레이싱 반사.

3.2.3 레이 트레이싱 투명성

레이 트레이싱의 투명성은 반사, 흡수, 굴절 및 유리와 유체와 같은 기타 재료의 물리적으로 정확한 표면 특성을 정확하게 시뮬레이션할 수 있습니다.

상단: 래스터라이제이션된 투명도; 하단: 레이 트레이싱된 투명도.

3.2.4 레이트레이싱을 위한 앰비언트 오클루전(AO)

SSAO(Screen Space Ambient Occlusion)는 포스트 프로세스 단계에서 수행되는 AO 프로세스입니다. 이는 가장자리 감지와 더 유사하고 빛 누출이 있으며 그다지 현실적이지 않습니다. 레이 트레이싱의 주변광 폐색은 장면 내 다양한 ​​객체의 폐색 관계를 기반으로 각 픽셀의 AO를 정확하게 계산할 수 있으며 환경에 매우 잘 통합될 수 있습니다.

상위: SSAO; 하단: 레이 트레이싱 AO.

3.2.5 레이 트레이싱을 사용한 전역 조명

레이 트레이싱 모드의 전역 조명은 장면에 여러 개의 빛 바운스를 추가하고 이에 가중치를 부여하여 개체와 개체, 개체와 광원 간의 관계를 물리적으로 더욱 정확하게 만들고 렌더링 효과를 더욱 사실적으로 만듭니다.

상단: 스카이라이트만; 하단: 레이 트레이싱 전역 조명.

위의 내용은 모두 기존 렌더링 기술과 레이 트레이싱의 효과에 대한 정적 비교입니다. 다음 링크는 레이 트레이싱의 특징을 보다 직관적으로 이해할 수 있도록 이들 간의 차이점을 동적으로 비교하는 비디오를 제공합니다.

  • NVIDIA RTX 및 GameWorks Ray Tracing 기술 시연

UE는 경로 추적 렌더링 모드도 제공합니다. 장면 편집 창에서 보기 모드를 경로 추적으로 선택하여 켭니다.

아래는 Ray Tracing과 Path Tracing의 비교 차트입니다.

상단: 레이 트레이싱; 하단: 경로 추적.

3.2.6 레이트레이싱의 기타 기능

UE 에디터에서 위 기능을 활성화하는 것 외에도, 콘솔 명령을 사용하여 보다 세련된 방식으로 레이 트레이싱을 설정할 수도 있습니다:

cpp
// General Settings
r.RayTracing.Reflections [0|1] 
r.RayTracing.Shadows [0|1] 
r.RayTracing.AmbientOcclusion [0|1]

// Material Sorting
r.RayTacing.Reflections.SortMaterials [0|1]

// Shadow Materials
r.RayTracing.Shadows.EnableMaterials [0|1]

// Reflection Screen Percentage
r.RayTracing.Reflections.ScreenPercentage [50|100]

// Maximum Roughness
r.RayTracing.Reflections.MaxRoughness [-1.0 | 0.0-1.0]

// Samples Per Pixel
r.RayTacing.Reflections.SamplesPerPixel [0-N] 
r.RayTacing.AmbientOcclusion.SamplesPerPixel [0-N] 
r.RayTacing.Shadow.SamplesPerPixel [0-N]

// Maximum Bounces
r.RayTracing.Reflections.MaxBounces [0-N]

// Minimum and Maximum Ray Distance
r.RayTracing.Reflections.MinRayDistance [0-N] 
r.RayTracing.Reflections.MaxRayDistance [0-N]

// Lighting in Reflections
r.RayTracing.Reflections.Shadows [0|1] 
r.RayTracing.Reflections.DirectLighting [0|1]
r.RayTracing.Reflections.EmissiveAndIndirectLighting [0|1]

// Height Fogging
r.RayTracing.Reflections.HeightFog [0|1]

// Two Sided Geometry
r.RayTracing.Shadows.EnableTwoSidedGeometry [0|1] 
r.RayTracing.AmbientOcclusion.EnableTwoSidedGeometry [0|1]

// Materials
r.RayTracing.EnableMaterials [0|1]

// Force Opaque
r.RayTracing.DebugForceOpaque [0|1]

// Texture LOD
r.RayTacing.UseTextureLOD [0|1]

// Normal Offset Bias
r.RayTacing.NormalBias <float, default 0.1>

자세한 내용은 언리얼 엔진 4.22의 레이 트레이싱 소개를 참조하세요.

3.3 UE4 레이 트레이싱 디버깅

UE4의 레이 트레이싱은 DXR을 사용하므로 Microsoft의 PIX를 사용하여 UE4 레이 트레이싱 애플리케이션을 디버깅할 수 있습니다.

게다가 UE4 자체는 레이 트레이싱 정보와 성능을 디버그하기 위한 몇 가지 명령과 GUI도 제공합니다.

  • Stat GPU: GPU 레이 트레이싱의 다양한 기능 사용을 추적합니다.

  • Stat D3D12RayTracing: 레이 트레이싱에 사용되는 리소스를 감지합니다.

  • 보기 모드의 디버깅 창: 조명 각 부분의 GBuffer 데이터를 실시간으로 볼 수 있습니다.

3.4 UE4 레이 트레이싱의 단점

RTX, DRX 등의 기술 표준은 아직 초기 단계이기 때문에 플랫폼과 기술 표준에는 결함이 많으며 이는 UE4의 레이 트레이싱에도 존재합니다.

  • 소프트웨어 및 하드웨어에 대한 엄격한 요구 사항.

UE4에서 레이 트레이싱을 켜기 위한 전제조건만으로도 이를 확인할 수 있습니다. 레이 트레이싱을 켠 후 작성자의 RTX 2060은 노이즈 감소 알고리즘 없이 동일한 장면을 렌더링했는데 프레임 속도는 래스터 렌더링의 절반도 안 되는 수준이었습니다.

  • 일부 기존 렌더링 기능은 지원되지 않습니다.

더 구체적으로 말하면, Light Transmission, Volumetric Fog, Light Functions, World Position Offset, Foliage 등은 지원되지 않거나 완전히 지원되지 않습니다.

자세한 내용은 공식 문서인 Ray Tracing 지원 기능을 참조하세요.

  • 화면 소음.

실시간 레이 트레이싱에서는 표면의 각 BxDF에 대해 반구 적분을 수행하는 것이 불가능하므로 중요도 샘플링은 조명 적분을 추정하는 데에만 사용할 수 있습니다. 일반적으로 샘플링 횟수가 부족하기 때문에 매우 낮은 샘플링 횟수(예: 1)만 사용할 수 있으며 조명 적분은 실제 값에서 크게 벗어나므로 특히 그림자에 심각한 노이즈가 형성됩니다. (아래 사진)

다행스럽게도 현재 1-샘플 고잡음 맵을 사용하고 소음 감소 알고리즘을 사용하여 좋은 소음 감소 결과를 얻을 수 있는 NV의 AI 소음 감소와 같이 소음을 줄이는 방법이 많이 있습니다.

위: 1개 샘플링의 원본 노이즈 맵; 하단: 노이즈 감소를 켠 사진입니다.

노이즈 감소 알고리즘에 대한 자세한 내용은 다음을 참조하세요.

NVIDIA RTX를 사용한 게임의 레이 트레이싱(NVIDIA 제공)

  • Real-Time_Rendering_4th-Real-Time_Ray_Tracing

4. UE의 기본 구현

UE 소스 코드의 많은 로직이 레이 트레이싱을 켤지 여부를 결정하므로 영향이 매우 광범위하고 관련된 C++ 및 셰이더 파일의 수가 수백 또는 수천 개에 이릅니다. 셰이더 코드는 주로 다음에 중점을 둡니다.

  • Engine\Shaders\Private\RayTracing\ 디렉터리입니다.

이 디렉토리에는 기본적으로 레이 트레이싱의 모든 기능에 대한 셰이더 구현 코드가 포함되어 있습니다.

  • Engine\Shaders\Private\PathTracing\ 디렉터리입니다.

이 디렉터리에는 셰이더 코드의 경로 추적 버전이 있습니다.

제한된 에너지로 인해 레이 트레이싱과 관련된 모든 논리를 분석하는 것은 불가능합니다. 다음은 Ray Tracing 버전의 전역 조명 셰이더만 분석합니다. 다른 특성(반사, AO, 투명도, 그림자 등)이 있는 셰이더의 경우 UE 소스 코드를 직접 확인할 수 있습니다.

전역 조명 셰이더의 레이 트레이싱 버전과 관련된 파일은 주로 다음과 같습니다.

  • \Engine\Shaders\Private\RayTracing\RayTracingCommon.ush

  • \Engine\Shaders\Private\RayTracing\RayTracingGlobalIlluminationRGS.usf

  • \Engine\Shaders\Private\RayTracing\RayTracingGlobalIlluminationCompositePS.usf

다음은 RayTracingGlobalIlluminationRGS.usf의 코드입니다.

cpp
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.

#include "../Common.ush"
#include "../RectLight.ush"
//#include "../MonteCarlo.ush"
#include "../DeferredShadingCommon.ush"
#include "../ShadingModels.ush"
#include "RayTracingCommon.ush"
#include "RayTracingHitGroupCommon.ush"

#include "../PathTracing/Utilities/PathTracingRandomSequence.ush" 
#include "../PathTracing/Light/PathTracingLightSampling.ush"
#include "../PathTracing/Material/PathTracingMaterialSampling.ush"

#define USE_PATHTRACING_MATERIALS 0

// 가속 구조체
RaytracingAccelerationStructure TLAS; 

// RWTexture2D는 텍스처(Texture),뷰(View)(unordered access view,UAV),:https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/sm5-object-rwtexture2d
RWTexture2D<float4> RWGlobalIlluminationUAV;
RWTexture2D<float2> RWRayDistanceUAV;

uint SamplesPerPixel;
uint MaxBounces;
uint UpscaleFactor;
float MaxRayDistanceForGI;
float MaxRayDistanceForAO;
float NextEventEstimationSamples;
float DiffuseThreshold;
bool EvalSkyLight;
bool UseRussianRoulette;
float MaxNormalBias;

// #dxr_todo: Unify with reflections and translucency in RayTracingCommon.ush
uint2 GetPixelCoord(uint2 DispatchThreadId)
{
	uint UpscaleFactorPow2 = UpscaleFactor * UpscaleFactor;

	// TODO: find a way to not interfer with TAA's jittering.
	uint SubPixelId = View.StateFrameIndex & (UpscaleFactorPow2 - 1);

	return DispatchThreadId * UpscaleFactor + uint2(SubPixelId & (UpscaleFactor - 1), SubPixelId / UpscaleFactor);
}

uint CalcLinearIndex(uint2 PixelCoord)
{
	return PixelCoord.y * View.BufferSizeAndInvSize.x + PixelCoord.x;
}

// 을(를) 활용하여 CosineSampleHemisphere샘플링,로써 。
void GenerateCosineNormalRay(
	float3 WorldPosition,
	float3 WorldNormal,
	inout RandomSequence RandSequence,
	out float3 RayOrigin,
	out float3 RayDirection,
	out float RayTMin,
	out float RayTMax,
	out float RayPdf
)
{
	// Draw random variable
	float2 BufferSize = View.BufferSizeAndInvSize.xy;
	uint DummyVariable;
	float2 RandSample = RandomSequence_GenerateSample2D(RandSequence, DummyVariable);

	// Perform cosine-hemispherical sampling and convert to world-space
	float4 Direction_Tangent = CosineSampleHemisphere(RandSample);
	float3 Direction_World = TangentToWorld(Direction_Tangent.xyz, WorldNormal);

	RayOrigin = WorldPosition;
	RayDirection = Direction_World;
	RayTMin = 0.01;
	RayTMax = max(MaxRayDistanceForGI, MaxRayDistanceForAO);
	RayPdf = Direction_Tangent.w;
}

float GetHitT(FMaterialClosestHitPayload HitInfo)
{
	return HitInfo.HitT;
}

bool IsHit(RayDesc Ray, FMaterialClosestHitPayload HitInfo)
{
	return HitInfo.HitT >= 0.0;
}

// 광선(Ray)Shader,즉, 2.3.2 및 의 Ray Generation。
[shader("raygeneration")]
void GlobalIlluminationRGS()
{
	// 초기화(Initialize)현재(Current)의 텍스처(Texture)。
	uint2 DispatchThreadId = DispatchRaysIndex().xy;
	RWGlobalIlluminationUAV[DispatchThreadId] = 0.0;
	RWRayDistanceUAV[DispatchThreadId] = float2(-1.0, 0.0);
	
	// 계산/산출(Calculate)픽셀(Pixel)좌표
	uint2 PixelCoord = GetPixelCoord(DispatchThreadId);
	RandomSequence RandSequence;
	uint LinearIndex = CalcLinearIndex(PixelCoord);
	RandomSequence_Initialize(RandSequence, LinearIndex, View.FrameNumber);

	bool IsUnidirectionalEnabled = false;

	// 페치/가져오기(Fetch)머티리얼(Material)테이블 의 G-Buffer데이터 。
	float2 InvBufferSize = View.BufferSizeAndInvSize.zw;
	float2 UV = (float2(PixelCoord) + 0.5) * InvBufferSize;
	FScreenSpaceData ScreenSpaceData = GetScreenSpaceData(UV);
	// Remap DiffuseColor when using SubsurfaceProfile (GBuffer decoding replaces with 100% albedo)
	if (UseSubsurfaceProfile(ScreenSpaceData.GBuffer.ShadingModelID))
	{
		ScreenSpaceData.GBuffer.DiffuseColor = ScreenSpaceData.GBuffer.StoredBaseColor;
	}
	float Depth = ScreenSpaceData.GBuffer.Depth;
	float3 WorldPosition = ReconstructWorldPositionFromDepth(UV, Depth);
	float3 CameraOrigin = ReconstructWorldPositionFromDepth(UV, 0.0);
	float3 CameraDirection = normalize(WorldPosition - CameraOrigin);
	float3 WorldNormal = ScreenSpaceData.GBuffer.WorldNormal;
	uint ShadingModelID = ScreenSpaceData.GBuffer.ShadingModelID;
	if (ShadingModelID == SHADINGMODELID_UNLIT
		|| ShadingModelID == SHADINGMODELID_TWOSIDED_FOLIAGE
		)
	{
		return;
	}

	// Diffuse color rejection threshold
	float3 DiffuseColor = ScreenSpaceData.GBuffer.DiffuseColor;
	if (Luminance(DiffuseColor) < DiffuseThreshold)
	{
		return;
	}

	float3 Irradiance = 0;
	float HitDistance = 0.0;
	float HitCount = 0.0;
	float AmbientOcclusion = 0.0;
	// 픽셀(Pixel)샘플 수의 。
	for (uint SampleIndex = 0; SampleIndex < SamplesPerPixel; ++SampleIndex)
	{
		// 을(를) 활용하여 Scrambled Halton저불일치 준난수 시퀀스
		uint FrameIndex = View.FrameNumber % 1024;
		RandomSequence_Initialize(RandSequence, LinearIndex, FrameIndex * SamplesPerPixel + SampleIndex);
		RandSequence.Type = 2;

		float3 RayThroughput = 1.0;

		// Russian roulette based on DiffuseColor
		if (UseRussianRoulette)
		{
			uint DummyVariable;
			float RRSample = RandomSequence_GenerateSample1D(RandSequence, DummyVariable);
			float ProbabilityOfSuccess = Luminance(DiffuseColor);
			float ProbabilityOfTermination = 1.0 - ProbabilityOfSuccess;
			if (RRSample < ProbabilityOfTermination) continue;
			RayThroughput /= ProbabilityOfSuccess;
		}

		// Initialize ray
		RayDesc Ray;
		float RayPdf = 1.0;
		// 을(를) 활용하여 샘플링광선(Ray),계산/산출(Calculate)BxDF라이팅 결과 。
#if 1
		GenerateCosineNormalRay(WorldPosition, WorldNormal, RandSequence, Ray.Origin, Ray.Direction, Ray.TMin, Ray.TMax, RayPdf);
		half3 N = WorldNormal;
		half3 V = -CameraDirection;
		half3 L = Ray.Direction;
		float NoL = saturate(dot(N, L));
		FShadowTerms ShadowTerms = { 0.0, 0.0, 0.0 };
		// 레이 트레이싱(Ray Tracing)의 BxDF 및 래스터라이제이션 의 ,는 호출(Call)EvaluateBxDF。
		FDirectLighting LightingSample = EvaluateBxDF(ScreenSpaceData.GBuffer, N, V, L, NoL, ShadowTerms);
		// 계산/산출(Calculate)컬러 채널/패스(Pass)반사(Reflection)계수。
		RayThroughput *= LightingSample.Diffuse / DiffuseColor;
#else
		uint DummyVariable;
		float2 RandSample = RandomSequence_GenerateSample2D(RandSequence, DummyVariable);
		float2 ViewportUV = (PixelCoord.xy + RandSample.xy) * View.BufferSizeAndInvSize.zw;
		Ray.Origin = ReconstructWorldPositionFromDepth(ViewportUV, 0.0f);
		Ray.Direction = normalize(ReconstructWorldPositionFromDepth(ViewportUV, 1.f) - Ray.Origin);
		Ray.TMin = 0.0;
		Ray.TMax = 1.0e12;
		float3 RayThroughput = 1.0;
#endif
		Ray.TMax = max(MaxRayDistanceForGI, MaxRayDistanceForAO);
        ApplyPositionBias(Ray, WorldNormal, MaxNormalBias);
		
		float MaterialPdf = 0.0;
		uint Bounce = 0;
		// 에 따라 반사(Reflection)회 ,처리(Process)반사(Reflection)
		while (Bounce < MaxBounces)
		{
			// 계산/산출(Calculate)광선(Ray)
			uint RayFlags = 0;
			FRayCone RayCone = (FRayCone)0;
			// TraceRayInternal는 UE캡슐화 의 인터페이스 ,호출(Call)TraceRay로써 및 Payload데이터 。
			FMaterialClosestHitPayload Payload = TraceRayInternal(
				TLAS,   // AccelerationStructure
				RayFlags,
				RAY_TRACING_MASK_OPAQUE,
				RAY_TRACING_SHADER_SLOT_MATERIAL, // RayContributionToHitGroupIndex
				RAY_TRACING_NUM_SHADER_SLOTS,     // MultiplierForGeometryContributionToShaderIndex
				0,      // MissShaderIndex
				Ray,    // RayDesc
				RayCone
			);

			// Environment hit
			// 만약 광선(Ray) 및 ,이면 。
			if (!IsHit(Ray, Payload))
			{
				// Optional multi-bounce SkyLight contribution
				if (EvalSkyLight && Bounce > 0)
				{
					uint SkyLightId = 0;
					float3 EnvironmentRadiance = 0.0;
					SkyLight_EvalLight(SkyLightId, Ray.Direction, Ray, EnvironmentRadiance);
					Irradiance += EnvironmentRadiance * RayThroughput / RayPdf;
				}
				break;
			}
			// #dxr_todo: Allow for material emission?

			if (Bounce == 0)
			{
				HitDistance += Payload.HitT;
				HitCount += 1.0;
				if (Payload.HitT < MaxRayDistanceForAO)
				{
					AmbientOcclusion += 1.0;
				}
			}
			if (Payload.HitT > MaxRayDistanceForGI) break;

			// Update intersection
			Ray.Origin += Ray.Direction * Payload.HitT;

			// Create faux GBuffer to use with EvaluateBxDF
			FGBufferData GBufferData = (FGBufferData)0;
			GBufferData.Depth = 1.f; // Do not use depth
			GBufferData.WorldNormal = Payload.WorldNormal;
			GBufferData.BaseColor = Payload.BaseColor;
			GBufferData.CustomData = Payload.CustomData;
			GBufferData.GBufferAO = Payload.GBufferAO;
			GBufferData.IndirectIrradiance = (Payload.IndirectIrradiance.x + Payload.IndirectIrradiance.y + Payload.IndirectIrradiance.z) / 3.f;
			GBufferData.SpecularColor = Payload.SpecularColor;
			GBufferData.DiffuseColor = Payload.DiffuseColor;			
			GBufferData.Metallic = Payload.Metallic;
			GBufferData.Specular = Payload.Specular;
			GBufferData.Roughness = Payload.Roughness;
			GBufferData.ShadingModelID = Payload.ShadingModelID;
			GBufferData.CustomData = Payload.CustomData;

			// 의 (Perform next-event estimation)。
			// NextEventEstimationSamplesr.RayTracing.GlobalIllumination.NextEventEstimationSamples설정(Set)。
			float SplitFactor = 1.0 / NextEventEstimationSamples;
			for (uint NeeTrial = 0; NeeTrial < NextEventEstimationSamples; ++NeeTrial)
			{
				// Light selection
				int LightId;
				float3 LightUV;
				float NeePdf = 0.0;
				uint DummyVariable;
				float4 RandSample4 = RandomSequence_GenerateSample4D(RandSequence, DummyVariable);
				SampleLight(Ray, Payload, RandSample4, LightId, LightUV, NeePdf);

				if (NeePdf > 0.0)
				{
					RayDesc LightRay;
					GenerateLightRay(Ray, LightId, LightUV, LightRay);
					ApplyPositionBias(LightRay, Payload.WorldNormal, MaxNormalBias);

					// Trace visibility ray
					uint RayFlags = RAY_FLAG_ACCEPT_FIRST_HIT_AND_END_SEARCH | RAY_FLAG_SKIP_CLOSEST_HIT_SHADER;
					FRayCone LightRayCone = (FRayCone)0;
					FMaterialClosestHitPayload NeePayload = TraceRayInternal(
						TLAS,   // AccelerationStructure
						RayFlags,
						RAY_TRACING_MASK_OPAQUE,
						RAY_TRACING_SHADER_SLOT_MATERIAL, // RayContributionToHitGroupIndex
						RAY_TRACING_NUM_SHADER_SLOTS,     // MultiplierForGeometryContributionToShaderIndex
						0,      // MissShaderIndex
						LightRay,    // RayDesc
						LightRayCone
					);

					// No hit indicates successful next-event connection
					if (!IsHit(LightRay, NeePayload))
					{
						// Evaluate radiance
						float3 Radiance;
						EvalLight(LightId, LightUV, LightRay, Radiance);

						// Evaluate material
						float3 MaterialThroughput;
						float MaterialEvalPdf = 0.0;
#if USE_PATHTRACING_MATERIALS
						EvalMaterial(Ray.Direction, LightRay.Direction, Payload, MaterialThroughput, MaterialEvalPdf);
#else
						half3 N = Payload.WorldNormal;
						half3 V = -Ray.Direction;
						half3 L = LightRay.Direction;
						float NoL = saturate(dot(N, L));
						FShadowTerms ShadowTerms = { 0.0, 0.0, 0.0 };
						FDirectLighting LightingSample = EvaluateBxDF(GBufferData, N, V, L, NoL, ShadowTerms);
						MaterialThroughput = LightingSample.Diffuse;
						MaterialEvalPdf = 1.0;
#endif
						// Apply material Pdf for correct MIS weight
						float MisWeight = 1.0;
#if 0
						if (IsUnidirectionalEnabled && IsPhysicalLight(LightId))
						{
							MisWeight = NeePdf / (NeePdf + MaterialEvalPdf);
						}
#endif
						// Record the contribution
						float3 ExitantRadianceSample = Radiance * MaterialThroughput * RayThroughput * SplitFactor * MisWeight / (NeePdf * RayPdf);
						Irradiance += isfinite(ExitantRadianceSample) ? ExitantRadianceSample : 0.0;
					}
				}
			}

			// 처리(Process)머티리얼(Material)샘플링。
			// dxr_todo: only worth doing when Bounce + 1 < MaxBounces
			if (Bounce + 1 < MaxBounces)
			{
				float3 Direction;
				float3 Throughput = 1.0;
#if USE_PATHTRACING_MATERIALS
				uint DummyVariable;
				float4 RandSample = RandomSequence_GenerateSample4D(RandSequence, DummyVariable);
				// 샘플링머티리얼(Material),에 따라 반사(Reflection)、반사(Reflection)투과 、라이팅 타입/유형 샘플링。
				SampleMaterial(Ray.Direction, Payload, RandSample, Direction, Throughput, MaterialPdf);
#else
				float3 RayOrigin = Ray.Origin;
				GenerateCosineNormalRay(RayOrigin, Payload.WorldNormal, RandSequence, Ray.Origin, Direction, Ray.TMin, Ray.TMax, MaterialPdf);
				
				half3 N = Payload.WorldNormal;
				half3 V = -Ray.Direction;
				half3 L = Direction;
				float NoL = saturate(dot(N, L));
				FShadowTerms ShadowTerms = { 0.0, 0.0, 0.0 };
				FDirectLighting LightingSample = EvaluateBxDF(GBufferData, N, V, L, NoL, ShadowTerms);
				Throughput = LightingSample.Diffuse;
#endif
				// #dxr_todo: Degenerate guard?
				if (MaterialPdf <= 0.0)
				{
					break;
				}

				// Update ray
				Ray.Direction = Direction;
				RayThroughput *= Throughput;
				RayPdf *= MaterialPdf;

				// #dxr_todo: Russian roulette?

				// #dxr_todo: Firefly rejection?
			}

			Bounce++;
		}
	}
	
	// 및 AO필수: 정규화(Normalize),가중치(Weight)。
	if (SamplesPerPixel > 0)
	{
		Irradiance /= SamplesPerPixel;
		AmbientOcclusion /= SamplesPerPixel;
	}

	if (HitCount > 0.0)
	{
		HitDistance /= HitCount;
	}
	else
	{
		HitDistance = -1.0;
	}

	AmbientOcclusion = saturate(AmbientOcclusion);

#if USE_PREEXPOSURE
	Irradiance *= View.PreExposure;
#endif

	Irradiance = ClampToHalfFloatRange(Irradiance);
	RWGlobalIlluminationUAV[DispatchThreadId] = float4(Irradiance, AmbientOcclusion);
	RWRayDistanceUAV[DispatchThreadId] = float2(HitDistance, SamplesPerPixel);
	// For AO denoiser..
	//RWRayDistanceUAV[DispatchThreadId] = float2(Luminance(Irradiance), HitDistance);
}

// 2.3.2 및 의 Miss Shader。
[shader("miss")]
void RayTracingGlobalIlluminationMS(inout FPackedMaterialClosestHitPayload PackedPayload)
{
	PackedPayload.HitT = -1;
}

// 2.3.2 및 의 Closest Hit Shader。
[shader("closesthit")]
void RayTracingGlobalIlluminationCHS(inout FPackedMaterialClosestHitPayload PackedPayload, in FDefaultAttributes Attributes)
{
	// 포인트 처리(Process)Payload데이터 (HitT、노멀(Normal)),로써 shader을(를) 활용하여 。
	FMaterialClosestHitPayload Payload = (FMaterialClosestHitPayload)0;
	Payload.HitT = RayTCurrent();

	FTriangleBaseAttributes Triangle = LoadTriangleBaseAttributes(PrimitiveIndex());
	float3 Edge0 = Triangle.LocalPositions[2] - Triangle.LocalPositions[0];
	float3 Edge1 = Triangle.LocalPositions[1] - Triangle.LocalPositions[0];
	float3x3 WorldToLocal = (float3x3)WorldToObject();
	float3x3 LocalToWorldNormal = transpose(WorldToLocal);
	Payload.WorldNormal = normalize(mul(LocalToWorldNormal, cross(Edge0, Edge1)));

	PackedPayload = PackRayTracingPayload(Payload, PackedPayload.RayCone);
}

위에서 볼 수 있듯이 UE가 레이 트레이싱 전역 조명을 처리할 때 픽셀당 샘플 수인 SamplesPerPixel과 최대 반사 수인 MaxBounces를 기반으로 다양한 샘플링 전략을 사용하고 Next-Event 평가, 경로 추적 등을 고려하므로 전체 프로세스가 더 복잡해집니다.

이 섹션에서는 전역 조명의 셰이더에 대해서만 분석하지만, UE가 레이 트레이싱을 처리하는 과정과 기술을 엿볼 수 있어 레이 트레이싱의 구현 및 적용을 보다 자세히 이해할 수 있습니다.

5. 요약

이 글은 레이 트레이싱의 개념, 특징, 역사, 적용으로 시작하여 레이 트레이싱의 원리와 일반적인 의사코드 구현 형태를 소개하고 RTX 및 DXR 기술을 소개하며 마지막으로 UE의 사용법과 내부 구현을 분석합니다. 레이 트레이싱에 관한 비교적 체계적이고 포괄적인 기술 논문이라고 볼 수 있습니다.

물론 레이 트레이싱의 전체와 미래를 이 글에 반영할 수는 없습니다. 시간이 지남에 따라 점점 더 새로운 레이 트레이싱 기술이 점차 등장하고 있습니다. 이미지 렌더링 실무자로서 당신은 항상 학습 동기와 탐색 속도를 유지해야 합니다.

레이 트레이싱 기술은 이제 시작점일 뿐 결코 끝이 아닙니다.

미래는 이제 막 시작되었습니다!

특별 지침

  • 모든 참고문헌의 저자에게 감사드립니다!

  • 원본 기사, 모든 권리 보유, 복제는 허용되지 않습니다!








📚 참고 자료 및 공식 링크 (References)


🔗 연관 지식 베이스 (Wiki & Tools)

Based on the legendary technical series by Timlly (0向往0 / 毛星云)