




版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
第10章OpenGL程序设计基础10.1
OpenGL概述
10.2基于OpenGL的基本图形绘制
10.3基于VC++的OpenGL坐标变换
10.4用OpenGL生成曲线和曲面
10.5
OpenGL的光照处理
10.6
OpenGL对交互绘图的支持
习题10
10.1
OpenGL概述
10.1.1
OpenGL编程的两个基本特点
在Windows环境中编写程序,一般具有两个特点:采用事件驱动方式和面向对象方法编写应用程序。
在PC的DOS操作系统时代,DOS操作系统每次只能接受一个用户程序,然后把计算机CPU的控制权交给应用程序,应用程序执行完毕之后,再把CPU的控制权交回DOS。通过阅读这种应用程序的程序清单,用户可以很清楚地了解该程序的逻辑执行顺序。它的缺点是用户开发新的程序时编程的工作量大,应用程序会独占CPU的资源,这种程序不
便在多任务操作系统中使用。最简单的事件驱动方式也就是中断处理方式,即由操作系统用中断方式统一管理计算机的外部设备,每当外设发生一个中断时操作系统分清该中断产生的数据与类型,然后把对该事件处理的中断服务程序交用户程序处理。该事件处理完毕后,操作系统把CPU的控制权收回并等待新的事件发生,如此循环。故在这种方式下操作系统可以同时接受多个用户的应用程序。Windows操作系统就是采用这种方式处理各种应用程序的。
Windows是一个多任务的操作系统,其编程风格转向采用事件驱动。Windows操作系统统一接收管理计算机中发生的所有事件,并负责消息的发送;而用户程序主要根据这些消息,分别处理每个消息对应事件的响应,此时,用户程序主要由多个事件的响应程序等组成;同时,Windows操作系统还为应用程序编写了发送消息的函数,用户可以通过发送消息,经Windows操作系统来间接控制用户程序的所有分支都得到有效的执行。从这个意义上说,产生事件或发送消息是面向对象程序得到执行处理的唯一机会。这种程序实现方法的好处是每个用户程序只占用事件处理等少量的CPU时间,剩余时间把对CPU的控制权都交还给操作系统,由此,操作系统可以腾出时间处理其他用户的程序,这样就使得鼠标、键盘、窗口等事件的处理变得非常简单,且编程工作量小。但是这种编程方式也使得程序执行的逻辑关系变得相对复杂,阅读理解相对困难。另外,目前Windows程序在实时控制中的稳定性还有待提高。10.1.2
OpenGL的主要功能
1.OpenGL基本操作
虽然客观事物的形状千变万化,但用计算机将之描述,只需把一系列基本操作组合起来即可。OpenGL提供了以下基本操作:
(1)绘制物体:真实世界里的任何物体都可以在计算机中用简单的点、线、多边形来描述,OpenGL提供了丰富的基本图元绘制命令,从而可以方便地绘制物体。
(2)变换:无论多么复杂的图形都是由基本图元组成并经过一系列变换来实现的,OpenGL提供了一系列基本的变换,如取景变换、模型变换、投影变换及视口变换。
(3)光照处理:正如自然界不可缺少光一样,绘制有真实感的三维物体必须做光照处理。
(4)着色:OpenGL提供了两种物体着色模式,一种是RGBA颜色模式,另一种是颜色索引模式。
(5)反走样:在OpenGL绘制图形过程中,由于使用的是位图,所以绘制出的图像边缘会出现锯齿形状,称为走样。为了消除这种缺陷,OpenGL提供了点、线、多边形的反走样技术。
(6)融合:为了使三维图形更加具有真实感,经常需要处理半透明或透明的物体图像,这就需要用到融合技术。
(7)雾化:正如自然界中存在烟雾一样,OpenGL提供了“fog”的基本操作来达到对场景进行雾化的效果。
(8)位图和图像:在图形绘制过程中,位图和图像操作是非常重要的方面。OpenGL提供了一系列函数来实现位图和图像的操作。
(9)纹理映射:在计算机图形学中,把包含颜色、alpha值、亮度等数据的矩形数组称为纹理。纹理映射可以理解为将纹理粘贴在所绘制的三维模型表面,以使三维图形显得更生动。
(10)动画:出色的动画效果是OpenGL的一大特色。OpenGL提供了双缓存技术来实现动画绘制,即在显示前台缓存中图像的同时,后台缓存绘制第二幅图像;当后台绘制
完成后,后台缓存中的图像就显示出来,此时原来的前台缓存开始绘制第三幅图像。如此循环往复,以增加图像的输出速度。
2.OpenGL函数及数据类型
OpenGL的库函数分为四类:核心库函数、实用库函数、辅助库函数和专用库函数。
(1)核心库函数:OpenGL有115个核心库函数,均以“gl”为前缀,它们提供了最基本的功能,比如实现三维建模、建立光照模型、反走样和纹理映射等功能。
(2)实用库函数:OpenGL的实用库函数有43个,均以“glu”为前缀,它们在核心函数的上层。其实质是对核心函数进行组织和封装,提供比较简单的函数接口和用法,可减轻开发者的编程负担。
(3)辅助库函数:OpenGL的辅助库函数有31个,均以“aux”为前缀。应用程序只能在Win32环境中使用这些函数,可移植性较差,在Windows应用程序中一般用于窗口管
理、输入/输出处理以及绘制一些简单的三维形体。
(4)专用库函数:专用库函数由6个以“wgl”为前缀的函数和5个Win32API函数组成。“wgl”函数用于连接Windows和OpenGL、初始化窗口,能够使用OpenGL在窗口中进行绘制;Win32API函数用于处理像素存储格式、双缓存等函数的调用。
OpenGL中函数的命名规则为“前缀—词根—数字后缀—类型后缀”。前缀表明函数的类型;词根为函数的功能描述;数字后缀可以是“2”、“3”、“4”,表明参数向量的维数;类型后缀可以是“b”、“i”、“f”等,表明函数参数的数据类型。例如函数“glColor3f”:“gl”表明这个函数属于核心函数,“Color”表明它用来设置当前颜色,“3”表明函数需要3个参数,“f”表明函数的每一个参数都为浮点型数值。
OpenGL定义的常量都以GL开头,并且所有字母都大写,单词之间以下划线来分隔,例如GL_COLOR_BUFFER_BIT。
3.OpenGL的缓冲区
OpenGL进行图形显示时需要用到4个缓冲区,即颜色缓存、深度缓存、模板缓存和累积缓存。这里只简单介绍其概念,使用方法将在后面介绍。
1)颜色缓存
颜色缓存由红、绿、蓝、alpha位平面组成,有前缓存、后缓存、左前缓存、右前缓存、左后缓存、右后缓存等几种。其中左前缓存是必需的颜色缓存,前缓存是可见缓存,后缓存是不可见缓存。前后缓存技术可实现动画操作。
2)深度缓存
深度缓存也叫Z-buffer,记录每个像素点所对应的物体点到视点的距离,由此决定表面的可见性,用于物体的消隐。
3)模板缓存和累积缓存
模板缓存和累积缓存主要用于图形的特殊效果绘制。模板缓存存放像素的模板字,用于控制像素是否被改写,实现禁止在屏幕的某些区域绘图。模板缓存可用于多种复杂图形的绘制(凸/凹区域、凹多边形等)、屏蔽屏幕区域、遮挡物体、制作物体的交集等。累积缓存是一系列绘制结果的积累,可用来实现场景的反走样、景深模拟和运动模糊等。10.1.3
OpenGL绘图程序开发方法
利用VC++和OpenGL来开发绘图程序,需要构造一个基于消息的Windows窗口,在这个窗口中使用OpenGL的函数进行绘制和渲染。有两种构造窗口的方法,一种是应用OpenGL的GLU和AUX函数库,另一种是使用VC++的MFC。
使用辅助函数库和使用MFC来构造应用程序框架的不同之处在于,MFC应用程序是由Windows来分发消息,在消息响应函数中处理初始化、设置绘图场景和绘制图形的工作;而使用辅助函数库则是编写回调函数,然后在主函数中使用auxReshapeFunc和auxMainLoop调用回调函数来实现消息的循环。
下面通过一个绘制颜色渐变的三角形的实例,来说明用VisualC++开发OpenGL绘图程序的方法。
1.利用OpenGL的GLU和AUX的绘图实例
1)新建工程
在VC++中的“File”菜单中选择“New”命令,在弹出的对话框中选择项目的类型为“Win32ConsoleApplication”,然后输入项目的名字“FirstAuxOpenGL”。接着单击“OK”按钮进入应用程序向导对话框,在其中选择项目的类型为“AnEmpetyProject”,即一个空的项目。选择完成以后,单击“Finish”按钮,结束项目设置,即可自动生成应用程序的框架。
2)添加文件
单击 按钮,选择“File”菜单中的“另存为”项,将其保存为“FirstAuxOpenGL.c”文件。右键单击“SourceFiles”,再单击“AddFilestoFolder”即可把文件“FirstAuxOpenGL.c”添加到项目中。接下来,在FirstAuxOpenGL.c文件中添加下列代码:函数auxReshapFunc和函数auxMainLoop的参数是两个回调函数(使用CALLBACK声明),在这两个函数中分别处理场景设置和绘制操作。
初始化函数、设置场景函数、绘制函数和主函数这4个主要函数构成了使用辅助函数库创建OpenGL应用程序的框架。
3)项目设置
在“Project”菜单中选择“Settings”,再单击“Link”,弹出如图10-1所示的对话框。在“对象/库模块”编辑框中添加在程序链接中用到的3个函数库:OpenGL32.lib、glu32.lib和glAux.lib。
运行程序,效果如图10-2所示。图10-1在工程中加入链接库函数图10-2
FirstAuxOpenGL的运行结果
2.使用MFC和专用函数的绘图实例
在开发过程中,需要使用MFC所提供的基于Windows的消息发送机制的“文档—视”结构来组织应用程序。这里不能使用辅助函数,但可以应用OpenGL所提供的专用函数来与MFC应用程序相衔接。
1)新建工程
在VC++的“File”菜单中选择“New”命令,在弹出的对话框中选择项目的类型为“MFCAppwizard[exe]”,输入项目的名字“FirstMfcOpenGL”,然后单击“OK”按钮进入应用程序向导对话框。创建单文档应用程序,然后单击“Finish”按钮,接受默认设置,即可自动生成应用程序的框架。
2)添加初始化及终止代码
在视类CFirstMfcOpenGLView的头文件CFirstMfcOpenGLView.h中加入头文件“gl.h”和“glu.h”以及两个记录绘图设备的成员变量,如下列程序所示:在“View”菜单中选择“ClassWizard”命令,弹出如图10-3所示的“类向导”对话框。在该对话框的“Classname”下拉列表框中选择视类CFirstMfcOpenGLView,然后在“Messages”列表框中选择WM_CREATE、WM_DESDROY和WM_SIZE这三个消息,为它们添加对应的消息响应函数OnCreate、OnDestroy和OnSize。图10-3
FirstMfcOpenGL工程中的“类向导”对话框接下来,在OnCreate函数中添加如下初始化代码:在OnDestroy函数中添加如下代码:在PreCreateWindow函数中对窗口的风格进行设置:
3)根据窗口大小设置场景
在MFC应用程序中,窗口的位置及大小的改变都会激发一个WM_SIZE消息,在视类的消息响应函数OnSize中执行它,因此在OnSize中应该添加响应的处理,根据窗口大小设置场景,代码如下:4)添加绘制三角形的代码
绘制三角形的代码如下:
5)项目设置
在“Project”菜单中选择“Settings”,加入OpenGL的两个函数库,即OpenGL32.lib和glu32.lib。 10.2基于OpenGL的基本图形绘制
在OpenGL中,最基本的图形是点,又称顶点(Vertex),任何其他的图形都是由顶点的集合来描述的,顶点间是否或怎样连接是由绘制图形的类型决定的。因此,在OpenGL中,所有的图形都采用一系列有序的顶点来描述。需要指出的是,OpenGL所定义的点、线、多边形等图元与一般数学定义不一样,存在一定的差别。一种差别源于基于计算机计算的限制。由于OpenGL中浮点计算精度有限,故点、线、多边形的坐标值存在一定的误差。另一种差别源于位图显示的限制,以这种方式显示图形,最小的显示图元是一个像素,尽管每个像素很小,但它们仍然比数学上所定义的点或线宽要大得多。当用OpenGL进行计算时,虽然是用一系列浮点数定义点,但每个点仍然是用单个像素显示,只是近似拟合。
10.2.1点
OpenGL中,点称为顶点,是一个n维向量,n=2,3,4。在缺省状态下,点为一个像素。所有顶点在OpenGL内部计算时都使用三维坐标(x,y,z)来处理,用二维坐标(x,y)定义的点在OpenGL中默认z值为0。例如glVertex(2.0,1.0),定义了一个坐标为(2.0,1.0,0)的点。OpenGL定义点的函数为
glVertex{2,3,4}{sifd}(v)(TYPEcords)其中cords为用一个数组或坐标表示的顶点坐标,也可以是齐次坐标。例如:
glVertex2f(1.5,3.8);
glVertex3d(5.4,21.5,16.2);
glVertex2i(20,30);
glVertex3sv(coNstGLshortp)
/*p为一个点的数组*/要想在显示器上显示一点,必须用函数glBegin()通知OpenGL画图的类型,例如:
glBegin(GL_POINTS);
glVertex2f(1.5,3.8);
glVertex3d(5.4,21.5,16.2);
glVertex2i(20,30);
glEnd();
glBegin函数中的参数GL_POINTS是通知OpenGL下面的点要解释成点并在显示器上显示点。
OpenGL还提供了控制点大小的函数glPointsize(),默认为一个像素,函数形式是:
voidglPointsize(glfloatsize)10.2.2线
在OpenGL中,绘制一条直线是通过定义两个顶点来完成的,在定义顶点之前,应告诉OpenGL对应两个顶点画直线的动作。例如要画一条直线,两个端点是(30,30)和
(100,200),利用OpenGL的代码段为:
glBegin(GL_LINES);
glVertex2f(30,30);
glVertex2f(100,200);
glEnd();如有多个顶点,则表示要画的是折线。glBegin函数的参数应改为GL_LINE_STRIP,例如:
glBegin(GL_LINE_STRIP);
glVertex2f(30,30);
glVertex2f(80,30);
glVertex2f(40,60);
glVertex2f(70,50);
glEnd(
如果glBegin的参数是GL_LINE_LOOP,则画出来的是封闭的多边形。
用OpenGL的线型属性定义函数,可以画出直线的线型模式,函数为:
voidglLineStipple(Glintfactor,Glushortpattern);此命令有pattern和factor两个参数。pattern是画线操作时的一个模板,是一个0和1的二进制序列,在这个序列中每一位能影响一个像素,1表示画点,0表示不画点。例如pattern=0x3E03,化为二进制是0011111000000011,意味着绘制该线型时,先画3个像素,然后空7个像素,再画5个像素,空2个像素。如果启动线型操作,线上的点由pattern决定是否绘制,即从pattern的最低位开始,逐个绘制线上的点,如果线还没有画完,线型的样板已经用完,则将重复使用线型的样板至完成画线。参数factor是为了扩展线型,factor从1到255,表示pattern参数中所规定的像素的重复次数。当patten=2时,则参数pattern=0x3E03实际上表示的线型是00001111111111000000000000001111。具体的函数表示是:
glLineStipple(2,0x3E03);
结束时,调用glDisable(GL_LINE_STIPPLE)关闭。下面的代码绘制了一个点线:启动线型用glEnable(GL_LINE_STIPPLE)函数,关闭线型用glDisable(GL_LINE_STIPPLE)函数。
除了在OpenGL中可以定义线型外,还可以在画线时使用函数glLineWidth来控制线的宽度。glLineWidth()函数形式如下:
glLineWidth(GLFloatwidth)
函数中的参数width指定要画的线以像素计的近似宽度,在缺省情况下,width=1.0,此外要求width>0.0。
由于各图形系统的具体性能是不一样的,并不是所有线宽都支持,因此为了确保指定的线宽是可用的,可以通过下面的代码获得系统支持的线宽的范围和它们之间的最小间隔:
Glfloatsizes[2];
Glfloatstep;
GLGetFloatv(GLLINEWIDTHRANGE,sizes);
GLGetFLoatv(gllinewidthgranularity,&step);
定义的sizes数组包含两个元素,保存了glLineWidth的最小有效值和最大有效值;变量step保存了线宽之间允许的最小增量。
下面的程序给出了OpenGL的完整程序的框架实现,综合演示了点的大小、线型和线宽的实现。10.2.3多边形
OpenGL定义的多边形是由一系列线段依次连接而成的封闭区域,多边形可以是平面多边形(即所有顶点在一个平面上),也可以是空间多边形。OpenGL规定多边形中的线段不能交叉,区域内不能有空洞,也即多边形必须是凸多边形(指多边形任意非相邻的两点的连线位于多边形的内部),不能是凹多边形,否则不能被OpenGL函数接受。这些限制是为特别需要设定的。首先所有的多边形都可以分割为多个凸多边形。限制多边形的类型容易实现硬件加速。在OpenGL中,多边形的绘制也是由函数glBegin()和glEnd()来完成的。glBegin()函数的参数是GL_TRIANGLES。最简单的多边形是三角形,它只有三条边。在实际应用中,往往需要绘制一些凹多边形,通常解决的办法是对它们进行分割,用多个三角形来替代。显然,绘制这些三角形时,有些边不应该绘制,否则,多边形内部就会出现多余的线框。OpenGL提供的解决办法是通过设置边标志命令glEdgeFlag()来控制某些边是否绘制,这个命令如下:
voidglEdgeFlag(GLbooleanflag);
voidglEdgeFlag(PGLbooleanpflag);
多边形有多种绘制模式,如全填充式、轮廓点式、轮廓线式、图案填充式及指定正反面等。下面分别介绍相应的OpenGL函数形式。
(1)多边形模式设置。其函数为:
voidglPolygonMode(GLenumface,GLenummode);
参数face为GL_FRONT、GL_BACK或GL_FRONT_AND_BACK;参数mode为GL_POINT、GL_LINE或GL_FILL,分别表示绘制轮廓点式多边形、轮廓线式多边形或全填充式多边形。在OpenGL中,多边形分为正面和反面,对这两个面都可以进行操作。在缺省状况下,OpenGL对多边形正反面是以相同的方式绘制的,要改变绘制状态,必须调用PolygonMode()函数。
(2)设置图案填充式多边形。其函数为:
voidglPolygonStipple(constGLubyte*mask);
参数mask是一个指向32x32位图的指针。与线型绘制的道理一样,二进制位为1时绘制,为0时不绘。注意,在调用这个函数前,必须先启动glEnable(GL_POLYGON_STIPPLE);不用时用glDisable(GL_POLYGON_STIPPLE)关闭。
下面为一个多边形扩展绘制实例: 10.3基于VC++的OpenGL坐标变换
OpenGL通过相机模拟,可以实现计算机图形学中最基本的三维变换,即几何变换、投影变换、裁剪变换、视图变换等;同时,OpenGL还实现了矩阵堆栈等。理解、掌握了有关坐标变换的内容,即可真正走进精彩的三维世界。
10.3.1
OpenGL中三维物体的显示
1.坐标系统
在现实世界中,所有的物体都具有三维特征,但计算机本身只能处理数字,故只能显示二维的图形。将三维物体及二维数据联系在一起的唯一纽带就是坐标。为了使被显示的三维物体数字化,要在被显示的物体所在的空间中定义一个坐标系。这个坐标系的长度单位和坐标轴的方向要适合对被显示物体的描述,这个坐标系被称为世界坐标系。世界坐标系是始终固定不变的。
OpenGL还定义了局部坐标系的概念。所谓局部坐标系,也就是坐标系以物体的中心为坐标原点,物体的旋转或平移等操作都是围绕局部坐标系进行的,这时,当物体模型进行旋转或平移等操作时,局部坐标系也执行相应的旋转或平移操作。需要注意的是,如果对物体模型进行缩放操作,则局部坐标系也要进行相应的缩放。如果缩放比例在各坐标轴上不同,那么再经过旋转操作后,局部坐标轴之间可能不再相互垂直。无论是在世界坐标系中进行转换还是在局部坐标系中进行转换,程序代码都是相同的,只是不同的坐标系考虑的转换方式不同罢了。
计算机对数字化的显示物体做了加工处理后,要在图形显示器上显示,就必须在图形显示器屏幕上定义一个二维直角坐标系,这个坐标系被称为屏幕坐标系。这个坐标系坐标轴的方向通常取成平行于屏幕的边缘,坐标原点取在左下角,长度单位常取成一个像素。
2.三维物体的相机模拟
为了说明在三维物体到二维图像之间需要经过什么样的变换,我们引入了相机(Camera)模拟的方式。假定用相机来拍摄这个世界,那么在相机的取景器中,就存在人眼和现实世界之间的一个变换过程,如图10-4所示。
从三维物体到二维图像,就如同用相机拍照一样,通常都要经历以下几个步骤:
(1)将相机置于三角架上,让它对准三维景物,这相当于OpenGL中调整视点的位置,即视点变换(ViewingTransformation)。
(2)将三维物体放在场景中的适当位置,这相当于OpenGL中的模型变换(ModelingTransformation),即对模型进行旋转、平移和缩放。图10-4相机模拟OpenGL中的各种坐标变换
(3)选择相机镜头并调焦拍照,使三维物体投影在二维胶片上,这相当于OpenGL中把三维模型投影到二维屏幕上的过程,即OpenGL中的投影变换(ProjectionTransformation)。
OpenGL中投影的方法有两种,即正投影和透视投影。为了使显示的物体能以合适的位置、大小和方向显示出来,必须要通过投影。有时为了突出图形的一部分,只把图形的某一部分显示出来,这时可以定义一个三维视景体(ViewingVolume)。正投影时一般是一个长方体的视景体,透视投影时一般是一个如棱台似的视景体。只有视景体内的物体能被投影在显示平面上,其他部分则不能。
(4)冲洗照片,决定二维相片的大小,这相当于OpenGL中的视图变换(ViewportTransformation)。在屏幕窗口内可以定义一个矩形,称为视图(Viewport),物体投影后的图形就在视图内显示。视图变换规定了屏幕上显示场景的范围和尺寸。
通过上面几个步骤,一个三维空间里的物体就可以用相应的二维平面物体表示了,也就能在二维的电脑屏幕上正确显示了。总的来说,三维物体的显示过程如图10-5所示。图10-5三维物体的显示过程10.3.2
OpenGL中的几种变换
OpenGL中的各种转换是通过矩阵运算实现的,具体地说,就是当发出一个转换命令时,该命令会生成一个4×4阶的转换矩阵(OpenGL中的物体坐标一律采用齐次坐标,即(x,y,z,w),故所有变换矩阵都采用4×4矩阵),当前矩阵与这个转换矩阵相乘,从而生成新的当前矩阵。例如,对于顶点坐标v,转换命令通常在顶点坐标命令之前发出,若
当前矩阵为C,转换命令构成的矩阵为M,则发出转换命令后,生成的新的当前矩阵为CM,这个矩阵再乘以顶点坐标v,从而构成新的顶点坐标CMv。上述过程说明,程序中绘制顶点前的最后一个变换命令最先作用于顶点之上。这同时也说明,在OpenGL编程中,实际的变换顺序与指定的顺序是相反的。
1.视点变换
视点变换确定了场景中物体的视点位置和方向,就如上边提到的,它像是在场景中放置了一架照相机,让相机对准要拍摄的物体。缺省时,相机(即视点)定位在坐标系的原点(相机初始方向都指向z轴的负方向),它同物体模型的缺省位置是一致的,显然,如果不进行视点变换,则相机和物体是重叠在一起的。执行视点变换的命令和执行模型变换的命令是相同的。在用相机拍摄物体时,我们可以保持物体的位置不动,而将相机移离物体,这就相当于视点变换;另外,我们也可以保持相机的固定位置,将物体移离相机,这就相当于模型变换。这样,在OpenGL中,以逆时针旋转物体就相当于以顺时针旋转相机。因此,我们必须把视点变换和模型变换结合在一起考虑,而对这两种变换单独进行考虑是毫无意义的。除了用模型变换命令执行视点变换之外,OpenGL实用库还提供了gluLookAt()函数,该函数有三个变量,分别定义了视点的位置、相机瞄准方向的参考点以及相机的向上方向。该函数的原型为:
voidgluLookAt(GLdoubleeyex,GLdoubleeyey,GLdoubleeyez,GLdoublecenterx,GLdoublecentery,GLdoublecenterz,GLdoubleupx,GLdoubleupy,GLdoubleupz);该函数定义了视点矩阵,并用该矩阵乘以当前矩阵。eyex、eyey、eyez变量定义了视点的位置;centerx、centery、centerz变量指定了参考点的位置,该点通常为相机所瞄准的场景中心轴线上的点;upx、upy、upz变量指定了向上向量的方向。
通常,视点变换操作在模型变换操作之前发出,以便模型变换先对物体发生作用。场景中物体的顶点经过模型变换之后移动到所希望的位置,然后再对场景进行视点定位等操
作。模型变换和视点变换共同构成模型视景矩阵。
2.模型变换
模型变换是在世界坐标系中进行的。缺省时,物体模型的中心定位在坐标系的中心处。OpenGL在这个坐标系中有三个命令,可以进行模型变换。
(1)glTranslate{fd}(TYPEx,TYPEy,TYPEz);
该函数用指定的x、y、z值沿着x轴、y轴、z轴平移物体(或按照相同的量值移动局部坐标系)。
(2)glRotate{fd}(TYPEangle,TYPEx,TYPEy,TYPEz);
该函数中第一个变量angle指定模型旋转的角度,单位为度(°);后三个变量表示以原点(0,0,0)到点(x,y,z)的连线为轴线逆时针旋转物体。例如,glRotatef(45.0,0.0,0.0,1.0)的结果是绕z轴旋转45°。
(3)glScale{fd}(TYPEx,TYPEy,TYPEz);
该函数可以对物体沿着x、y、z轴分别进行放大缩小。函数中的三个参数分别是x、y、z轴方向的比例变换因子。缺省时都为1.0,即物体没有变化。程序中物体y轴比例为2.0,其余都为1.0,即将立方体变成长方体。
3.投影变换
经过模型视景的转换后,场景中的物体放在了所希望的位置上,但由于显示器只能用二维图像显示三维物体,因此就要靠投影来降低维数(投影变换类似于选择相机的镜头)。
事实上,投影变换的目的就是定义一个视景体,使得视景体外多余的部分被裁剪掉,最终进入图像的只是视景体内的有关部分。投影包括透视投影(PerspectiveProjection)和正视投影(OrthographicProjection)两种。
1)透视投影
透视投影符合人们的心理习惯,即离视点近的物体大,离视点远的物体小,远到极点即为消失,成为灭点。它的视景体类似于一个顶部和底部都被切割过的棱椎,也就是棱台透视投影通常用于动画、视觉仿真以及其他许多具有真实性反映的方面。
OpenGL透视投影函数有以下两个:
(1)voidglFrustum(GLdoubleleft,GLdoubleright,GLdoublebottom,GLdoubletop,GLdoublenear,GLdoublefar);
它创建一个透视视景体。其操作是创建一个透视投影矩阵,并且用这个矩阵乘以当前矩阵。这个函数的参数只定义近裁剪平面的左下角点和右上角点的三维空间坐标,即(left,bottom,-near)和(right,top,-near);最后一个参数far是远裁剪平面的z负值,其左下角点和右上角点空间坐标由函数根据透视投影原理自动生成。near和far表示离视点的
远近,它们总为正值。该函数形成的视景体如图10-6所示。图10-6函数glFrustum()形成的视景体
(2)voidgluPerspective(GLdoublefovy,GLdoubleaspect,GLdoublezNear,GLdoublezFar);
它也创建一个对称透视视景体,但它的参数定义与函数glFrustum()的不同:参数fovy定义视野在x-z平面的角度,范围是[0.0,180.0];参数aspect是投影平面宽度与高度的比率;参数zNear和zFar分别是近、远裁剪面沿z负轴到视点的距离,它们总为正值。该函数形成的视景体如图10-7所示。图10-7函数gluPerspective()形成的视景体以上两个函数缺省时,视点都在原点,视线沿z轴指向负方向。
2)正视投影
正视投影简称正投影,又叫平行投影。这种投影的视景体是一个矩形的平行管道,也就是一个长方体,如图10-8所示。正投影的最大一个特点是无论物体距离相机多么远,投影后的物体大小尺寸都不变。这种投影通常用在建筑蓝图绘制和计算机辅助设计等方面,这些行业要求投影后的物体尺寸及相互间的角度不变,以便施工或制造时物体比例大小正确。图10-8正投影
OpenGL正投影函数有以下两个:
(1)voidglOrtho(GLdoubleleft,GLdoubleright,GLdoublebottom,GLdoubletop,GLdoublenear,GLdoublefar);
它创建一个平行视景体。实际上这个函数的操作是创建一个正投影矩阵,并且用这个矩阵乘以当前矩阵。其中近裁剪平面是一个矩形,矩形左下角点三维空间坐标是(left,bottom,-near),右上角点是(right,top,-near);远裁剪平面也是一个矩形,左下角点空间坐标是(left,bottom,-far),右上角点是(right,top,-far)。所有的near和far值同时为正或同时为负。如果没有其他变换,正投影的方向平行于z轴,且视点朝向z负轴。这意味着物体在视点前面时far和near都为负值,物体在视点后面时far和near都为正值。
(2)voidgluOrtho2D(GLdoubleleft,GLdoubleright,GLdoublebottom,GLdoubletop);
它是一个特殊的正投影函数,主要用于二维图像到二维屏幕上的投影。它的near和far缺省值分别为-1.0和1.0,所有二维物体的z坐标都为0.0。因此它的裁剪面是一个左下角点为(left,bottom)、右上角点为(right,top)的矩形。
4.视图变换
视图变换就是将视景体内投影的物体显示在二维的视图平面上。运用相机模拟方式,我们很容易理解视图变换类似于照片的放大与缩小。在计算机图形学中,它的定义是将经过几何变换、投影变换和裁剪变换后的物体显示于屏幕窗口内指定的区域内,这个区域通常为矩形,称为视图。
OpenGL中的相关函数是:
glViewport(GLintx,GLinty,GLsizeiwidth,GLsizeiheight);这个函数定义一个视图。函数参数(x,y)是视图在屏幕窗口坐标系中的左下角点坐标,参数width和height分别是视图的宽度和高度。缺省时,参数值即(0,0,winWidth,winHeight)指的是屏幕窗口的实际尺寸大小。所有这些值都是以像素为单位,全为整型数。
5.裁剪变换
在OpenGL中,除了视景体定义的六个裁剪平面(上、下、左、右、前、后)外,用户还可自己再定义一个或多个附加裁剪平面,以去掉场景中无关的目标,如图10-9所示。图10-9附加裁剪平面附加平面裁剪函数为:
voidglClipPlane(GLenumplane,ConstGLdouble*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)关闭相应的附加裁剪功能。
下面这个例子不仅说明了附加裁剪函数的用法,而且调用了gluPerspective()透视投影函数,其中的用法读者可以细细体会。例程如下:
6.矩阵栈的操作
在讲述矩阵栈之前,首先介绍两个基本OpenGL矩阵操作函数。
(1)voidglLoadMatrix{fd}(constTYPE*m);
它设置当前矩阵中的元素值。函数参数*m是一个指向16个元素(m0,m1,…,m15)的指针,这16个元素就是当前矩阵M中的元素,其排列方式如下:
(2)voidglMultMatrix{fd}(constTYPE*m);
它用当前矩阵去乘*m所指定的矩阵,并将结果存放于*m中。当前矩阵可以是用glLoadMatrix()指定的矩阵,也可以是其他矩阵变换函数的综合结果。
OpenGL的矩阵堆栈指的就是内存中专门用来存放矩阵数据的某块特殊区域。一般说来,矩阵堆栈常用于构造具有继承性的模型,即由一些简单目标构成的复杂模型。矩阵堆栈对复杂模型运动过程中的多个变换操作之间的联系与独立十分有利。因为所有矩阵操作函数如glLoadMatrix()、glMultMatrix()、glLoadIdentity()等只处理当前矩阵或堆栈顶部矩阵,这样堆栈中下面的其他矩阵就不受影响。堆栈操作函数有以下两个:
(1)voidglPushMatrix(void);
该函数表示将所有矩阵依次压入堆栈中,顶部矩阵是第二个矩阵的备份;压入的矩阵数不能太多,否则会出错。
(2)voidglPopMatrix(void);
该函数表示弹出堆栈顶部的矩阵,令原第二个矩阵成为顶部矩阵,接受当前操作,故原顶部矩阵被破坏;当堆栈中仅存一个矩阵时,不能进行弹出操作,否则会出错。 10.4用OpenGL生成曲线和曲面
10.4.1样条曲线的绘制
1.定义求值器
在OpenGL中,为了绘制一条样条曲线,必须先定义求值器,然后才能计算曲线上点的坐标并完成曲线的绘制。求值器(Evaluator)是利用Bernstein多项式进行工作的一般机制,可以应用任意次数的多项式,可以处理一到四维的情形,可以自动生成法向和纹理坐标,在GLU中支持NURBS二次曲面。求值程序是OpenGL中用控制点来描述曲线或曲面上的点的工具。利用求值程序,可以绘制出由控制点所定义的曲线曲面。对于样条曲线,OpenGL使用一维基函数,并且只使用如下形式的多项式:
上述多项式是n次Bezier多项式。设Pi是控制点,则Bezier曲线为
这样,C(u)就是样条曲线的求值器。u的取值范围为[0,1]。如果u的取值范围为[U1,U2],则求值器为
2.一维求值器函数glMap1()
OpenGL求值器是以Bezier曲线曲面为基础来绘制样条曲线或曲面的。定义一维求值器的函数是glMap1(),该函数对应于Bezier曲线方程。
函数的原型如下:
VoidglMap1{fd}(GLenumtarget,TYPEu1,TYPEu2,Glintstride,Glintorder,constTYPE*points);
参数说明:
target:指定控制点所描述的内容。其可取的值及意义如表10-1所示。表10-1
target的取值及含义
u1,u2:限定参数u的变化范围,并把它提供给函数glEvalCoord1()。
stride:指定控制点之间的浮点数或双精度数的个数。
order:等于次数加1,与控制点数目一致,必须是正整数。
points:指向控制点数组的指针,指向第一个控制点的第一个坐标。
在调用该函数之前,还需要用表10-1中的参数值来激活求值器程序。只需以target的值作为映射名调用glEnable(映射名)和gldisable(映射名)函数,即可激活或者取消求值器程序。
3.计算曲线坐标
计算曲线坐标的函数是glEvalCoord1(),函数的原型如下:
voidglEvalCoord1{fd}(TAPEu);
功能:对已定义并激活的一维的求值器程序进行计算,得到Bezier曲线上点的坐标值。
参数说明:
u:指定沿着曲线进行计算的u参数值。
该函数每调用一次只产生一个顶点的坐标值。这里的坐标是广义的,既可以是物体的顶点坐标,也可以是颜色、法线或纹理坐标。
4.计算均匀间隔坐标
在通常情况下,计算曲线坐标时,采用均匀分割定义域的方法。OpenGL提供了两个函数:glMapGrid1()和glEvalMesh1()。
函数glMapGrid1()的原型如下:
voidglMapGrid1{fd}(GLintun,TAPEu1,TAPEu2);
功能:定义一个一维的网格。
参数说明:
un:指定网格范围[u1,u2]之间的等份数,必须是正整数。u1、u2指定整型网格域值,取值范围是i=0到i=un。函数glEvalMesh1()的原型如下:
voidglEvalMesh1{fd}(GLenummode,GLinti1,GLinti2);
功能:计算点或线的一维网格。
参数说明:
mode:指定是计算点还是线的一维网格,可取的符号常量是GL_POINT和GL_LINE。
i1,i2指定网格区域变量i的第一个和最后一个整数值。
这两个函数的使用方法是首先调用glMapGrid1()定义一个一维网格,然后调用glEvalMesh1()计算相应的坐标值。函数glEvalMesh1()穿过一个一维网格的整数区域,其范围是函数glMap1()所指定的求值影射的区域。参数mode决定所得的顶点是由孤立的点绘制还是连成线。5.编程实例10.4.2样条曲面的绘制
样条曲面的绘制方法在原理上与样条曲线基本相同,所不同的是曲面使用二维求值器,并且控制点连接起来形成一个网格。
1.定义二维求值器
对于曲面,求值器函数使用两个参数u、v,使用如下形式的多项式:其中,Pij是m×n个控制点,基函数和与一维求值器基函数相同。二维求值器函数即对应于Bezier曲面方程:
voidglMap2{fd}(GLenumtarget,TYPEu1,TYPEu2,Glintustride,GLintuorder,TYPEv1,TYPEv2,GLintvstride,GLintvorder,constTYPE*points);
功能:定义二维求值器程序,即Bezier曲面方程。
参数说明:
target:可以使用表5.1中的任何值,只需将MAP1改为MAP2。
u1,u2:限定参数u的变化范围。u1,u2的初始值是0,1。
v1,v2:限定参数v的变化范围。v1,v2的初始值是0,1。
ustride,vstride:指定控制点之间的浮点数或双精度数的个数。
uorder,vorder:指定在u方向和v方向的控制点个数。
*points:指向控制点的指针。
定义了求值函数之后,即可对其进行计算,以根据给定参数值得到曲线曲面上的点,从而绘制出相应的Bezier曲线曲面。
2.计算曲面坐标
计算曲面坐标的函数是glEvalCoord2(),函数的原型如下:
voidglEvalCoord2{fd}[v](TAPEu,TAPEv);
功能:求取有效的二维映射值。
参数说明:
u,v:指定已经由函数glMap2定义的基础函数的域坐标u和v的值。
该函数计算曲面上顶点的坐标值。这里的坐标也是广义的,既可以是物体的顶点坐标,也可以是颜色、法线或纹理坐标。
3.计算均匀曲面坐标
与一维的情况相同,OpenGL提供了两个函数glMapGrid2()和glEvalMesh2()计算曲面坐标,函数原型如下:
voidglMapGrid2{fd}(GLintun,TAPEu1,TAPEu2,GLintvn,TAPEv1,TAPEv2);
voidglEvalMesh2{fd}(GLenummode,GLinti1,GLinti2,GLintj1,GLintj2);
功能:
定义和计算二维网格的坐标值。参数说明:
un,vn:指定网格范围[u1,u2]和[v1,v2]之间的等份数,必须是正整数。u1,u2指定整型网格域值,取值范围是i=0到i=un。v1,v2指定整型网格域值,取值范围是j=0到j=vn。
mode:指定是计算点、线还是多边形的二维网格。可取的符号常量是
GL_POINT,GL_LINE和GL_FILL。
i1,i2:指定网格区域变量i的第一个和最后一个整数值。
j1,j2:指定网格区域变量j的第一个和最后一个整数值。4.编程实例
OpenGL的实用函数库(GLU)提供了另一种曲线、曲面绘图接口,即NURBS接口。这一接口同样是建立在求值器的基础上的,但更灵活,使用起来也更方便。 10.5
OpenGL的光照处理
从上述可见,要显示一张简单的曲面所涉及的内容和计算是相当复杂的,OpenGL把相当复杂的工作集成在相应的
函数中,只需对参数进行处理便可得到需要的光照模型。下面就介绍如何使用OpenGL进行光照处理。
10.5.1光源的定义
光源有许多特性,如颜色、位置、方向等。不同特性的光源,作用在物体上的效果是不一样的。定义一个光源的主要工作就是定义它的各种特性,OpenGL通过光源特性的函数glLight*()来定义光源。
voidglLight{if}(GLenumlight,GLenumpname,TYPE*param);
voidglLight{if}v(GLenumlight,GLenumpname,TYPE*param);
此函数定义光源的某特性。其中,light指定光源编号,在一个场景最多定义八个不同的光源,编号为GL-LIGHT0,GL-LIGHT1,…,GL-LIGHT7;参数pname指定光源特性的名称,即是什么种类的光,其取值见表10-2;参数param为指向param所指属性值的指针,它可以指向一个数值,也可以指向一个值,具体由所定义的属性而定。表10-2
pname参数的取值及意义下面是定义编号为GL_LIGHT光源的例子:
GLfloatlight_ambient[]={0.0,0.0,0.0,1.0};
GLfloatlight_diffuse[]={1.0,1.0,1.0,1.0};
GLfloatlight_specular[]={1.0,1.0,1.0,1.0};
GLfloatlight_position[]={1.0,1.0,1.0,1.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);
光源的特性定义后这个光源是否打开是要说明的,用glenable(光源号)打开光源,否则光源对场景中的物体光照不起作用。
1.颜色
在glLight()参数pname取值为GL_AMBIENT、GL_DIFFUSE、GL_SPECULAR时指定光源中相应组成部分的颜色强度。
GL_AMBIENT指定环境光的RGBA强度,当其特性值为{0.0,0.0,0.0,1.0}时,表示没有环境光,这是默认状态。
GL_DIFFUSE指定漫反射光的RGBA强度,默认情况下对于GL_LIGHT0、GL_DIFFUSE特性值为{1.0,1.0,1.0,1.0},对于其他编号为GL_LIGHT1,…,LIGHT7的光源,该特性值为{0.0,0.0,0.0,0.0},此特性可以视为光源的颜色。
GL_SPECULAR用于指定镜面反射的RGBA强度,默认时,对于GL_LIGHT0该特性值为{1.0,1.0,1.0,1.0},对于其他光源,该特性值为{0.0,0.0,0.0,0.0}。
2.位置
使用GL_POSITION定义光源位置坐标(x,y,z,w)。当w=0时,表示定义的位置在无穷远,而(x,y,z)仅说明光的方向,此时叫方向光源。当ω≠0时,则定义一个较近的光源,(x,y,z)表示光源的坐标。例如:
Glfloatlight_position[]={1.0,1.0,1.0,1.0};
//定义了一个方向光源
glLightfv(GL_LIGHT0,GL_POSITION,light_position);
3.衰减
光线强度随光源距离的增加而减弱,若光源是无限远,则光线强度不衰减。对于一般的位置光源,OpenGL使用下面的衰减因子来衰减光线的强度:
其中:d为光源位置到物体顶点的距离;kc为GL_CONSTANT_ATTENUATION,默认值为1.0;ke为GL_LINER_ATTENUATION,默认值为0.0;kg为GL_QUADRATIC_ATTENUATION,默认值为0.0。
4.聚光
一般情况下,光源光线是向四周发射,可以使用GL_DOT_CUTDFF来限制光线的范围,缺省是180°。当然,除了指定光发散角度外,还需指定聚光状态下的方向。例如:
glLlightf(GL_LIGHT0,GLSPOT_CUTOFF,45.0);
//45°角范围
GLfloatspot_direction[]={-1.0,-1.0,0.0};
glLightfv(GL_LIGHTO,GL_SPOT_DIRECTION,spot_direction)10.5.2材质的定义
OpenGL用材质来描述物体表面特性,通过指定物体表面对光的反射率来确定物体的颜色。设置材质属性的函数是:
voidglMaterial{if}(GLenumface,GLenumpname,TYPEparam);
其中,参数face是GL_FRONT、GL_BACK或GL_FRONT_AND_BACK指定当前材质作用物体的哪一面;参数pname设置材质的属性,其取值见表10-3。表10-3
pname的取值及意义例如,将当前材质的散射和环境反射率设置为(0.1,0.5,0.8,1.0):
GLfloatmat_amb_diff[]={0.1,0.5,0.8,1.0}
glMaterialfv(GL_FRONT_AND_BACK,GL_AMBIENT_AND_DIFFUSE,mat_amb_diff)
10.5.3
OpenGL的光照实例
下面的程序实现了球面的光照效果。球体的光照效果如图10-10所示。图10-10
OpenGL画出的光线照射曲面 10.6
OpenGL对交互绘图的支持
OpenGL提供了一些机制,通过它们可实现多种交互技术。本节仅介绍OpenGL对选择、反馈和橡皮筋技术的支持与实现。OpenGL提供了一种简单直观的选择机制,来实现图形元素的拾取;反馈机制(Select&Feedback)用来满足用户使用鼠标交互操作图形的需要;利用OpenGL的双缓存机制可方便地实现橡皮筋功能。
10.6.1使用OpenGL的选择机制实现拾取功能
使用OpenGL选择机制的操作步骤为:设置选择缓冲区→进入选择模式→创建名字栈→设置拾取矩阵→绘制图元→切换回渲染模式→确定拾取对象。
1.设置选择缓冲区
在进入选择模式之前,需调用glSelectBuffer()为返回的选择信息设置缓冲区,以便最后能通过分析选择缓冲中的数据来确定被选中图元。
glSelectBuffer()的原型如下:
voidglSelectBuffer(GLsizeisize,GLuint*buffer);
其中,参数*buffer是指向无符号整数数组的指针,该数组就是所设置的用于存储返回选择数据的缓冲区;参数size说明数组中最多能够保存的值的个数。
2.进入选择模式
OpenGL的绘图模式有渲染模式、选择模式和反馈模式,其中渲染模式为默认工作模式。若想使用选择机制,则必须设置当前绘图模式为选择模式。
模式设置函数GLintglRenderMode(GLenummode);能控制应用程序进入的模式。参数mode的取值可以是GL_RENDER(渲染模式)、GL_SELECT(选择模式)或GL_FEEDBACK(反馈模式),分别指定相应模式。
一旦设置为某种模式,应用程序将保持处于给定模式,直到再次以不同的参数调用glRenderMode()为止。
3.创建名字栈
名字栈是返回选择信息的基础,其中存放一系列图元的标识(整数),每当发布绘制命令时便将此图元的标识压入名字栈。经计算后,若图元被选中,则名字栈中此图元的标识便会返回到选择缓冲区。然后通过提取选择缓冲中的名字栈的内容,便可得到有关被选中图元的信息。
在使用名字堆栈之前,需要建立名字栈。用glInitNames()创建并初始化(简单地清空)名字栈。名字栈的控制命令有三个:glPushName()将图元名字压入栈,glPopName()从栈中弹出名字,glLoadName()替换栈顶的名字。
glPushName()的原型为voidglPushName(GLuintname);,它将name压入名字栈。压入名字超过栈容量时将生成一个GL_STACK_OVERFLOW错误。名字栈深度因OpenGL实现不同而不同,但最少也能容纳64个名字。可以用参数GL_NAME_STACK_DEPTH调用glGetIntegerv()以获取名字栈的深度。
glPopName()的原型为voidglPopName(void);,它弹出名字栈的栈顶名字。从空栈中弹出名字会引发GL_STACK_UNDERFLOW错误。
glLoadName()的原型为voidglLoadName(GLuintname);,实现用name取代名字栈栈顶的那个名字。如果栈是空的,比如刚调用过glInitName()后就是这样,glLoadName()生成一个GL_INVALID_OPRATION错。为避免这些出错情况,如果栈初始时是空的,那么在调用glPopName()和glLoadName()之前至少要调用一次glPushName()以在名字栈中放进一个名字。
4.设置拾取矩阵
要想使OpenGL能够正确判断哪个图元被选中,还要设置合适的投影变换矩阵和模型视图矩阵,使得在选择模式下的绘图代码能够与绘图模式下的相吻合。要想让OpenGL判断哪个图元被选中,则必须提供一个拾取位置信息,通常是鼠标的位置。所以,此处还要设置拾取矩阵,用于保存拾取位置信息。
设置拾取矩阵
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 二零二五年度医院皮肤科医师定期坐诊服务合同
- 2025年度鸡棚租赁合同模板(含生态农业认证)
- 2025年多场合股份期权激励与股权变更合同
- 甲型肝炎减毒活疫苗项目绩效评估报告
- 光学复杂水体不同类型颗粒物的光学特征及遥感反演研究
- 湖北省巴东县农村居家养老服务现状与对策研究
- 基于PPG信号的无创连续血压测量方法研究
- 快递网点装修合同安全要点
- 2025年度安全生产隐患排查治理合同样本
- 宾馆木工修缮工程承包合同
- 最终版附件1:“跨学科主题学习”教学设计(2025年版)
- 4.2依法履行义务 教案 -2024-2025学年统编版道德与法治八年级下册
- NB/T 11526-2024煤矿微震监测系统通用技术条件
- 2025年福建长汀金龙稀土有限公司招聘笔试参考题库含答案解析
- 文化差异下的教育国外的小学音乐教育方式探讨
- 2025年无锡科技职业学院高职单招职业技能测试近5年常考版参考题库含答案解析
- 2024年黑龙江建筑职业技术学院高职单招语文历年参考题库含答案解析
- 贵州省贵阳市普通中学2024-2025学年高二上学期期末监测历史试题(含答案)
- Python金融数据挖掘与分析实战课程教案教学教案
- 2024年地铁车站照明系统安装与维护劳务分包协议3篇
- 脱硫自动化控制-洞察分析
评论
0/150
提交评论