画面の位置にあたるローカル座標を求める

unproject.png

glm::unProjectを使う場合

glmにもglm::unProjectという関数があります。
glUnProjectではモデル行列や投影行列がdouble型固定で不便ですが、glmだったらfloat型でもokだし、引数の数も少なくてスマートです。

#include <glm/gtc/matrix_transform.hpp>
結果= glm::unProject(glm::vec3(ウィンドウ座標), モデルビュー行列, 投影行列,glm::vec4(ビューポート));

gluUnProjectを使う場合

GLint gluUnProject(
GLdouble      winX,GLdouble      winY,GLdouble      winZ,//画面上での位置(ピクセル)
     const GLdouble *      モデル行列,const GLdouble *      投影行列,const GLint *      glViewPortの値,
     GLdouble*      objX,GLdouble*      objY,GLdouble*      objZ//答えの格納先
);

gluUnProjectは、ある特定のウィンドウ座標から、オブジェクトの座標系へマッピングする役割をする関数である。
引数としてモデル行列、投影行列、ビューポート行列を使う。
ビューポート行列は行列、といっても要素が4個の配列である。
普通は
glVewPort(0,0,width,height);

と指定して、そうすると、0,0,width,height,の配列が返ってくる
glGetIntegerv(GL_VIEWPORT,viewport);

したときに
戻り値
trueが返ってきたら、うまくいった。falseが返ってきたら失敗したということである。

引数

double wiinX ウィンドウ座標のx
duble winY ウィンドウ座標のy
double winZ デプスバッファの値

このデプスバッファの値というのが曲者である。
こんなのわかるわけねーじゃんって思うよね

glReadPixels(50,WindowHeight - 50,1,1,GL_DEPTH_COMPONENT,GL_FLOAT,&z);

で調べるという方法もありますが、もっと簡単な見積もり方があります
公式リファレンスによると、
winZは次のように変換されます。
この段階の変換は、ビューポート変換の逆変換ですよっ
ウィンドウ座標→ビューポート変換の逆変換→正規化デバイス座標するときは、正しくはこうです。(1)
\begin{align} z_{NDC}=\frac{2Z_{win}}{f-n}*-1 \end{align}

しかし、gluUnprojectの中ではf=1,n=0とみなされ変換されます。
なのでこうしています

(2)
\begin{equation} z_{NDC}=2Z_{win}*-1 \end{equation}
なので、winZを、0で渡して一回ニアークリッピング面にひっついたオブジェクト座標を取得し、
次に2回目としてwinzを1としてファークリッピング面にひっついたオブジェクト座標を取得します。
その得られた2つの点から、レイ(光線)を作って
好きなオブジェクト座標値での、逆投影変換した値を得るという方法が妥当です。
もししりたいオブジェクト座標のz値だけ既にわかっているならばこれが正確です。
unproject.png

マウス関数に実装した例

マウスで押した箇所の座標がわかる!

void mouse(int button , int state , int x , int y) {
    if (button != GLUT_LEFT_BUTTON || state != GLUT_DOWN) return;
    GLdouble modelview[16];
    GLdouble projection[16];
    GLint viewport[4];
    glGetDoublev(GL_MODELVIEW_MATRIX,modelview);
    glGetDoublev(GL_PROJECTION_MATRIX,projection);
    glGetIntegerv(GL_VIEWPORT,viewport);
    GLdouble objX,objY,objZ;
     gluUnProject(x,win_size.y-y,0,modelview,projection,viewport,&objX,&objY,&objZ);//yを反転させないとおかしくなるので注意
    //yを反転させないといけない場合もあるので注意
   printf("ウィンドウ座標%d,%dはワールド座標で%f,%f,%fです。",x,y,objX,objY,objZ);
     glutPostRedisplay();
}

ウィンドウ座標で押した地点から、任意のオブジェクト平面まで伸ばしたい!

そういう時はレイを使う必要がある。

ニアー平面と、ファー平面でunprojectする

