投影の計算法

draft CG

投影の手順 では、ウィンドウとクリッピング面でビューボリュームを決め、それを正規化ビューボリュームへ移すまでの流れを見ました。
ただし、どの点がどこへ移るのかという計算そのものには触れていませんでした。

ここでは、ウィンドウまでの距離・ウィンドウの大きさ・クリッピング面までの距離をもとに、この変換を行列として組み立てていきます。

カメラ座標系
CG

投影の計算は、視点を原点にとった座標系の上で行います。カメラを基準にとることから、この座標系をカメラ座標系といい、視点座標系とも呼ばれます。

x軸を右、y軸を上に向けると、残るz軸は奥行きの方向になります。ここでは、この3軸を右手系 でとることにします。右手系ではz軸の正の向きが画面の手前を向くため、視線はz軸のの向きになります。

右手系と左手系のどちらを採用するかは環境によって異なります。はじめから左手系でカメラ座標系をとる環境では、次の節で行うz軸の反転は要りません。

ビューボリュームの形と大きさは、ウィンドウとクリッピング面の取り方で決まりました。ウィンドウまでの距離・ウィンドウの大きさ・クリッピング面までの距離を、次のように記号で置いておきます。

  • ウィンドウまでの距離:d
  • ウィンドウの横幅の半分:r
  • ウィンドウの縦幅の半分:t
  • 前方クリッピング面までの距離:n
  • 後方クリッピング面までの距離:f

いずれも視点を基準に測った長さなので、5つとも正の値です。ウィンドウは、視線がその中心を通るように置かれているものとします。

視線がz軸の負の向きであるため、ビューボリュームの中の点はz座標が負になります。視点から遠ざかるほどzは小さくなり、ウィンドウはz = -d2枚のクリッピング面はz = -nz = -fの面に来ます。

左手系への変換
CG

負の奥行きをそのまま持ち回ると、距離として与えたdnfと、点のz座標とで符号が食い違ったままになります。奥にあるものほどzが小さいという並びも、前後を比べる処理では扱いにくくなります。

そこで、投影の計算に入る前にz軸の向きだけを反転させ、左手系へ移します。向きの反転は、鏡映 のうちxy平面に関する鏡映にあたります。変わるのはz座標の符号だけなので、行列は次のようになります。

この鏡映を通したあとの座標系では、視線がz軸のの向きになります。ウィンドウはz = d、前方クリッピング面はz = n、後方クリッピング面はz = fの面に置かれ、視点からの距離として与えた値が、そのままz座標になります。ウィンドウの右端はx = r、上端はy = tです。

右手系のカメラ座標系と、xy平面に関する鏡映で移したあとの左手系を並べた図。視線の向きと、ウィンドウ・前方クリッピング面・後方クリッピング面がz軸上のどこに来るかを対比して示す

奥へ行くほどzが大きくなる並びも、このあとで効いてきます。ビューボリュームの前後を判定するクリッピングでも、重なった面のどちらが手前かを比べる処理でも、z座標の大小をそのまま前後関係として使えます。

以降の計算はすべてこの左手系で行い、鏡映を通したあとの座標を改めて(x, y, z)と書きます。

z軸の反転は投影の種類によらず必要なので、実装では、このあと求める行列と合成 して1本の行列にまとめておきます。

透視投影の計算法
CG

透視投影 のビューボリュームは、奥へ行くほど断面が大きくなる四角錐台でした。求めるのは、この四角錐台を正規化ビューボリュームへ移す変換です。

ここでは、次の直方体を正規化ビューボリュームとします。

まず、ビューボリュームの中の点(x, y, z)が、ウィンドウの上のどこへ写るかを求めます。視点と点を結んだ投射線がウィンドウの面と交わる位置が、その点の像です。

点と像は、視点から伸びる同じ投射線の上にあります。それぞれからz軸へ垂線を下ろすと、視点を頂点とする2つの直角三角形ができ、この2つは相似です。
奥行きの比がd : zなので、z軸に垂直な向きの座標も同じ比で縮み、像は次の点になります。

視点・空間の点・ウィンドウ上の像の位置関係を横から見た図。視点を頂点とする2つの相似な直角三角形を示し、奥行きの比 d : z が像の縮み方になることがわかるようにする

ビューボリュームの中の点は前方クリッピング面より奥にあるので、z0になってこの割り算が破綻することはありません。視点に近すぎる点を計算の対象から外すのも、前方クリッピング面の役割です。

