[转]gluProject 和 gluUnproject 的详解

gluProject 和 gluUnproject 的详解

简介

三维空间中,经常需要将 3D 空间中的点转换到 2D(屏幕坐标),或者将 2D 点转换到 3D 空间中。当你使用 OpenGL 的时候,简单使用 gluProject() 和 gluUnproject() 函数就可以实现这个功能了。但这两个神奇的函数是怎样实现的,一直困扰着我,经过一番仔细研究,将自己的思路写在这里:

gluPorject()

先通过看代码,来一步一步分析它的数学原理吧!(其实代码是次要的,数学原理在这里才是关键所在!)这里的代码据说是赖在 mesa OpenGL 中的!

PS:这里为了更好理解,我修改了一下里面的代码,但没有兼顾效率的!     

#include<GL/gl.h>

/*

* Transform a point (column vector) by a 4x4 matrix. I.e. out = m * in

* Input: m - the 4x4 matrix

* in - the 4x1 vector

* Output: out - the resulting 4x1 vector.

*/

static void transform_point(GLdouble out[4],

              const GLdouble m[16],

              const GLdouble in[4])

{

#define M(row,col) m[col* 4+ row]

out[0] = M(0, 0) * in[0] + M(0, 1) * in[1] + M(0, 2) * in[2] + M(0, 3) * in[3];

out[1] = M(1, 0) * in[0] + M(1, 1) * in[1] + M(1, 2) * in[2] + M(1, 3) * in[3];

out[2] = M(2, 0) * in[0] + M(2, 1) * in[1] + M(2, 2) * in[2] + M(2, 3) * in[3];

out[3] = M(3, 0) * in[0] + M(3, 1) * in[1] + M(3, 2) * in[2] + M(3, 3) * in[3];

#undef M

}

 

GLint gluProject(GLdouble objx,

         GLdouble objy,

         GLdouble objz,

         const GLdouble model[16],

         const GLdouble proj[16],

         const GLint viewport[4],

         GLdouble*winx,

         GLdouble*winy,

         GLdouble*winz) /* transformation matrix */

{

GLdouble objCoor[4];

GLdouble objProj[4], objModel[4];

/* initilise matrix and vector transform */

// 4x4 matrix must be multi to a 4 dimension vector( it a 1 x 4 matrix)

// so we need to put the original vertex to a 4D vector

objCoor[0] = objx;

objCoor[1] = objy;

objCoor[2] = objz;

objCoor[3] = 1.0;

// 由于原来的向量位于标准基向量 (1, 0, 0), (0, 1, 0), (0, 0, 1) 中,所以需要先转换到当前的模型矩阵中

transform_point(objModel, model, objCoor);

// 然后将模型矩阵中的顶点转换到投影矩阵所在坐标系的矩阵中

transform_point(objProj, proj, objModel);

// scale matrix

/* GLdouble scaleMat[4][4] =

{ {0.5, 0, 0, objPr0j[3]}, {0, 0.5, 0,

objProj[3]}, {0, 0, 0.5, objProj[3]}, {1, 1, 1,

1} }; GLdouble objProjTemp[4];

memcpy(objProjTemp, objProj, sizeof(objProjTemp);

transfrom_point(objProj, scaleMat, objProjTemp); */

/* or the result of normalized between -1 and 1 */

if(objProj[3]== 0.0)

return GL_FALSE;

 

objProj[0] /= objProj[3];

objProj[1] /= objProj[3];

objProj[2] /= objProj[3]; /* in screen coordinates */

// 由于投影矩阵投影在 [-1, 1] 之间,所以需要将转换后的投影坐标放置到 [0, 1] 之间

// 最后再在一个 offset 矩形中转换为屏幕坐标就可以了( viewport[4] 可以简单的认为一个 offset 矩形)

#define SCALE_FROM_0_TO_1(_pt) (((_pt)+ 1) / 2)

objProj[0] = SCALE_FROM_0_TO_1(objProj[0]);

objProj[1] = SCALE_FROM_0_TO_1(objProj[1]);

objProj[2] = SCALE_FROM_0_TO_1(objProj[2]);

#undef SCALE_FROM_0_TO_1

*winx= viewport[0] + objProj[0] * viewport[2];

*winy= viewport[1] + objProj[1] * viewport[3]; /* between 0 and 1 */

*winz= objProj[2];

return GL_TRUE;

}

基本的思路就是:

1、将输入的顶点,通过模型视图矩阵,变换到模型视图矩阵的坐标系中;

2、将模型视图矩阵中的顶点,再变换到投影矩阵中;

3、将顶点缩放到 [0, 1] 的映射区间中;

4、通过视口的位置和大小, 计算出当前 3D 顶点中的屏幕坐标( 2D 坐标)

gluUnproject

其实 gluUnproject 和 gluProject 是非常类似的, 代码我暂时没有去找,但我认为应该是这样的(其实就是 gluPorject 反过来的过程,只是有一些数学运算要注意一下) :

1、首先,需要将输入的顶点,通过视口变换到 [0, 1] 之间;

2、然后将顶点缩放到 [-1, 1] 之间,就是上面代码中的 scaleMat矩阵的逆矩阵

3、然后乘上投影矩阵的逆矩阵;

4、最后就是乘上模型视图矩阵的逆矩阵; gluProject,这里我暂时还没有验证,待我有空的时候再检查一下是否确实如此!呵呵

逆变换和模拟变换:http://book.51cto.com/art/201002/185504.htm

引用:

gluProject/gluUnProject don't modify these matrices. In the case of gluProject it applies the transformations (the modelview, projection, and viewport) the same way the standard pipeline does.

In the case of gluUnProject(4) they use the inverses of those matrices.

if v is the vertex, M is the modelview matrix, P is the projection matrix, and V is the viewport matrix:

v given in object space
gluProject returns V*P*M*v

v given in screen space
gluUnProject returns Mi*Pi*Vi*v
(where i represents the matrix inverse)

原文地址:https://www.cnblogs.com/MakeView660/p/10482988.html