gluUnproject(の引数でwin_zにいつも何を渡せばいいやらと思っていましたが、
ニアー平面上にある点を知りたい場合は0.0を、
ファー平面上にある点を知りたい場合は1.0を渡すといいのです。

//ニアークリッピング面
m_rayBegin = glm::unProject(glm::vec3(m_mousPos.x, m_win_size.y - m_mousPos.y,0), m_modelview, m_proj_matrix, m_viewport);
//ファークリッピング面
m_rayEnd = glm::unProject(glm::vec3(m_mousPos.x, m_win_size.y - m_mousPos.y, 1.0), m_modelview, m_proj_matrix, m_viewport);

取得できたオブジェクト座標からレイを作る。

今回は、ファー平面点からニアー平面点で引き算して、
ニアー平面点→ファー平面点と伸びるような方向ベクトルを作ります。

m_rayDir = m_rayEnd - m_rayBegin;
m_rayDir = glm::normalize(m_rayDir);//長さ1.0に正規化する

知りたいオブジェクト平面に向ってレイを伸ばす。

私の場合、今回はオブジェクト座標においてZ=0になるような平面と、
レイが交差する点が知りたいのです
なのでこうしました

vec3<float> intersect_pt=near_pos+ray*near_pos.z;

なんかコレでいけました。
ほんとにこれでいいんだっけ?

全体のソースコード

上の話をいっぺんにまとめた部分がこれ
もっと全部のソースコードはgitへ!

mat4<float> mvp=m_proj_matrix*m_modelview;
            vec3<float> near_pos;
            unproject((float)m_win_size.x/4.0f,(float)m_win_size.y/4.0f,0.0f,mvp,m_win_size,&near_pos);
            vec3<float> far_pos;
            unproject((float)m_win_size.x/4.0f,(float)m_win_size.y/4.0f,1.0f,mvp,m_win_size,&far_pos);
            vec3<float> ray=far_pos-near_pos;
            ray.normalize();
            vec3<float> intersect_pt=near_pos+ray*near_pos.z;
            glPointSize(24);
            glBegin(GL_POINTS);
            glColor4ub(255,255,0,255);
            intersect_pt.glVertex();
            glColor4ub(255,0,0,255);

マウスで押した箇所を画面に文字で表示する

mousepos.png

まずはマウスが動く時に呼ばれるコールバック関数でマウスの位置をメンバ変数に入れておく

    void mousemove(double _mx, double _my) {
        m_mousPos.set(_mx, _my);
        m_Mouse->run(_mx, _my);    
    }

マウスで押した箇所を描画したい、などの場合はなるべく描画する直前でgluUnProjectをした方が確実に正確な位置を出せます。
mousemoveでgluUnProjectした値を使っていたら、マウスをプッシュしている間、点がおかしな位置に描画されるという現象になってしまいました。
    void drawMouseInfo() {
        vec3<double> nearpos, farpos;
        //ニアークリッピング面
        gluUnProject(m_mousPos.x, m_win_size.y - m_mousPos.y, 0, m_modelview.m, m_proj_matrix.m, m_viewport, &nearpos.x, &nearpos.y, &nearpos.z);
        //ファークリッピング面
        gluUnProject(m_mousPos.x, m_win_size.y - m_mousPos.y, 1.0, m_modelview.m, m_proj_matrix.m, m_viewport, &farpos.x, &farpos.y, &farpos.z);
        vec3<double> ray = farpos - nearpos;
        double depthlength = farpos.z - nearpos.z;
        double zero_parameter = (-nearpos.z) / depthlength;
        ray.normalize();
        //マウスで押した箇所、かつローカル座標でz=0地点における位置
        vec3<double> z_zero_pos = nearpos + ray*zero_parameter;
 
        glPointSize(8);
        glColor4ub(171, 82, 240,200);//purple
        glBegin(GL_POINTS);
        z_zero_pos.glVertex();
        glEnd();
        //マウスカーソル情報を文字で表示
        glRasterPos3d(z_zero_pos.x, z_zero_pos.y, z_zero_pos.z);
        stringstream message;
        message.setf(ios::floatfield, 8);
        message << "(" << z_zero_pos << ")";
        glutBitmapString(GLUT_BITMAP_HELVETICA_18, reinterpret_cast<const unsigned char*>(message.str().c_str()));
    }

gluUnprojectの中身

gluUnProject()関数は、呼ばれる度に「逆行列」を計算する。
従って、この関数を何度も使う場合は、自分で逆行列作って再利用した方が効率的な可能性がある。

    template <typename T>    
    int unproject(T deviceX, T deviceY, T deviceZ, const mat4<T>& _modelVieProj,vec2<int> _winsize, T _near,T _far,vec3<T> *obj_pos)
    {
        mat4<T> mvp_inv;
        if(!_modelVieProj.inv(&mvp_inv)){
            return 0;
        }
        //Transformation of normalized coordinates between -1 and 1
        vec4<T> normalized_device_pos;
        normalized_device_pos.x=(deviceX)/(T)_winsize.x*2.0-1.0;
        normalized_device_pos.y=(deviceY)/(T)_winsize.y*2.0-1.0;
        normalized_device_pos.z=2.0*deviceZ-1;//本当は(2.0*deviceZ-_near-_far)/(_far-_near);という意味
        normalized_device_pos.w=1.0;
        //Objects coordinates
        vec4<T> clip_pos=mvp_inv*normalized_device_pos;
        //MultiplyMatrixByVector4by4OpenGL_FLOAT(out, m, in);
        if(clip_pos.w==0.0)
            return 0;
        obj_pos->x=clip_pos.x/clip_pos.w;
        obj_pos->y=clip_pos.y/clip_pos.w;
        obj_pos->z=clip_pos.z/clip_pos.w;
        return 1;
    }
(3)
\begin{equation} screen^{-1}-> viewport^{-1}-> projection^{–1} -> model-view^{–1}-> point in “world” \end{equation}

正規化デバイス座標に(モデル行列×投影行列)の逆行列を掛け算している。

(4)
\begin{align} \left[ \begin{array}{cc} objX \\ objY \\ objZ \\ W\\ \end{array} \right] =INV(PM) \left[ \begin{array}{cc} \frac{2(winX-view[0])}{view[2]}-1 \\ \frac{2(winY-view[1])}{view[3]}-1 \\ 2(winZ)-1 \\ 1\\ \end{array} \right] \end{align}

Wの値も計算できるけど、使用しない。


frustum opengl-debug raycast viewport-matrix

サポートサイト Wikidot.com frustumopengl-debugraycastviewport-matrix