三维空间中,经常需要将3D空间中的点转换到2D(屏幕坐标),或者将2D点转换到3D空间中。当你使用OpenGL的时候,简单使用gluProject()和gluUnproject()函数就可以实现这个功能了。但这两个神奇的函数是怎样实现的,一直困扰着我,经过一番仔细研究,将自己的思路写在这里:
先通过看代码,来一步一步分析它的数学原理吧!(其实代码是次要的,数学原理在这里才是关键所在!)这里的代码据说是赖在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坐标)