gluProject 和 gluUnproject 解析

简介

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

gluPorject()

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

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

</pre><pre name="code" class="cpp">/*
* 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,这里我暂时还没有验证,待我有空的时候再检查一下是否确实如此!呵呵

你可能感兴趣的:(vector,input,Matrix,transformation,output)