はじめに
RTXに対応しリアルタイムレンダリングで勢いを見せるOptiX 7の環境構築を行い、大まかな流れを学習がてらにまとめました。
NVIDIA公式のチュートリアルとSIGGRAPH2019でのプレゼンテーションを参考にしてます。
この記事では
- OptiX 環境構築 (Windows) の解説
- 簡単なOptiXでのレイトレーシングプログラムの大まかな流れを解説
目次
OptiX 環境構築
インストール
- NVIDIA製GPU (Maxwellまたはそれ以降、RTX推奨)
- Visual Studio 2017 または 2019
- CUDA 10.1 以降
- OptiX 7
サンプルプロジェクトのダウンロード
以下のGitHubリポジトリから今回のサンプルプロジェクトをダウンロード
プロジェクトのビルド
CMakeのGUIを開き、 source directory
にダウンロードしたサンプルプロジェクトを指定、 build directory
にbuild
フォルダーを指定して新規フォルダーを作成する。
OptiX_INCLUDE
とOptiX_INCLUDE_DIR
にはインストールしたOptiXのルートディレクトリとinclude
フォルダを指定する。
すべて正しくインストールされていればConfigure
をした後にGenerate
でビルドが成功する。
サンプルプロジェクトの実行
build
内のVisual Studioプロジェクトを開き、Visual Studioでビルドすれば.exe
実行ファイルができ、実行できる。
OptiXレイトレーシングの大まかな流れ
OptiX 7 では Optix 6 に比べ、自由度が高まった反面、画像を描画するだけの簡単なプログラムでもメモリの確保、シェーダーバインディングテーブルの設定などやることが多くなってしまったので下のような画像を描画するのに必要な最小限の作業をまとめてみる。
ところどころ省略しているため実際のコードは examples02_pipelineAndRayGen
を参照してください。
画像を描画するためには必要な工程は4つに分けられます。
- デバイスサイドでのレイ生成関数の定義
- OptiX パイプラインの作成
- シェーダーバインディングテーブルの作成
- レイトレーシングの開始
1. デバイスサイドでのレイ生成関数の定義
まずはデバイス側(GPU)に送りたい情報を格納する構造体を定義する。この例ではレンダリング結果の色を格納するバッファーのみだが、カメラ位置などレイトレーシングの計算に関わる情報はここに格納することになる。
// LaunchParams.h
struct LaunchParams
{
int frameID { 0 };
uint32_t *colorBuffer;
vec2i fbSize;
};
デバイス側で先ほどの構造体をグローバル関数として定義する。
それぞれのピクセルからのレイ(光源)の動作を担当する__raygen__renderFrame()
関数を定義し、任意の色をバッファーに入れる。
今回はレイ生成の関数しか使わないが、レイトレーシングの一連の流れに必要な関数 (__miss__radiance()
, __anyhit__radiance()
, __closesthit__radiance()
など)は定義しておかなければならない。
// 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コンテントの作成を行う。
// SampleRenderer.cpp
void SampleRenderer::initOptix()
{
optixInit(); // OptiXを初期化する
...
SetDevice(...); // 使用するGPUの番号を指定
StreamCreate(...); // ストリームの作成
cuCtxGetCurrent(...); // 使用するGPUデバイスの情報を入手
optixDeviceContextCreate(...); // OptiX コンテントを作成
...
}
2b. OptiX モジュールの作成
次に、作成するプログラムの設定(モーションブラーの有無など)を設定した上でOptiXモジュールを作成することでパイプラインを最大限に最適化してくれる。
// SampleRenderer.cpp
void SampleRenderer::createModule()
{
moduleCompileOptions = ...
pipelineCompileOptions = ...\
optixModuleCreateFromPT(...);
}
2c. プログラムグループの設定
レイトレーシングに関わる関数それぞれの紐づけを行う。
// 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. パイプラインの作成
上記で定義したレイトレーシングに関わる関数をひとまとめにし、パイプラインを作成する。
// 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. シェーダーバインディングテーブルの作成
パイプライン中の各関数にユーザー定義の変数などを格納した上でデバイスにアップロードする。
// 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. レイトレーシングの開始
フレームバッファーを作成し、レイトレーシングを開始する。
// SampleRenderer.cpp
void SampleRenderer::render()
{
launchParamsBuffer.upload(&launchParams,1);
optixLaunch(...);
}
以上をビルド、実行すると同フォルダに画像が書き出されるはずです。
感想
OpenGLなど他のAPIと比べると確かに導入部分が大変な気がする。ただここでの自由なメモリー確保などが最適化やカスタマイズの自由度に貢献していそう。
シャドーレイなど複数のレイの定義、Acceleration Structure、リアルタイムノイズ除去などできることがたくさんあり、一度基盤ができてしまえば機能の付け足しは比較的簡単な模様。
Example 12ではソフトシャドウとノイズ除去を実装しており、リアルタイムで以下のようなクオリティーのレンダリングを行うことができる。RTX 2070 で実行したが60FPS以上出てるっぽい。