DXR でスキニングを対応する (1)

DirectX Raytracing でモデル(3D形状)を描画できるようになると、次にキャラクターを出したくなります。このキャラクターというのが多くの場合スキニングモデル(エンベロープモデル)で作られているので、これに対応する必要が出てきます。

スキニングについて

各関節に見立てたボーンが動くことで、それによる頂点変形を施して描画する、という処理は、
従来のレンダリングパイプラインとうまく合致していました。ボーンの行列を配列として頂点シェーダーに設定し、透視変換する過程で簡単に対応できました。

しかし、DXR ではそのようなパイプラインではなく、TLAS にはモデルを配置するのに使うワールド行列を設定するのみとなります。また、 BLAS には頂点バッファを設定しますが、3角形ポリゴンで構成された静的なモデル形状になってしまいます(※)。

Intersection Shader を明示的に使う場合にはこの限りではないです。スキニングモデルは3角形ポリゴンの集合で出来ていると想定しています

対応方法

対応するための方法としていくつか考えられます。

従来のレンダリングパイプラインで処理

従来のレンダリングパイプラインでスキニング処理を実装し、結果をレイトレーシングパイプラインで使うようにする方法があります。素直にやるのであれば、ジオメトリシェーダーからストリームアウトプットによりスキニング後の頂点位置を取り出すという方法が使えるでしょう。

近年のハードウェアであれば、頂点シェーダーで UAV を用いることで、ジオメトリシェーダーを介さずに出力することも可能です。

ストリームアウトプット設定周辺は、D3D12_GRAPHICS_PIPELINE_STATE_DESC構造体に含まれる D3D12_STREAM_OUTPUT_DESC 構造体の要素を確認します。

D3D12_GRAPHICS_PIPELINE_STATE_DESC (d3d12.h) - Win32 apps
Describes a graphics pipeline state object.

コンピュートシェーダーで処理

スキニングの処理は描画パイプラインを通さずに処理することが可能です。頂点の位置を計算するだけなので、コンピュートシェーダーからスキニング処理後のバッファを出力するだけの比較的簡単なコードで実装できます。

場合によっては非同期コンピュートも使えると思います。

CPU で処理

頂点の変形をいっそ CPU で処理してしまう方法もあるでしょう。
ダイナミックな頂点バッファを用意して、CPUで計算後の形状を書き込むという処理になります。ただし変形頂点の数が多い場合は実用にならないでしょう。

この実装の一番のメリットは、変形後の頂点データを簡単に確認することができ、デバッグが容易なことがあります。また DXR の BLAS に設定する頂点バッファにはアップロードヒープに確保したものでも設定可能だったので、まず実装するには手軽だと思います。

アップロードヒープに確保したバッファには、 UAV を使えないため注意が必要です。
CPU 実装から GPU 実装に実装を切り替える作業をしているとこの点を見落としがちです。

実装の流れ

純粋な DXR プログラミングをしているときに、従来のレンダリングパイプラインとはなるべく離れていたいので、実装にはコンピュートシェーダーで行うものを選択してみました。この記事冒頭でアニメGIF を貼っていますが、これが成果映像となります。

アニメーション可能なスキニングモデルを対応していくための処理のステップは以下の通りです。

  1. スキニングモデルを読み込み、頂点の入力・出力用のバッファを準備する
  2. コンピュートシェーダーで計算するために必要なボーン行列の配列を準備する
  3. スキニング計算用のコンピュートシェーダーを準備する
  4. ボーンの行列を計算する (CPU)
  5. ボーン行列の配列を転送
  6. コンピュートシェーダーで形状変形
  7. 変形後のデータを用いて、 BLAS を作成・更新する
  8. TLAS を作成・更新する
  9. DispatchRays を実行する
  10. 描画ループとして、 (4) に戻る

もう少し実装に踏み込んだ話は、次回に行います。

その他

ハイブリッドレンダリングで一部の描画処理を DXR にやらせるのであれば、スキニングの形状描画処理は考慮しなくてよかったりもするんですよね。G-Buffer に変形後の描画を従来のレンダリングパイプラインで実行できてしまいますし。

DirectX プログラミング
すらりんをフォローする
すらりん日記

コメント

タイトルとURLをコピーしました