그래픽스/DX12

레이 트레이싱 Ray-trace 예제와 원리

게임 개발 2023. 10. 15. 17:17

극사실적 렌더링의 목표는 사진과 구분할 수 없을 정도의 3D 장면 이미지를 생성한 것이다.

거의 대부분의 극사실적 렌더링 시스템은 레이트레이싱(ray-tracing)알고리즘에 의존한다.

 

사실 레이트레이싱은 매우 단순한 알고리즘이다.

환경에 있는 물체와 간섭하고 반사되는 장면을 지나는 광선 경로를 추적하는 방법이다.

 

레이 트레이싱에 깊게 알기 앞서,

DX12 API에 있는 레이 트레이싱 샘플을 살펴보자. (번역 수준이 높지 않으니 원문을 통해 이해하길 추천)

- 출저, NVIDIA DEVELOPER (https://developer.nvidia.com/rtx/raytracing/dxr/dx12-raytracing-tutorial-part-1)

By Martin-Karl Lefrançois and Pascal Gautron

 

해당 예제의 목적은

DX12에서 레이 트레이싱 및 래스터 패쓰에

동일한 기하 버퍼를 공유하도록 기존 프로그램에 레이트레이싱을 추가하는 방법이다.

 

단계별로 다음과 같다.

 

1. 레이 트레이싱 기능 및 활성화

2. 고성능 레이-기하 교차 기능을 제공하는 저상 및 최상위 가속 구조 (BLAS 및 TLAS) 생성

3. 셰이더의 생성 및 추가 : 레이 생성 및 적중 혹은 미스,

어떻게 새로운 레이를 생성할지에 대한 설명

4. 레이 제너레이션, 적중 및 미스 다양한 경우의 레이 트레이싱의 파이프라인 생성은

레이트레이싱 과정에 사용되는 모든 셰이더를 하나로 통합하는 과정.

5. 기하 형상을 해당 셰이더와 연결하는 셰이딩 바인딩 테이블 생성

 

레이 트레이싱 활성화 코드는 다음과 같다.

 

1) 레이트레이싱 지원 장치 선언

ComPtr<id3d12device5> m_device;
ComPtr<id3d12graphicscommandlist4> m_commandList;

 

 

2) 레이트레이싱 지원 확인

// D3D12HelloTriangle.h

void CheckRaytracingSupport();
//D3D12HelloTriangle.cpp


void D3D12HelloTriangle::CheckRaytracingSupport() {
    D3D12_FEATURE_DATA_D3D12_OPTIONS5 options5 = {};
    ThrowIfFailed(m_device->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS5,
                                                &options5, sizeof(options5)));
    if (options5.RaytracingTier < D3D12_RAYTRACING_TIER_1_0)
      throw std::runtime_error("Raytracing not supported on device");
}

해당 함수의 본문에서 레이 트레이싱 기능은 D3D12_FEATURE_DATA_D3D12_OPTIONS5 내 일부에 있다.

이후 코드 끝에 우리는 다음과 같은 메서드 OnInit 를 추가해준다.

 

    // Check the raytracing capabilities of the device
    CheckRaytracingSupport();

 

또 우리는 래스터와 레이 트레이싱을 바꿔주는 기능인 스페이스바에 추가해주면 된다.

 

    virtual void OnKeyUp(UINT8 key);
    bool m_raster = true;

 

3) OuInit() 메서드

 

아까 설명했던 OuInit 메서드에 대하여 의문을 가졌을 것이다.

이번 섹션에서 조금 더 이해가기 쉽게 자세하게 설명하겠다.

OnInit()은 D3D12HelloTriangle 샘플의 원본에서의 메서드 LoadAssets 의 역할은

명령 목록을 만들고, 초기화하고, 닫아주는 역할을 수행한다.

레이 트레이싱 구성에는 이러한 역할을 수행하는 명확한 오픈 코맨드 리스트가 필요하며,

해당 예제에서 선호하는 방법은 OnInit 메서드로 레이 트레이싱을 초기화하는 것이다.

따라서, 우리는 LoadAssets() 라는 해당 함수가 따라 붙어야 하며,

그 함수를 놓은 뒤에는 OnInit() 을 호출해줘야 한다.

 

