请选择 进入手机版 | 继续访问电脑版

Lazarus中文社区

 找回密码
 立即注册

QQ登录

只需一步,快速开始

Lazarus IDE and 组件 下载地址版权申明
查看: 1808|回复: 0

OpenGL入门学习2:显示细节控制

[复制链接]

该用户从未签到

发表于 2014-3-10 22:06:38 | 显示全部楼层 |阅读模式
1.三维空间的变化
我们生活在一个三维的世界——如果要观察一个物体,我们可以:

    1、从不同的位置去观察它。(视图变换

    2、移动或者旋转它,当然了,如果它只是计算机里面的物体,我们还可以放大或缩小它。(模型变换

    3、如果把物体画下来,我们可以选择:是否需要一种“近大远小”的透视效果。另外,我们可能只希望看到物体的一部分,而不是全部(剪裁)。(投影变换

    4、我们可能希望把整个看到的图形画下来,但它只占据纸张的一部分,而不是全部。(视口变换
这些,都可以在OpenGL中实现。

    OpenGL变换实际上是通过矩阵乘法来实现。无论是移动、旋转还是缩放大小,都是通过在当前矩阵的基础上乘以一个新的矩阵来达到目的。关于矩阵的知识,这里不详细介绍,有兴趣的朋友可以看看线性代数(大学生的话多半应该学过的)。

    OpenGL可以在最底层直接操作矩阵,不过作为初学,这样做的意义并不大。这里就不做介绍了。

    1、模型变换和视图变换

    从“相对移动”的观点来看,改变观察点的位置与方向和改变物体本身的位置与方向具有等效性。在OpenGL中,实现这两种功能甚至使用的是同样的函数。

    由于模型和视图的变换都通过矩阵运算来实现,在进行变换前,应先设置当前操作的矩阵为“模型视图矩阵”。设置的方法是以GL_MODELVIEW为参数调用glMatrixMode函数,像这样:

    glMatrixMode(GL_MODELVIEW);

    通常,我们需要在进行变换前把当前矩阵设置为单位矩阵。这也只需要一行代码:

    glLoadIdentity();

    然后,就可以进行模型变换和视图变换了。进行模型和视图变换,主要涉及到三个函数:

    glTranslate*,把当前矩阵和一个表示移动物体的矩阵相乘。三个参数分别表示了在三个坐标上的位移值。

    glRotate*,把当前矩阵和一个表示旋转物体的矩阵相乘。物体将绕着(0,0,0)到(x,y,z)的直线以逆时针旋转,参数angle表示旋转的角度。

    glScale*,把当前矩阵和一个表示缩放物体的矩阵相乘。x,y,z分别表示在该方向上的缩放比例。

    注意我都是说“与XX相乘”,而不是直接说“这个函数就是旋转”或者“这个函数就是移动”,这是有原因的,马上就会讲到。

    假设当前矩阵为单位矩阵,然后先乘以一个表示旋转的矩阵R,再乘以一个表示移动的矩阵T,最后得到的矩阵再乘上每一个顶点的坐标矩阵v。所以,经过变换得到的顶点坐标就是((RT)v)。由于矩阵乘法的结合率,((RT)v) = (R(Tv)),换句话说,实际上是先进行移动,然后进行旋转。即:实际变换的顺序与代码中写的顺序是相反的。由于“先移动后旋转”和“先旋转后移动”得到的结果很可能不同,初学的时候需要特别注意这一点。

    OpenGL之所以这样设计,是为了得到更高的效率。但在绘制复杂的三维图形时,如果每次都去考虑如何把变换倒过来,也是很痛苦的事情。这里介绍另一种思路,可以让代码看起来更自然(写出的代码其实完全一样,只是考虑问题时用的方法不同了)。

    让我们想象,坐标并不是固定不变的。旋转的时候,坐标系统随着物体旋转。移动的时候,坐标系统随着物体移动。如此一来,就不需要考虑代码的顺序反转的问题了。

    以上都是针对改变物体的位置和方向来介绍的。如果要改变观察点的位置,除了配合使用glRotate*和glTranslate*函数以外,还可以使用这个函数:gluLookAt。它的参数比较多,前三个参数表示了观察点的位置,中间三个参数表示了观察目标的位置,最后三个参数代表从(0,0,0)到(x,y,z)的直线,它表示了观察者认为的“上”方向。

    2、投影变换

    投影变换就是定义一个可视空间,可视空间以外的物体不会被绘制到屏幕上。(注意,从现在起,坐标可以不再是-1.0到1.0了!)

    OpenGL支持两种类型的投影变换,即透视投影和正投影。投影也是使用矩阵来实现的。如果需要操作投影矩阵,需要以GL_PROJECTION为参数调用glMatrixMode函数。

    glMatrixMode(GL_PROJECTION);

    通常,我们需要在进行变换前把当前矩阵设置为单位矩阵。

    glLoadIdentity();

     透视投影所产生的结果类似于照片,有近大远小的效果,比如在火车头内向前照一个铁轨的照片,两条铁轨似乎在远处相交了。

    使用glFrustum函数可以将当前的可视空间设置为透视投影空间。其参数的意义如下图:


声明:该图片来自www.opengl.org,该图片是《OpenGL编程指南》一书的附图,由于该书的旧版(第一版,1994年)已经流传于网络,我希望没有触及到版权问题。

    也可以使用更常用的gluPerspective函数。其参数的意义如下图:


声明:该图片来自www.opengl.org,该图片是《OpenGL编程指南》一书的附图,由于该书的旧版(第一版,1994年)已经流传于网络,我希望没有触及到版权问题。

    正投影相当于在无限远处观察得到的结果,它只是一种理想状态。但对于计算机来说,使用正投影有可能获得更好的运行速度。


    使用glOrtho函数可以将当前的可视空间设置为正投影空间。其参数的意义如下图:



声明:该图片来自www.opengl.org,该图片是《OpenGL编程指南》一书的附图,由于该书的旧版(第一版,1994年)已经流传于网络,我希望没有触及到版权问题。

    如果绘制的图形空间本身就是二维的,可以使用gluOrtho2D。他的使用类似于glOrgho。

3、视口变换

    当一切工作已经就绪,只需要把像素绘制到屏幕上了。这时候还剩最后一个问题:应该把像素绘制到窗口的哪个区域呢?通常情况下,默认是完整的填充整个窗口,但我们完全可以只填充一半。(即:把整个图象填充到一半的窗口内)



声明:该图片来自www.opengl.org,该图片是《OpenGL编程指南》一书的附图,由于该书的旧版(第一版,1994年)已经流传于网络,我希望没有触及到版权问题。

    使用glViewport来定义视口。其中前两个参数定义了视口的左下脚(0,0表示最左下方),后两个参数分别是宽度和高度。

    4、操作矩阵堆栈


    介于是入门教程,先简单介绍一下堆栈。你可以把堆栈想象成一叠盘子。开始的时候一个盘子也没有,你可以一个一个往上放,也可以一个一个取下来。每次取下的,都是最后一次被放上去的盘子。通常,在计算机实现堆栈时,堆栈的容量是有限的,如果盘子过多,就会出错。当然,如果没有盘子了,再要求取一个盘子,也会出错。

    我们在进行矩阵操作时,有可能需要先保存某个矩阵,过一段时间再恢复它。当我们需要保存时,调用glPushMatrix函数,它相当于把矩阵(相当于盘子)放到堆栈上。当需要恢复最近一次的保存时,调用glPopMatrix函数,它相当于把矩阵从堆栈上取下。OpenGL规定堆栈的容量至少可以容纳32个矩阵,某些OpenGL实现中,堆栈的容量实际上超过了32个。因此不必过于担心矩阵的容量问题。

     通常,用这种先保存后恢复的措施,比先变换再逆变换要更方便,更快速。

    注意:模型视图矩阵和投影矩阵都有相应的堆栈。使用glMatrixMode来指定当前操作的究竟是模型视图矩阵还是投影矩阵。



此部分转载自:http://www.c3dn.net/forum.php?mod=viewthread&tid=34

2.举例
  1. procedure TForm1.OpenGLPanel1Paint(Sender: TObject);
  2. begin

  3.   //===============================================================================================
  4.   glClearColor(0.0, 0.0, 0.0, 0.0);
  5.   glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);   // 清空颜色缓冲区和深度缓冲区
  6.   glEnable(GL_DEPTH_TEST);                                                     //开启深度测试,保存物体的前后关系,前端图形会遮挡后端图形。
  7.   glMatrixMode(GL_PROJECTION);                                             //调节投影矩阵   
  8.   glLoadIdentity();                                                                      // 投影矩阵设置为单位矩阵
  9.   gluPerspective(45.0, double(width) / height, 0.1, 100.0);        //设置视椎体参数
  10.   glMatrixMode(GL_MODELVIEW);                                            //调节模型矩阵
  11.   glLoadIdentity();                                                                    //  模型矩阵设置为单位矩阵(即原点位于显示区正中央的情况)
  12.   //=========================================================
  13.   glTranslatef(-1.5,0.0,-6.0);                                                    //原点平移至(-1.5,0.0,-6.0)
  14. glRotatef(rtri,0.0,1.0,0.0);                                                 // 以向量【0,1,1】为旋转轴,旋转rtri度                                             
  15.   glBegin(GL_TRIANGLES);                              
  16.     glColor3f(1.0,0.0,0.0);                           
  17.     glVertex3f( 0.0, 1.0, 0.0);                       
  18.     glColor3f(0.0,1.0,0.0);                           
  19.     glVertex3f(-1.0,-1.0, 0.0);                        
  20.     glColor3f(0.0,0.0,1.0);                           
  21.     glVertex3f( 1.0,-1.0, 0.0);                        
  22.   glEnd();                                             
  23.   glLoadIdentity();                                    
  24.   glTranslatef(1.5,0.0,-6.0);                        
  25.   glRotatef(rquad,1.0,0.0,0.0);                        
  26.   glColor3f(0.5,0.5,1.0);                              
  27.   glBegin(GL_QUADS);                                   
  28.     glVertex3f(-1.0, 1.0, 0.0);                        
  29.     glVertex3f( 1.0, 1.0, 0.0);                        
  30.     glVertex3f( 1.0,-1.0, 0.0);                        
  31.     glVertex3f(-1.0,-1.0, 0.0);                        
  32.   glEnd();                                             
  33.   rtri := rtri + 0.9;                                 
  34.   rquad := rquad - 0.3;
  35.   //==========================================================================================
  36.   OpenGLPanel1.SwapBuffers;
  37. end;
  38. procedure TForm1.OnAppIdle(Sender: TObject; var Done: Boolean);
  39. begin
  40.   Done:=false;
  41.   OpenGLPanel1.Invalidate;
  42. end;
复制代码
3.Tips
    双缓冲技术

    在计算机上的动画与实际的动画有些不同:实际的动画都是先画好了,播放的时候直接拿出来显示就行。计算机动画则是画一张,就拿出来一张,再画下一张,再拿出来。如果所需要绘制的图形很简单,那么这样也没什么问题。但一旦图形比较复杂,绘制需要的时间较长,问题就会变得突出。


    让我们把计算机想象成一个画图比较快的人,假如他直接在屏幕上画图,而图形比较复杂,则有可能在他只画了某幅图的一半的时候就被观众看到。而后面虽然他把画补全了,但观众的眼睛却又没有反应过来,还停留在原来那个残缺的画面上。也就是说,有时候观众看到完整的图象,有时却又只看到残缺的图象,这样就造成了屏幕的闪烁。

    如何解决这一问题呢?我们设想有两块画板,画图的人在旁边画,画好以后把他手里的画板与挂在屏幕上的画板相交换。这样以来,观众就不会看到残缺的画了。这一技术被应用到计算机图形中,称为双缓冲技术。即:在存储器(很有可能是显存)中开辟两块区域,一块作为发送到显示器的数据,一块作为绘画的区域,在适当的时候交换它们。由于交换两块内存区域实际上只需要交换两个指针,这一方法效率非常高,所以被广泛的采用。

    注意:虽然绝大多数平台都支持双缓冲技术,但这一技术并不是OpenGL标准中的内容。OpenGL为了保证更好的可移植性,允许在实现时不使用双缓冲技术。当然,我们常用的PC都是支持双缓冲技术的。

    要启动双缓冲功能,最简单的办法就是使用GLUT工具包。我们以前在main函数里面写:

    glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE);


    其中GLUT_SINGLE表示单缓冲,如果改成GLUT_DOUBLE就是双缓冲了。


TOpenGLPanel默认开启双缓冲

    IDLE
    OnPaint意思是对系统说:如果你需要绘制窗口了,请调用这个函数。为什么我们不直接调用,而要采用这种看似“舍近求远”的做法呢?原因在于——我们自己的程序无法掌握究竟什么时候该绘制窗口。因为一般的窗口系统——拿我们熟悉一点的来说——Windows和X窗口系统,都是支持同时显示多个窗口的。假如你的程序窗口碰巧被别的窗口遮住了,后来用户又把原来遮住的窗口移开,这时你的窗口需要重新绘制。很不幸的,你无法知道这一事件发生的具体时间。因此这一切只好委托操作系统来办了。

   为了产生“动”画,基本思路是:绘制,然后等待一段时间;再绘制,再等待一段时间。但如果去掉等待的时间,就变成了绘制,绘制,……,不停的绘制。——当然了,资源是公用的嘛,杀毒软件总要工作吧?我的下载不能停下来吧?我的mp3播放还不能给耽搁了。总不能因为我们的动画,让其他的工作都停下来。因此,我们需要在CPU空闲的时间绘制。
  1. Application.AddOnIdleHandler(@OnAppIdle);
复制代码
4.源码



评分

参与人数 1金钱 +10 收起 理由
bugxiong + 10 很给力!

查看全部评分

回复

使用道具 举报

QQ|手机版|小黑屋|Lazarus中国|Lazarus中文社区 ( 鄂ICP备16006501号-1

GMT+8, 2020-11-29 21:13 , Processed in 0.054571 second(s), 30 queries .

Powered by Discuz! F1.0 Build 20160930

© 2001-2020 Comsenz Inc. & Discuz! Fans

快速回复 返回顶部 返回列表