像がウィンドウに収まっているかどうかは、像のx座標をry座標をtと比べればわかります。正規化ビューボリュームでは、このrtにあたる位置を1にそろえるので、それぞれをrtで割ります。

もとの座標に掛かっているのはd / rd / tという定数倍、つまり拡大・縮小です。これに、点ごとに値の変わるzでの割り算が加わります。

残るはz座標です。前方クリッピング面を0、後方クリッピング面を1へ写したいのですが、z座標の写り方だけを自由に決めることはできません。同次座標では、変換後の4つの成分をすべて同じ値で割って通常の座標へ戻すため、x座標とy座標をzで割るのなら、z座標もzで割られることになります。

ここには2種類の「正規化」が現れます。ビューボリュームを決まった大きさの直方体へそろえるのがビューボリュームの正規化、変換後の同次座標をwで割って通常の座標へ戻すのが同次座標の正規化です。

そこで、zの一次式をzで割った形を置き、2枚のクリッピング面での値から係数を決めます。z = n0になるには分子がz - nに比例していればよいので、定数kを使って次のように書けます。

あとは、z = fのときに1になるようにkを決めます。

kを戻すと、z座標の写り方が決まります。

3つの式を1つの行列にまとめます。zで割る操作そのものは行列の掛け算では書けませんが、最下行を(0 0 1 0)にすれば変換後のwzになり、割り算は通常の座標へ戻す段階に任せられます。

得られた同次座標をw成分のzで割ると、先に求めた3つの式に戻ります。

ここで注目したいのは、最下行が(0 0 0 1)ではないという点です。最下行を一般の値まで広げた変換が射影変換 で、透視投影の行列はまさにその形をしています。点ごとに違うzで割ることが、奥のものほど小さく写る縮み方をつくっているのです。

変換後のz値

z座標は、x座標とy座標と同じzで割られました。この割り算のために、変換後のはもとのzに比例しません。
先ほどの式を分けて書くと、zの逆数の一次式になっていることがわかります。

zが分母に入っているので、zの関係は直線ではなく双曲線です。znからfへ動くあいだ、0から1へ増えていきます。その増え方は一定ではなく、手前では急で、奥へ行くほど緩やかになります。

横軸をもとのz、縦軸を変換後のz値としたグラフ。z = n で 0、z = f で 1 を通る双曲線を描き、等間隔に並べたzが、変換後には手前へ詰まって写ることがわかるようにする

増え方が緩やかになっても、増えることは変わりません。もとのzの大小との大小は一致するので、クリッピングでの前後の判定や、重なった面の奥行きの比較には、変換後の値をそのまま使えます。

ただし、奥ではの差がごく小さくなります。限られた桁数でを保持すると、遠くにある面どうしの差が値として残らず、どちらが手前なのか判定できなくなってしまいます。前方クリッピング面を視点へ近づけるほど、この偏りは強まります。
そのため、前方クリッピング面までの距離nは、必要以上に小さくとらないようにします。

平行投影の計算法
CG

平行投影 では投射線が視線に平行なので、点は奥行きに関わらずウィンドウへまっすぐ写ります。像のx座標とy座標は、もとの座標のままです。

ビューボリュームも、どこで切っても断面が変わらない直方体でした。ウィンドウの大きさとクリッピング面の位置がそのまま範囲になり、x-rからry-tからtznからfまでがビューボリュームです。

あとは、3本の軸の目盛りを正規化ビューボリュームに合わせるだけです。x座標とy座標はrtで割れば-1から1に収まり、z座標はf - nで割ってnの分だけずらせば0から1に収まります。

この関係を行列にまとめると、拡大・縮小と平行移動を組み合わせた行列になります。

透視投影の行列と見比べると、違いは2か所です。ウィンドウまでの距離dがどこにも現れず、最下行が(0 0 0 1)に戻っています。

dが消えるのは、平行投影では写る範囲がウィンドウの大きさだけで決まり、投影面をどこに置いても写り方が変わらないためです。

最下行が(0 0 0 1)であることは、変換後のw1のまま、つまり座標を割る操作が要らないことを意味します。平行投影の正規化はアフィン変換の範囲に収まり、z座標も一次式で写るため、奥行きの間隔はもとの比のまま保たれます。

アフィン変換は平行性を保つので、空間で平行な直線は、像の上でも平行なままです。一方、透視投影の行列はzで割る射影変換なので、平行だった直線が像の上では1点に集まります。その集まる先は消失点とn点透視 で見ていきます。