自己皇帝感

をください

CGのノート

Wについて

シェーダを触るとき頂点情報にWがついている。Wの役目をまとめる。
1. 3次元空間の頂点に対する変形行列のため
wの次元があることで3x3行列では表せられない平行移動を4x4行列で表すことができる。MV行列まではこの役割しかない。

2. 透視投影をするときの深度除算をするためにwにカメラからの距離(=ビュー空間でのz座標)を保存しておく。
http://marupeke296.com/DXG_No70_perspective.html

3. ラスタライザをするときに、頂点情報をパースペクティブコレクトに補間するための深度情報としてもっておく。
かなり調査に時間をかけた。
ラスタライザをするときに、線分上や面上の法線情報やUV座標情報は、その線分や面を構成する頂点に付加された情報で補間しなければならない。しかし、画面上の位置関係で補間すると遠近間が正しく反映されないので歪む。これはPS1などの時代にあった問題であり、ポリゴン分割を工夫するなどでごまかしていた*1*2

これを解決するための方法を示した論文がある
https://www.comp.nus.edu.sg/~lowkl/publications/lowk_persp_interp_techrep.pdf
具体的には、頂点A,Bがスクリーン上に映っているとき、AB間をスクリーン上でs,1-sで内分する点Tでの頂点情報を補間によってもとめるには
1. Tでの(クリッピング空間での)深度Ztを求める(論文中の(1)から(12)まで)。 
Z_t = \frac{1}{\frac{1}{Z_a} + s(\frac{1}{Z_b} - \frac{1}{Z_a})}
2. Tでの頂点情報Itを求める。 
I_{t}=\left(\frac{I_{1}}{Z_{1}}+s\left(\frac{I_{2}}{Z_{2}}-\frac{I_{1}}{Z_{1}}\right)\right) / \frac{1}{Z_{t}}
2についてはZtを経由せずに求めることもできる
I_{t}=\left(\frac{I_{1}}{Z_{1}}+s\left(\frac{I_{2}}{Z_{2}}-\frac{I_{1}}{Z_{1}}\right)\right) /\left(\frac{1}{Z_{1}}+s\left(\frac{1}{Z_{2}}-\frac{1}{Z_{1}}\right)\right)

2式は変形でき,「頂点情報については、頂点情報を深度で割ったものでスクリーン上の線形補間を行えばよい」と解釈できる。

\frac{I_{t}}{Z_t} =\frac{I_{1}}{Z_{1}}+s\left(\frac{I_{2}}{Z_{2}}-\frac{I_{1}}{Z_{1}}\right)
実際の実装については
社内勉強会「レンダリング合宿1」 ソフトウェアラスタライザー基本実装編 07.パースペクティブコレクト - Qiita
が参考になりました。
「HLSLのSV_Positionや、OpenGLのgl_Positionは、XYZ要素がW除算され、NDC空間の値としてフラグメントシェーダに渡される。このときはスクリーン上の線形補間がされる(式1)」
「HLSLのTEXCOORDや、OpenGLのvarying変数などは、パースペクティブ補間され(式2)、View空間での値としてフラグメントシェーダに渡される」



深度値と頂点情報のラスタライズの計算の違い

zバッファには、「ビュー空間での深度=Zview」ではなく「射影変換した後(=正規化デバイス空間(NDC))での深度=Zndc」が入っている。このため、「物体の現実世界の奥行きの距離(=Zview)とZndcの値には線形関係がない」単純な発想として、zバッファに「Zviewの値を0~1にスケールしたもの」を使えばよさそうである。しかし、Zndcの値を使うには理由がある。
1. 「物体の前後関係を調べるだけならZndcの大小関係の情報だけでよく、線形性は必要でない」ため
2. near平面からfar平面に分布するZndcの値が非線形に分布し、nearに多く分布する。カメラに近いほど深度の精度を要求したいという要求にあったため。
しかし、「zバッファに浮動小数点を使い、かつnearに0、farに1を割り当てた」場合、Zndcの非線形分布と浮動小数点数のレンジの関係で、遠い面での深度の精度が落ちてしまうという問題があった。
これを改善するためにreversed z sortが提案された 
Depth Precision Visualized | NVIDIA Developer


3. ラスタライズするときに、Zndcはスクリーン上の線形補間だけで計算できるため(というか、Zndcは正規化デバイス空間での値なので、パースペクティブコレクトとかを考える必要がない)。
具体的に言うと、zバッファを作る際ピクセルシェーダに渡されたSV_Positionやgl_FragCoordのZ要素をそのまま使えるということである。特別な処理が必要でないので、zバッファはOpenGLDirectXでは設定一つで勝手に作ってくれる。
たとえばこの例*3では、zバッファを使ったシャドウマッピングを行う際(depthBuffer == Trueのとき)、深度バッファの値としてピクセルシェーダに渡されたgl_FragCoord.zをgl_FragColorに直接代入している。なぜならgl_Positionは勝手にW除算されてgl_FragCoordに渡されるため、フラグメントシェーダに渡されるZ座標がそのままZndcを示しているからだ。
(記事では,zバッファにFragCoord.zを入れた場合(depthBuffer == Trueの場合)と、ビュー空間での深度(varyingで頂点情報を保存してフラグメントシェーダに渡す。式2に該当)(depthBuffer == Falseの場合)を代入した場合で場合分けしている。場合分け変数名がdepthBufferなのは違和感がある...おそらく「FragCoord.zはOpenGLが勝手に作成したzバッファのz値と一致していて、それを利用している」という意味であるとおもう。depthBuffer==Trueのとき、「シャドウマッピングのための深度バッファはOpenGLの作ったzバッファの値を使う。(直接OpenGLのzバッファにアクセスしているわけではない)」。depthBuffer == falseのとき、「シャドウマッピングのための深度バッファは頂点情報から自前で計算したZviewの値を使う」という意味で考えてそう)