マウスクリックRAYの計算

3次元レンダリングのビューポート内における、任意点クリックによって定義される直線(RAY)を求めます。つまり、カメラ視点と画面のクリックしたポイントとを結ぶ直線のことです。これを私はマウスクリックRAYと呼んでいるわけです。それを計算するためのピースオブコードを今回は以下にメモしておきます。
入力情報は、

  1. クリックした点のビューウィンドウにおける座標
  2. ビューウィンドウ座標系にて定義されたビューポート情報
  3. 視体積の情報(これがいろいろ指定方法あります)

(ビューウィンドウとは、glViewport()に指定する座標が定義される座標系を言います。Windowsなんかの場合だと、コンテキストを定義したウィンドウの左下が原点となる座標系です。WindowsAPIなどで慣例の左上が原点な座標系ではないので注意が必要です)
出力情報は、

  1. RAY情報 org(直線の通過点)
  2. RAY情報 dir(直線の向き)

です。
(これで出てくるのはもちろんレンダラー座標系におけるRAYの情報です。レンダラー座標系というのは、OpenGLにおけるデフォルトのカメラ位置、つまり自分が原点にいて、Z軸負方向を向いており、頭上鉛直方向が、Y軸となる、例のあれです。これをワールド座標系におけるRAY情報にするには、カメラのビューマトリクスの逆行列M~をかけることで得られます。(M~をorgにかけ、M~の回転成分のみを、dir にかけます))
2タイプ視体積(PERSPECTIVE/ORTHO)の指定方法に、それぞれ2種類分用意してあるので、4バージョンあります。

class CalcClickRay
{
public:
  // ビューポート内における、任意点が表す、
  //PERSPECTIVE視体積内における直線(RAY)を求めます。
  template< class FloatType >
  static void ClickToVCSRay_Perspective( 
    int x, int y,
    int vx, int vy, int vw, int vh,// ビューポート情報
    FloatType aspect_ratio, FloatType fovy,// 視体積情報
    FloatType org[3],// [3] 出力(通過点)
    FloatType dir[3] // [3] 出力(方向)
  )
  {
    FloatType nc = 1.0;// マジック★ナンバー
    FloatType t = nc * tan( M_PI/180.0*fovy*.5 ); 
    FloatType r = t * aspect_ratio;
    ClickToVCSRay_Frustum( x, y, vx, vy, vw, vh, -r, r, -t, t, nc, org, dir );
    return;
  }
  template< class FloatType >
  static void ClickToVCSRay_Frustum(
    int x, int y,
    int vx, int vy, int vw, int vh, // ビューポート情報	
    FloatType l, FloatType r, FloatType b, FloatType t, FloatType nc, // 視体積情報
    FloatType org[3],// [3] 出力レイ情報(レイの通過点)
    FloatType dir[3] // [3] 出力レイ情報(レイの方向)
    )
  {
    ClickToVCSRay_Ortho( x, y, vx, vy, vw, vh, l, r, b, t, dir, org );
    org[2] = 0.0;
    dir[2] = -nc;
    return;
  }

  // ビューポート内における、任意点が表す、
  // ORTHO視体積内における直線(RAY)を求めます。
  template< class FloatType >
  static void ClickToVCSRay_Ortho(
    int x, int y,
    int vx, int vy, int vw, int vh, // ビューポート情報
    FloatType aspect_ratio, FloatType screen_half_height, // 視体積情報
    FloatType org[3],// [3] 出力レイ情報(通過点)
    FloatType dir[3]	// [3] 出力レイ情報(方向)
  )
  {
    const FloatType& t = screen_half_height;
    const FloatType r = aspect_ratio * t;
    ClickToVCSRay_Ortho( x, y, vx, vy, vw, vh, -r, r, -t, t, org, dir );
    return;
  }
  template< class FloatType >
  static void ClickToVCSRay_Ortho(
    int x, int y,
    int vx, int vy, int vw, int vh, // ビューポート情報
    FloatType l, FloatType r, FloatType b, FloatType t, // 視体積情報
    FloatType org[3],// [3] 出力レイ情報(通過点)
    FloatType dir[3]	// [3] 出力レイ情報(方向)
  )
  {
    dir[0]=dir[1]=0.0; dir[2]=-1.0;
    org[0] = l + (r-l) * ( x-vx )/vw;
    org[1] = b + (t-b) * ( y-vy )/vh;
    org[2] = 0;
    return;
  }

};


ま、上記4番目に定義した関数さえあれば、最初の3つはそれに依存した形で記述できるってことなのです。
gluUnProj() 使えば一発じゃん、という噂もあるのですが、RAYが直接得られるわけではないし、あるいはProjectionマトリクスの逆行列計算しろよとかありますが、そう言われても逆行列の計算とかあるし、Projectionマトリクスってビューポート情報入り?あるいはなし?とか、考えるのめんどくさいので。