OptiX7 環境構築と大まかな流れ

# はじめに
<!-- 発端や概要を記載 -->

RTXに対応しリアルタイムレンダリングで勢いを見せるOptiX 7の環境構築を行い、大まかな流れを学習がてらにまとめました。

NVIDIA公式のチュートリアルとSIGGRAPH2019でのプレゼンテーションを参考にしてます。

https://github.com/ingowald/optix7course

https://dl.acm.org/doi/10.1145/3305366.3340297



# この記事では
1. OptiX 環境構築 (Windows) の解説
2. 簡単なOptiXでのレイトレーシングプログラムの大まかな流れを解説

# 目次
<!-- タイトルとアンカー名を編集 -->
1. [OptiX 環境構築](#Chapter1)
2. [OptiXレイトレーシングの大まかな流れ](#Chapter2)
3. [感想](#Chapter3)
4. [参考文献](#reference)

<!-- 各チャプター -->
<a id="#Chapter1"></a>
# OptiX 環境構築
### インストール

- NVIDIA製GPU (Maxwellまたはそれ以降、RTX推奨)
- Visual Studio 2017 または 2019
- CUDA 10.1 以降
    - https://developer.nvidia.com/cuda-10.1-download-archive-base
- OptiX 7
    - https://developer.nvidia.com/optix

### サンプルプロジェクトのダウンロード

以下のGitHubリポジトリから今回のサンプルプロジェクトをダウンロード

https://github.com/ingowald/optix7course

### プロジェクトのビルド

CMakeのGUIを開き、` source directory` にダウンロードしたサンプルプロジェクトを指定、` build directory`に`build`フォルダーを指定して新規フォルダーを作成する。

`OptiX_INCLUDE`と`OptiX_INCLUDE_DIR`にはインストールしたOptiXのルートディレクトリと`include`フォルダを指定する。

<img width="600" alt="CMake 3.18.0 - D__Documents_OptiX_optix7course_build 3_3_2021 4_22_51 PM.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1014868/c2781c28-3ffb-4e71-a0d9-fb4761cd0439.png">

すべて正しくインストールされていれば`Configure`をした後に`Generate`でビルドが成功する。

### サンプルプロジェクトの実行

`build` 内のVisual Studioプロジェクトを開き、Visual Studioでビルドすれば`.exe`実行ファイルができ、実行できる。

<a id="#Chapter2"></a>
# OptiXレイトレーシングの大まかな流れ

OptiX 7 では Optix 6 に比べ、自由度が高まった反面、画像を描画するだけの簡単なプログラムでもメモリの確保、シェーダーバインディングテーブルの設定などやることが多くなってしまったので下のような画像を描画するのに必要な最小限の作業をまとめてみる。


<img width="300" alt="osc_example2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1014868/d2d3ca38-49db-8124-54e9-ca3cc2781e45.png">

ところどころ省略しているため実際のコードは `examples02_pipelineAndRayGen` を参照してください。

画像を描画するためには必要な工程は４つに分けられます。

1.  デバイスサイドでのレイ生成関数の定義
2. OptiX パイプラインの作成
3. シェーダーバインディングテーブルの作成
4. レイトレーシングの開始


### 1. デバイスサイドでのレイ生成関数の定義


まずはデバイス側（GPU）に送りたい情報を格納する構造体を定義する。この例ではレンダリング結果の色を格納するバッファーのみだが、カメラ位置などレイトレーシングの計算に関わる情報はここに格納することになる。

```cpp
// LaunchParams.h
struct LaunchParams
{
    int       frameID { 0 };
    uint32_t *colorBuffer;
    vec2i     fbSize;
};
```

デバイス側で先ほどの構造体をグローバル関数として定義する。
それぞれのピクセルからのレイ（光源）の動作を担当する`__raygen__renderFrame()
`関数を定義し、任意の色をバッファーに入れる。

今回はレイ生成の関数しか使わないが、レイトレーシングの一連の流れに必要な関数 (`__miss__radiance()`, `__anyhit__radiance()`, `__closesthit__radiance()`など）は定義しておかなければならない。



```cuda
// devicePrograms.cu
extern "C" __constant__ LaunchParams optixLaunchParams;

// レイ生成の挙動を定義
extern "C" __global__ void __raygen__renderFrame()
{
    ...
    optixLaunchParams.colorBuffer[fbIndex] = rgba;
}

// レイがどのオブジェクトにも当たらなかった時の挙動を定義
extern "C" __global__ void __miss__radiance()
{ /*! for this simple example, this will remain empty */ }
```

### 2. OptiX パイプラインの作成

レイトレーシングに必要な、レイの定義、物体に当たった時、当たらなかった時などを関数として定義していきます。

#### 2a. OptiXの初期化、コンテントの作成

まずはOptiXの初期化、GPUの選択、OptiXコンテントの作成を行う。

```cpp
// SampleRenderer.cpp
void SampleRenderer::initOptix()
{
    optixInit();    // OptiXを初期化する

    ...

    SetDevice(...);    // 使用するGPUの番号を指定
    StreamCreate(...);    // ストリームの作成
    cuCtxGetCurrent(...);    // 使用するGPUデバイスの情報を入手
    optixDeviceContextCreate(...);    // OptiX コンテントを作成

    ...
}
```

#### 2b. OptiX モジュールの作成

次に、作成するプログラムの設定（モーションブラーの有無など）を設定した上でOptiXモジュールを作成することでパイプラインを最大限に最適化してくれる。

```cpp
// SampleRenderer.cpp
void SampleRenderer::createModule()
{
    moduleCompileOptions = ...
    pipelineCompileOptions = ...\
    optixModuleCreateFromPT(...);
}
```

#### 2c. プログラムグループの設定

レイトレーシングに関わる関数それぞれの紐づけを行う。

```cpp
// SampleRenderer.cpp
void SampleRenderer::createRaygenPrograms()
{
    OptixProgramGroupOptions pgOptions = {};
    OptixProgramGroupDesc pgDesc    = {};
    pgDesc.kind                     = OPTIX_PROGRAM_GROUP_KIND_RAYGEN;
    pgDesc.raygen.module            = module;           
    pgDesc.raygen.entryFunctionName = "__raygen__renderFrame";    // レイ生成関数を開始点とする

    optixProgramGroupCreate(...);
}

void SampleRenderer::createMissPrograms()
{
    ... // 同様
}
```

#### 2d. パイプラインの作成

上記で定義したレイトレーシングに関わる関数をひとまとめにし、パイプラインを作成する。

```cpp
// SampleRenderer.cpp
void SampleRenderer::createPipeline()
{
    std::vector<OptixProgramGroup> programGroups;
    for (auto pg : raygenPGs)
      programGroups.push_back(pg);
    for (auto pg : missPGs)
      programGroups.push_back(pg);
    for (auto pg : hitgroupPGs)
      programGroups.push_back(pg);

    optixPipelineCreate(...);
}
```

### 3. シェーダーバインディングテーブルの作成

パイプライン中の各関数にユーザー定義の変数などを格納した上でデバイスにアップロードする。

```cpp
// SampleRenderer.cpp
void SampleRenderer::buildSBT()
{
    std::vector<RaygenRecord> raygenRecords;
    for (int i=0;i<raygenPGs.size();i++) {
      RaygenRecord rec;
      OPTIX_CHECK(optixSbtRecordPackHeader(raygenPGs[i],&rec));
      rec.data = nullptr; /* for now ... */
      raygenRecords.push_back(rec);
    }
    raygenRecordsBuffer.alloc_and_upload(raygenRecords);
    sbt.raygenRecord = raygenRecordsBuffer.d_pointer();

    std::vector<MissRecord> missRecords;
    ... // 同様
}
```

### 4. レイトレーシングの開始

フレームバッファーを作成し、レイトレーシングを開始する。

```cpp
// SampleRenderer.cpp
void SampleRenderer::render()
{
    launchParamsBuffer.upload(&launchParams,1);
    optixLaunch(...);
}
```

以上をビルド、実行すると同フォルダに画像が書き出されるはずです。



<a id="#Chapter3"></a>
# 感想

OpenGLなど他のAPIと比べると確かに導入部分が大変な気がする。ただここでの自由なメモリー確保などが最適化やカスタマイズの自由度に貢献していそう。

シャドーレイなど複数のレイの定義、Acceleration Structure、リアルタイムノイズ除去などできることがたくさんあり、一度基盤ができてしまえば機能の付け足しは比較的簡単な模様。

Example 12ではソフトシャドウとノイズ除去を実装しており、リアルタイムで以下のようなクオリティーのレンダリングを行うことができる。RTX 2070 で実行したが60FPS以上出てるっぽい。

![Optix 7 Course Example 3_4_2021 1_33_56 AM.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1014868/cf6be4a5-d476-b621-99db-d903a5a715f5.png)

<a id="#reference"></a>
# 参考文献
- [RTX accelerated ray tracing with OptiX](https://dl.acm.org/doi/10.1145/3305366.3340297)
- [ingowald/optix7course](https://github.com/ingowald/optix7course)