凹みTips

C++、JavaScript、Unity、ガジェット等の Tips について雑多に書いています。

uRaymarching の VR における影の問題の修正をした

はじめに

前回の記事で uRaymarching を使ったワールドを VRChat に公開することを初めてやってみました。

tips.hecomi.com

ただ、この過程で以下のように右目のみ影が変に見えてしまうバグに遭遇しました:

f:id:hecomi:20200823230518p:plain

どうやら右目の影用のデプスの位置が実際の描画の位置とはずれているようで、CollectShadows のステージでスクリーンスペースで影を計算する際にはみ出た形になってしまっているようです。次のつぶやきはデプスの出力結果と重ねてみた図です(頭がプルプル動いてしまう関係で少しずれてしまっていますが、右目だけそれとは関係なく大きくずれているのが見て取れます)

今回は原因の考察(根本原因はわかりませんでした)とその解決法について書きたいと思います。

原因

原因箇所

デプスの出力方法について見ていきましょう。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 を参照しています。この変数はシングルパス環境下では各目の座標を与える形になっています。詳しくは以下の記事にまとめてあります。

tips.hecomi.com

おさらいすると、次のように各目を表すインデックスの 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 を定義した上で実行されていました。

f:id:hecomi:20200831232929p:plain

abs(_WorldSpaceCameraPos.x) を書き出して見てみるとデプスの出力値が異なったのでどうやら左右の目の値は入っているようですが、何かしらの原因でこの値が描画に使われているカメラの位置と異なるのではないかと思います。

解決法

この _WorldSpaceCameraPos を使わずに逆ビュー行列からカメラ座標を取り出します。ビュー行列はオブジェクトをカメラ座標系に変換するためのものなので、逆ビュー行列からはカメラの座標が取り出せる感じです。フォーラムでおなじみの Ben Golus さん(bgolus)が質問に回答されています:

forum.unity.com

Unity では UNITY_MATRIX_I_V が逆ビュー行列になり、HLSL ではゼロベースで _m00 や 1 ベースで _11 といった具合に各要素へアクセスできます。なので、UNITY_MATRIX_I_V._m03_m13_m23_WorldSpaceCameraPos に一致することになります。なるはずなんですが、こちらの変数を使ってアクセスすると上記問題を回避できるようです。。

uRaymarching 更新

これをもとに更新した uRaymarching の v2.1.1 を公開しています。VRChat で利用される方はこれ以降のバージョンをお使いください。

github.com

おわりに

ちょっと根本原因がわからず気持ち悪いですが、取り敢えずワークアラウンドで修正することは出来ました。また、別途原因がわかりましたら本記事に追加します。今回は VRChat に合わせて 2018.4.20f1 でテストしていますが、将来のバージョンで修正されているかも今後チェックしたいと思います。