[Unity] Depthについてあれこれ


はじめに

これからまた情報を付加させていくつもりですが,現時点でも何かの参考になればと思っております.
間違っている点や補足などがありましたら教えてください.

パースペクティブ射影変換(ProjectionMatrix)

DirectX

(x, y, z, 1) 
\begin{pmatrix}
\frac{H}{W}\cot(\frac{\theta}{2}) & 0 & 0 & 0 \\
0 & \cot(\frac{\theta}{2}) & 0 & 0 \\
0 & 0 & fz \cdot \frac{1}{fz - nz} & 1 \\
0 & 0 & fz \cdot \frac{-nz}{fz - nz} & 0 \\
\end{pmatrix}

上の式に関してz成分に関して計算してみると,

\frac{1}{z} (\frac{fz\cdot z}{fz-nz} - \frac{fz \cdot nz}{fz - nz}) \\
\frac{fz}{z} \frac{z - nz}{fz - nz}

Depth

具体的に数値を当てはめてみると

fz = 10 \\
fz = 1 \\
\frac{10}{9} - \frac{10}{9z} \\

そして,グラフにしてみるとこんな感じになります(横軸がビュー空間のdepth, 縦が変換後のdepth)

実際にNearClip(z = 1)を代入してみると値が0,FarClip(z = 10)を代入してみると値が1になっていることからDepthの範囲が0 ~ 1に収収まっていることが確認出来ました.

x,y値

yについて値を具体的に考えてみます.
$\frac{\theta}{2} = 30$とし,$near=2.0$, $far=10.0$としたとき,それぞれの地点でのyのtopの高さは$y_{NearTop} = \frac{2}{\sqrt 3}$, $y_{FarTop} = \frac{10}{\sqrt 3}$ となります.(tanを使用することで計算出来ます)

それに対して上に示した行列を作用させると,

Near_Topについて

y'_{NearTop} = \frac{ \frac{2}{\sqrt 3} \cot{\frac {\pi}{6}}}{nz} \\
y'_{NearTop} = \frac{2}{nz} \\
y'_{NearTop} = 1.0

Far_Topについて

y'_{FarTop} = \frac{ \frac{10}{\sqrt 3} \cot{\frac {\pi}{6}}}{fz} \\
y'_{NearTop} = \frac{10}{fz} \\
y'_{FarTop} = 1.0

よってどちらも1.0になっています.他のbottom, right, leftについても同様に計算していけば正規化(-1.0 ~ 1.0)させれていることが確認できるかと思います.
またw除算する前のタイミングでは-w ~ wとなってます.

OpenGL

(x, y, z, 1) 
\begin{pmatrix}
\frac{2near}{right - left} & 0 & 0 & 0 \\
0 & \frac{2near}{top - bottom} & 0 & 0 \\
0 & 0 & -\frac{fz +nz}{fz - nz} & -1 \\
0 & 0 & -2 \cdot \frac{fz \cdot nz}{fz - nz} & 0 \\
\end{pmatrix}

上の式に関してz成分に関して計算してみると

(\frac{z \cdot (fz + nz) - 2fzfn}{fz-nz}) \frac{1}{z}

Depth値

具体的に数値を当てはめてみると

fz = 10 \\
fz = 1 \\
\frac{11}{9} - \frac{20}{9z} \\

実際にNearClip(z = 1)を代入してみると値が-1,FarClip(z = 1)を代入してみると値が1になっていることからDepthの範囲が0 ~ 1に収収まっていることが確認出来ました.

ここまでで分かったこととしてはDirectXではDepthの値が0.0 ~ 1.0, OpenGLではDepthの値が-1.0 ~ 1.0, といういことです.

LinearDepth

UnityObjectToClipPos

UnityObjectToClipPosではx,yは-w ~ w, zは0 ~ w(GraphicsAPIによっては-w ~ w)の範囲までの計算が行われ,w除算が行われていない状態です.

ComputeScreenPos

入ってくる値としてはUnityObjectToClipPosによって変換されたw除算される前の値です.
x, yについて0 ~ wに変換している.z, wに関しては前の値が再代入されています.

inline float4 ComputeNonStereoScreenPos(float4 pos) {
    float4 o = pos * 0.5f;
    o.xy = float2(o.x, o.y*_ProjectionParams.x) + o.w;
    o.zw = pos.zw;
    return o;
}

_CameraDepthTexture

0.0 ~ 1.0までの値が格納されているTexture
https://soramamenatan.hatenablog.com/entry/2019/11/10/133420d

COMPUTE_EYEDEPTH

#define COMPUTE_EYEDEPTH(o) o = -UnityObjectToViewPos(v.vertex).z

inline float3 UnityObjectToViewPos(in float3 pos)
{
    return mul(UNITY_MATRIX_V, mul(unity_ObjectToWorld, float4(pos, 1.0))).xyz;
}

通常Unityは左手座標系(z軸の奥の方がプラス)なのですが,カメラ座標系にしたときは右手座標系(z軸の手前がプラス)になります.その為,今回-1.0をかけている理由としてはDepthの値(正)を必要とするためです.

参考:https://tech.drecom.co.jp/knowhow-about-unity-coordinate-system/

線形にしたい場合

fixed4 frag(v2f i) : SV_Target
{
    half depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv);
    depth = Linear01Depth(depth);
    return depth;
}

GrapchisAPIによる違い