魚眼レンズ


~お魚の気持ちになって…~




■はじめに

今回は、一息ついて魚眼レンズです。 単純な頂点シェーダープログラムなので、すぐにおわかりになられるでしょう。

まぁ、いつものように適当にファイルが入っています。

vs.vsh頂点シェーダー。
ps.pshピクセルシェーダー。
draw.cppメインの描画部分。
draw.h描画の各関数の定義。
bg.cpp背景の描画。
main.h基本的な定数など。
main.cpp描画に関係しないシステム的な部分。
load.cppロード。
load.hロードのインターフェイス。
tile.bmp (床デカール)
sky.bmp (空デカール)

あと、モデルのファイル、実行ファイル及び、プロジェクトファイルが入っています。

■やってること

魚の目は、横に付いています。
従って、見える角度が非常に広くなっています。
魚のに見える景色を一枚の写真に焼き付けようとしたレンズが魚眼レンズです。
一番上に出ている画像を普通に(視野角60度で)レンダリングした場合には、次のようになります。

魚眼レンズ的なゆがませ方は色々あるのですが、今回は、次の絵にあるような射影をします。

原点と頂点を結ぶ直線と、2次曲線の交わる交点を垂直に落とした場所を射影空間の位置Prにします。
その射影空間で上での距離Prを、xy平面でのそれぞれの方向に射影します。
さて、問題なのは、射影空間の距離Prです。
Prを求めましょう。

Pr は、次の式を連立させて求めることが出来ます。

Pr = Pz tanθ

    1
Pz = - (1-Pr2)
     2

(X,Y,Z) が、頂点の位置です。
Pz を消すように連立すれば、Pr は、

     1
Pr = - (1-Pr2) tanθ
    2
                     ↓Prの冪乗でまとめて
tanθ Pr2 + 2Pr - tanθ =0
                     ↓2次方程式を解く
      -1+sqrt(1+tan2θ)
Pr = ――――――
         tanθ
    -cosθ + 1
   = ――――――
      tanθ cosθ
      1 - cosθ
   = ――――――
        sinθ

に、なります。
この式を代入すると、

           1 - cosθ
X射影空間 = ――――― cosφ
          sinθ 
 
         1 - cosθ
Y射影空間 = ――――― sinφ
          sinθ

に成ります。さて、それぞれの角度は、頂点座標で、

        x           y
cosφ = ―, sinφ = ―
        Pxy         Pxy

        z          Pxy
cosθ = ―, sinθ = ―
        d          d

Pxy2 = x2 + y2,
d2 = x2 + y2 + z2,

と書けます。射影空間の位置に関して、これらの値を代入すると、

           1 - (z / d)  x
X射影空間 = ―――――― ―
           (Pxy / d)    Pxy
 
           (d - z)  x
        = ―――― ―
             Pxy    Pxy

            x
        = ―― (d - z),
           Pxy2

            y
Y射影空間 = ―― (d - z),
           Pxy2

になります。これが欲しかった量です。
一つ忘れてはいけないことは、これはカメラから見た位置です。
座標はビュー座標系で処理する必要があります。

■頂点シェーダ

今回、特別なプログラムは、頂点シェーダの座標変換しかありません。

vs.vsh
0001: ; c0-3   -- world + ビュー 行列
0002: ; c12    -- {0.0, 0.5, 1.0, 2.0}
0003: ; c13    -- ライトのベクトル (w成分は環境光の強さ)
0004: ; c14    -- モデルの色
0005: ; c15    -- 深度調整
0006: ;
0007: ; v0    頂点の座標値
0008: ; v7    テクスチャ座標
0009: 
0010: vs.1.0
0011: 
0012: ;座標変換
0013: m4x4 r0,  v0,   c0
0014: 
0015: dp3 r0.w, r0, r0
0016: rsq r1.w, r0.w
0017: mul r0.w, r0.w, r1.w
0018: mov oPos.w, r0.w                ; oPos.w = r0.w = d
0019: 
0020: mad oPos.z,  c15.x, r0.w, c15.y ; oPos.z = z_max*(d-z_min)/(z_max-z_min)
0021: 
0022: add r1.w, r0.w, -r0.z
0023: dp4 r1.z, r0.xxyy, r0.xxyy
0024: rcp r1.z, r1.z
0025: mul r0, r0, r0.w
0026: mul r0, r0, r1.w
0027: mul r0, r0, c12.w
0028: mul oPos.xy, r0, r1.z           ; oPos.xy = r0.xy*d*(d-r0.z)/(v0.x^2+v0.y^2)
0029: 
0034: ; ((L・N) + L.w)*c14 (平行光源のライティング)
0035: dp4 r0.w,   v3,   c13
0036: mul oD0,    r0.w, c14
0037: 
0038: ; テクスチャーを張る
0039: mov oT0,    v7

最初にビュー空間に座標変換します。
この変換によって、カメラを原点に置いた座標系に移動します。
射影空間に出力する座標は、次の通りです。

w成分として出力するビュー空間での深度は、カメラからの距離dを出力します。
z成分は、カメラからの距離に関する0~1に補正される深度、

         zmax  d - zmin
Z射影空間 =  ―  ――――
          1  zmax- zmin

を使います。ただし、0~1に補正されるのは、w成分で割った値です。

x、y成分に関しては、先ほど説明した座標を、w成分で割られるときを考え、w成分の値を掛けた座標、

           x
X射影空間 =  d ―  (d - z)
           Pxy2

           y
Y射影空間 =  d ―  (d - z)
           Pxy2

を、出力します。
特別な方法といえば、xy平面での距離を計算するのに、内積計算で求めている

0023: dp4 r1.z, r0.xxyy, r0.xxyy
r1.z = r0.x*r0.x + r0.x*r0.x + r0.y*r0.y + r0.y*r0.y
     = 2 (r0.x2 + r0.y2)

ぐらいでしょうか。
あまり、最適化は考えていないので、もっと高速化は出来ると思います。

■最後に

いや、勿論あれへの前振りですよ。
といっても、まだ作っていないので、出来るかは不明ですが。
次回のネタが違ったら、間に合わなかったんだと思ってください。




もどる

imagire@gmail.com