admin管理员组

文章数量:1566222

2024年7月23日发(作者:)

第一、准备好OpenGL

windows平台下:

OpenGL是由SGI公司开发的低层三维图形API,目前已经成为工业标准,由独立非赢利组

织ARB管理。它在WINDOWS中以动态链接库的形式存在,Win95 osr2以上版本及Win NT

自带有微软公司实现的OpenGL。但本人推荐使用SGI实现的OpenGL来调试程序,快速可靠而

且功能丰富,缺点是它是纯软件实现,无法利用硬件加速。

下载了或之后执行之,按提示安装即可。默认情况情况下会在C:

oglsdk,包括了VC和BC的库及头文件,还有两个演示程序,不妨看看,Fly in the Sky在窗

口中移动鼠标就可控制飞行了,还有一份说明书,也不妨看看。

相关文件请到资源区下载

Linux平台下:

装一个Mesa好了,另外在gtk+(GtkGLArea)/qt(QGLWidget+QGLContext)中均包括了Open

GL支持构件(WIDGET),gnome下有许多OpenGL的应用。注意在LD_LIBRARY_PATH加上必

要的路径,详见各软件的安装说明(通常叫README或INSTALL)。

Solaris: OpenGL Library

装上随Solaris发售的OpenGL library 。如果执行以下命令:

> cd /usr/openwin/lib

> ls libGL*

结果显示如下:

@ @ @

.1* .1* .1*

则意味着OpenGL运行库已经装好了。

通常LD_LIBRARY_PATH里会有该目录,如果没有自己加上就成。

在/usr/openwin/include/GL下有以下头文件:

gl.h glu.h glxmd.h glxtokens.h

glmacros.h glx.h glxproto.h

在sun的网站有OpenGL开发者FAQ,可参阅。

Mac OS:OpenGL SDK for Apple

OS/2:不详。

BeOS:不详。

2*******************************************************************

本文的 2.1 - 2.4 部分是针对Borland C++ Builder用户的头文件是O

C++5.0以上版本的头文件也行。将C:oglskdbclib下的文件拷至$BCBLib下。

为便于编写代码,要将opengl的帮助文件加入搜索范围。在BCB3.0中可用

Help|Customize调出OpenHelp设定程

序,在每一页中用Edit|Add Files到program filecommonBorland SharedMSHelp下找到必要

的文件。

说明:系统安装的dll与程序链接时使用的lib要一致,否则像素格式设置调用会失败。

2.2、GLUT库

为了在初期简化编程负担,可以使用独立于平台的GLUT库,在网上可以找到库、源

文件、说明书,基于GLUT的

WINDOWS应用程序是WIN32 CONSOL程序,主程序是int main(int,void **),在其中可以

只用两三行打开一个窗口,

并以填写几个回调函数的方式实现程序功能,省掉了与窗口系统打交道的麻烦。另外,该库

是做跨平台应用的首选

,它在几乎所有的平台上都可用。目前还没有找到将glut与VCL可视开发结合的方法,所

以本教程并没使用它。

安装glut运行库:将这些DLL文件(V3.6)(V3.7β)复制到windowssystem下,其中有两

种版本的dll,分别对应

MS(*)和SGI实现的OpenGL(*.dll)。将其中的glut.h复制到$BCBIncludegl下, 用

BCB的制作导

入库(*.lib),放到$BCBLib下。

GLUT使用指南<稍候>

2.3、GLAUX库

有一个glaux库(即red Book所用的编程环境),可以大简化窗口界面设定过程,但太过

简陋, 不推荐使用。可

是为了便于阅读Red Book,现将在BCB中使用glaux的方法简述如下:

安装glaux运行库:将BCB版aux库的DLL文件复制到windowssystem下,将其中的

复制到$BCBlib

下,或者用BCB的制作导入库(*.lib)再复制到$BCBLib下。BCB中已经带有aux

库的头文件, aux库的源

码在VC中带有。在程序之前加入:

USELIB("C:Program ");

引号中为导入库所在的位置和名字。

GLAUX使用指南<稍候>

2.4、VCL元件

在CBuilder/Delphi环境下还可以使用各种VCL元件,常见的有:

下载并展开后,在BCB中加以安装即可,详情请自

行查阅其自带的readme,以后有空再逐个解说。本人推荐使用Daniel Plakosh的,简洁易用

且带源程序,还可以用

BMP格式的图象作纹理。本教程后期将会基于TOpenGLPanel,但前面会用通用的方法,不

必借助任何附加元件。

Daniel Plakosh said:“ Feel free to whatever you want with the component”

说明:该控件对中文支持不灵,另外其内部会自动调用wglMakeCurrent(null,null),所

