スポンサーサイト

--年--月--日 --:--

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

透視投影変換

2008年01月11日 01:40

PSM や LSPSM では透視投影変換の手法を利用してオブジェクトをゆがませるんですが、なんであの行列の形になるのかあまり理解していなかったので勉強してみました。

それで、わかったことをメモ代わりに残しておきます。


3次元上の点P(1,0,0) は同次座標上では無数に表すことができます。
例えば、3次元上の点P(1,0,0) は同次座標上では P[1,0,0,1] とも、P[20,0,0,20] とも表せます。

これを利用すると、同次座標では無限遠の点を簡単に記述できます。
w 成分を小さくしていくと、実空間での xyz 成分はどんどん大きくなり、やがて無限遠に到達します。
w = 0 の場合はもちろん割り切れないので、実空間で表すことは不可能ですが、同次座標系では表すことが可能です。

では w がマイナスになった場合はどうなるのか?
w がマイナスでも点は表せます。
先ほどの点 P(1,0,0) は例えば、P[-1,0,0,-1] とも記述可能です。
ではこれはどういった意味を表すのか?

単純に2次元で考えてみます。
同次空間で、点 A[1,2,3,1] の w だけをどんどん小さくして行くと
w = -0.1 になった場合は、実空間では A'(-10,-20,-30) という点になります。
要するに、w が 0(無限遠)を超えてマイナスの値を取った場合は
実空間上では原点に対して対称の空間に移動してしまうわけです。
ずーっと右下に落ちていくと、左上から降ってくるような感じでしょうか。
注意しないといけないのは、原点に対して対称となっている点で、x,y,z全てに対して鏡写しになっている点です。


さて、以上が同次座標系の基本。
次に透視投影変換について話します。

一番簡単な例として次の様な事例を考えます。

proj1


点P(Y,Z) を視点から D の距離にある「視線に垂直な平面」に写像した点がP'(Ys,D) という座標になります。
このときの点P'(Ys,D) を求めるのが一番簡単な透視投影変換になります。

まず、どうやって求めるか?
視点をCとすると、C,D,P' と C,Z,P は相似になるので、比率で求めることが出来ます。


  Ys / D = Y / (Z+D)
     Ys = Y / (Z/D + 1)

ここで、(Z/D + 1) を w と置くと、同次座標系の点として表せます。
また、図は2次元でしたが、3次元に拡張すると、
  Xs = X / w
  Ys = Y / w
  Zs = Z / w
となり、P(Xs, Ys, Zs) = [X, Y, Z, w] = [X, Y, Z, (Z/D + 1)] と記述できます。

上の計算は行列に記述できるので記述してみます。
               | 1, 0, 0,  0|
  (X,Y,Z,w) = [X,Y,Z,1] | 0, 1, 0,  0 |
               | 0, 0, 1, 1/D |
               | 0, 0, 0,  1|

これが一番簡単な透視投影変換です。

ここで、Z = -D のときのことを考えてみると、w = 0 となり、
Z = -D の点は無限遠に位置することになり、実空間に戻せません。

ちょっと考えると当たり前ですね。視点と同じZ座標に位置しているわけですから、
視点と寸分たがわず同じ位置にない限り絶対にカメラには移りません。

また、Z = 0 の点は w = 1 となり、スクリーン上の点を表します。
他にも、上記の行列を使って3次元上で無限前方にある点(Z > 0の点。例えば[1,0,D,0])を変換すると
有限の点(前述の例なら[1,0,D,1])になります。
これは「実空間で無限遠前方にある点を同次空間を使い、歪ませた場合に、[1,0,D,1] になる」ということを表しているのに注意してください。
要するに、「スクリーンに写像する = 同次空間上で歪ませた後、視点と各頂点を結ぶ」ということをやっているわけです。
歪ませた結果、無限遠前方の点が写像できるようになっただけです。

図にしてみると、、、
proj2a

proj2b

proj2c

こんな感じになります。



さて、3次元空間を2次元に写像するとき、実際に起こる変換を考えて見ます。

まず、カメラのローカル座標系にします。
1 : 3次元空間(ワールド座標系)→ 3次元空間(カメラのローカル座標系)

次に、カメラのローカル座標系上で Z 軸方向に D だけ離れた場所に写像平面を設けます。
要するに、ローカル座標系を -D だけ平行移動します。
2 : 3次元空間(カメラのローカル座標系)→ 3次元空間(写像平面のために -D だけ平行移動)

次に、写像するため、同次座標系で計算を行い、空間を歪ませます。
3 : 3次元空間(写像平面のために -D だけ平行移動)→ 同次座標空間

最後に、w = 1 に写像し、平面に投影します。
4 : 同次座標空間 → 3次元空間(w = 1の平面に投影)

ここで、2と3の部分を行列として纏めてみます。
| 1, 0,  0, 0 |   | 1, 0, 0,   0 |   | 1, 0,  0,  0 |
| 0, 1,  0, 0 | ・ | 0, 1, 0,   0 | =  | 0, 1,  0,  0 |
| 0, 0,  1, 0 |   | 0, 0, 1, 1/D |   | 0, 0,  1, 1/D |
| 0, 0, -D, 1 |   | 0, 0, 0,   1 |   | 0, 0, -D,  0 |


だんだん普段よく見る透視投影行列に似てきましたね。


上のフローを見ると、カメラの位置に写像平面をおくので、D だけ後ろに下がっています。
これをもうちょっと詳しく書いてみましょう。
同次座標系に変換するときに使った、
  Xs = D * X / (Z + D)
  Ys = D * Y / (Z + D)
  Zs = D * Z / (Z + D)
  Ws = D / (Z + D)
