テクスチャ座標の補間とパースペクティブコレクション(Perspective-Correction), GLSL・HLSL の noperspective 修飾子の挙動
本稿では透視投影変換の行列は 透視投影行列 の定義を用いる.
カメラ座標系での座標を $ P それを投影変換した正規化デバイス座標系での座標を $ vとする.
三角形の各頂点に割り当てられたなんらかの量(例えばテクスチャの uv 座標とか) $ \phi_i を,世界座標系あるいはカメラ座標系での重心座標(この二つはアフィン変換で行き来できるので,重心座標は保たれる) $ w_i で内挿すると,$ \sum_i w_i \phi_iとなる.
一方で,正規化クリップ座標系あるいはスクリーン空間での重心座標(こちらも線形変換で行き来できるので重心座標は保たれる) $ \alpha_i で内挿すると, $ \sum_i \alpha_i \phi_i となるが,これは $ w での内挿とは一致しないことがある.例えば 3D モデルに貼られたテクスチャの uv 座標の補間では,奥行のある状態で不自然な見た目になる(↓の動画みたいな).
https://www.youtube.com/shorts/imEQvR9p0ls
正規化クリップ座標系での重心座標 $ \alpha を用いて,世界座標系での重心座標での補間(これが立体物として自然に見える)を実現するには,$ k 番頂点の重みを $ \frac{ \alpha_k / P^k_z }{ \sum_i \alpha_i / P^i_z } として補間すればよい.
GLSL,HLSL,Slang などのシェーダーでは varying 変数の補間には(ハードウェアで実装された)パースペクティブ補正が適用されるが noperspective 修飾子を使用すれば補正を無効にできる(正規化クリップ座標での重心座標 $ \alpha で補間される).
noperspective の変数に対して,パースペクティブ補正をコードで実装したい場合は次のようにする(ハードウェアでなされる計算をあえてシェーダーで再現している)
頂点シェーダーでは補間する値 $ \phi_i をカメラ座標系の z 座標 $ P_z で割った結果を noperspective な varying 変数に格納する.
フラグメントシェーダーではそいつの補間結果( $ \sum_k \alpha_k \phi_k / P^k_z )を, noperspective に補間されたカメラ座標系の z 座標の逆数 ( $ \sum_i \alpha_i / P^i_z )で割ればよい.
冒頭で説明した透視投影行列を使った場合,頂点シェーダーの出力座標の w 成分にはカメラ座標系の z 座標を -1 倍したやつが入っていて,フラグメントシェーダーではその逆数の補間が取得できる.そのため $ P_z を得るために varying 変数を定義する必要はない.
──────────────────────────────────
まず,カメラ座標系と正規化クリップ座標系で同じ重心座標で補間した結果が異なることについての証明だが,
例えば,カメラ座標系の三つの頂点 $ P^0, P^1, P^2 を $ w_0, w_1, w_2 で補間した結果を $ P として,それを射影変換行列に掛けて正規化クリップ座標 $ v が得られる.その x 座標は
code:tex
v_x = \frac
{
\frac{2n}{r-l} \left( \sum_i w_i P^i_x \right) +
\frac{r+l}{r-l} \left( \sum_i w_i P^i_z \right)
}
{
- \sum_i w_i P^i_z
}
=
- \frac{2n}{r-l}
\left( \frac{ \sum_i w_i P^i_x }{ \sum_i w_i P^i_z } \right)
-
\frac{r+l}{r-l}
となるが,正規化クリップ座標を $ w_i で補間したもの
code:tex
w_i v^i_x = \frac{2n}{r-l} \left( \sum_i w_i \frac{ P^i_x }{ P^i_z } \right) - \frac{r+l}{r-l}
とは一致しない.
次に正規化クリップ空間で補間するときの重み $ \frac{ \alpha_k / P^k_z }{ \sum_i \alpha_i / P^i_z } の正当性について説明する.
まずカメラ座標系において重心座標 $ w_i に対応する点 $ P = \sum w_i P^i が透視投影の結果,正規化クリップ座標の重心座標 $ \alpha_i に対応する点 $ v = \sum_i \alpha_i v^i に投影されたとする.
パースペクティブ補正は頂点シェーダーからフラグメントシェーダーの間に行われるが,ここで分かっているのは正規化クリップ空間での重心座標 $ \alpha (これは二次元三角形上の点の重心座標を求めればよい)だけで, $ w については知りえない.
パースペクティブ補正は, $ \alpha から $ w を求めることを目的としているので, 前者を使った式で後者を表現できればよい.
まず, x 座標について
code:tex
- \frac{2n}{r-l} \left( \frac{ \sum_i w_i P^i_x }{ \sum_j w_j P^j_z } \right) - \frac{r+l}{r-l}
=
- \frac{2n}{r-l} \left( \sum_i \alpha_i \frac{P^i_x}{P^i_z} \right) - \frac{r+l}{r-l}
を充たす必要がある.y 座標は似たような感じなので省略, z 座標については
code:tex
\frac{2fn}{ f - n } \left( \frac{1}{ \sum_i w_i P^i_z } \right) + \frac{f+n}{f-n}
=
\frac{2nf}{f - n} \left( \sum_i \alpha_i \frac{1}{P^i_z} \right) + \frac{f+n}{f-n}
となる.
天下り的だが,ここで,$ w_i = \frac{ \alpha_i / P^i_z }{ \sum_j \alpha_j / P^j_z } とすると,
code:tex
\frac{ \sum_i w_i P^i_x }{ \sum_j w_j P^j_z }
= \frac{ \sum_i \alpha_i \frac{P^i_x}{P^i_z} }{ \sum_j \alpha_j }
= \sum_i \alpha_i \frac{P^i_x}{P^i_z} \quad \left( \because \sum_j \alpha_j = 1 \right)
となり,左辺と右辺が一致する.
このとき,
code:tex
\frac{1}{ \sum_i w_i P^i_z } = \frac{ \sum_i \alpha_i / P^i_z }{ \sum_j \alpha_j } = \sum_i \alpha_i / P^i_z
も成立するので, z 座標についても一致する.
よって,$ w_i = \frac{ \alpha_i / P^i_z }{ \sum_j \alpha_j / P^j_z } が成り立つ.