はじめに
uRaymarching は Unity 上で簡単にレイマーチングするオブジェクトを生成することの出来るアセットです。詳細については以前の記事をご参照ください。
これまではレガシーなパイプライン(Forward / Deferred)の対応みでしたが、Scriptable Render Pipeline の登場以後 UniversalRP や HDRP の要望をチラホラ頂いていたので、まずは手始めに UniversalRP 対応を行ってみました。本エントリでは今回のアップデートで追加された UniversalRP テンプレートの紹介と、実装で行ったことや解決した問題などのノウハウについて共有したいと思います。
デモ
12 ポリゴンのキューブで描画されています。UniversalRP 下で従来と同じように Lit / Unlit が動きます。
ダウンロード
執筆時の最新バージョンは v2.0.1 です。
使い方
テンプレート
Shader > uShaderTemplate > Generator でジェネレータを生成した後、インスペクタから Shader Template
を以下に設定:
- Universal > UniversalRP > Lit
- ライティング有り(Universal Render Pipeline/Lit と同じ)
- Universal > UniversalRP > Unlit
- ライティング無し(Universal Render Pipeline/Unlit と同じ)
UI
Lit / Unlit テンプレートともに同じ項目になります。
Conditions
次の項目があります。
- Shadow Caster
ShadowCaster
パスの生成を行うかどうか- チェックを入れると影が出ます
- World Space
- チェック無し時はポリゴン中心を原点としてレイマーチングします
- チェックを入れるとワールド座標で行いそれをクリッピングするようになります
- Object Scale
- チェックを入れるとオブジェクトをスケールするとレイマーチングした世界も拡大します
- Check If Inside Object
- チェックを入れるとポリゴン内部に入った際に Near Clip 位置からレイを開始します
Culling
をNone
にする必要がある- 内部にいるか判定負荷は少しあるので使わないときはオフにしてください
- Ray Stops At Depth Texture
- 通常はループ回数が MAX になった時かレイが Far Clip まで到達した場合に終了しますが、チェックを入れた場合はレイがデプステクスチャ位置に到達すると終了します
ZWrite
がOff
な半透明オブジェクトで使用してください
- Ray Starts From Depth Texture
- デプスプリパスが使われている際、実際のレンダーターゲットへの描画時にレイの開始位置をデプスプリパスで計算した最終位置を使います
- 詳細は後述します
Variables
- Render Type
Tags
ブロックのRenderType
の設定です
- Render Queue
Tags
ブロックのQueue
の設定です
- LOD
LOD
の数値です
- Object Space
CUBE
かNONE
かどちらかを選択してくださいCUBE
はキューブポリゴンで奥側をクリッピングしますNONE
はキューブポリゴンを超えて奥まで描画します
CUBE
NONE
問題点
時間ベースの動きが非実行時にずれる
UniversalRP では ゲームを実行しないとパス毎に独立した _Time
が使われている?のか実際の形状と影がずれる現象が生じます。
引き続き調査しますが、実行すれば正常になりますのでしばらくはそちらでご対応ください。。
フルスクリーンモードはない
レガシーパイプラインでは RaymarchingRenderer
コンポーネントを使うと CommandBuffer
を利用したフルスクリーンのレイマーチングシーンを作成できましたが、ライティングがうまくいかない問題などが有り、現在は冒頭のリンクの記事で解説したように、大きいキューブを作成・カリングオフ・カメラ内判定フラグオンで代替してもらうように薦めています。
VR は未確認
一応コード上は動くように書いているつもりですが未だ確認していないです。。時間が出来たときに調査しますが、もし先行してバグなど見つけられましたらご連絡いただけると助かります。
実装メモ
以下今回の実装に関するメモです。
コード修正
以前の記事で UniversalRP の調査を行いましたが、各関数の変化(UnityObjectToClipPos()
がなくなって TransformObjectToHClip()
を代わりに使うなど)に対応したのと、ビルトインのシェーダを見てみると positionWS
のように「変数名 + 空間名」となっていることが多いので、新ルールに合わせるよう対応を行いました。
また拡張子も .cginc
から .hlsl
に変更します。ポイントライト影がなくなったり、ShadowCaster
の中で UpdateDepthTexture
対応の分岐を書かないとならない(該当コード)...といった直感的でない挙動がなくなったりなどで、全体的にはよりシンプルになりました。
DepthPrepass に関して
カスケードシャドウマップを使わない場合は Render Main Shadowmap
でライトから見たデプスを生成し、その深度値を Render Opaques
時にカメラから見たデプスと比較して遮蔽されているか否かを判断、遮蔽部はライティングの影響を受けないようにします。
一方、カスケードシャドウマップ利用時には Render Main Shadowmap
でライトから見たデプスを(分割数に応じて複数)生成し、次に Depth Prepass
というステージでカメラから見たデプスのみを先に生成、そして Resolve Shadows
でスクリーンスペースでシャドウ部分だけを先に計算したテクスチャを生成、そのテクスチャを Render Opaques
時に使って影を描画する、という流れになっています。
そして、この Depth Prepass
を通る場合は、不透明オブジェクト描画時にデプステクスチャが _CameraDepthTexture
という変数で使えるようになります。
なお、URP アセットで設定できる Depth Texture
のチェックボックスをオンにすると、不透明オブジェクト描画"後"に、デプステクスチャが使えるようになります(半透明時でのソフトパーティクル表現やポスプロ用)。
DepthPrepass の結果の利用
さて上述の DepthPrepass を通る場合、ここで使われる DepthOnly
パスと、ライティングを行う ForwardLit
両方でレイマーチングが行われてしまいます。レイマーチングするオブジェクトのコストの殆どはこのループで消費されるので、ほぼ 2 倍のコストが掛かってしまう形になります。そこで DepthOnly
パスを通った際に、ForwardLit
パスはその結果を使うオプションとして Ray Starts From Depth Texture
条件を Conditions の中に作成しました。ここにチェックを入れると、次のようなコードを通ることになります。
#if defined(RAY_STARTS_FROM_DEPTH_TEXTURE) TEXTURE2D(_CameraDepthTexture); SAMPLER(sampler_CameraDepthTexture); inline void InitRaymarchWithCameraDepthTexture(inout RaymarchInfo ray, float3 positionWS, float4 positionSS) { // デプステクスチャの UV float2 uv = positionSS.xy / positionSS.w; // 描画先ピクセルのデプステクスチャの値を取得 float depth = SAMPLE_DEPTH_TEXTURE( _CameraDepthTexture, sampler_CameraDepthTexture, uv); // デプステクスチャの値をデコード depth = LinearEyeDepth(depth, _ZBufferParams); // カメラからの距離に変換 float dist = depth / dot(ray.rayDir, GetCameraForward()); // デプステクスチャに対応するワールド座標を算出 ray.startPos = GetCameraPosition() + ray.rayDir * dist; } #endif
余談ですが、デプス取得のコードも UniversalRP ではレガシーと比べて変わっていますのでご参考まで。
これにより ForwardLit
のレイマーチングはループなしで終わるという流れになります。ただし、この場合は Post Process ブロックで ray.loop
にアクセスしてなにかしたい場合、常に 1
が降ってきてしまうので、簡易 AO を行いたいユースケース等は対応できなくなってしまう点にご留意下さい。
おわりに
UniversalRP 対応でも結構疲れました...。HDRP はグッと難易度が上がると思うので、目標は低めに...、取り敢えず今年中に対応を目指して進めていきたいと思います。