Model View 之前的vertices是模型坐标系下的坐标MC,最后的vertices指设备坐标DC。
Each vertex of polygons will pass through two main stages of transformations:
There is one global matrix internally for each of the two stage above:
Given a 3D vertex of a polygon, P = [x, y, z, 1]T, in homogeneous coordinates, applying the model view transformation matrix to it will yield a new coordinate:
P’ = [x’, y’, z’, 1]T = Mmodelview*P. 注意P是模型坐标系下的点,P’则是相机坐标系下的点。此时相机坐标系下是3D物体
By applying projection to P’, a 2D coordinate in homogeneous form is produced:
P” = [x”, y”, 1]T = Mprojection*P’.
The final coordinate [x”, y”] will be a point on the screen to be drawn.
Each matrix can be, and should be, initialized to identity at the beginning of an application.
For example, setting the model view matrix to the identity matrix can be done by:
glMatrixMode(GL_MODELVIEW); //selecting the global matrix to be modified
glLoadIdentity(); //set the currently selected global matrix to identity
Similarly, we can set the projection matrix to identity as well:
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
These two global transformation matrices can be modified by the following elementary transformation functions as required by individual applications.
2. Elementary Transformation
where [dx, dy, dz] is the translation vector.
The effect of calling this function is to concatenate the translation matrix defined by the parameters [dx, dy, dz] to the global model view matrix:
In general, a new transformation matrix is always concatenated to the global matrix from the right. This is often called post-multiplication.
glRotate*(angle, x, y, z)
Where angle is the angle of counterclockwise rotation in degrees, and x, y and z define a vector(originating at the origin) to rotate about.
Typically we rotate about only one of the major axes, which simplifies x, y and z to be a unit vector.
For example, if we want to rotate about the x-axis, then x=1, y=0, and z=0.
The effect of calling a rotation matrix is similar to translation. For example, the function call:
glRotatef(a, 1, 0, 0);
will have the following effect:
Mmodelview = Mmodelview * Rx(a);
Where Rx(a) denote the rotation matrix about the x-axis for degree a。
Rotations about the y-axis or z-axis can be achieved respectively by functions calls:
glRotatef(a, 0, 1, 0); // rotation about the y-axis
glRotatef(a, 0, 0, 1); // rotation about the z-axis
glScale*(sx, sy, sz);
where sx, sy and sz are the scaling factors along each axis with respect to the local coordinate system of the model. The scaling transformation allows a transformation matrix to change the dimensions of an object by shrinking or stretching along the major axes centered on the origin.
Example: to make the wire cube three times as high, we can stretch it along the y-axis by a factor of 3 by using the following commands.
glScalef(1, 3, 1); // make the y dimension 3 times larger
glutWireCube(1); // draw the cube
It should be noted that the scaling is always about the origin along each dimension with the respective scaling factors.
Example: Suppose we want to rotate a cube 30 degrees and place it 5 unit length away from the camera for drawing. You might write the program intuitively as below:
// first rotate about the x axis by 30 degrees glRotatef(30, 1, 0, 0);
// then translate back 5 glTranslatef(0, 0, -5);
// use the GLUT library's cube routine to draw // a cube centered(by default) at the origin.实际上可以从右乘的角度来看,glTranslatef和glRotatef施加在模型上容易理解。
If you run this program, you might be surprised to find that nothing appears in the picture! Think about WHY.
If we modify the program slightly as below:
// first translate back 5 glTranslatef(0, 0, -5);
// then rotate about the x axis by 30 degrees glRotatef(30, 1, 0, 0);
// use the GLUT library's cube routine to draw // a cube centered(by default) at the origin
4. Modeling Transformation v.s. Viewing Transformation
视点变换。视点变换是在视点坐标系中进行的。视点坐标系于一般的物体所在的世界坐标系不同,它遵循左手法则,即左手大拇指指向Z正轴,与之垂直的四个手指指向X正轴,四指弯曲90度的方向是Y正轴。而世界坐标系遵循右手法则的。当矩阵初始化glLoadIdentity()后,调用glTranslatef()作视点变换。函数参数(x, y, z)表示视点或相机在视点坐标系中移动的位置,这里z=-5.0,意思是将相机沿Z负轴移动5个单位。通常相机位置缺省值同场景中的物体一样,都在原点处,而且相机初始方向都指向Z负轴。这里相机移走后,仍然对准立方体。如果相机需要指向另一方向,则调用glRotatef()可以改变。
模型变换。模型变换是在世界坐标系中进行的。在这个坐标系中,可以对物体实施平移glTranslatef()、旋转glRotatef()和放大缩小glScalef()。例子里只对物体进行比例变换,glScalef(sx, sy, sz)的三个参数分别是X、Y、Z轴向的比例变换因子。缺省时都为1.0,即物体没变化。程序中物体Y轴比例为2.0,其余都为1.0,就是说将立方体变成长方体。
glTranslatef(0, 0, -5); glRotatef(30, 0, 1, 0); DrawTheModel();
Using the local coordinate, 世界坐标系不动,局部坐标会随物体而变化we first move the local coordinate's origin down the negative z-axis by 5 units and then rotate that coordinate system about y-axis by 30 degrees.据后续的矩阵是右乘当前矩阵以上过程用矩阵描述为NewModel=Translate*Rotate*Model。可以看出local coordinate实际上指模型坐标系下的坐标,但该模型坐标系下的的坐标在世界坐标系中有唯一值。此时若给出模型坐标系在世界坐标系中的表示矩阵MC,则NewModel在世界坐标系中应该表示为WC=MC*NewModel。这其实类似于求模型坐标系下的点,在世界坐标系下的坐标。当然这个坐标有唯一确定的值。
Using the global coordinate,we first rotate the global coordinate system about its origin by -30 degrees, then move the global coordinate system's origin down the positive z-axis by 5 units. The model is fixed, but the global coordinate system is rotated and translated. The viewpoint locates at the origin of the global coordinate system. Remember that in the global coordinate approach, the order is reversed, and the orientation order is also reversed.
该物体在世界坐标系下WC=MC*NewModel=MC*T*R*Model。使用global coordinate表示该物体在模型坐标系下的坐标的方法Model=inv(R)*inv(T)*inv(MC)*WC。所以用global coordinate描述这个Model时存在以上问题。请注意人眼与世界坐标系的原点重合。
因为人眼与物体总是正对着,人眼向左转,则物体相对向右转;人坐着时眼睛和屏幕平齐,站起来后屏幕相对向下运动。这里的解释占在物体的相对移动的角度上讲viewing transformation(视点变换)。实际上从照相机(或观察者的角度来讲)glTranslatef(0, 0, -5)实质上是将相机沿Z负轴移动5个单位拉近了与物体的距离,若以相机的当前位置为原点建立观测坐标系则物体相对相机之前的位置沿Z正轴移动5个单位拉近了与相机的距离。glRotatef(30, 0, 1, 0)实质上是将相机沿y轴逆时针转30度,想象一下一个透明的钟表,当正对着表盘时看到表针顺时针转动,当从钟表后面看时就会发现表针在逆时针转动。呵呵,挺简单和有意思的吧,并不是外国人说的对初学者比较难。通常相机位置缺省值同场景中的物体一样,都在原点处,而且相机初始方向都指向Z负轴。
实际上,上述所说的视点变换和模型变换本质上都是一回事,即图形学中的几何变换。
只是视点变换一般只有平移和旋转,没有比例变换。当视点进行平移或旋转时,视点坐标系中的物体就相当于在世界坐标系中作反方向的平移或旋转。世界坐标系中的物体类似于镜子中的物体,其上下左右和人的直觉是相反的。因此,从某种意义上讲,二者可以统一,只是各自出发点不一样而已。
The following is the local approach to a rotation 下坐标系为模型坐标系,将此坐标系进行平移,旋转。
上图变换实际上MODELVIEW矩阵中的变换之一modeling transformation,该变换将模型坐标系下的坐标变换为世界坐标系下的坐标。
The following is the global approach to a rotation: ----相机(世界坐标系)的移动,物体的反方向相对移动
上图变换实际上是MODELVIEW矩阵中的另外一个矩阵viewing transformation,世界坐标系下的点通过该矩阵可以转换为观测坐标系下的点(相机坐标系下的点)。
重要的一点是以上两种情况的实质效果是一样的,都是为了描述Model这个客观物体。仅仅是角度不同罢了。
5. The gluLookAt() Function: define a viewing transformation
void gluLookAt( GLdouble eyex, GLdouble eyey, GLdouble eyez, GLdouble centerx, GLdouble centery, GLdouble centerz,GLdouble upx, GLdouble upy, GLdouble upz )Parameters
eyex, eyey, eyez: specifies the position of the eye point; centerx, centery, centerz: specifies the position of the reference point; upx, upy, upz: specifies the direction of the up vector.
Note that, these parameters are equivalent to the camera parameters specified in the textbook:
The gluLookAt() function makes it easy to move both the "from" and the "to" points in a linear manner. For example, if you need to pan along the wall of a building located away from the origin and aligned along no axes in particular, you could simply take the "to" point to be one corner of the building and calculating the "from" as a constant distance from the "to" point. To pan along the building, just vary the "to" point.
Mmodelview = Mviewing * Mmodeling
What this means is that your viewing transformations must be entered into the Modelview matrix before modeling transformations.
6. Manipulating the Matrix Directly
Three OpenGL commands are provided to directly manipulate the current matrix: glLoadIdentity(), glMultMatrix*() and glLoadMatrix*(). The glMultMatrix*()is used to perform matrix multiplication. glMultMatrix*(m) multiplies the current matrix with the one specified in m. That is, if M is the current matrix and T is the matrix passed to glMultMatrix, then M is replaced with MT. glLoadMatrix*(m) replaces the current matrix with the one specified in m. In the array m, the elements are stored in column order, whereas arrays in C or C++ are stored in row order. Do not be confused about these.
Viewport Transformation
For viewport transformations, glViewport() takes four parameters, which used to specify the lower-left corner coordinates and the width and height of the viewport. Since Windows sends a WM_SIZE message before it sends a WM_PAINT message, the WM_SIZE handler is the ideal place to set the viewpoint parameters and the projection transformation parameters. In the MFC class structure the current view class has an Onsize()member that is good for handling the viewport configuration.
Projection Transformation
Projection Transformations: OpenGL provides two methods of converting 3D images into 2D ones.
(一)视点变换
视点变换确定了场景中物体的视点位置和方向,就向上边提到的,它象是在场景中放置了一架照相机,让相机对准要拍摄的物体。确省时,相机(即视点)定位在坐标系的原点(相机初始方向都指向Z负轴),它同物体模型的缺省位置是一致的,显然,如果不进行视点变换,相机和物体是重叠在一起的。
执行视点变换的命令和执行模型转换的命令是相同的,想一想,在用相机拍摄物体时,我们可以保持物体的位置不动,而将相机移离物体,这就相当于视点变换;另外,我们也可以保持相机的固定位置,将物体移离相机,这就相当于模型转换。这样,在OpenGL中,以逆时针旋转物体就相当于以顺时针旋转相机。因此,我们必须把视点转换和模型转换结合在一起考虑,而对这两种转换单独进行考虑是毫无意义的。
除了用模型转换命令执行视点转换之外,OpenGL实用库还提供了gluLookAt()函数,该函数有三个变量,分别定义了视点的位置、相机瞄准方向的参考点以及相机的向上方向。该函数的原型为:
void gluLookAt(GLdouble eyex,GLdouble eyey,GLdouble eyez,GLdouble centerx,GLdouble centery,GLdouble centerz,GLdouble upx,GLdouble upy,GLdouble upz); |
该函数定义了视点矩阵,并用该矩阵乘以当前矩阵。eyex,eyey,eyez定义了视点的位置,eye表示我们眼睛在"世界坐标系"中的位置,;centerx、centery和centerz变量指定了参考点的位置,center表示眼睛"看"的那个点的坐标,可简单理解为视线的终点,该点通常为相机所瞄准的场景中心轴线上的点;upx、upy、upz变量指定了向上向量的方向,up坐标表示观察者本身的方向,如果将观察点比喻成我们的眼睛,那么这个up则表示我们是正立还是倒立异或某一个角度在看,所看的影像大不相同,。
通常,视点转换操作在模型转换操作之前发出,以便模型转换先对物体发生作用。场景中物体的顶点经过模型转换之后移动到所希望的位置,然后再对场景进行视点定位等操作。模型转换和视点转换共同构成模型视景矩阵。
变换局部坐标系的函数——glLoadIdentity()与glTranslatef()和glRotatef()
glLoadIdentity() --使用glLoadIdentity()使原点重新回到屏幕中心来
将当前的用户坐标系的原点移到了屏幕中心:类似于一个复位操作
1.X坐标轴从左至右,Y坐标轴从下至上,Z坐标轴从里至外。
2.OpenGL屏幕中心的坐标值是X和Y轴上的0.0f点。
3.中心左面的坐标值是负值,右面是正值。
移向屏幕顶端是正值,移向屏幕底端是负值。
移入屏幕深处是负值,移出屏幕则是正值。
glTranslatef(x, y, z)
其作用就是将你绘点坐标系的原点在当前原点的基础上平移一个(x,y,z)向量。物体是相对原点定义与绘制的,所以移动坐标原点就相当于物体的平移。将当前原点移动到(x,y,z)位置,既物体向左移动x,向上移动y,向前移动z。注意在glTranslatef(x, y, z)中,当您移动的时候,您并不是相对屏幕中心移动,而是相对与当前所在的屏幕位置。
glRotatef(angle, x, y, z)
glRotatef(angle, x, y, z)也是对坐标系进行操作。旋转轴经过原点,方向为(x,y,z),旋转角度为angle,方向满足右手定则。这里要注意物体如果不是在坐标原点,则不仅仅是简单的旋转。
(二)模型变换
模型变换是在模型坐标系中进行的。缺省时,物体模型的中心定位在世界坐标系的中心处。OpenGL在这个坐标系中,有三个命令,可以模型变换。
1、模型平移
glTranslate{fd}(TYPE x,TYPE y,TYPE z); |
该函数用指定的x,y,z值沿着x轴、y轴、z轴平移物体(或按照相同的量值移动局部坐标系)。
2、模型旋转
glRotate{fd}(TYPE angle,TYPE x,TYPE,y,TYPE z); |
该函数中第一个变量angle制定模型旋转的角度,单位为度,后三个变量表示以原点(0,0,0)到点(x,y,z)的连线为轴线逆时针旋转物体。例如,glRotatef(45.0,0.0,0.0,1.0)的结果是绕z轴旋转45度。
3、模型缩放
glScale{fd}(TYPE x,TYPE y,TYPE z); |
该函数可以对物体沿着x,y,z轴分别进行放大缩小。函数中的三个参数分别是x、y、z轴方向的比例变换因子。缺省时都为1.0,即物体没变化。程序中物体Y轴比例为2.0,其余都为1.0,就是说将立方体变成长方体。
(三)投影变换 投影变换。投影变换类似于选择相机的镜头。
经过模型视景的转换后,场景中的物体放在了所希望的位置上,但由于显示器只能用二维图象显示三维物体,因此就要靠投影来降低维数(投影变换类似于选择相机的镜头)。
事实上,投影变换的目的就是定义一个视景体,使得视景体外多余的部分裁剪掉,最终进入图像的只是视景体内的有关部分。投影包括透视投影(Perspective Projection)和正视投影(Orthographic Projection)两种。
透视投影,符合人们心理习惯,即离视点近的物体大,离视点远的物体小,远到极点即为消失,成为灭点。它的视景体类似于一个顶部和底部都被进行切割过的棱椎,也就是棱台。这个投影通常用于动画、视觉仿真以及其它许多具有真实性反映的方面。
OpenGL透视投影函数有两个,其中函数glFrustum()的原型为:
void glFrustum(GLdouble left,GLdouble Right,GLdouble bottom,GLdouble top,GLdouble near,GLdouble far); |
它创建一个透视视景体。其操作是创建一个透视投影矩阵,并且用这个矩阵乘以当前矩阵。这个函数的参数只定义近裁剪平面的左下角点和右上角点的三维空间坐标,即(left,bottom,-near)和(right,top,-near);最后一个参数far是远裁剪平面的Z负值,其左下角点和右上角点空间坐标由函数根据透视投影原理自动生成。near和far表示离视点的远近,它们总为正值。
另一个透视函数是:
void gluPerspective(GLdouble fovy,GLdouble aspect,GLdouble zNear, GLdouble zFar); |
它也创建一个对称透视视景体,但它的参数定义于前面的不同,参数fovy定义视野在X-Z平面的角度,范围是[0.0, 180.0]。fovy这个最难理解,我的理解是,眼睛睁开的角度,即,视角的大小,如果设置为0,相当你闭上眼睛了,所以什么也看不到,如果为180,那么可以认为你的视界很广阔;参数aspect是投影平面宽度与高度的比率,就是实际窗口的纵横比,即x/y如16:9,4:3等;参数zNear和Far分别是远近裁剪面沿Z负轴到视点的距离,它们总为正值。以上两个函数缺省时,视点都在原点,视线沿Z轴指向负方向。
再解释下那个"眼睛睁开的角度fovy"是什么意思,首先假设我们现在距离物体有50个单位距离远的位置,在眼睛睁开角度设置为45时,我们可以看到,在远处一个球,现在我们将眼睛再张开点看,将"眼睛睁开的角度"设置为178(180度表示平角,那时候我们将什么也看不到,眼睛睁太大了,眼大无神)我们只看到一个点,,,,,,,,,,,,,,,,,,,,,,,,,,,因为我们看的范围太大了,这个球本身大小没有改变,但是它在我们的"视界"内太小了,
反之,我们将眼睛闭小些,改为1度看看会出现什么情况呢?在我们距离该物体3000距离远,"眼睛睁开的角度"为1时,我们似乎走进了这个球内,这个是不是类似于相机的焦距?当我们将"透视角"设置为0时,我们相当于闭上双眼,这个世界清静了,我们什么也看不到,,,,,,,,,
正射投影,又叫平行投影。这种投影的视景体是一个矩形的平行管道,也就是一个长方体,如图五所示。正射投影的最大一个特点是无论物体距离相机多远,投影后的物体大小尺寸不变。这种投影通常用在建筑蓝图绘制和计算机辅助设计等方面,这些行业要求投影后的物体尺寸及相互间的角度不变,以便施工或制造时物体比例大小正确。
OpenGL正射投影函数也有两个,一个函数是:
void glOrtho(GLdouble left,GLdouble right,GLdouble bottom,GLdouble top, GLdouble near,GLdouble far) |
它创建一个平行视景体。实际上这个函数的操作是创建一个正射投影矩阵,并且用这个矩阵乘以当前矩阵。其中近裁剪平面是一个矩形,矩形左下角点三维空间坐标是(left,bottom,-near),右上角点是(right,top,-near);远裁剪平面也是一个矩形,左下角点空间坐标是(left,bottom,-far),右上角点是(right,top,-far)。所有的near和far值同时为正或同时为负。如果没有其他变换,正射投影的方向平行于Z轴,且视点朝向Z负轴。这意味着物体在视点前面时far和near都为负值,物体在视点后面时far和near都为正值。
另一个函数是:
void gluOrtho2D(GLdouble left,GLdouble right,GLdouble bottom,GLdouble top) |
它是一个特殊的正射投影函数,主要用于二维图像到二维屏幕上的投影。它的near和far缺省值分别为-1.0和1.0,所有二维物体的Z坐标都为0.0。因此它的裁剪面是一个左下角点为(left,bottom)、右上角点为(right,top)的矩形。
(四)视口变换。视口变换就是将视景体内投影的物体显示在二维的视口平面上。
运用相机模拟方式,我们很容易理解视口变换就是类似于照片的放大与缩小。在计算机图形学中,它的定义是将经过几何变换、投影变换和裁剪变换后的物体显示于屏幕窗口内指定的区域内,这个区域通常为矩形,称为视口。OpenGL中相关函数是:
glViewport(GLint x,GLint y,GLsizei width, GLsizei height); |
这个函数定义一个视口。函数参数(x, y)是视口在屏幕窗口坐标系中的左下角点坐标,参数width和height分别是视口的宽度和高度。缺省时,参数值即(0, 0, winWidth, winHeight) 指的是屏幕窗口的实际尺寸大小。所有这些值都是以象素为单位,全为整型数。
(五)裁剪变换
注意:OpenGL中的物体坐标一律采用齐次坐标,即(x, y, z, w),故所有变换矩阵都采用4X4矩阵。一般说来,每个顶点先要经过视点变换和模型变换,然后进行指定的投影,如果它位于视景体外,则被裁剪掉。最后,余下的已经变换过的顶点x、y、z坐标值都用比例因子w除,即x/w、y/w、z/w,再映射到视口区域内,这样才能显示在屏幕上。
在OpenGL中,除了视景体定义的六个裁剪平面(上、下、左、右、前、后)外,用户还可自己再定义一个或多个附加裁剪平面,以去掉场景中无关的目标。附加平面裁剪函数为:
1、void glClipPlane(GLenum plane,Const GLdouble *equation);
函数参数equation指向一个拥有四个系数值的数组,这四个系数分别是裁剪平面Ax+By+Cz+D=0的A、B、C、D值。因此,由这四个系数就能确定一个裁剪平面。参数plane是GL_CLIP_PLANEi(i=0,1,...),指定裁剪面号。
在调用附加裁剪函数之前,必须先启动glEnable(GL_CLIP_PLANEi),使得当前所定义的裁剪平面有效;当不再调用某个附加裁剪平面时,可用glDisable(GL_CLIP_PLANEi)关闭相应的附加裁剪功能。