OpenGL基础

OpenGL是一套图形标准,它严格按照计算机图形学原理设计而成,符合光学和视觉原理,非常适合可视化仿真系统。 首先,在OpenGL中允许视景对象用图形方式表达,如由物体表面顶点坐标集合构成的几何模型,这类图形数据含有丰富的几何信息,得到的仿真图像能充分表达出其形体特征;而且在OpenGL中有针对三维坐标表示的顶点的几何变换,通过该变换可使顶点在三维空间内进行平移和旋转,对于由顶点的集合表达的物体则可以实现其在空间的各种运动。 其次,OpenGL通过光照处理能表达出物体的三维特性,其光照模型是整体光照模型,它把顶点到光源的距离、顶点到光源的方向向量以及顶点到视点的方向向量等参数代入该模型,计算顶点颜色。因此,可视化仿真图像的颜色体现着物体与视点以及光源之间的空间位置关系,具有很强的三维效果。 另外,为弥补图形方法难于生成复杂自然背景的不足,OpenGL提供了对图像数据的使用方法,即直接对图像数据读、写和拷贝,或者把图像数据定义为纹理与图形方法结合在一起生成视景图像以增强效果。为增强计算机系统三维图形的运算能力,有关厂家已研制出了专门对OpenGL进行加速的三维图形加速卡,其效果可与图形工作站相媲美。 一个完整的窗口系统的OpenGL图形处理系统的结构为:最底层为图形硬件,第二层为操作系统,第三层为窗口系统,第四层为OpenGL,第五层为应用软件。见图1.1所示。 图1.1 OpenGL图形处理系统的层次结构 OpenGL是网络透明的,在客户机/服务器体系结构中,允许本地或远程调用OpenGL。所以在网络系统中,OpenGL在X窗口、Windows或其它窗口系统下都可以以一个独立的图形窗口出现。 由于OpenGL是一个与平台无关的三维图形接口,操作系统必须提供像素格式管理和渲染环境管理。下面以Windows NT操作系统为例具体介绍OpenGL运行的体系结构。 OpenGL在Windows NT上的实现是基于Client/Server模式的,应用程序发出OpenGL命令,由动态链接库OpenGL32.DLL接收和打包后,发送到服务器端的WINSRV.DLL,然后由它通过DDI层发往视频显示驱动程序。如果系统安装了硬件加速器,则由硬件相关的DDI来处理。 OpenGL/NT的体系结构图如图1.2所示。 从程序员的角度看,在编写基于Windows的OpenGL应用程序之前必须清除两个障碍,一个是OpenGL本身是一个复杂的系统,这可以通过简化的OpenGL辅助库函数来学习和掌握;另一个是必须清楚地了解和掌握Windows与OpenGL的接口。 图1.2 OpenGL/NT体系结构 1.3.2 渲染上下文(RC) OpenGL的绘图方式与Windows的一般的绘图方式是不同的,其区别主要表现在以下三个方面: (1) Windows采用的是GDI绘图; (2) OpenGL采用的是渲染上下文RC(Render Context,又称渲染描述表)绘图; (3) OpenGL使用的是特殊的像素格式。 在Windows中使用GDI绘图时必须指定在哪个设备上下文(Device Context,又称设备描述表)中绘制,同样地,在使用OpenGL函数时也必须指定一个所谓的渲染上下文。正如设备上下文DC要存储GDI的绘制环境信息如笔、刷和字体等,渲染上下文RC也必须存储OpenGL所需的渲染信息如像素格式等。 渲染上下文主要由以下六个wgl函数来管理,下面分别对其进行介绍。 l HGLRC wglCreateContext(HDC hdc) 该函数用来创建一个OpenGL可用的渲染上下文RC。Hdc必须是一个合法的支持至少16色的屏幕设备描述表DC或内存设备描述表的句柄。该函数在调用之前,设备描述表必须设置好适当的像素格式。成功创建渲染上下文之后,hdc可以被释放或删除。函数返回NULL值表示失败,否则返回值为渲染上下文的句柄。 2 BOOL wglDeleteContext(HGLRC hglrc) 该函数删除一个RC。一般应用程序在删除RC之前,应使它成为非现行RC。不过,删除一个现行RC也是可以的。此时,OpenGL系统冲掉等待的绘图命令并使之成为非现行RC,然后删除之。注意在试图删除一个属于别的线程的RC时会导致失败。 3 HGLRC wglGetCurrentContext(void) 该函数返回线程的现行RC,如果线程无现行RC则返回NULL。 4 HDC wglGetCurrentDC(void) 该函数返回与线程现行RC关联的DC,如果线程无现行RC则返回NULL。 5 BOOL wglMakeCurrent(HDC hdc, HGLRC hglrc) 该函数把hdc和hglrc关联起来,并使hglrc成为调用线程的现行RC。如果传给hglrc的值为NULL,则函数解除关联,并置线程的现行RC为非现行RC,此时忽略hdc参数。 传给该函数的hdc可以不是调用wglCreateContext时使用的值,但是,它们所关联的设备必须相同并且拥有相同的像素格式。注意,如果hglrc是另一个线程的现行RC,则调用失败。 6 BOOL wglUseFontBitmaps(HDC hdc, DWORD dwFirst, DWORD dwCount, DWORD dwBase) 该函数使用hdc的当前字体,创建一系列指定范围字符的显示表。可以利用这些显示表在OpenGL窗口画GDI文本。如果OpenGL窗口是双缓冲的,那么这是往后缓冲区中画GDI文本的唯一途径。 一般地,在使用单个RC的应用程序中,在相应WM_CREATE消息时创建RC,当WM_CLOSE或WM_DESTROY到来时再删除它。在使用OpenGL命令往窗口中绘图之前,必须先建立一个RC,并使之成为现行RC。OpenGL命令无需提供RC,它将自动使用现行RC。若无现行RC,OpenGL将简单地忽略所有的绘图命令。 一个RC是指现行RC,这是针对调用线程而言的。一个线程在拥有现行RC进行绘图时,别的线程将无法同时绘图。一个线程一次只能拥有一个现行RC,但是可以拥有多个RC;一个RC也可以由多个线程共享,但是它每次只能在一个线程中是现行RC。 在使用现行RC时,不应该释放或者删除与之关联的DC。如果应用程序在整个生命期内保持一个现行RC,则应用程序也一直占有一个DC资源。注意,Windows系统只有有限的DC资源。 下面介绍两种管理RC与DC的方法。 方法一:RC由WM_CREATE消息响应时创建,创建后立即释放DC;当WM_PAINT消息到来时,程序再获取DC句柄,并与RC关联起来,绘图完成后,立即解除RC与DC的关联,并释放DC;当WM_DESTROY消息到来时,程序只需简单地删除RC即可。如图1.3所示。 图1.3 RC与DC的管理方法一 方法二:RC在程序开始时创建并使之成为现行RC。它将保持为现行RC直至程序结束。相应地,GetDC在程序开始时调用,ReleaseDC在程序结束时才调用。此种方法的好处是在响应WM_PAINT消息时,无需调用十分耗时的wglMakeCurrent函数,一般它要消耗几千个时钟周期。如图1.4所示。 如果应用程序需要使用动画或实时图形,建立采用第二种方法。 在VC++中使用OpenGL绘制典型曲面 [正文]   摘要: 本文主要讨论了在VC++中使用OpenGL绘制Bezier、NURBS等典型曲面的一般性方法。   关键词: OpenGL;Bezier;NURBS;曲面绘制   OpenGL中对复杂物体的建摸   基本几何图元是OpenGL进行建模的最基本的方法,但其对较复杂真实物体的建模则比较困难。对于这些复杂物体的建模,需要用到OpenGL基本库和功能库函数(gl库和glu库)以对图元进行扩展并完成法向计算、曲线生成和曲面构造等内容。这种对基本图元的扩展实际也就是对点、线及多边形的扩展。OpenGL中定义的点可具有不同大小的尺寸,其扩展的函数形式为: void glPointSize(GLfloat size);   其参数size以象素为单位设置了点的宽度,其值必须为正,缺省值1.0。对于线的扩展,可通过下面的函数来分别指定其宽度和绘制类型: void glLineWidth(GLfloat width); void glLineStipple(GLint factor,GLushort pattern);   glLineWidth()的参数width以象素为单位指定线宽,其值必须为正,缺省值为1.0。glLineStipple()的参数factor为对模式进行拉伸的比例因子,参数pattern指定了线的模式(例如11001100将绘制一条虚线,为1时绘制,为0时不绘制)。该函数只有在启用了函数glEnable(GL_LINE_STIPPLE)后才可以使用,当不再使用时调用glDisable(GL_LINE_STIPPLE)将其关闭。扩展多边形的绘制模式包括全填充式、轮廓点式、轮廓线式及图案填充式等几种。使用时,首先调用glPolygonMode()设置多边形的模式设置: void glPolygonMode(GLenum face,GLenum mode);     参数face为GL_FRONT、GL_BACK或GL_FRONT_AND BACK;mode取值可以是GL_POINT、GL_LINE或GL_FILL,分别表示多边型的轮廓点、轮廓线和填充模式的绘制方式。缺省设置为填充模式。设置完成后可进行图案填充的设置: void glPolygonStipple(const GLubyte *mask);   其参数mask必须为一指向32×32大小的位图的指针,值为1时绘制、为0不绘制。该函数的使用同样也需要进行如下启动、关闭设置: glEnable(GL_POLYGON-STIPPLE); glDisable(GL_POLYGON_STIPPLE);   复杂模型的建模不同与简单模型的建模,在简单模型中一个平面上各点的法向(mormal vector)是一样的,均等于此平面的法向。对于复杂模型中由众多小的平面多边形逼近而成的曲面,其每个顶点的法向量都不一样,因此曲面上每个点的法向计算结果根据采取的不同算法而有不同的结果。OpenGL只提供赋予当前顶点法向量的函数,而不提供对法向量计算的方法,法向量的计算需要由开发者来完成。下面给出一种简单的计算方法: void getNormal(GLfloat gx[3],GLfloat gy[3],GLfloat gz[3],GLfloat *ddnv)   {    GLfloat w0,w1,w2,v0,v1,v2,nr,nx,ny,nz;    w0=gx[0]-gx[1];    w1=gy[0]-gy[1];    w2=gz[0]-gz[1];    v0=gx[2]-gx[1];    v1=gy[2]-gy[1];    v2=gz[2]-gz[1];    nx=(w1*v2-w2*v1);    ny=(w2*v0-w0*v2);    nz=(w0*v1-w1*v0);    nr=sqrt(nx*nx+ny*ny+nz*nz);    ddnv[0]=nx/nr;    ddnv[1]=ny/nr;    ddnv[2]=nz/nr;   }      其参数gx[3],gy[3]和gz[3]为逼近曲面的一个三角形的三个顶点P0,P1和P2。通过计算矢量P0-P1与矢量P2-P1的叉乘而得到其平面法向量,并在归一化后保存到由参数ddnv所指向的数组中。至于顶点法向的计算则多是取邻近平面法向量的均值。OpenGL提供的法向定义函数为:   void glNormal3{bsifd}(TYPE nx,TYPE ny,TYPE nz);   void glNormal3{bsifd}v(const TYPE *v);   通过这两个函数可以设置当前法向值。对于非向量形式的定义采用前一种方式,通过参数nx、ny和nz分别给出法向三个分量值;对于向量形式的定义采取后一种方式,将v设置为指向法向三分量的指针。在应用时,通常要对法向进行归一化处理。   构造曲线、曲面   在进行复杂物体建模时,使用的光滑曲线、曲面都是由一些线段和多边形逼近而成,并通过少数几个控制点对其进行描述。曲线的定义由glMap1*()函数完成: void glMap1{fd}(GLenum target,TYPE u1,TYPE u2,GLint stride, GLint order,const TYPE *points);   参数target指出了控制顶点的意义以及在points参数中需要提供多少值;points指针可以指向控制点集、RGBA颜色值或是纹理坐标串等。参数u1和u2限定了变量U的取值范围,通常是从0变化到1;stride表示跨度(在每块存储区内浮点数或双精度数的个数,即两个控制点间的偏移量);最后的参数order为阶数,是次数加1,与控制点数一致。曲线定义后必须再glEnable()函数显式启动后才能起作用,其参数与target保持一致。在使用完毕后通过glDisable()函数将其关闭。曲线坐标可通过glEvalCoord1*()函数进行计算: void glEvalCoord1{fd}[v](TYPE u);   该函数将产生曲线坐标值并将其绘制。参数u为定义域内的任意值,每调用一次将只产生一个坐标,此坐标值也是任意的。但目前较多采用的是定义均匀间隔曲线坐标值,依次调用glMapGrid1*()和glEvalMesh1()可以获得等间隔值。这两个函数分别用来定义一个一维网格和计算相应的坐标值。   曲面的构造可以是网格线和填充曲面形式,与曲线的构造很类似只是将其扩展为二维而已。下面给出曲面的定义函数: void glMap2{fd}(GLenum target,TYPE u1,TYPE u2,GLint ustride,GLint uorder,TYPE v1,TYPE v2,GLint vstride,GLint vorder,TYPE points);     这里target的意义与在glMap1*()中的意义相同;(u1,u2),(v1,v2)是二维曲面坐标;其他参数如uorder,vorder,ustride和vstride等的定义都类似于在曲线中的定义;points为控制点坐标。对曲面任意一点的计算可通过函数 void glEvalCoord2{fd}[v](TYPE u,TYPE v);   来完成,通过在定义域内的曲线坐标值u,v来计算曲面内任意一点的世界坐标位置。对于曲面,也可以象曲线一样通过函数来定义均匀间隔的曲面坐标值:   void glMapGrid2{fd}(GLenum nu,TYPE u1,TYPE u2, GLenum nv,TYPE v1,TYPE v2);   void glEvalMesh2(GLenum mode,GLint p1,GLint p2,GLint q1,GLint q2);   第一个函数定义曲面参数空间均匀网格,从u1到u2分为等间隔nu步,从v1到v2分为等间隔nv步,然后由glEvalMesh2()将此网格应用到已经启动的曲面计算上。glEvalMesh2()的mode参数除了可以是GL_POINT和GL_LINE外,也可以是GL_FILL(生成填充空间曲面)。   Bezier曲面的绘制   下面给出一个通过定义曲面和均匀网格绘制一个具有光照和明暗处理效果的Bezier曲面(图1)的部分主要代码: GLfloat ctrlpoints[4][4][3] = {// 控制点坐标 {{-2.5, 1.5, 2.0}, {0.5, -1.5, 2.0}, {0.5, -1.5, -1.0}, {1.5, -1.5, 2.0}}, {{-1.5, -0.5, 1.0}, {0.5, 1.5, 2.0}, {0.5, 0.5, 1.0}, {1.5, -0.5, -1.0}}, {{-1.5, 0.5, 2.0}, {-1.5, 0.5, 1.0}, {0.5, 0.5, 3.0}, {1.5, -1.5, 1.5}}, {{-1.5, 1.5, -2.0}, {-0.5, 1.5, -2.0}, {0.5, 0.5, 1.0}, {1.5, 1.5, -1.0}}}; void Init() {  glClearColor(0.0, 0.0, 0.0, 1.0); // 清屏  glEnable(GL_DEPTH_TEST); // 激活深度比较  glMap2f(GL_MAP2_VERTEX_3,0,1,3,4,0,1,12, 4, &ctrlpoints[0][0][0]);// 定义曲面  glEnable(GL_MAP2_VERTEX_3); // 启用曲面  glEnable(GL_AUTO_NORMAL); // 启用曲面法向向量计算  glEnable(GL_NORMALIZE); // 启用法向归一化  glMapGrid2f(20, 0.0, 1.0, 20, 0.0, 1.0); // 定义参数空间的均匀网格  GLfloat ambient[4] = {0.4, 0.6, 0.2, 1.0}; // 初始化光照、材质的过程  GLfloat position[4] = {0.0, 1.0, 3.0, 1.0};  GLfloat mat_diffuse[4] = {0.8, 0.6, 0.3, 1.0};  GLfloat mat_specular[4] = {0.8, 0.6, 0.3, 1.0};  GLfloat mat_shininess[1] = {45.0};  glEnable(GL_LIGHTING);  glEnable(GL_LIGHT0);  glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);  glLightfv(GL_LIGHT0, GL_POSITION, position);  glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);  glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);  glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess); } 图1 绘制的Bezier曲面   NURBS曲面的绘制   上面的例程通过给定少量控制点就可以很好的控制曲面的形状。在实际应用时也可以通过程序来动态生成或调整控制点。在本例中,对曲面的定义等操作均是显示调用本节前面介绍的glMap2f()、glMapGrid2f()等函数来实现的。除了这种方式,还可以利用OpenGL的功能库提供的绘制非均匀有理B样条曲面(NURBS曲面)的函数进行曲面绘制,下面给出实现此功能的部分示例代码: GLfloat ctlpoints[4][4][3]; // 控制点的存储空间 GLUnurbsObj *theNurb; // 指向NURBS曲面对象的指针 void InitSurface() {  int u, v;  for (u = 0; u < 4; u++) {   for (v = 0; v < 4; v++) {    ctlpoints[u][v][0] = 2.0 * ((GLfloat)u - 1.5);    ctlpoints[u][v][1] = 2.0 * ((GLfloat)v - 1.5);    if ((u == 1 || u == 2) && (v == 1 || v == 2)) ctlpoints[u][v][2] = 6;    else ctlpoints[u][v][2] = -6;   }  } } void Init(void) {  GLfloat mat_diffuse[] = {0.8, 0.6, 0.3, 1.0}; // 定义曲面材质  GLfloat mat_specular[] = {0.8, 0.6, 0.3, 1.0};  GLfloat mat_shininess[] = {45.0};  glClearColor(0.0, 0.0, 0.0, 1.0);  glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);  glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);  glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess);  glEnable(GL_LIGHTING);  glEnable(GL_LIGHT0);  glDepthFunc(GL_LESS);  glEnable(GL_DEPTH_TEST);  glEnable(GL_AUTO_NORMAL);  glEnable(GL_NORMALIZE);  InitSurface(); // 初始化控制点  theNurb = gluNewNurbsRenderer(); // 创建一个NURBS曲面对象  // 修改NURBS曲面对象的属性  gluNurbsProperty(theNurb, GLU_SAMPLING_TOLERANCE, 5.0);  gluNurbsProperty(theNurb, GLU_DISPLAY_MODE, GLU_FILL); } void CALLBACK Display() {  GLfloat knots[8] = {0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0}; // NURBS曲面的控制向量  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // 清屏  glPushMatrix(); // 入栈  glRotatef(30.0, -1.0, 0.0, 0.0); // 旋转变换  glScalef (0.5, 0.5, 0.5); // 缩放变换  gluBeginSurface(theNurb); // 开始曲面绘制  gluNurbsSurface(theNurb, 8, knots, 8, knots, 4 * 3, 3, &ctlpoints[0][0][0], 4, 4, GL_MAP2_VERTEX_3); // 定义曲面的数学模型,确定其形状  gluEndSurface(theNurb); // 结束曲面绘制  glPopMatrix(); // 出栈  glFlush(); // 强制刷新 } 图2 绘制的NURBS曲面   该示例中对控制点的设定即是通过InitSurface()函数动态计算的,在初始化控制点后通过glu库的gluNewNurbsRenderer()函数创建一个NURBS曲面对象theNurb,并可通过gluNurbsProperty()函数修改其属性。在绘制NURBUS曲面时,通过gluBeginSurface()和gluEndSurface()进行界定,在其中通过gluNurbsSurface()函数完成对曲面数学模型的定义并确定曲面的形状,图2给出了上述代码的绘制结果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值