4) LoadPipeline()

 

해당 예제에서 꼭 필요로 하는 설정은 아니지만,

일관성을 위해서 예제 작성자가 feature level을 D3D_FEATURE_LEVEL_12_1로 변경 가능하다.

 

++ plus

 

directx feature level 이 뭐냐?

(참고 사이트 : https://learn.microsoft.com/en-us/windows/win32/direct3d12/hardware-feature-levels)

하드웨어 기능 수준에서 GPU 기능 집합이다.

 

CS지식에서 병렬형 컴퓨터라고 규모에 따라

소규모 멀티코어 환경과 대규모 병렬 컴퓨팅 환경이 나뉘는데

우리같은 사용자가 주로 쓰는 환경은 소규모 멀티 코어 환경이다.

해당 소규모 멀티 코어환경에서는 많은 소프트웨어 개발 환경이 지원되며

좀 더 과거에 비해 쉽게 병렬 프로그래밍이 원활하게 돌아가도록 지원해주는 기능이 많다.

그 중 하나가 GPGPU인데,

내 생각에는 GPGPU같은 소프트웨어 개발 환경 지원인 거 같다.

해당 주제 관련 포스팅은
따로 공부한 뒤 10월 말 안으로 DX12 목록에 포스팅 업로딩 하겠다.

 

++

 

5) PopulateCommandList()

버퍼를 지우고 그리기 명령을하는 블록을 만들어준다.

const float clearColor[] = { 0.0f, 0.2f, 0.4f, 1.0f };
m_commandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
m_commandList->ClearRenderTargetView(rtvHandle, clearColor, 0, nullptr);
m_commandList->IASetVertexBuffers(0, 1, &m_vertexBufferView);
m_commandList->DrawInstanced(3, 1, 0, 0);

그리고 이 블록을 레스터화 모드에서만 실행될 수 있도록 다음과 같은 코드로 작성한다.

이는 레이 트레이싱 경로에 들어오면 이 버퍼에 색상을 다른 색상으로 간단하게 바꿔주기만 하면 된다.

// Record commands.
// #DXR
if (m_raster)
{
    const float clearColor[] = { 0.0f, 0.2f, 0.4f, 1.0f };
    m_commandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
    m_commandList->ClearRenderTargetView(rtvHandle, clearColor, 0, nullptr);
    m_commandList->IASetVertexBuffers(0, 1, &m_vertexBufferView);
    m_commandList->DrawInstanced(3, 1, 0, 0);
}
else
{
    const float clearColor[] = { 0.6f, 0.8f, 0.4f, 1.0f };
    m_commandList->ClearRenderTargetView(rtvHandle, clearColor, 0, nullptr);
}

 

6) OnKeyUp()

래스터 모드와 광선 추적 모드를 전환하기 위해 다음 함수를 추가한다.

 

//-----------------------------------------------------------------------------
//
//
void D3D12HelloTriangle::OnKeyUp(UINT8 key)
{
    // Alternate between rasterization and raytracing using the spacebar
    if (key == VK_SPACE)
    {
    m_raster = !m_raster;
    }
}

 

 

7) WindowProc()

 

7번은 꼭 필요하지는 않지만, ESC 키를 누르면 종료할 수 있는 편리함을 더해줄 수 있다.

Win32Application.cpp  파일 안에 WindowProc 에서 WM_KEYDOWN 캐이스의

해당 코드를 추가하여, 응용 프로그램을 종료하도록 하면 된다.

 

if (static_cast<uint8>(wParam) == VK_ESCAPE)
    PostQuitMessage(0);

 

1~7번의 예제를 잘 따라했다면,

다음과 같은 결과를 확인 할 수 있다.

스페이스바를 통해서 래스터 모드와 레이트레이싱 모드 사이를 전환하며 확인 할 수 있고,

아직 레이트레이싱을 수행하고 있지는 않지만, 이를 통해서 레이 트레이싱을 가동시키는 코드를 연습해 볼 수 있다.

 

 

레이 트레이싱을 작성하는 데 위 예제와 달리 많은 방법이 있다지만,

모든 렌더링 시스템은 레이 트레이싱을 위해

최소한 다음과 같은 물체와 현상을 시뮬레이션한다.