以在OnMouse事件中进

行选取时要调用先MakeOpenGLPanelCurrent()。

TOpenglPanel安装说明:

如果曾经装过老版本的TOpenGLPanel,请先御掉。

在File菜单下选Close All File以关闭所有文件。

在Component菜单下选Install Component。

在Install Component对话框中选Into New Package页 <必须Into New Package>

在Inot New Package页中如下填写各项:

Unit File Name:填入或浏览(Browse)寻找含路径的单元文件名,如:

C: //不必完全相同,就看你把文件放

在哪了

Search Path:这一条应该会自动填好。如:c:OpenGLv0.3BCB3Component

Package File Name:应该填为OpenGLPanel_DP

Package Description:应该填为OpenGL Panel Component

单击OK按纽

当提示“Package OpenGLPanel_ will be built then installed. Conitnue?”时点

“Yes”。

当编译完成后,在File菜单下选Save All,用默认的文件名!

现在OpenGL Panel Component就装好了,你可以在工具栏的最右端发现它。

调整BCB3的include及Lib的路径,例如:

INCLUDE:$(BCB)include;$(BCB)includevcl;c:dplakoshopenglv0.3bcb3component

LIB:$(BCB)libobj;$(BCB)lib;c:dplakoshopenglv0.3bcb3component

// 不必完全一样,你的文件在哪就填哪

$(BCB)代表CBuilder所在目录。在你的系统路径中加入元件所在的目录,以便程序

运行。

2.5、非Borland C++ Builder用户

Borland C++/OWL

Delphi /Pascal+API

VisiIt是很好用的,不过封装得太厉害,对于学习者

3******************************************************************************

如果使用Aux/Glut/VCL则可免去这一步。

如果在窗口系统中使用OpenGL则必须给窗口加上WS_CLIPCHILDREN和

WS_CLIPSIBLINGS两个属性,否则只能得到

黑屏。注:只在MDI应用中需要这样做。

在BCB中可在任何窗口控件的CREATEPARAMS成员函数中加入如下语句使之支持

OpenGL:

//--以TForm为例

//--记得在unit1.h中class TForm1的priviate部分加入原型:

//--void __fastcall CreateParams(TCreateParams &Params);

//------------------------------------------

TForm1::CreateParams(TCreateParams &Params)

{

// 先调用父类中的成员函数,该函数继承自TWindowControl类

TForm::CreateParams(Params);

|= (WS_CLIPCHILDREN | WS_CLIPSIBLINGS);

}

//--------------------------------------

样本工程

所谓窗口控件是指从TWindowControl派生出来的各类控件,通常可见的控件都是窗

口件,前面提到过TOpenglP

anel就是以TCustomPanel为基类派生的,查看其源码就会发现它对窗口属性的修改与上

面所述完全相同。注:这是

最简的示例,为使程序在256色等环境中能正常运行,还要在此处理调色板问题

4***************************************************

OpenGL是一种基于客户/服务器和管道(PipeLine)的图形库。即一个应用程序(客户)将各种命令和数

据写入管道,这命令和数据将被暂时保存起来,图形库(服务器)直到收到一条专门的指令才会开始处理

它们,并将处理结果通过管道传给用户。 所以OpenGL应用程序的基本结构就是:

建立管道根据需要向管道中写入数据和指令关闭管道在Windows95/NT环境中,这个管道就是OpenGL着

色环境——(OpenGL Render Context)简称RC。因此建立管道实际上就是获取一个可用的RC。

而在Windows的GDI系统中进行图形显示是通过图形设备上下文——(Device Context)简称DC——进行

的。所以获取RC就是: ①先获取一个DC, ②调整这个DC的象素格式(PixelFormat)以便OpenGL库进

行绘制, ③用DC去调用wglCreateContext(hDC)建立一个RC, ④调用wglMakeCurrent(hRC,hDC)将刚

才建立的RC指定为当前的RC。

当一个RC不再使用时就可以删除它,方法是:

使RC不是“当前RC”: wglMakeCurrent(NULL,NULL); //不再有当前RC wglMakeCurrent(hAnotherRC,hDC);

//另一个RC成为当前RC wglDEleteContent(hRC); //删除之

为了避免反复建设/删除RC所带来的不必要的开支,我们在TForm1中定义了hDC、hRC以保存DC和RC,

并在TForm1的构造函数中建立并保存RC,直到清除这个Form即Destory()成员函数运行时才删除RC和

DC。

主要代码如下:

