Tuesday, October 9, 2012

How to get the camera parameters from OpenGL perspective/modelview matrix? (1)


Abstract

この話はコンピュータグラフィクスとプログラミングの話である.以前 gluLookat 関数の作る Matrix に関して書いた(http://shitohichiumaya.blogspot.de/2011/01/what-matrix-glulookat-generates-1.html).この記事を書いた動機はOpenGLとは独立したレンダラ(ray tracer など)を書いており,しかし OpenGL と画像をoverlay したいということがあったからである.そういう意味では特殊な話であるがこの記事は意外に参照されている.今回,我々の顧客が特殊なシステムを利用していて,この逆演算をする必要がでてきた.つまり OpenGL のProjection/Modelview matrix からカメラのパラメータを知りたいという要求である.これについてここに書いておく.

はじめに

世の中にはいろいろな可視化システムがあるもので,可視化システムのカメラのパラメータが不明,つまりカメラがどこにあるのかわからない,というものがあるようだ.話を聞くと実は OpenGL に依存した大規模なシステムで,あるソフトウェアのレイヤーではカメラのパラメータがわからないという状況のようだ.しかし,OpenGL を使っているので,projection matrix と model view matrix にはアクセスできるということである.そこでこれらの matrix からカメラのパラメータを抽出する方法というものを尋ねられた.

これはちょっと特殊な状況であると思うが,OpenGL の Projection/Modelviewmatrix が与えられた場合に,カメラのパラメータを抽出するにはどうするかというパズルとして考えれば面白いかもしれないと思い,ここに blog として記す.

パースペクティブ行列からカメラのパラメータを計算する.

まずは Perspective matrix に関連したパラメータを取りだそう.

gluPerspective 関数は以下のような関数である.詳しくは OpenGL の manual を参照のこと.

void gluPerspective(GLdouble fovyd,  GLdouble aspect,
                    GLdouble zNear, GLdouble zFar);

  • fovyd: field of view y (degree)
  • aspect: aspect 比
  • zNear: 視点に近い方のクリッピング平面までの距離
  • zFar:  視点から遠い方のクリッピング平面までの距離

OpenGL のマニュアルによると,このパラメータによって与えられる
perspective matrix は以下のようになる.
\begin{eqnarray*}
 \left[
  \begin{array}{cccc}
   \frac{fr}{asp}  & 0  & 0 & 0  \\
   0               & fr & 0 & 0  \\
   0               & 0  & \frac{z_f + z_n}{z_n - z_f} & -1 \\
   0               & 0  & \frac{2 z_f z_n}{z_n - z_f} & 0  \\
  \end{array}
  \right]
\end{eqnarray*}
ここで,\(fr = \frac{1}{\tan(\frac{fovy}{2})}\) であり,\(fovy\) はradian で示した \(fovyd\) である.\(asp\) は aspect ratio であり,\(z_n\) は視点に近い方のクリッピング平面までの距離,\(z_f\) は視点から遠い方のクリッピング平面までの距離である.perspective matrix の要素を次のように示すと,
\begin{eqnarray*}
 \left[
  \begin{array}{cccc}
   aa & 0  & 0  & 0  \\
   0  & bb & 0  & 0  \\
   0  & 0  & cc & -1 \\
   0  & 0  & dd & 0  \\
  \end{array}
  \right]
\end{eqnarray*}
\begin{eqnarray*}
 asp  &=& \frac{bb}{aa}\\
 fovy &=& 2 \arctan(\frac{1}{bb})\\
 z_f  &=&\frac{c-1}{c+1} z_n
\end{eqnarray*}
ここで,\(kk = \frac{c-1}{c+1}\)と置くと,
\begin{eqnarray*}
 z_n &=& \frac{dd(1-kk)}{2k}\\
 z_f &=& \frac{dd(1-kk)}{2}
\end{eqnarray*}
が得られる.ただし,\(z_n \neq 0\) とした.

これを元にした実装 (C++) は以下のようになる.

/// get perspective camera parameters
/// from the OpenGL projection matrix
///
/// \param[out] fovy_rad     field of view in radian
/// \param[out] aspect_ratio aspect ratio
/// \param[out] clip_min     clipping min distance
/// \param[out] clip_max     clipping max distance
void gl_get_camera_parameter_from_perspective_matrix(
    double & fovy_rad,
    double & aspect_ratio,
    double & clip_min,
    double & clip_max)
{
    GLdouble mat[16];
    glGetDoublev(GL_PROJECTION_MATRIX, mat);

    GLdouble const aa = mat[0];
    GLdouble const bb = mat[5];
    GLdouble const cc = mat[10];
    GLdouble const dd = mat[14];

    aspect_ratio = bb / aa;
    fovy_rad     = 2.0f * atan(1.0f / bb);

    GLdouble const kk = (cc - 1.0f) / (cc + 1.0f);
    clip_min = (dd * (1.0f - kk)) / (2.0f * kk);
    clip_max = kk * clip_min;
}

簡単のために division by zero のチェックは省いている.もちろん正しくOpenGL が設定されていればこれでも動くが,個人的には assert は入れておきたい.

No comments:

Post a Comment