キューブマップ面をレンダリングするときに視野角度を90度より大きくして重なる箇所を設けているため、
重なる部分を合成することで境界を目立たなくできます。
単純に合成すると以下のようにぶれたようになります。
この状態は、VR-HMDで見たときに目立ってしまいます。
これを緩和するために、"境界部にWeightを与えて変位させる"ようにしました。
ここでは"
Stitch with border weight"と呼ぶことにします。
境界部はスクリーンでのEquirectangularの球投影の角度で固定値を割り当てています。
下画像は-X/+X面に近いほど赤色に、+Z/-Y/+Y面に近いほど青色になるようにしています。
ウエイト値として、-X/+X面に近いほど1.0に近づき、+Z/-Y/+Y面に近いほど0.0に近づくとし、
境界部ではない箇所を0.0のウエイト値としました。
境界部ではない箇所(ウエイト値0.0の箇所)はキューブマップ面をそのまま採用とし、
境界部は補正処理を行います。
下画像は、上から見た図になります。
cPosが左目のカメラの中心。
キューブマップ面の境界となる部分を緑色にしています。
cPosを中心に、半球上の走査したワールド座標上の位置をwPosとします。このwPosは、cPosから十分遠い位置にあります。
cPosからwPosに向けた直線上と+Z面が交差する位置をpWzとします。
また、-Xの面を走査するとき、カメラの位置と向きが変わります。
この時のカメラ位置をcPosMXとします。
cPosMXからwPosに向けた直線上と-X面が交差する位置をpWxとします。
cPosから見た+Z面上の交点pWz、
cPosMXから見た-X面上の交点pWX、
この情報より、境界上のウエイト値を元に補間します。
ウエイト値が0.0に近い場合は、+Z面上の交点pWzのピクセル値を採用、
ウエイト値が1.0に近い場合は、-X面上の交点pWxのピクセル値を採用することになります。
pWzは+Z面、pWxは-X面への投影となるため、depthバッファを参照してワールド座標に変換します。
このとき、共に背景に到達する場合は単純な合成を行います。
float3 sPos1 = wPosをcPosから見た+Z面上のテクスチャ位置に変換;
float3 sPos2 = wPosをcPosMXから見た-X面上のテクスチャ位置に変換;
float3 col1 = tex2D(+Z面のテクスチャ, sPos1.xy).rgb;
float3 col2 = tex2D(-X面のテクスチャ, sPos2.xy).rgb;
float3 col = (col1 + col2) * 0.5;
カメラから十分遠い背景のピクセルは、カメラの左右の視差やcPos/cPosMXなどの位置の違いの影響はほとんど受けません。
-X面上のdepthバッファより、
pWxの位置から物体と衝突する位置iwPosを計算します。
iwPosはワールド座標上の位置になります。
iwPosが+Z面上ではどの位置に投影されるかを計算します。
// カメラから十分遠い背景までの距離.
float _FarDistance = 500.0;
// カメラの中心の差分.
float3 dd = cPos - cPosMX;
// iwPosをcPos中心のカメラから見たワールド座標位置に変換.
float3 iwPos2 = normalize((iwPos - cPosMX) + dd) * _FarDistance + cPos;
-X面を投影するカメラ(中心cPosMX)では、
iwPosがiwPos2に推移することになります。
ただし、このiwPos2は推測された位置となり正しい位置とは限りません。
境界部のウエイト値をwとします。
ウエイト値は-X/+X面に近い箇所を1.0、+Z/-Y/+Y面に近い箇所を0.0としています。
このときのカメラ位置cPosMX(-X面を向く)での補間されたワールド座標を計算し、そのときのピクセル値も計算します。
// 境界上のワールド位置を補間.
float3 wPos2 = iwPos * w + iwPos2 * (1.0 - w);
float3 sPos = wPos2をcPosMXから見た-X面上のテクスチャ位置に変換;
float3 col2 = tex2D(-X面のテクスチャ, sPos.xy).rgb;
この時に計算できた色をcol2としました。
これは、ウエイト値が1.0から0.0に推移するときの色に相当します。
0.0に近づくにつれて正しくなくなりますので、次に逆方向の+Z面で同様の処理を行います。
+Z面上のdepthバッファより、
pWzの位置から物体と衝突する位置iwzPosを計算します。
iwzPosはワールド座標上の位置になります。
iwzPosが-X面上ではどの位置に投影されるかを計算します。
// カメラから十分遠い背景までの距離.
float _FarDistance = 500.0;
// カメラの中心の差分.
float3 dd = cPos - cPosMX;
// iwzPosをcPosMX中心のカメラから見たワールド座標位置に変換.
float3 iwzPos2 = normalize((iwzPos - cPos) - dd) * _FarDistance + cPosMX;
+Z面を投影するカメラ(中心cPos)では、
iwzPosがiwzPos2に推移することになります。
ただし、このiwzPos2は推測された位置となり正しい位置とは限りません。
このときのカメラ位置cPos(+Z面を向く)での補間されたワールド座標を計算し、そのときのピクセル値も計算します。
// 境界上のワールド位置を補間.
float3 wzPos2 = iwzPos * (1.0 - w) + iwzPos2 * w;
float3 sPos = wzPos2をcPosから見た+Z面上のテクスチャ位置に変換;
float3 col1 = tex2D(+Z面のテクスチャ, sPos.xy).rgb;
この時に計算できた色をcol1としました。
これは、ウエイト値が0.0から1.0に推移するときの色に相当します。
これで以下の2つの色が求められました。
- ウエイト値1.0から0.0に推移するときの、-X面から見た色 col2
- ウエイト値0.0から1.0に推移するときの、+Z面から見た色 col1
これをウエイト値を元に合成します。
float3 col = col2 * w + col1 * (1.0 - w);
計算されたcolが、境界上の補間された色情報に相当します。
補正前と後を比べると、以下のように変化しました。
これだけではカメラの近い位置では、ずれが残る場合があります。
+Z/-Y/+Y面での走査しているピクセル値をbaseColとしたとき、
wが0.5よりも小さいときに以下の合成処理を行って重ね合わせます。
float3 sPos1 = wPosをcPosから見た+Z面上のテクスチャ位置に変換;
float3 baseCol = tex2D(+Z面のテクスチャ, sPos1.xy).rgb;
if (w < 0.5) {
float w2 = w / 0.5;
col = baseCol * (1.0 - w2) + col * w2;
}
以上のように、視差が異なる(カメラの中心が異なる)レンダリング情報(RGB + Depth)を使用することで、
ある程度はスティッチを滑らかにできます。
depthが計算できない背景があるときは、誤差が出る場合があります。