はじめに
前回の記事で uRaymarching を使ったワールドを VRChat に公開することを初めてやってみました。
ただ、この過程で以下のように右目のみ影が変に見えてしまうバグに遭遇しました:
どうやら右目の影用のデプスの位置が実際の描画の位置とはずれているようで、CollectShadows のステージでスクリーンスペースで影を計算する際にはみ出た形になってしまっているようです。次のつぶやきはデプスの出力結果と重ねてみた図です(頭がプルプル動いてしまう関係で少しずれてしまっていますが、右目だけそれとは関係なく大きくずれているのが見て取れます)
右目のデプスパスの結果がちょっとずれている感じがする... pic.twitter.com/fDLPJFNRrm
— 凹 (@hecomi) 2020年8月1日
今回は原因の考察(根本原因はわかりませんでした)とその解決法について書きたいと思います。
原因
原因箇所
デプスの出力方法について見ていきましょう。uRaymarching では次のように SHADOW_CASTER
パスで場合分けをしています。
// Perspective かどうか if (any(UNITY_MATRIX_P[3].xyz)) { // UpdateDepthTexture で実行 if (abs(unity_LightShadowBias.x) < 1e-5) { ray.rayDir = normalize(i.worldPos - _WorldSpaceCameraPos); ... // SpotLight 影で実行 } else { ray.rayDir = GetCameraDirection(i.projPos); } // Directional 影で実行 } else { ray.rayDir = GetCameraForward(); }
ポイントライト用の影のパスは別関数内にあります。ここで注目したいのは、UpdateDepthTexture ステージがこの SHADOW_CASTER
パスで実行されている点です。シャドウマップ生成では各ライトから見たデプスとカメラから見たデプスとを比較しますが、両方ともデプスを出力するので、通常は同じコードが共有できるわけです。ただレイマーチングではこのデプスもいじるため、Ortho や Perspective なカメラかどうかなどを考慮してやる必要があり、変数を使って色々場合分けをしています。
そしてどうやらこの _WorldSpaceCameraPos
が駄目なようで、これを後述するように逆ビュー行列から抽出すると直りました。
考察
元のコードではカメラの座標はビルトイン変数である _WorldSpaceCameraPos
を参照しています。この変数はシングルパス環境下では各目の座標を与える形になっています。詳しくは以下の記事にまとめてあります。
おさらいすると、次のように各目を表すインデックスの unity_StereoEyeIndex
を利用して定義されています:
#if defined(UNITY_SINGLE_PASS_STEREO) || \ defined(UNITY_STEREO_INSTANCING_ENABLED) || \ defined(UNITY_STEREO_MULTIVIEW_ENABLED) #define USING_STEREO_MATRICES #endif ... #if defined(USING_STEREO_MATRICES) GLOBAL_CBUFFER_START(UnityStereoGlobals) ... float3 unity_StereoWorldSpaceCameraPos[2]; ... GLOBAL_CBUFFER_END #endif ... #if defined(USING_STEREO_MATRICES) ... #define _WorldSpaceCameraPos unity_StereoWorldSpaceCameraPos[unity_StereoEyeIndex] #endif
USING_STEREO_MATRICES
が定義されていれば通常の _WorldSpaceCameraPos
を上書きするコードを通り、左右の目で異なる値が代入されるはずです。実際キャプチャしても UNITY_SINGLEPASS_STEREO
を定義した上で実行されていました。
abs(_WorldSpaceCameraPos.x)
を書き出して見てみるとデプスの出力値が異なったのでどうやら左右の目の値は入っているようですが、何かしらの原因でこの値が描画に使われているカメラの位置と異なるのではないかと思います。
解決法
この _WorldSpaceCameraPos
を使わずに逆ビュー行列からカメラ座標を取り出します。ビュー行列はオブジェクトをカメラ座標系に変換するためのものなので、逆ビュー行列からはカメラの座標が取り出せる感じです。フォーラムでおなじみの Ben Golus さん(bgolus)が質問に回答されています:
Unity では UNITY_MATRIX_I_V
が逆ビュー行列になり、HLSL ではゼロベースで _m00
や 1 ベースで _11
といった具合に各要素へアクセスできます。なので、UNITY_MATRIX_I_V._m03_m13_m23
が _WorldSpaceCameraPos
に一致することになります。なるはずなんですが、こちらの変数を使ってアクセスすると上記問題を回避できるようです。。
uRaymarching 更新
これをもとに更新した uRaymarching の v2.1.1 を公開しています。VRChat で利用される方はこれ以降のバージョンをお使いください。
おわりに
ちょっと根本原因がわからず気持ち悪いですが、取り敢えずワークアラウンドで修正することは出来ました。また、別途原因がわかりましたら本記事に追加します。今回は VRChat に合わせて 2018.4.20f1 でテストしていますが、将来のバージョンで修正されているかも今後チェックしたいと思います。