を思い出してください。

ここで、D だけ後ろに下がるので、Z = Z-D となり、
  Xs = D * X / Z
  Ys = D * Y / Z
  Zs = D * (Z-D) / Z
  Ws = D / Z
と、変換されます。

ここで、写像平面の領域を +1 ~ -1 の範囲と決めると、D = 2 / tan(fov) と書けます。
図にしてみると、判りやすいので、、、

proj3


というような感じです。

fov は視野角と呼ばれるものです。
視野角は写像平面上ではスケーリングにしか影響していないことに注目してください。
要するに、視野角は写像平面上に投影された形状のスケーリングにのみ影響を与えます。
同次空間上では Z 軸にのみ影響を与えます。


Z の値はZ比較に頻繁に利用されます。
物体の描画しかり、シャドウマップでの手前にあるかどうかの判定しかり。

しかし、上で紹介した行列は、D ~ 無限遠までの座標を写像平面にクリップする行列です。
ためしに、変換をしてみましょう。
判りやすいように、D = 2(視野角は約53度)としたとき、
  Z が 1000 の物体は Zs = 2 * (1000 - 2) / 1000 = 1.996000000
  Z が 1001 の物体は Zs = 2 * (1001 - 2) / 1000 = 1.996003996
差は 0.000003996 しかありません。

これでは、単精度の浮動小数点では正確な判定は無理です。

ではどうするか?

奥行きを決め、その間を 0 ~ 1 の範囲でスケーリングしてやればよさそうです。
上の行列で Z に関係している部分は、第3列の部分だけなので、とりあえず、
 | 1, 0, 0,   0 |
 | 0, 1, 0,   0 |
 | 0, 0, A, 1/D |
 | 0, 0, B,   0 |
と置きます。

これに写像平面までの距離をZnとおくと、
          | 1, 0, 0,   0 |
|0, 0, Zn, 1| ・ | 0, 1, 0,   0 | = |0, 0, AZn + B, Zn / D|
          | 0, 0, A, 1/D |
          | 0, 0, B,   0 |
となります。

写像平面での Z を 0 と置くので、
  AZn + B = 0
       B = -AZn
となりました。

行列を書き換えて見ます。
  | 1, 0,   0,   0 |
  | 0, 1,   0,   0 |
  | 0, 0,   A, 1/D |
  | 0, 0, -AZn,   0 |

次に、限界平面までの距離をZfとすると、
          | 1, 0,   0,   0 |
|0, 0, Zf, 1| ・ | 0, 1,   0,   0 | = |0, 0, A(Zf - Zn), Zf / D|
          | 0, 0,   A, 1/D |
          | 0, 0, -AZn,   0 |
となります。

限界平面での Z は 1 にしたいので、W 成分と等しくなければいけません。
よって、
  A(Zf - Zn) = Zf / D
        A = Zf / D(Zf - Zn)
となります。

纏めてみると、
  | 1, 0,     0,   0 |
  | 0, 1,     0,   0 |
  | 0, 0,     R, 1/D |
  | 0, 0, -Zn * R,   0 |
  ( R = Zf / D(Zf - Zn) )
となります。


これで、先ほどの計算を行ってみます。
Zn を 800, Zf を 1200 と置くと、
  Zs = (3Z - 2400) / Z
になるので、
  Z が 1000 の物体は Zs = 0.600000
  Z が 1001 の物体は Zs = 0.602397

差は 0.002397 となり、単精度でも問題なく判定できます。



ここから、見知った透視投影行列にするには、さらに D を置き換えます。
  D = cot(fov/2) より、
    = cos(fov/2) / sin(fov/2)


行列は w 成分で最終的に割られるので、行列をスケーリングしても写像平面の形状と大きさは変わりません。
(あくまで写像平面上での話です。同次空間上ではスケーリングによって、大きさが変わります)

よって、cos( fov/2 ) をスケーリングしてやると、、、
  | 1, 0,     0,   0 |    | c, 0, 0, 0 |   | c, 0,     0, 0 |
  | 0, 1,     0,   0 | ・ | 0, c, 0, 0 | =  | 0, c,     0, 0 |
  | 0, 0,     R, 1/D |    | 0, 0, c, 0 |   | 0, 0,     Q, s |
  | 0, 0, -Zn * R,   0 |   | 0, 0, 0, c |   | 0, 0, -Zn * Q, 0 |
 
 ( R = Zf / D(Zf - Zn),
  Q = s * Zf / (Zf - Zn) = s / (1 - Zn/Zf)
  c = cos(fov/2),
  s = sin(fov/2) )
となります。

この形にする利点は、D の割り算が消えたので、D = 0 の時でも行列が破綻しない点と、
限界平面を無限遠に置いたとき(要するに Zf が∞)、Q が s に収束する点です。


注意しないといけないのは、Zn の値が極端に小さいとスケーリングにより、
透視投影変換後に Z が 1 の付近に固まってしまい、Z 比較の精度が極端に悪くなってしまう点です。
Zn の値を適切に取ることは凄く重要なことです。

スポンサーサイト


コメント

    コメントの投稿

    (コメント編集・削除に必要)
    (管理者にだけ表示を許可する)

    トラックバック

    この記事のトラックバックURL
    http://angra.blog31.fc2.com/tb.php/114-14d767b8
    この記事へのトラックバック


    最近の記事


    上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。