void __fastcall TForm1::CreateParams(TCreateParams& Params) {//再次说明:只在MDI应用有必要进行

修改。2000.2.17. TForm::CreateParams(Params); //调用原有函数预处理

|=WS_CLIPCHILDREN|WS_CLIPSIBLINGS;//加上必要的属性 }

//---------------------------------------------------- __fastcall TForm1::TForm1(TComponent*

Owner) : TForm(Owner) { hDC=GetDC(Handle); //获取一个DC,中保存有

Form的窗口句柄 SetDCPixelFormat(hDC); //调整该DC的象素格式 hRC=wglCreateContext(hDC);

//用这种DC去创建一个RC wglMakeCurrent(hDC,hRC); //指定当前DC、当前RC为hDC、hRC }

//------------------------------------------------------------------- void __fastcall TForm1::SetDCPixelFormat(HDC

hDC) { //本函数用于调整DC的象素格式,如缓冲区、颜色数等 //先不深究,只要知道它的作用

就行 int nPixelFormat; static PIXELFORMATDESCRIPTOR pfd = { sizeof(PIXELFORMATDESCRIPTOR),

// Size of this structure 1, // Version of this structure

PFD_DRAW_TO_WINDOW | // Draw to Window (not to bitmap)

PFD_SUPPORT_OPENGL | // Support OpenGL calls in window

// RGBA Color mode 24, // Want 24bit color 0,0,0,0,0,0,

// Not used to select mode 0,0, // Not used to select mode

0,0,0,0,0, // Not used to select mode 32,

// Size of depth buffer 0, // Not used to select mode 0,

// Not used to select mode PFD_MAIN_PLANE, // Draw in main plane

0, // Not used to select mode 0,0,0 };

// Not used to select mode // Choose a pixel format that best matches that described in pfd

nPixelFormat = ChoosePixelFormat(hDC, &pfd); // Set the pixel format for the device context

SetPixelFormat(hDC, nPixelFormat, &pfd); } //----------------------------------------------------------------- void

__fastcall TForm1::FormPaint(TObject *Sender) { //该Form的绘制响应函数

glClearColor(0.5,0.7,0.9,1.0); //指定背景颜色(依次为RGBA) glClear(GL_COLOR_BUFFER_BIT); //用背

景色清窗口 RenderScence(); //场景绘制 SwapBuffers(hDC); //切交缓冲区,这就

是可以启动图形库处理流程并得相应结果的两条命令之一 //如果当前RC相联DC具有DoubleBuffer

的PixelFormat则用本命令 //否则就是单缓冲DC,用glFlush(void)进行更新。 }

//----------------------------------------------------- void __fastcall TForm1::FormDestroy(TObject *Sender)

{ wglMakeCurrent(NULL,NULL); //取消当前RC和当前DC wglDeleteContext(hRC); //删除该RC

PFD_DOUBLEBUFFER // Double buffered mode PFD_TYPE_RGBA,

DeleteObject(hDC); //删除Windows DC。 }

//-------------------------------------------------------------------

5**************************************************************************

如果用过3DS/LIGHTWAVE等任何一种三维图形软件包,就可以发现制作一个三维场景无非以

下几项工作:

建模:制作各种物体。

放置:将做好的各物体通过平移、旋转等放到场景空间的适当位置。

上色:给物体模型指定颜色或表面纹理。

打灯:在场景中适当位置放置几盏灯以照亮场景。

摄像:在空间适当位置放上适当角度的摄像机,以得到所需的视觉效果。

用OpenGL开发应用程序与之类似,通常是:

设定视见体

定义光源

生成场景

而三维图形生成的流程如下:

放置几何变换:平移、旋转、缩放等

视见变换:裁剪、消隐、投影等

视见体设定:

视见体有两种:正投影体和透视投影体,正投影视见体中的物体在屏幕上的投影不会出现近大远

小的现象,而透视投影视体则与人眼的观察结果类似,离观察点越远的物体在屏幕上的投影越小,

因此透视投影体中的观察结果看起来更真实一些。

定义透视投影体的方法是:

glFrustum(left,right,bottom,top,near,far);

//物体在这六个参数界定的范围内可见,超出边界将被裁掉。

//left,bottom,代表左下 right,top代表右上

定义透视投影体的方法是:

glOrtho(left,right,bottom,top,near,far);

//物体在这六个参数界定的范围内可见,超出边界将被裁掉。

综上所述,一个三维图形应用程序在建好OpenGL Pipleline之后就要向管道发如下一系列的命

令,以建立一个三维观察环境:

//1、设定视见体,在窗体的Resize事件时

void __fastcall TForm1::FormResize(TObject *Sender)

{

glViewport(0, 0, Width, Height);

//使用Form窗体的指定区域为显示区

//如改为Width/4,Height/4,Width/2,Width/2,

//则只用Form1的-窗体的中间1/4进行显示

glMatrixMode(GL_PROJECTION); //切换到投影矩阵栈

glLoadIdentity(); //清除投影矩阵

glFrustum(1.0,-1.0,-1.0,1.0,1.0,-1.0);

//指定透视视见体范围

//超出部分将被裁去

//或正投影glOrtho(1.0,-1.0,-1.0,1.0,1.0,-1.0);

glMatrixMode(GL_MODELVIEW); //切换到模型视见矩阵栈

glLoadIdentity(); //清除视见矩阵

}

6*******************************************************************

OpenGL中从三维场景到屏幕图形要经历如下所示的变换过程:

其中四种坐标经常要在程序中用到:世界坐标,物体坐标,设备坐标和眼坐标。

世界坐标是OpenGL中用来描述场景的坐标,Z+轴垂直屏幕向外,X+从左到右,Y+轴从下到上,是右手

笛卡尔坐标系统。我们用这个坐标系来描述物体及光源的位置。

将物体放到场景中也就是将物体平移到特定位置、旋转一定角度,这些操作就是坐标变换。OpenGL中

提供了glTranslate*/glRotate*/glScale*三条坐标变换命令,利用OpenGL的矩阵运算命令,则可以实现

任意复杂的坐标变换。

非常重要:OpenGL中有一个坐标变换矩阵栈(ModelView),栈顶就是当前坐标变换矩阵,进入OpenGL

管道的每个坐标(齐次坐标)都会先乘上这个矩阵,结果才是对应点在场景中的世界坐标。OpenGL中的坐标

变换都是通过矩阵运算完成的,与图形学课本的描述完全一致。要注意的是变换中的矩阵乘法是左乘,而

矩阵乘法与算术乘法不同,不符合交换律(万一不明白去看矩阵代数书好了)。

glTranslate*(x,y,z):平移,参数为各轴向的移动量。

glRotate(d,x,y,z):旋转,第一个参数为转动的度数,后三个参数表明是否绕该轴旋转。通常x,y,z

中只有一个为1,其余为0,用连续几条旋转命令完成复杂旋转。由于矩阵运算的左乘特点,旋转命令的顺

序与旋转动作的顺序正好相反。

物体坐标是以物体某一点为原点而建立的“世界坐标”,该坐标系仅对该物体适用,用来简化对物体

各部分坐标的描述。物体放到场景中时,各部分经历的坐标变换相同,相对位置不变,所以可视为一个整

体,与人类的思维习惯一致。

眼坐标是以视点为原点,以视线的方向为Z+轴正方向的坐标系中的方向。OpenGL管道会将世界坐标先

变换到眼坐标,然后进行裁剪,只有在视线范围(视见体)之内的场景才会进入下一阶段的计算。

同样的,有投影变换矩阵栈(Projection),栈顶矩阵就是当前投影变换矩阵,负责将场景各坐标变换

到眼坐标,由所得到的结果是裁剪后的场景部分,称为裁剪坐标。前面提到过的视见体设定其实就是在建

立该矩阵。

OpenGL的重要功能之一就是将三维的世界坐标经过变换、投影等计算,最终算出它在显示设备上对应

的位置,这个位置就称为设备坐标。在屏幕、打印机等设备上的坐标是二维坐标。值得一提的是,OpenGL

可以只使用设备的一部分进行绘制,这个部分称为视区或视口(viewport)。投影得到的是视区内的坐标(投

影坐标),从投影坐标到设备坐标的计算过程就是设备变换了。

矩阵栈切换:glMatrixMode(GL_MODELVIEWING或GL_PROJECTION);本命令执行后参数所指矩阵栈就成

为当前矩阵栈,以后的矩阵栈操纵命令将作用于它。

矩阵栈操纵命令:

glPushMatrix(); 当前矩阵入栈,这时矩阵栈将栈顶值压入栈。

glPopMatrix(); 栈顶出栈,通常与上一条命令配合使用。

glLoadIdentity(); 将栈顶设为不变矩阵(就是对角线全为1其它为0的那个)。

glMultMatrix(M);将栈顶T设为M?T。

7**********************************************************************

OpenGL中的物体是用面边界模型来描述的。我们将物体表面分割成许多个平面,记录面的形状、颜色、

纹理,OpenGL根据这些参数再把这些个面绘制出来,我们就能在场景中看到立体的实体了。

物体模型最基本的参数是它的形状,形状可以用表面来记录。而记录面的边缘就能描述面的形状。描

述边缘的方法是记录边上所有线段的端点。所以用面边界模型描述物体形状的核心就是记录物体表面所有

的顶点。这些顶点按所在边的顺序排列起来就能描述一个平面,许多的平面就能表示一个物体的表面,显

然平面越多形状越逼真。

OpenGL中描述一个顶点的方法是指定其坐标和属性,命令是:

glVertex*(Coordination);

该命令以参数指定的坐标和当前颜色、纹理坐标、法向等属性定义一个顶点。

OpenGL中描述一个面的方法是glBegin/glEnd命令组:

glBegin(形状);

glVertex(顶点1);

glVertex(顶点2);

„„

glEnd();

OpenGL支持点(GL_POINTS)、线段(GL_LINES)、三角形(GL_TRIANGLES)、四边形(GL_QUADS)、多边形

(GL_POLYGON)。可以在一个begin/end对中定义多个同一类型的平面,如在glBegin(GL_TRIANGLES);与

glEnd();之间输入11个顶点,则会形成3个三角形,最后两个顶点则被舍弃。

OpenGL还支持平面带(STRIP)和平面扇(FAN)。

扇:在glBegin(GL_TRIANGLE_FAN);/glEnd();间输入若干顶点,则第一个顶点做为共同的顶点,第二、

三个做为底边定义一个三角形,此后的每个顶点与前一个顶点做为底边定义一个三角形,所以11个顶点就

会定义9个共顶点的三角形,这些三角形将形成一个扇形。

带:输入顶点编号为123456789„„;定义的三角形为132 243 354 465 567„„,相邻三角形共用一

条边,形成一条带状;依此类推,定义的四边形为1243 3465 5687„„。

实际使用中,可用3ds max/softimage/maya之类的工具以直观的方式建立物体模型保存成文件,然后

再用专门的解码工具将模型转换成一系列的顶点。解码工具有三类:插件、工具软件、程序库。目前已经

有许多的代码库可以解析多种格式的三维场景,如果还没有合适的还也可以自己编写。

3ds max带有一个简单的.asc导出器,但功能很弱,而3dxexp则强大得多,要是不满意还可以自己写,

这就得学习学习MAX SDK了。

使用3ds2c之类的工具将模型转换为C代码是非常简便的一种方法,但从效率方面考虑,并不是最好

的,特别是象PolyTrans之类的软件会为每个面生成一个Begin/End,象下面的这个模型,.3ds文件只有

978K,而转换得到的.C文件却有6.8M之巨。稍好一点的办法是充分利用STRIP或使用VertexPointer技

术,或者利用wc2pov之类的工具将转换后的多边形数据保存在文件中,使用时再调入;还有一种办法就是

view3ds所用的将场景转化为编译后的glList保存起来,在使用时装入系统直接调用,速度非常快。

T_FIGHTER(Hight detail)

根本的解决办法还是针对自己的应用设计优化的文件格式,并制作配套的编码、解码库。

处理各种格式三维文件的程序库非常多,到网上随便搜搜就能找到一大把。目前俺最喜欢的是

GLScene(类示意图1 2 3),它内建树形场景支持,可解析.3ds文件和.md2文件,可扩展性极好,可惜只

有delphi版本。

8**************************************************************************************

OpenGL中使用RGBA色彩体系,RGB为红绿蓝三原色,A为α值,该值代表色彩融合时所占的比例。颜

色是顶点的重要属性之一,没有色彩的世界是毫无生气的。

使用glColor*(R,G,B,A);设定当前颜色,此后定义所有的顶点都将是这个颜色的,直到再次改变当前

颜色。该命令有两种常用形式glColor*f和glColor*ub,后者可以直接使用GetRValue(pixel)取得的颜色

分量作为参数。下面左边的代码就可以生成右边漂亮的三角形:

glBegin(GL_TRIANGLES);

glColor3f(1.0,0.0,0.0);

glVertex3f(-1.0,0.0,0.0);

glColor3f(0.0,1.0,0.0);

glVertex3f(0.0,1.0,0.0);

glColor3f(0.0,0.0,1.0);

glVertex3f(1.0,0.0,0.0);

glEnd();

可以看出,相邻顶点之间的部份也会有颜色,实际上OpenGL会在顶点之间进行插值分割,最终计算出

对应光栅每一点的颜色,这个计算结果就是最终显示在屏幕上的图像。

也许上边的代码和文字暗示你给顶点指定的颜色就是屏幕图像上显示的颜色,其实最终的颜色取决于

多个因素:顶点材质、法线方向、光照条件。不同颜色的光从不同角度照射在顶点上,顶点的颜色也会发

生变化。上面的图是在白色光垂直照射条件下得到的(这也是默认设定)。

我们也可以不简单的指定顶点颜色,而是详细的说明该顶点产生的环境光、漫反射光、镜面反射光的

颜色。OpenGL使用一种简易方法计算出面的图像:根据法向和光源位置的关系将以上几种光的颜色结合灯

光对应成份颜色分别指定平面的各部份。从一个顶点产生的反射光不会再照射到其它顶点上,它对其它部

份的影响直接在环境光中定义。要想取得更逼真的图像,可以使用光线追踪算法,该方法沿着每一条光线

的轨迹,计算出它对所有顶点的影响,将所有光线的效果合并就是最终的图像,由于反射光的照明效果也

是精确计算出来的,所以图像逼真。但是OpenGL并不直接支持该方法,需要自己编写计算例程,感兴趣的

话去看看光线追踪的书好了。

用glMaterial*指定当前材质,第一个参数说明是给面的哪一侧指定材质(前或者后,详见法向部份说

明),第二个参数指定要修改的分量,最后部份参数是相应的值。

GLfloat ambient[] ={0.8,0.8,0.8,1.0};//环境光

GLfloat diffuse[] ={0.8,0.0,0.8,1.0};//漫反射特性

GLfloat specular[] ={1.0,0.0,1.0,1.0};//镜面反射光色

GLfloat shininess[] ={50.0}; //镜面反射的光亮度

glMaterialfv(GL_FRONT,GL_AMBIENT,ambient);

glMaterialfv(GL_FRONT,GL_DIFFUSE,diffuse);

glMaterialfv(GL_FRONT,GL_SPECULAR,specular);

glMaterialfv(GL_FRONT,GL_SHININESS,shininess);

gluSphere(obj,0.1,10,10);//用10X10个面绘制半径0.1的球面

OpenGL还支持自动双面模式,这种模式下定义的每个面都将生成两个面,位置重合但法向相反,这可

以简化对背面和物体内部的描述。该模式默认为关闭。

开/关命令是:void glLightModel* GL_LIGHT_MODEL_TWO_SIDE,0/1);

打开颜色材质可以简化材质设定,在颜色材质打开时,可以用glColor直接给定顶点材质的颜色,设

定材质的哪部分可用glColorMaterial(face,mode);命令设定。而不用象上面那样写一串罗嗦的代码。但

是由于不能对材质属性做细节控制,只适用于一些特定的场合。命令是:glEnable(GL_COLOR_MATERIAL);

默认值为Disabled。

9***************************************************************************************

OpenGL中用表面来表示物体,一个物体就是一组平面。光线照射在平面上会产生反射,入射线与反射线

的角平分线就是法线,它垂直于平面。面法线有两种可能的方向,我们称面有两侧。当描述封闭物体的外

表面时,法线应该从内部指向外部;而表示在物体内部时,法线应该从外部指向内部。

用glNormal*(N_Vector);指定当前法向矢量。此后的顶点都会使用该法向,直到再次改变。

面的法向矢量是怎么确定的呢?右手手指沿顶点顺序握拳,拇指竖立所指方向即为一个面的法向,如

果面对该平面,以逆时针序指定顶点,则法向指向你。当你用这样的顺序指定平面的顶点时,OpenGL能

够正确的计算出法向矢量。法矢与顶点坐标一样要经过模型视矩阵变换,这样物体坐标系内的法向矢量就

可以正确的变成世界坐标系内的法向矢量,保证最终生成的图像是正确的。

当一个顶点属于多个平面时,该顶点的法向应该取所在所有平面法向的平均值,以使表面棱角部份平

滑自然的,否则会有一道黑线。

我们把平面的法向所指一侧称为前面,另一侧称为后面,可以为两侧分别指定不同的属性,所以

glMaterial*命令的第一个参数必须是GL_FRONT/GL_BACK/GL_FRONT_AND_BACK之一。

下面这组图片展示了法向对场景的影响,角度表示图像是绕Y旋转多少后观察到的:

头两行中两三角法向从两者之间指向外部,简称外法向。

前三角的面法向(0,0,1),后三角的面法向(0,0,-1),光射线方向(0,0,-1)

第一行中,前三角,后三角呈现的是其GL_FRONT部份的材质

第二列中,两个三角形的前后关系已经反过来了。

三四两行中两三角法向从两者外部指向两者之间的内部,简称内法向。

前三角的面法向(0,0,-1),后三角的面法向(0,0,1),光射线方向(0,0,-1)。

第五行前两图中两三角法向相同均为(0,0,1)。

外法向的平面组可用来表示物体外观(外表面),第五行最后一张图则是这样表示的一个立方体。

内法向则适于表示从内部观察物体(灯光、视点均在内部,内表面),程序从略。

双三角场景的

绘制程序代码如下:

glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

glEnable(GL_LIGHTING);glEnable(GL_DEPTH_TEST);

glEnable(GL_COLOR_MATERIAL);

glEnable(GL_LIGHT0);

glLoadIdentity();

glRotatef(rot,0,1,0);//全局变量rot即为旋转角度

glBegin(GL_TRIANGLES);

glNormal3f(0.0,0.0,1.0);//法向与光射线指向同侧

glColor3f(1.0,0.0,0.0);

glVertex3f(-1.0,0.0,-0.30);

glColor3f(0.0,1.0,0.0);

glVertex3f(0.0,1.0,-0.30);

glColor3f(0.0,0.0,1.0);

glVertex3f(1.0,0.0,-0.30);

glNormal3f(0.0,0.0,-1.0);//法向与光射线指向相反

glColor3f(1.0,0.0,0.0);

glVertex3f(-1.0,0.0,0.0);

glColor3f(0.0,1.0,0.0);

glVertex3f(0.0,1.0,0.0);

glColor3f(0.0,0.0,1.0);

glVertex3f(1.0,0.0,0.0);

glEnd();

使用OpenGL默认的光照(光源在0,0,1沿-Z轴方向的白色平行光)和默认的平行投影。为简化代码,打

开了GL_COLOR_MATERIAL直接用glColor设定顶点材质。另外为便于阅读图片做了减肥。

以上图片是在PII266/FireGL 1000pro/Win2000环境下获取的。

这两段代码都放在TOpenGL Panel的OnPaint事件里执行。

检讨:右边代码的法向全反了,如果你运行它就会发现虽然看起来仍是一个立方体,但是前后左右上

下全都反过来了。这是因为各面法向朝里,前面的后侧对着我们,它不可见,看到的前面实际是后面前侧,

而正常情况下后面应该会被前面挡住。

立方体场景的

绘制程序代码如下:

glBegin(GL_QUADS);

glNormal3f(0,0,-1);

glColor3f(1,0,0);

glVertex3f(0,0,0);

glVertex3f(1,0,0);

glVertex3f(1,1,0);

glVertex3f(0,1,0);

glNormal3f(-1,0,0);//右

glColor3f(0,1,0);

glVertex3f(1,0, 0);

glVertex3f(1,0,-1);

glVertex3f(1,1,-1);

glVertex3f(1,1, 0);

glNormal3f(0,-1,0);//上

glColor3f(0,0,1);

glVertex3f(0,1, 0);

glVertex3f(1,1, 0);

glVertex3f(1,1,-1);

glVertex3f(0,1,-1);

glNormal3f(0,0,1); //后

glColor3f(1,1,0);

glVertex3f(0,1,-1);

glVertex3f(1,1,-1);

glVertex3f(1,0,-1);

glVertex3f(0,0,-1);

glNormal3f(1,0,0);//左

glColor3f(0,1,1);

glVertex3f(0,1, 0);

glVertex3f(0,1,-1);

glVertex3f(0,0,-1);

glVertex3f(0,0, 0);

glNormal3f(0,1,0);//下

glColor3f(1,0,1);

glVertex3f(0,0,-1);

glVertex3f(1,0,-1);

glVertex3f(1,0, 0);

glVertex3f(0,0, 0);

glEnd();

附加说明*************************************************************

由于Delphi自带是1.0版的,而现在实际使用的至少是1.1版,Windows纯软

件模拟方式也是1.1版的,所以要自己导入一些必要的函数。也可用一些开源的免费单元,

如Mike

Lischke的。当然,自己写可以设计得更简洁,而且不必

在过于超前完备的庞大代码中找错误。

首先引入必要的单元Windows, Messages, OpenGL

要增加一些必要的扩展。

const

// GL_EXT_bgra

GL_BGR_EXT = $80E0;

GL_BGRA_EXT = $80E1;

// polygon offset

GL_POLYGON_OFFSET_UNITS = $2A00;

GL_POLYGON_OFFSET_POINT = $2A01;

GL_POLYGON_OFFSET_LINE = $2A02;

GL_POLYGON_OFFSET_FILL = $8037;

GL_POLYGON_OFFSET_FACTOR = $8038;

procedure glBindTexture(target: GLEnum; texture: GLuint);

stdcall; external opengl32;

procedure glDeleteTextures(n: GLsizei; textures: PGLuint);

stdcall; external opengl32;

procedure glGenTextures(n: GLsizei; textures: PGLuint);

stdcall; external opengl32;

function glIsTexture(texture: GLuint): GLboolean; stdcall;

external opengl32;

procedure glPolygonOffset(factor, units: GLfloat); stdcall;

external opengl32;

// 此声明用于纠正的一个bug

function gluBuild2DMipmaps(target: GLEnum; components, width,

height: GLint; format, atype: GLEnum; Data: Pointer): GLint;

stdcall; external opengl32;

现在接口已经基本升级到1.1版。如果还需要其他扩展,可类似增加。

接下来,要创建OpenGL的绘图上下文RC,为此需要GDI窗口的设备

上下文DC。属性或其他TWinControl的Handle属性都是DC。可使用如下函

数由DC创建RC,返回值为RC的句柄。之后即可使用OpenGL绘图。一般可在Form的

OnCreate事件内使用。此函数的选项含义分别为深度缓冲区,模版缓冲区,积累缓冲区,

生成Alpha通道的值。

type

TRCOptions = set of (roDepth, roStencil, roAccum, roAlpha);

function CreateRC(dc: HDC; opt: TRCOptions): HGLRC;

var

PFDescriptor: TPixelFormatDescriptor;

PixelFormat: Integer;

begin

FillChar(PFDescriptor, SizeOf(PFDescriptor), 0);

with PFDescriptor do

begin

nSize := SizeOf(PFDescriptor);

nVersion := 1;

dwFlags := PFD_SUPPORT_OPENGL or

PFD_DRAW_TO_WINDOW or

PFD_DOUBLEBUFFER;

iPixelType := PFD_TYPE_RGBA;

cColorBits := GetDeviceCaps(DC, BITSPIXEL) *

GetDeviceCaps(DC, PLANES);

if roDepth in opt then cDepthBits := 24;

if roStencil in opt then cStencilBits := 8;

if roAccum in opt then cAccumBits := 64;

iLayerType := PFD_MAIN_PLANE;

end;

PixelFormat := ChoosePixelFormat(DC, @PFDescriptor);

Assert(PixelFormat <> 0);

Assert(SetPixelFormat(DC, PixelFormat, @PFDescriptor));

Result := wglCreateContext(DC);

Assert(Result <> 0);

wglMakeCurrent(dc, Result);

end;

在Form的OnPaint事件里绘图。记住,绘图完成后要用SwapBuffers(dc:

HDC)交换绘图缓冲和显示缓冲,这样图象才会显示出来。还要记得在

Form的OnResize事件里调用

glViewport(0, 0, ClientWidth, ClientHeight); 好让RC和DC同步。

在Form的OnDestroy事件里销毁RC。

procedure DestroyRC(rc: HGLRC);

begin

if rc = 0 then Exit;

wglMakeCurrent(0, 0);

wglDeleteContext(rc);

end;

至此,一个OpenGL程序的框架就大致成型。但还有问题要解决。

第一,要防止Windows擦除背景而影响速度。在Form中加入成员函数

private

procedure WMEraseBkgnd(var Message: TWmEraseBkgnd);

message WM_ERASEBKGND;

procedure eBkgnd(var Message: TWmEraseBkgnd);

begin

:= 1;

end;

第二,为了更保险些。再增加以下成员函数。

protected

procedure CreateParams(var Params: TCreateParams);

override;

procedure Params(var Params: TCreateParams);

begin

inherited;

with Params do

begin

Style := Style or WS_CLIPCHILDREN or WS_CLIPSIBLINGS;

:= CS_VREDRAW or CS_HREDRAW or

CS_OWNDC;

end;

end;

好,现在就可以忘掉这些麻烦的东西了,写你的精彩3D显示吧:)

还得唠叨几句,在一个线程里不要创建多个RC,这样会严重影响性能。

有些个人的OpenGL窗口控件演示有在一个Form上放多个控件,其实并非好主义。应该用

一个OpenGL窗口显示多个视图。另外,不要跨线程访问OpenGL函数。

还有Windows自动安装显卡驱动时不会安装OpenGL的硬件加速,一

定要自己安装显卡厂商的驱动!

另外,副赠全屏显示的函数:)

function FullScreen(win: TWinControl; width, height, bitdepth:

integer): boolean;

var displaymode: DEVMODE;

begin

FillChar(displaymode, sizeof(displaymode), 0);

with displaymode do

begin

dmSize := sizeof(displaymode);

dmPelsWidth := width;

dmPelsHeight := height;

dmBitsPerPel := bitdepth;

dmFields := DM_BITSPERPEL or DM_PELSWIDTH or

DM_PELSHEIGHT;

end;

if ChangeDisplaySettings(displaymode, CDS_FULLSCREEN) =

DISP_CHANGE_SUCCESSFUL

then begin

ShowWindow(, WS_MAXIMIZE);

result := true;

end

else result := false;

end;

procedure RestoreDisplay(win: TWinControl);

begin

ChangeDisplaySettings(PDEVMODE(0)^, 0);

ShowWindow(, SW_RESTORE);

end;

本文标签: 物体顶点坐标使用矩阵