2021年12月25日土曜日

Godot4 に実装されるグローバルイルミネーションについて調べてみる

Godot4 に実装されるグローバルイルミネーションについて調べてみる

このページは レイトレーシング(レイトレ) Advent Calendar 2021 の14日目の記事です。

はじめに

Godot は2D及び3Dゲームを製作できるクロスプラットフォームなゲームエンジンです。 Godot はオープンソースであり MIT ライセンスの下で開発・配布されており無料で利用することができます。 現在は Godot3 が最新のメジャーバージョンですが、既に次期メジャーバージョンである Godot4 の計画は発表されており開発も進んでいます。 Godot4 ではレンダリングバックエンドとして新しく Vulkan サポートが追加されるなど様々なアップデートが予定されており (マイルストーン)、 その中の一つにリアルタイムグローバルイルミネーション (以降 GI と表記) の追加があります。 本記事ではそのリアルタイムGIの実装である Signed Distance Field Global Illumination (以降は SDFGI と表記) について簡単に紹介します。
最新の Godot4 の実装は github の master ブランチで見ることができます。 また、この記事は commit e53e357 の時点での Godot4 を使用して書いています。

Signed Distance Field について

Signed Distance Field (以降 SDF と表記) はスカラー場で、ここでは空間上のある点からシーン内の最も近いジオメトリの表面までの距離を表します。また、 SDF が正の値の時は自分がジオメトリの外側にいる、負の値の時はジオメトリの内側に位置していることを意味します。
レイキャスト時はこの SDF を用いてスフィアトレーシングを行います。下の図はレイがカメラ (点 p0p_0) から出て表面 (点 p4p_4) にヒットするまでを表しています。始めに、レイは開始地点である点 p0p_0 の SDF の値を見ます。 SDF が示している距離の間には何も無いことが判明しているのでレイは点 p1p_1 まで進む事ができます。これを繰り返す事で表面にヒットする点 p4p_4 (点から表面までの距離が閾値以下) までレイを進めます。

SDFGI

SDFGI は名前の通り SDF を利用した GI 実装です。Godot4 の SDFGI 実装はまだ完成ではないそうですが機能としては既に利用できます。また、Godot公式サイトの記事でデモを見ることができます。以下は SDFGI を無効/有効にした時のレンダリング画像です。
SDFGI無効 SDFGI有効
このシーンでは 道路や建物がディレクショナルライトで照らされていますが SDFGI を有効にした場合は影になっている部分も間接光によって明るくなっているのがわかります。
以下の画像はもう少し単純なシーンで SDFGI を試した例です。このシーンでは箱の中を天井にあるエリアライトで照らしています。真ん中に立っている小さな箱の側面を見ると GI 効果 (Color Bleeding) が出ているのがわかります (ただし現在エリアライトからの影はうまく出ないようです) 。
CornellBox
SDFGI は RayTracing Core を使っていないので比較的古い GPU でも動きます。そして、古い GPU でもパフォーマンスが出て程良いクオリティの GI レンダリングができるように設計されています。

ソースコード中では、SDFGI 関連の実装は C++側は servers/rendering/renderer_rd/ 内の renderer_scene_render_rd.cpprenderer_scene_gi_rd.cppforward_clustered/render_forward_clustered.cpp に記述されており、Shaderコードは servers/rendering/renderer_rd/shaders/ 内に glsl で実装されています。 Godot4 でレンダリングを開始すると renderer_scene_render_rd.cpp 内の RendererSceneRenderRD::render_scene() が実行されているのでこの関数から見ていきます。
残念ながら私が実装の詳細まで理解することができなかったため、ここでは処理の概要のみ説明します

SDFGI に関して render_scene() 内の処理は大まかに以下のようになっています、

  1. SDF の構築
  2. ライトプローブの更新
  3. 各ピクセルの GI の計算

まずは SDF の構築を行い、シーン上に一定間隔毎で設置された各ライトプローブ上の放射照度を計算します。 最後に各ピクセルから見える点の GI を周囲にあるライトプローブから計算します。

SDF の構築

シーン中の空間をグリッドに区切り各セルの SDF を計算します。Godot4 のデフォルトでは 128 x 128 x 128 のグリッドに区切っています。 各セルはまず自分のセル内にジオメトリがあるか確認します。この時オリジナルのジオメトリでは処理が重たくなってしまうので、下図のように簡易化されたジオメトリを用いています。

ジオメトリが見つかった場合はその表面までの距離をセルに格納します。 ジオメトリが見つからなかった場合は、次はセルの周囲にある別のセルを参照します。周囲のセルのどれかにジオメトリがある (SDFの値がセルに格納されている) 場合は自身のセルからジオメトリの表面までの距離をセルに格納します。周囲のセルを参照する処理を何度か繰り返す事でシーン全体の SDF の構築を完成させます。

ライトプローブの更新

ここでは GI を計算するために、シーン中の各ライトプローブ上の放射照度を計算します。ライトプローブは下の画像のようにシーン中に一定間隔で配置されています。

このプローブ上からランダムな方向にレイを飛ばし当たった光源からの放射輝度を計算します。この時のレイキャストには SDF を用いたスフィアトレーシングを行います。デフォルトの設定では 1 プローブ当たり 16 本のレイを飛ばしています。また、ディレクショナルライトがある場合はその方向にもレイを飛ばし、ディレクショナルライトからの放射輝度も計算します。
次に間接照明に関しては、今度は周囲にあるライトプローブを参照し、自身のライトプローブ方向への放射輝度を計算します。

各ピクセルの GI の計算

ライトプローブの更新が終了したら、次は各ピクセルからの GI を計算します。ピクセルから見える頂点をデプスバッファから計算します。頂点を見つけたら、ライトプローブで間接照明を計算したときと同様に頂点の周囲にあるライトプローブから頂点への放射輝度を計算します。

終わりに

本記事では Godot4 に実装された SDFGI について調べたことを簡単にまとめました。 ここで紹介した内容は SDFGI の概要程度で、実際の実装では色々な高速化が施されていますが私が解説できるほどの理解ができなかったためスキップしています。また、似た手法として SDFDDGI が発表されているのでこちらを読んでみるのも良いかもしれません。 法線の八面体へのエンコード (codepaper) やRGB9995エンコード (code) など手軽に自分のレンダラーでも使えそうな圧縮手法もあったので、ここにコードへのリンクだけ貼っておきます。
大規模なソースコードから使われている技術を読み解いていくのは慣れていない事ですが、自分の知らない技術が使われているのを見つけられるので今後も他のレンダラーのコードも読んでいこうと思います。

参考文献

  1. Amazon Lumberyard, Amazon Lumberyard Bistro, Open Research Content Archive (ORCA), url=http://developer.nvidia.com/orca/amazon-lumberyard-bistro
  2. Jinkai H, Milo K. Y, Guillermo E. A, Shihao G, Xiangjun T, Xiaogang J Efficient real-time dynamic diffuse global illumination using signed distance fields (2021)