阅读:1422回复:0
OpenGL系列讲座(18)
第三章 OpenGL复杂物体建模
在上一篇中已经讲述了最基本的几何图元建模,但在实际应用中只有这些图元是很不够 的,要生成一个稍微复杂点的曲线、曲面或一些不规则的物体(如山等)相当费事。令人比 较高兴的是,OpenGL基本库(gl库)和功能库(glu库)为我们提供了很大的方便,这一章 将详细介绍图元扩展、法向计算、曲线生成和曲面构造等内容。 3.1 图元扩展 3.1.1 点和线 下面分别介绍点和线的扩展形式及用法。 一、点。OpenGL中定义的点可以有不同的尺寸,其函数形式为: void glPointSize(GLfloat size); 设置点的宽度(以象素为单位)。参数size必须大于0.0,缺省时为1.0。 二、线。OpenGL能指定线的各种宽度和绘制不同的虚点线,如点线、虚线等。相应的函 数形式如下: void glLineWidth(GLfloat width); 设置线宽(以象素为单位)。参数width必须大于0.0,缺省时为1.0。 void glLineStipple(GLint factor,GLushort pattern); 设置线为当前的虚点模式。参数pattern是一系列的16位数(0或1),它重复地赋给所 指定的线。其中每一位代表一个象素,且从低位开始,1表示用当前颜色绘制一个象素(或 比例因子指定的个数),0表示当前不绘制,只移动一个象素位(或比例因子指定的个数)。 参数factor是个比例因子,它用来拉伸pattern中的元素,即重复绘制1或移动0,比如, factor为2,则碰到1时就连续绘制2次,碰到0时连续移动2个单元。factor的大小范围 限制在1到255之间。在绘制虚点线之前必须先启动一下,即调用函数glEnable(GL_LINE_STIPPLE); 若不用,则调用glDisable(GL_LINE_STIPPLE)关闭。 下面举出一个点线扩展应用实例 expntlin.c : 例 3-5 点线扩展应用例程 expntlin.c #include "glos.h" #include <GL/gl.h> #include <GL/glu.h> #include <GL/glaux.h> void myinit(void); void line2i(GLint x1,GLint y1,GLint x2,GLint y2); void CALLBACK display(void); void myinit (void) { glClearColor (0 , 0.0, 0.0, 0.0); glShadeModel (GL_FLAT); } void line2i(GLint x1,GLint y1,GLint x2,GLint y2) { glBegin(GL_LINES); glVertex2f(x1,y1); glVertex2f(x2,y2); glEnd(); } void CALLBACK display(void) { int i; glClear (GL_COLOR_BUFFER_BIT); /* 第一行绘制的是一系列大小尺寸不同的点(以象素为基本扩展单元) */ glColor3f(0.8,0.6,0.4); for (i = 1; i <= 10; i++) { glPointSize(i*2); glBegin (GL_POINTS); glVertex2f (30.0 + ((GLfloat) i * 50.0), 330.0); glEnd (); } /* 第二行绘制的是三条不同线型的线段 */ glEnable (GL_LINE_STIPPLE); glLineStipple (1, 0x0101); /* 点线 */ glColor3f(1.0 ,0.0,0.0); line2i (50, 250, 200, 250); glLineStipple (1, 0x00FF); /* 虚线 */ glColor3f(1.0,1.0,0.0); line2i (250 , 250 , 400, 250 ); glLineStipple (1, 0x1C47); /* 虚点线 */ glColor3f(0.0,1.0,0.0); line2i (450 , 250 , 600 , 250 ); /* 第三行绘制的是三条不同宽度的线段 */ glLineWidth (5.0); glLineStipple (1, 0x0101); glColor3f(1.0 ,0.0,0.0); line2i (50 , 200 , 200 , 200 ); glLineWidth (3.0); glLineStipple (1, 0x00FF); glColor3f(1.0 ,1.0,0.0); line2i (250 , 200 , 400 , 200 ); glLineWidth (2.0); glLineStipple (1, 0x1C47); glColor3f(0.0 ,1.0,0.0); line2i (450 , 200 , 600 , 200 ); /* 设置以下线段的宽度为 1 */ glLineWidth(1); /* 第四行绘制的是一条虚点线 */ glLineStipple (1, 0xff0c); glBegin (GL_LINE_STRIP); glColor3f(0.0 ,1.0,1.0); for (i = 0; i < 12; i++) glVertex2f (50.0 + ((GLfloat) i * 50.0), 150.0); glEnd (); /* 第五行绘制的是十条独立的虚点斜线 */ glColor3f(0.4 ,0.3,0.8); for (i = 0; i < 10; i++) { line2i (50 + ( i * 50), 70, 75 + ((i+1) * 50), 100); } /* 第六行绘制的是一条虚点线,其中线型模式每个元素被重复操作5次 */ glLineStipple (5, 0x1C47); glColor3f(1.0 ,0.0,1.0); line2i (50 , 25 , 600 , 25 ); glFlush (); } void main(void) { auxInitDisplayMode (AUX_SINGLE | AUX_RGBA); auxInitPosition (0, 0, 650, 450); auxInitWindow ("External Points and Lines"); myinit (); auxMainLoop(display); } 以上程序运行结果是显示不同尺寸的点及不同线型和宽度的线的绘制方式。 图 3-3-1 扩展点线 3.1.2 多边形 多边形的绘制模式包含有好几种:全填充式、轮廓点式、轮廓线式以及图案填充式。下 面分别介绍相应的OpenGL函数形式。 一、多边形模式设置。其函数为: 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是一个指向32x32位图的指针。与虚点线绘 制的道理一样,某位为1时绘制,为0时什么也不绘。注意,在调用这个函数前,必须先启 动一下,即用glEnable(GL_POLYGON_STIPPLE);不用时用glDisable(GL_POLYGON_STIPPLE) 关闭。 下面举出一个多边形扩展绘制实例 polystpl.c : 例3-6 多边形图案填充例程 polystpl.c #include "glos.h" #include <GL/gl.h> #include <GL/glu.h> #include <GL/glaux.h> void myinit(void); void CALLBACK display(void); void myinit (void) { glClearColor (0.0, 0.0, 0.0, 0.0); glShadeModel (GL_FLAT); } void CALLBACK display(void) { /* 填充模式定义 (32x32) */ GLubyte pattern[]= { 0x00, 0x01, 0x80, 0x00, 0x00, 0x03, 0xc0, 0x00, 0x00, 0x07, 0xe0, 0x00, 0x00, 0x0f, 0xf0, 0x00, 0x00, 0x1f, 0xf8, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x7f, 0xfe, 0x00, 0x00, 0xff, 0xff, 0x00, 0x01, 0xff, 0xff, 0x80, 0x03, 0xff, 0xff, 0xc0, 0x07, 0xff, 0xff, 0xe0, 0x0f, 0xff, 0xff, 0xf0, 0x1f, 0xff, 0xff, 0xf8, 0x3f, 0xff, 0xff, 0xfc, 0x7f, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xff, 0xff, 0xfe, 0x3f, 0xff, 0xff, 0xfc, 0x1f, 0xff, 0xff, 0xf8, 0x0f, 0xff, 0xff, 0xf0, 0x07, 0xff, 0xff, 0xe0, 0x03, 0xff, 0xff, 0xc0, 0x01, 0xff, 0xff, 0x80, 0x00, 0xff, 0xff, 0x00, 0x00, 0x7f, 0xfe, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x1f, 0xf8, 0x00, 0x00, 0x0f, 0xf0, 0x00, 0x00, 0x07, 0xe0, 0x00, 0x00, 0x03, 0xc0, 0x00, 0x00, 0x01, 0x80, 0x00 }; glClear (GL_COLOR_BUFFER_BIT); /* 绘制一个指定图案填充的矩形 */ glColor3f(0.1,0.8,0.7); glEnable (GL_POLYGON_STIPPLE); glPolygonStipple (pattern); glRectf (48.0, 80.0, 210.0, 305.0); /* 绘制一个指定图案填充的三角形 */ glColor3f(0.9,0.86,0.4); glPolygonStipple (pattern); glBegin(GL_TRIANGLES); glVertex2i(310,310); glVertex2i(220,80); glVertex2i(405,80); glEnd(); glDisable (GL_POLYGON_STIPPLE); glFlush (); } void main(void) { auxInitDisplayMode (AUX_SINGLE | AUX_RGBA); auxInitPosition (0, 0, 500, 400); auxInitWindow ("Polygon Stippling"); myinit (); auxMainLoop(display); } 图3-3-2 图案填充多边形 3.2 法向计算 法向,又称法向量(Mormal Vector)。对于一个平面,其上各点的法向的一样,统一为这 个平面的法向,所以称为平面法向。对于一个曲面,虽然它在计算机图形中是由许多片小的 平面多边形逼近,但是每个顶点的法向都不一样,因此曲面上每个点的法向计算就可以根据 不同的应用有不同的算法,则最后效果也不相同。OpenGL有很大的灵活性,它只提供赋予当 前顶点法向的函数,并不在内部具体计算其法向量,这个值由编程者自己根据需要计算。下 面介绍一下法向基本计算方法和OpenGL法向定义。 3.2.1 法向基本计算方法 首先,讲述平面法向的计算方法。在一个平面内,有两条相交的线段,假设其中一条为 矢量W,另一条为矢量V,且平面法向为N,如图3-3-3所示,则平面法向就等于两个矢量的 叉积(遵循右手定则),即N=WxV。 图3-3-3 平面法向计算 比如计算一个三角形平面的法向,就可以用它的三个顶点来计算,如图3-3-4所示。 图 3-3-4 三角形平面法向计算 设三个顶点分别为P0,P1,P2 ,相应两个向量为W、V,则三角平面法向的计算方式见下列 一段代码: /*------ get value of N (normal vector) ----------*/ 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; } 以上函数的输出参数为指针ddnv,它指向法向的三个分量,并且程序中已经将法向单位 化(或归一化)了。 此外,对于曲面各顶点的法向计算有很多种,最常用的是平均平面法向法,如图3-3-5 所示。在图中,曲面顶点P的法向就等于其相邻的四个平面的法向平均值,即 Np=(N1+N2+N3+N4)/4 图3-3-3 曲面顶点的平均法向计算 3.2.2 法向定义 OpenGL法向定义函数为: void glNormal3{bsifd}(TYPE nx,TYPE ny,TYPE nz); void glNormal3{bsifd}v(const TYPE *v); 设置当前法向值。非向量形式定义法向采用第一种方式,即在函数中分别给出法向三个 分量值nx、ny和nz;向量形式定义采用第二种,即将v设置为一个指向拥有三个元素的指 针,例如v[3]={nx,ny,nz}。因为法向的各分量值只定义法向的方向,因此它的大小不固定, 但建议最好将各值限制在[-1.0,1.0]之间,即法向归一化;若法向不归一化,则在定义法向 之前必须启动法向归一,即调用函数glEnable(GL_NORMALIZE),这样会降低整个程序运行性 能。 下面举出一个自己定义法向的例子 nmlcolr.c : 例 3-7 自定义颜色立方体法向例程 nmlcolr.c #include "glos.h" #include <GL/gl.h> #include <GL/glu.h> #include <GL/glaux.h> static GLfloat p1[]={0.5,-0.5,-0.5}, p2[]={0.5,0.5,-0.5}, p3[]={0.5,0.5,0.5}, p4[]={0.5,-0.5,0.5}, p5[]={-0.5,-0.5,0.5}, p6[]={-0.5,0.5,0.5}, p7[]={-0.5,0.5,-0.5}, p8[]={-0.5,-0.5,-0.5}; static GLfloat m1[]={1.0,0.0,0.0}, m2[]={-1.0,0.0,0.0}, m3[]={0.0,1.0,0.0}, m4[]={0.0,-1.0,0.0}, m5[]={0.0,0.0,1.0}, m6[]={0.0,0.0,-1.0}; static GLfloat c1[]={0.0,0.0,1.0}, c2[]={0.0,1.0,1.0}, c3[]={1.0,1.0,1.0}, c4[]={1.0,0.0,1.0}, c5[]={1.0,0.0,0.0}, c6[]={1.0,1.0,0.0}, c7[]={0.0,1.0,0.0}, c8[]={1.0,1.0,1.0}; void myinit(void); void CALLBACK myReshape(GLsizei w, GLsizei h); void CALLBACK display(void); void DrawColorBox(void); void myinit(void) { GLfloat light_ambient[]={0.3,0.2,0.5}; GLfloat light_diffuse[]={1.0,1.0,1.0}; GLfloat light_position[] = { 2.0, 2.0, 2.0, 1.0 }; GLfloat light1_ambient[]={0.3,0.3,0.2}; GLfloat light1_diffuse[]={1.0,1.0,1.0}; GLfloat light1_position[] = { -2.0, -2.0, -2.0, 1.0 }; glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient); glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse); glLightfv(GL_LIGHT0, GL_POSITION, light_position); glLightfv(GL_LIGHT1, GL_AMBIENT, light1_ambient); glLightfv(GL_LIGHT1, GL_DIFFUSE, light1_diffuse); glLightfv(GL_LIGHT1, GL_POSITION, light1_position); glEnable(GL_LIGHTING); glEnable(GL_LIGHT0); glEnable(GL_LIGHT1); glDepthFunc(GL_LESS); glEnable(GL_DEPTH_TEST); glColorMaterial(GL_FRONT_AND_BACK,GL_DIFFUSE); glEnable(GL_COLOR_MATERIAL); } void CALLBACK display(void) { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glPushMatrix(); glRotatef(45,0.0,1.0,0.0); glRotatef(315,0.0,0.0,1.0); DrawColorBox(); glPopMatrix(); glFlush(); } void CALLBACK myReshape(GLsizei w, GLsizei h) { glViewport(0, 0, w, h); glMatrixMode(GL_PROJECTION); glLoadIdentity(); if (w <= h) glOrtho (-1.5, 1.5, -1.5*(GLfloat)h/(GLfloat)w, 1.50*(GLfloat)h/(GLfloat)w, -10.0, 10.0); else glOrtho (-1.5*(GLfloat)w/(GLfloat)h, 1.5*(GLfloat)w/(GLfloat)h, -1.5, 1.5, -10.0, 10.0); glMatrixMode(GL_MODELVIEW); glLoadIdentity (); } void DrawColorBox(void) { glBegin (GL_QUADS); glColor3fv(c1); glNormal3fv(m1); glVertex3fv(p1); glColor3fv(c2); glVertex3fv(p2); glColor3fv(c3); glVertex3fv(p3); glColor3fv(c4); glVertex3fv(p4); glColor3fv(c5); glNormal3fv(m5); glVertex3fv(p5); glColor3fv(c6); glVertex3fv(p6); glColor3fv(c7); glVertex3fv(p7); glColor3fv(c8); glVertex3fv(p8); glColor3fv(c5); glNormal3fv(m3); glVertex3fv(p5); glColor3fv(c6); glVertex3fv(p6); glColor3fv(c3); glVertex3fv(p3); glColor3fv(c4); glVertex3fv(p4); glColor3fv(c1); glNormal3fv(m4); glVertex3fv(p1); glColor3fv(c2); glVertex3fv(p2); glColor3fv(c7); glVertex3fv(p7); glColor3fv(c8); glVertex3fv(p8); glColor3fv(c2); glNormal3fv(m5); glVertex3fv(p2); glColor3fv(c3); glVertex3fv(p3); glColor3fv(c6); glVertex3fv(p6); glColor3fv(c7); glVertex3fv(p7); glColor3fv(c1); glNormal3fv(m6); glVertex3fv(p1); glColor3fv(c4); glVertex3fv(p4); glColor3fv(c5); glVertex3fv(p5); glColor3fv(c8); glEnd(); } void main(void) { auxInitDisplayMode (AUX_SINGLE | AUX_RGBA); auxInitPosition (0, 0, 500,400); auxInitWindow ("ColorBox"); myinit(); auxReshapeFunc (myReshape); auxMainLoop(display); } 以上程序运行结果是一个自定义法向的彩色正方体,其中每个顶点的颜色值不一样,且 为光滑的明暗处理模式。 图 3-3-6 自定义法向的彩色立方体 |
|
|