图形学第三次实验.docx_第1页
图形学第三次实验.docx_第2页
图形学第三次实验.docx_第3页
图形学第三次实验.docx_第4页
图形学第三次实验.docx_第5页
已阅读5页,还剩16页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

实验三 几何变换与光照1. 实验目的l 熟悉三维图形显示的基本流程l 熟悉视图变换的设置l 熟悉Windows环境下几何变换的设置与实现l 熟悉光照的设置2. 实验内容在实验一的基础上,增加如下功能:l 利用鼠标的中键(滚轮)实现几何物体的缩放l 利用鼠标的左键实现几何物体的旋转l 利用鼠标右键实现几何物体的平移l 在场景中设置好光照,要求包括:环境光、漫反射光、镜面反射光3. 实验指导在设置场景中的几何变换时,我们需要对三维图形的基本显示流程或者说显示流水线有个基本了解,而后熟悉OpenGL中每种几何变换的实现方法。3.1 三维图形的基本显示流程在计算机绘图中,对场景进行变换的整个过程类似于用一台相机拍摄照片。如图 2.1所示,相机拍摄照片的具体步骤(括号内表示相应的计算机绘图操作)如下:(1) 将相机固定在三脚架上并使相机对准场景(视图变换)(2) 使想要拍摄的场景处于取景框中的合适位置(模型变换)(3) 选择相机镜头或调整放大倍数(投影变换)(4) 决定最终照片尺寸的大小,比如想要得到一张经过放大后的照片(视区变换)(a)照相机(b)计算机环境图 0.1 和照相机类比的计算机显示流程这些步骤与程序中指定的变换顺序相对应,然而这一顺序并不是固定的。在源代码中,视图变换一定要在模型变换之前执行,但是投影变换和视区变换则可以在进行绘图之前的任何时刻进行。图 2.2显示了在计算机中上述操作的顺序。图 0.2 OpenGL的几何变换流水线及各阶段的主要函数既然在计算机中OpenGL的几何变换具有如图 0.2所示的固定流水线,为什么在源代码中,视图变换一定要在模型变换之前执行,但是投影变换和视区变换则可以在绘图之前的任何时刻进行呢?这主要是因为在OpenGL中,维护了一个模型视图矩阵和一个投影矩阵(准确的来说,是维护了一个模型视图矩阵堆栈和一个投影矩阵堆栈,栈顶的矩阵即为当前的变换矩阵),对于物体空间的任意一点,其投影以后的图像坐标由式得到,因此,模型视图变换和投影变换在源代码中的先后顺序并不影响程序的结果,视口变换也是一样,它也是单独保存的,规格化后的设备坐标经视区变换后即可得到屏幕坐标,它的顺序也不影响程序的结果。但是,模型变换和视图变换是由一个矩阵来管理的,在OpenGL中,由于各几何变换都是对当前矩阵栈栈顶的矩阵做相应的右乘操作,即,其中为当前矩阵栈栈顶的矩阵,为当前的几何变换,图 0.2中的模型变换和视图变换在源代码中的顺序需要反过来, 即视图变换必须在模型变换之前进行。这一规则对于任一变换序列均适用,即在源代码中的变换序列的顺序需要和实际的变换顺序相反,这主要是由于上述的矩阵操作是右乘操作引起的,如图 2.3所示。图 0.3 源代码中模型视图变换序列的顺序与实际场景中的变换顺序相反原理图对应于几何变换流水线的各个阶段,OpenGL提供了不同的函数以方便用户实现各种变换,如图 0.2所示。3.1.1视图变换在进行视图变换前,需确保当前操作的矩阵是模型视图矩阵,即调用:glMatrixMode(GL_MODELVIEW)。gluLookAt(x0, y0, z0, xRef, yRef, zRef, xUp, yUp, zUp)该函数的作用即确定场景中照相机的位置和方向,也即指定视点坐标系。函数参数所确定的视点坐标系如图 2.4所示,指定了视点坐标系的原点,指定了视点坐标系向上的方向, 指定了视点坐标系的正前方。图 0.4 gluLookAt()所定义的视点坐标系如果不引用gluLookAt()函数,默认的OpenGL参数是3.1.2 模型变换在进行模型变换前,需确保当前操作的矩阵是模型视图矩阵,即调用:glMatrixMode(GL_MODELVIEW)。glTranslated,f(xOffset, yOffset, zOffset)xOffset,yoffset,zOffset分别指明沿x,y,z轴方向的平移量glScaled,f(xScale, yScale, zScale)xScale,yScale,zScale分别指明沿x,y,z轴方向的缩放量glRotated,f(angle, x, y, z)绕轴逆时针方向旋转angle角度,这里需要注意如下两个方面的问题:(1) 旋转角度的单位是度(degree),而不是弧度(radian)(2) 旋转轴必须过坐标原点,如果旋转轴不过原点,需要先将旋转轴平移到坐标原点,而后做相应的逆变换,如图 2.5所示。这里需要说明一下变换顺序的问题,这里我们需要得到的最终变换矩阵应为:在OpenGL中,由于各几何变换(旋转,平移,缩放)都是对当前矩阵栈栈顶的矩阵做相应的右乘操作,即,其中为当前矩阵栈栈顶的矩阵,为当前的几何变换,上述的变换序列在OpenGL中的顺序需要反过来,用代码可以表示为: glTranslated(x0, y0, z0);glRotated(angle, x, y, z);glTranslated(-x0, y0, z0);图 0.5 旋转轴不过坐标原点时的处理3.1.3 投影变换在进行投影变换前,需确保当前操作的矩阵是投影矩阵,即调用:glMatrixMode(GL_PROJECTION)。投影变换包括正交投影和透视投影两种。正交投影变换可以通过如下函数指定:glOrtho(left, right, top, bottom, near, far)其各参数均是在视点坐标系中定义的,其意义如图 2.6所示,near和far值为近、远裁剪面距离视点坐标系原点的距离,两个参数中任意一个负值表明在观察原点的“后面”。若要使得该函数定义的视景体有效,需满足,OpenGL正交投影的默认参数值是,生成一个在视点坐标系中的规范化的立方体,即:glOrtho(-1, 1, -1, 1, -1, 1);图 0.6 glOrtho()函数所定义的视景体(View Volume)透视投影可以通过如下两个函数分别定义:gluPerspective(fovy, ratio, near, far)各参数的几何意义如图 2.7所示,该函数可以定义一个四凌锥的视景体。图 0.7 由gluPerspective()函数指定的透视视景体glFrustum(left, right, top, bottom, near, far)glFrustum()函数中各参数的意义与glOrtho()函数类似,与之不同的是,前四个参数仅用于定义近裁剪面,即参数和分别指定近裁剪面的左下角和右上角的x,y,z坐标;参数near和far分别表示视点与近裁剪面和远裁剪面的距离。图 0.8 由glFrustum()函数指定的透视视景体注意:为了避免视窗中显示的物体与实际的几何物体发生比例失调的现象,投影变换中的长宽比应该与Windows视窗的长宽比保持一致,该操作通常在Windows的WM_SIZE消息的响应函数OnSize()中实现。如果是利用gluPersepctive()函数指定视景体,其实现方式为:void CView:OnSize(UINT nType, int cx, int cy)/ select the full client areaglViewport(0, 0, cx, cy);/ compute the aspect ratio/ this will keep all dimension scales equalGLdouble aspect_ratio = (GLdouble)cx/(GLdouble)cy;gluPerspective(60.0f, aspect_ratio, .1f, 200.0f);如果是利用glOrth()或者glFrustum()函数指定的视景体,其实现方式为:void CView:OnSize(UINT nType, int cx, int cy)/ TODO: Add your message handler code hereif ( 0 = cx | 0 = cy )return;/ select the full client areaglViewport(0, 0, cx, cy);/ compute the aspect ratio/ this will keep all dimension scales equaldouble xExt = 1.0, yExt = 1.0;if(cx cy)xExt = cx / cy;elseyExt = cy / cx;glOrtho(-xExt, xExt, -yExt, yExt, 1.0f, 1000.0f);/glFrustum(-xExt, xExt, -yExt, yExt, 1.0f, 1000.0f);3.2 几何变换在MFC文档/视图结构中的实现位置对于几何变换流水线中的各个阶段,在MFC的文档/视图结构中,又该在什么时机进行设置呢?投影变换、视图变换和视区变换,由于与Windows的窗口大小相关,通常写在WM_SIZE消息的响应函数OnSize()中,模型变换则在具体绘制函数中实现,因而放置在WM_PAINT消息的响应函数OnDraw()中,如图 2.9所示。图 0.9 各几何变换在MFC中的实现位置以下是一个OnSize()函数的典型实现过程。void CView:OnSize(UINT nType, int cx, int cy)CView:OnSize(nType, cx, cy);/ TODO: Add your message handler code hereif ( 0 = cx | 0 = cy )return; /视区变换/ select the full client areaglViewport(0, 0, cx, cy);/投影变换/ compute the aspect ratio, this will keep all dimension scales equalGLdouble aspect_ratio = (GLdouble)cx/(GLdouble)cy;/ select the projection matrix and clear itglMatrixMode(GL_PROJECTION);glLoadIdentity();/ select the viewing volumegluPerspective(60.0f, aspect_ratio, .1f, 200.0f); /模型视图变换,以视图变换开始/ switch back to the modelview matrix and clear itglMatrixMode(GL_MODELVIEW);glLoadIdentity();gluLookAt(0, 0, 2, 0, 0, 0, 0, 1, 0);注意:在进行模型视图变换之前,需要将矩阵模式切换到模型视图变换矩阵状态,在进行投影变换之前,则需将阵阵模式切换到投影矩阵状态,其实现方式分别为:glMatrixMode(GL_MODELVIEW); 和glMatrixMode(GL_PROJECTION);3.3 利用Arc Ball方法实现几何物体的旋转三维几何物体通过投影变换最终显示在二维屏幕上,但如何利用鼠标在二维屏幕上的移动来方便直观的控制三维物体的旋转呢?最简单的方法是将鼠标在水平方向的移动量映射为绕x轴的转动角度,将垂直方向的移动量映射为绕y轴的转动角度,三维物体的转动即可表示为先绕x轴转动一个角度再绕y轴转动一个角度。即offset = point lastPoint;xAngle = factor * offset.x;yAngle = factor * offset.y;glScalef(xAngle, 1.0f, 0.0f, 0.0f);glScalef(yAngle, 0.0f, 1.0f, 0.0f);但这种方法控制起来不是十分灵活,不能很好的体现用户的旋转意图,ArcBall方法是一种比较好的解决方案。ArcBall的主要思想为:(1) 将二维屏幕上的任意一点映射为球面上的一个三维坐标,该球面通常以屏幕中心为球心,半径为使球面恰好能够包含整个屏幕。(2) 鼠标在屏幕上的移动可以转换为三维球面上的运动,从而通过相应的方法计算出相应的旋转轴和旋转角度,几何物体的旋转即为绕该轴旋转角度,如图 2.10所示。图 0.10 ArcBall方法中旋转轴和旋转角度的几何解释球面坐标的映射球面坐标的映射可按式进行计算,其中(x,y)为屏幕上的二维坐标,R为球面半径:具体计算时,为了便于计算,往往先将坐标进行单位化,即将屏幕映射为一个以屏幕中心为球心,半径为1的球体,而将屏幕坐标的x,y分量映射到-1, 1这个区间,设屏幕的宽和高分别为w,h,则计算公式为:这里需要注意Windows中的y坐标与OpenGL中的y坐标的不同,Windows中的坐标原点在屏幕左上角,而OpenGL中的坐标原点在屏幕的左下角,如图 2.11所示,因此,OpenGL中的y坐标与Windows中的坐标转换为:,式转化成:此时,公式可表示为图 0.11 Windows与OpenGL中的坐标系其代码实现可表示为:CPoint3D CArcBall:Coordinate2Sphere(int x, int y)double xScale = 2.0 / m_winWidth;double yScale = 2.0 / m_winHeight;double xPos = x * xScale - 1;double yPos = 1 - y * yScale;double zPos = 0.0;double len = xPos * xPos + yPos * yPos;if (len 1.0)zPos = sqrt(1.0 - len);return CPoint3D(xPos, yPos, zPos);旋转轴和旋转角度的计算设鼠标移动的起始点和终止点对应的球面坐标分别为和,则旋转轴:旋转角度:,这里的旋转角度通常取和夹角的2倍,如图 2.12所示。图 0.12 旋转角度通常取和夹角的2倍物体的旋转即为绕该旋转轴旋转角度。这里需要计算物体绕给定轴旋转给定角度的旋转矩阵。设旋转轴过坐标原点,单位化后为,即,旋转角度为,则旋转矩阵可表示为:其中,。其代码实现为:CRotateMatrix s_GetRotateMatrix(double angle, double x, double y, double z)#define ZERO_TOL 1.0e-7double s = sin(angle);double c = cos(angle);CRotateMatrix matrix;if (fabs(z) ZERO_TOL)if (fabs(y) ZERO_TOL)/if the axis is (0, 0, 0), assume to be identity matrixif(fabs(x) 0)matrix12 = -s; matrix21 = s;elsematrix12 = s; matrix21 = -s;return matrix;else if (fabs(x) 0)matrix02 = s; matrix20 = -s;elsematrix02 = -s; matrix20 = s;return matrix;else if (fabs(y) ZERO_TOL)if (fabs(x) 0)matrix01 = -s; matrix10 = s;elsematrix01 = s; matrix10 = -s;return matrix;/common case/normalize the rotation axisdouble mag = sqrt(x * x + y * y + z * z);mag = 1.0 / mag;x *= mag;y *= mag;z *= mag;double t = 1.0 - c;double tx = t * x;double ty = t * y;double tz = t * z;double sx = s * x;double sy = s * y;double sz = s * z;/-/| t*x*x + ct*x*y - s*zt*x*z + s*y |/|/R = | t*x*y + s*zt*y*y + ct*y*z - s*x |/|/| t*x*z - s*yt*y*z + s*xt*z*z + c|/ where c = cos(theta), s = sin(theta), t = 1 - c and(x, y, z) is a unit/ vector on the axis of rotation./-/ row onematrix00 = tx * x + c;matrix01 = tx * y - sz;matrix02 = tx * z + sy;/ row twomatrix10 = matrix01 + sz + sz;/ tx * y + szmatrix11 = ty * y + c;matrix12 = ty * z - sx;/ row threematrix20 = matrix02 - sy - sy;/ tx * z - symatrix21 = matrix12 + sx + sx;/ ty * z + sxmatrix22 = tz * z + c;return matrix;3.4 利用鼠标实现物体的平移三维空间中的点经过一系列的几何变换,投影变换,视口变换最终投影到二维屏幕上,如图 0.2所示,设几何变换为,投影变换为,视口变换为,则投影后的屏幕上的左边可表示为:因此,对于二维屏幕上的坐标点,可以通过上述过程的逆变换,如公式所示,得到物体空间中的点,从而将鼠标在屏幕上的移动映射到三维物体空间中的移动,实现鼠标控制的三维几何物体的平移。OpenGL中通过gluUnProject()函数实现这一逆变换过程。int gluUnProject (GLdouble winx, GLdouble winy, GLdouble winz, const GLdouble modelMatrix16, const GLdouble projMatrix16, const GLint viewport4, GLdouble *objx, GLdouble *objy, GLdouble *objz);该函数使用由模型视图矩阵(modelMatrix)、投影矩阵(projMatrix)和视口(viewport)所定义的变换,将指定的窗口坐标(winx, winy, winz)映射到对象坐标中来,变换后的对象左边通过(objx, objy, objz)返回。OpenGL中当前的模型视图矩阵和投影矩阵可以通过glGetDoublev()函数获得,其参数分别指定为GL_MODEL_VIEW_MATRIX和GL_PROJECTION_MATRIX;视口的参数可以通过glGetIntergerv()函数获得,其参数需指定为GL_VIEWPORT。具体实现为:CPoint3D WindowToModel(const CPoint &point, double depth /*= 0.0*/)double matModel16, matProj16;double coord3;/double zRange2;int viewPort4;MakeCurrent(true);/ Get transform matrix glGetDoublev(GL_PROJECTION_MATRIX, matProj);glGetDoublev(GL_MODELVIEW_MATRIX, matModel);/ Get view portglGetIntegerv(GL_VIEWPORT, viewPort);/ Get Depth range/glGetDoublev(GL_DEPTH_RANGE, zRange);/ Unproject screen point to model gluUnProject(point.x, m_nWinHeight - point.y, depth, matModel, matProj, viewPort, &coord0, &coord1, &coord2);MakeCurrent(false);return CPoint3D(coord0, coord1, coord2);与gluUnProject()函数相对的是gluProject(),gluProject()函数模拟三维物体的渲染流程,将三维空间中的给定点(objx, objy, objz)在模型视图矩阵、投影矩阵、视口矩阵的作用下变换到二维屏幕空间中的坐标(winx, winy, winz),其中winz对应的是该点的深度。int gluUnProject (GLdouble objx, GLdouble objy, GLdouble objz, const GLdouble modelMatrix16, const GLdouble projMatrix16, const GLint viewport4, GLdouble *winx, GLdouble *winy, GLdouble *winz);3.5 利用鼠标实现几何场景的缩放几何场景的缩放可以通过鼠标滚动的增量来实现,当增量为正时,表示放大,当增量为负时,表示缩小,这里需要注意的是,需要把增量映射到一个较小的粒度,使得缩放的变化比较平滑。典型的实现为:BOOL CTeapotView:OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)/ TODO: Add your message handler code here and/or call defaultconst static double scaleFactor = 0.001;double scale = 1 + zDelta * scaleFactor;if(scale 0)m_xform.Scale(scale);Invalidate(FALSE);return CView:OnMouseWheel(nFlags, zDelta, pt);3.6 几何场景中光照的设置OpenGL中对光照的模拟将光源分成环境光、漫反射光、镜面光几部分,而物体自身材质对光照的影响则由物体材质对不同颜色的光线的反射系数来决定,因此,场景中光照的设置主要包括四个个方面的设置:光源属性的设置、物体自身材质属性的设置、顶点法向的设置、ShadeModel的设置。光源的属性,主要包括光源的位置,环境光、漫反射光、镜面反射光的颜色,镜面光的高光指数。物体材质的属性,即为物体对不同光线的反射系数,也可认为是物体自身的颜色。3.6.1 光源属性的设置glLightif(light, pname, param)参数中light指定哪一个光源,OpenGL中可以指定GL_LIGHT0、GL_LIGHT1到GL_LIGHT7的8个光源。光源属性由pname定义,常用的光源属性及其默认参数如表 1所示:表 1 glLight*()中参数pname的默认值参数名默认值意义GL_AMBIENT(0.0, 0.0, 0.0, 1.0)光源的环境光亮度GL_DIFFUSE(1.0, 1.0, 1.0, 1.0) 或(0.0, 0.0, 0.0, 1.0)光源的散射光的光亮度(GL_LIGHT0时默认为白光,其他则为黑色)GL_SPECULAR(1.0, 1.0, 1.0, 1.0) 或(0.0, 0.0, 0.0, 1.0)光源的镜面光的光亮度(GL_LIGHT0时默认为白光,其他则为黑色)GL_SHININESS1.0光源镜面光的高光因子GL_POSITION(0.0, 0.0, 1.0, 0.0)光源的位置(x, y, z, w),w为0时,为方向光源,只表示光源的方向典型的定义的光源属性的代码GLfloat light_ambient = 0.0, 0.0, 0.0, 1.0;GLfloat light_diffuse = 1.0, 1.0, 1.0, 1.0;GLfloat light_specular = 1.0, 1.0, 1.0, 1.0;GLfloat light_position = 1.0, 1.0, 1.0, 0.0;glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient);glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse);glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular);glLightfv(GL_LIGHT0, GL_POSITION, light_position);如果需要定义多个光源只需重复上述代码,将其中的GL_LIGHT0改为其他的光源即可。为了产生接近真实世界的效果,通常将参数GL_SPECULAR和GL_DIFFUSE设为相同的值。3.4.2 ShadeModel的设置OpenGL的光照计算中,只是针对多边形的顶点进行计算,对于多边形内部各点的光照通过插值的方法实现。OpenGL允许两种不同的插值方式:Flat Shading和Smooth Shading。Flat Shading方法中多边形内部各点的光照与多边形中某个顶点的关照一致,Smooth Shading方法中多边形内部各点的光照采用双线性插值的方法得到,因此能够实现由一个顶点的颜色平滑的过渡到另一顶点的颜色的效果。如图 2.13所示,相同的绘制代码,在不同的ShadeModel下具有不同的效果。glBegin(GL_TRIANGLES);glColor3f(1.0f, 0.0f, 0.0f);glVertex2d(0.0, 0.0);glColor3f(0.0f, 1.0f, 0.0f);glVertex2d(1.0, 0.0);glColor3f(0.0f, 0.0f, 1.0f);glVertex2d(0.0, 1.0);glEnd(); (a)GL_FLAT的效果 (b)GL_SMOOTH的效果图 0.13 不同Shade Model的效果对比ShadeModel的设置由以下函数实现:glShadeModel(mode)mode参数取值可以为:GL_FLAT或者GL_SMOOTH3.4.3 物体材质属性的设置OpenGL中将物体的材质定义成对不同类型光源的反射系数,也可认为是在该类型光源下的材质颜色,其定义可以由以下函数实现:glMaterialI, f(face, pname, param)参数face分别可取GL_FRONT、GL_BACK、GL_FRONT_AND_BACK,即对物体的正面,背面以及双面分别设置材质。pname的常用的参数如表 2所示:表 2 glMaterial*()中参数pname的参数值参数名默认值意义GL_AMIBIENT(0.2, 0.2, 0.2, 1.0)材质的环境颜色GL_DIFFUSE(0.8, 0.8, 0.8, 1.0)材质的散射颜色GL_AMBIENT_AND_DIFFUSE材质的环境颜色和散射颜色GL_SPECULAR(0.0, 0.0, 0.0, 1.0)材质的镜面反射颜色GL_SHININESS0.0镜面反射高光指数,取值范围为0, 128GL_EMISSION(0.0, 0.0, 0.0, 1.0)材质的自发光颜色典型的定义材质属性的代码:GLfloat mat_ambient = 0.7, 0.7, 0.7, 1.0;GLfloat mat_diffuse = 0.1, 0.5, 0.8, 1.0;GLfloat mat_specular = 1.0, 1.0, 1.0, 1.0;GLfloat no_mat = 0.0, 0.0, 0.0, 1.0;GLfloat shininess = 50.0f;glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient);glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);glMaterialfv(GL_FRON

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论