그래픽스

물리 기반 렌더러 - 레이 트레이싱 (1)

게임 개발 2023. 10. 13. 02: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번의 예제를 잘 따라했다면,

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

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

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

 

 

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

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

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

 

 

 

 

카메라 ( Cameras )

 

카메라 모델은 장면의 이미지가 어떻게 센서에 저장되며
어디서 어떻게 장면을 보여줄 것인가에 대한 것을 결정한다.

화면에 보이지 않는 곳 까지 레이 트레이싱을 한다면 많은 비용을 지불해야한다.

레이 트레이싱은 연산 비용이 굉장히 크다.

때문에 카메라와 물체 현상의 시뮬레이션은 밀접한 관계가 있다.

 

광선 물체 교차점 ( Ray–object intersections )

 

광선이 주어진 기하학 물체를 정확히 어떤 지점에서 뚫는지,

그리고 교차점에서 물체의 표면 법선이나 재질같은 기하학적 특성을 특정할 수 있어야 한다.

쉽게 말해, 해당 레이 트레이싱에 충돌한 물체의 재질 같은 거다.

예시는 어떤 광선 ( = 햇빛) 에  어떠한 물체의 Hit가 일어났다.

그 물체가 소가 된다면 그 물체의 재질은 불투명도가 높을 것이다.

물이면 투명도가 높을 것이다.

반사의 정도 또한 다를 것이다. 이러한 것 특징을 가진 것은 광선 물체의 교차점이 되며,

레이 트레이싱의 교차점은 기하학적 특성에 영향을 받고 이러한 기하학적 특성을 특정할 수 있어야 한다.

대부분의 레이 트레이서는 여러 물체에 교차점을 테스트할 수 있으며,

보통 광선에 가장 가까운 교차점을 반환한다.

 

광원 ( Light sources )

 

광원은 빛이 나오는 곳 즉슨 조명이다.

광원이 없으면 반사체를 그려봤자다 의미가 거의 없다.

광원이 조금이라도 있어야 한다.

광원을 가장 잘 살리는 장면은 보는 이로 하여금 기시감을 불러일으킨다.

가상의 이미지임에도 불구하고 거부감이 없는 것이다.

이는 게임 렌더러에 필수적인 역할을 한다.

게임의 몰입감을 위해 레이트레이싱의 역할도 중요하지만,

레이 트레이싱을 이용한 광원도 잘 살려야한다.

빛의 분포를 모델링하고 빛 자체의 위치 뿐만 아니라

이들의 에너지가 공간에 분포되는 방식이 광원이다.

 

 

가시성 ( Visibility )

 

빛이 표면의 지점에서 에너지를 축적하는지 알기 위해

차단되지 않은 지점에서 광원까지의 경로를 알아야한다.

다행히 레이트레이서에서는 표면에서 광원을 향한 광선을 만들어

가장 가까운 광선-물체 교차점을 찾아서 광원과의 거리와 비교하면 매우 간단히 알 수 있다.

 

표면 및 산란 ( Surface scattering )

 

각각의 물체는 외관에 대한 설명,

빛이 표면에서 어떻게 반응하는지에 대해 재방사 혹은 산란된 빛의 속성 들의 정보를 제공해야 한다.

표면 산란 모델은 보통 매개변수화(= parameterized) 되어, 다양한 모습을 시뮬레이션할 수 있다.

 

간접 빛 전송( Indirect light transport )

 

빛이 다른 표면을 통해 반사되거나 투과된 뒤에 표면에 도달할 수 있으므로,

일반적으로 이 효과를 완전히 포착하려면 표면에서 시작되는 추가적인 광선을 추적해야 할 필요가 있다.

 

광선 전파( Ray propagation )

 

공간을 지나는 빛이 광선을 따라 진행할 때 어떻게 되는지 알아야 한다.

진공의 장면을 렌더링한다면 빛 에너지는 항상 일정하다.

진정한 진공은 지구 상에서는 드물지만, 많은 환경에 대해 합리적인 근사값이 된다.

( = they are a reasonable approximation for many environments. )

좀 더 복잡한 모델은 안개, 연기, 지구의 대기 등에서 광선의 전파를 추적할 수 있다.

 

짧게 각각의 시뮬레이션 작업에 대한 색션에 대해 논해보았다.

 

 

다음 게시글에서 기본적인 시뮬레이션 구성 요소,

pbrt’s high-level interface 에 대해 논하며, 각각의 섹션에 대해 좀 더 심도 있는 설명을 추가할 것이다.

주 렌더링 루프(= 렌더링 반복 작업)를 통해 단일 광선의 진행하는 경우를 따라가 볼 것이다.

또한 터너 휘터드 ( Turner Whitted’s ) 의

오리지날 레이트레이싱 알고리즘을 기반으로 한 표면 산란을 구현해볼 것이다.

 

우선, 해당 게시글에서 레이 트레이싱이란 개념을 확고하게 잡아가야지

다음 토픽에서 레이 트레이싱 알고리즘에 대한 깊은 이해가 가능해 질 것이다.

 

무언가를 이해할 때 좋은 방법 중 하나는 비교, 대조이다.

 

그래픽스 기술에 사실적 표현에 과연 레이 트레이싱만 있었을까?

아니다. 아래 사진을 보면 레스터화 패쓰 트레이스 레이 트레이싱에 대한 세 가지 예시가 있다.

 

 

출저 NVIDA 블로그

 

보이는 바와 같이 Path Traced 역시 훌륭한 그래픽스 기술이다.

하지만 레이 트레이싱에 왜 각종 게임이 집중하느냐?

이를 알기 위해서는 우선 몇 가지 용어를 설명하고 이해해보는 데에서 시작해보겠다.

 

래스터화는 단일 관점에서 보는 이미지를 생성하는 기술이다.

NVIDIA GPU는 초당 1000억 개의 이상의 래스터화된 픽셀 생성이 가능하다.

따라서 게이밍과 같은 실시간 그래픽에 이상적인 기술이다.

 

레이 트레이싱은 레스터화보다 더욱 강력한 기술이다.

더욱 비용이 크지만 더욱 사실적인 묘사가 가능하다는 뜻이다.

단일 지점에서 보이는 것을 알아내는 제약에서 벗어나

여러 방향으로 여러 지점에서 보이는 것이 무엇인지 결정할 수 있다는 것이다.

GPU는 레이 트레이싱을 계산하기 위해 초당 수십억 개의 광선이 추적 가능해야한다는 의미이다.

 

이러한 모든 광선을 추적할 수 있는 GPU가 있다면

실제 빛이 산란되는 방식을 레스터화로 묘사 가능한 것 보다

더욱 정확하게 시뮬레이션 할 수 있어야하는 비용 부담이 발생한다는 의미이다.

 

해당 설명을 통해 어떻게 레이 트레이싱이 고 비용을 요구하게 되는지,

왜 더 사실적인 묘사가 가능한지에 대한 이해가 된다.

 

이러한 광선 추적을 통해 물체를 추적하는 레이 트레이싱은 정확히 무엇인가를 안다면,

물리법칙에 의한 렌더링 훨씬 이해가 빠를 것이다.

 

 

 

 

 

 

 

'그래픽스' 카테고리의 다른 글

Mipmap_Normal Map_Diffusion Map  (0) 2024.07.23
밉맵(Mipmap)  (1) 2023.12.21
물리 기반 렌더러 - 레이 트레이싱 (2) - 카메라  (0) 2023.10.16