PyOpenGl的基本使用- 码迷-wjz - 博客园
文章推薦指數: 80 %
Python 旗下,影响较大的三维库有pyOpenGl / VT. ... 我第一次接触OpenGL 的GL / GLU / GLUT 的时候,一下就被这些长得像孪生兄弟的库名字给整懵圈 ...
首页
新闻
博问
专区
闪存
班级
我的博客
我的园子
账号设置
简洁模式...
退出登录
注册
登录
码迷-wjz
PyOpenGl的基本使用
在三维显示领域,OpenGL是神一样的存在,其地位就像编程语言里面的C一样。
基于OpenGL衍生出来的分支、派系,林林总总。
Python旗下,影响较大的三维库有pyOpenGl/VTK/Mayavi/Vispy等,它们各自拥有庞大的用户体。
VTK在医学领域应用广泛,Vispy在科研领域粉丝众多。
VTK和Vispy都是基于OpenGL的扩展,Mayavi则是基于VTK的,因此很多的医学影像应用都是采用Python+VTK+ITK+Mayavi的组合(ITK是图像处理库,类似于OpenCV或PIL)。
上述三维渲染库,包括PyOpenGl,都有一个共同的特点,那就是只专注于三维功能的实现,而疏于对UI的支持。
比Vispy,虽然支持以wx或者Qt作为后端,但绑定后端以后,在窗口管理、交互操作等方面还是存在不少问题。
PyOpenGl做得更简单,提供一个GLUT库就算是对UI的支持了。
事实上,在复杂的三维展示系统中,UI的重要性并不亚于OpenGL。
如果能为OpenGL找到一位UI搭档,必将提高程序的可靠性和可操作性,增强用户感受。
wxPython和PyOpenGL就是这样的一对黄金搭档。
pyOpenGL的入门教程有很多,我也有一篇博文《写给Python程序员的OpenGL教程》。
特别提醒一下,这篇博文最后提到顶点缓冲区对象VBO,并有演示代码。
VBO的概念很重要很重要很重要,只有学会使用VBO,才能真正进入OpenGL的精彩世界。
早期的OpenGL使用立即渲染模式(Immediatemode,也就是固定渲染管线),概念清晰易于理解,绘制图形也很方便,但效率太低。
从OpenGL3.2开始,规范文档开始废弃立即渲染模式,并鼓励开发者在OpenGL的核心模式(Core-profile)下进行开发,这个分支的规范完全移除了旧的特性。
VBO是OpenGL核心模式的基础。
VBO将顶点数据集存储在GPU中,这意味着渲染VBO数据会很快。
不过,数据从RAM传送到GPU是有代价的。
VBO虽然在GPU上,但并没有使用GPU的运算功能。
在VBO之上,还有VAO的概念,即VertexArrayObject,顶点数组对象。
这个概念很复杂,我们可以简单把VAO理解为VBO管理者。
由于VAO依赖于显卡,通用性较差,我选择绕过它。
官网:https://www.opengl.org/resources/libraries/glut/
预备知识
OpenGL是OpenGraphicsLibrary的简写,意为“开放式图形库”,是用于渲染2D、3D矢量图形的跨语言、跨平台的应用程序编程接口(API)。
OpenGL不是一个独立的平台,因此,它需要借助于一种编程语言才能被使用。
C/C++/python/java都可以很好支持OpengGL,我当然习惯性选择python语言。
如果读者是python程序员,并且了解numpy,接下来的阅读应该不会有任何障碍;否则,我建议先花半小时学习一下python语言。
关于numpy,可以参考我的另一篇博文《数学建模三剑客MSN》。
事实上,我觉得python语言近乎于自然语言,只要读者是程序员,即便不熟悉python,读起来也不会有多大问题。
另外,读者也不必担心数学问题。
使用OpenGL不需要具备多么高深的数学水平,只要能辅导初中学生的数学作业,就足够用了。
坐标系
在OpenGL的世界里,有各式各样的坐标系。
随着对OpenGL概念的理解,我们至少会接触到六种坐标系,而初始只需要了解其中的三个就足够用了(第一次阅读这段话的时候,只需要了解世界坐标系就可以了)。
世界坐标系(WorldCoordinates)
世界坐标系是右手坐标系,以屏幕中心为原点(0,0,0),且是始终不变的。
视点坐标系(EyeorCameraCoordinates)
视点坐标是以视点为原点,以视线的方向为Z+轴正方向的坐标系。
OpenGL管道会将世界坐标先变换到视点坐标,然后进行裁剪,只有在视线范围(视景体)之内的场景才会进入下一阶段的计算。
屏幕坐标系(WindoworScreenCoordinates)
OpenGL的重要功能之一就是将三维的世界坐标经过变换、投影等计算,最终算出它在显示设备上对应的位置,这个位置就称为设备坐标。
在屏幕、打印机等设备上的坐标是二维坐标。
值得一提的是,OpenGL可以只使用设备的一部分进行绘制,这个部分称为视区或视口(viewport)。
投影得到的是视区内的坐标(投影坐标),从投影坐标到设备坐标的计算过程就是设备变换了。
投影
三维场景中的物体最终都会显示在类似屏幕这样的二维观察平面上。
将三维物体变为二维图形的变换成为投影变换。
最常用的投影有两种:平行投影和透视投影。
如下图所示,F是投影面,p1p2为三维空间中的一条直线,p’1和p’2分别是p1和p2在F上的投影,虚线表示投影线,O为投影中心。
平行投影
这里所说的平行投影,特指正交平行投影——投影线垂直于投影面。
将一个三维点(x,y,z)正交平行投影到xoy平面上,则投影点坐标为(x,y,0)。
由于平行投影丢弃了深度信息,所以无法产生真实感,但可以保持物体之间相对大小关系不变。
透视投影
透视投影将投影面置于观察点和投影对象之间,距离观察者越远的物体,投影尺寸越小,投影效果具有真实感,常用于游戏和仿真领域。
视景体
无论是平行投影还是透视投影,投影成像都是在投影面上——我们可以把投影面理解成显示屏幕。
世界坐标系描述的三维空间是无限的,投影平面是无限的,但(我们能够看到的)屏幕面积总是有限的,因此在投影变换时,通常只处理能够显示在屏幕上的那一部分三维空间。
从无限三维空间中裁切出来的可以显示在屏幕上的部分三维空间,我们称之为视景体。
视景体有六个面,分别是左右上下和前后面。
对于平行投影而言,视景体是一个矩形平行六面体;对于透视投影来说,视景体是一个棱台。
理解这一点并不难:因为越远处的物体在投影窗口的透视投影越小,也就意味着填满投影窗口需要更大的体量,视景体自然就变成了棱台。
视口
对于平行投影而言,视口就是由视景体的左右上下四个面围成的矩形,对于透视投影来说,视口就是视景体的前截面在投影窗口上的透视投影。
视口是OpenGL中比较重要的概念,现阶段可以简单理解成屏幕(或其他输出设备)。
事实上,视口和屏幕是相关但又不相同的,屏幕有固定的宽高比,而视口大小可以由用户自行定义。
通常,为了适应不同宽高比的屏幕,在设置视口时,会根据屏幕宽高比调整视景体(增加宽度或高度)。
视点
现实生活中,人们看到的三维空间物体的样子取决于观察者站在什么角度去看。
这里面包含着三个概念:
观察者的位置:眼睛在哪儿?
观察者的姿势:站立还是倒立?左侧卧还是右侧卧?
观察对象:眼睛盯着哪里?
对应在OpenGL中,也有同样的概念,即视点的位置、瞄准方向的参考点,以及(向上的)方向。
OpenGL变换
下图是三维图形的显示流程。
世界坐标系中的三维物体经过视点变换和一系列几何变换(平移、旋转、缩放)之后,坐标系变换为视点坐标系;经过投影和裁剪之后,坐标系变换为归一化设备坐标系;最后经过视口变换显示在屏幕上,相应地,坐标系变成了窗口坐标系。
视点变换:相当于设置视点的位置和方向
模型变换:包括平移、旋转、缩放等三种类型
裁剪变换:根据视景体定义的六个面(和附加裁剪面)对三维空间裁剪
视口变换:将视景体内投影的物体显示在二维的视口平面上
安装pyopengl
下载编译好的PyOpenGL,也就是一个whl文件(这里不建议直接用命令“pipinstallPyOpenGL”进行安装,不然会出现“OpenGL.error.NullFunctionError”错误),地址:https://www.lfd.uci.edu/~gohlke/pythonlibs/#pyopengl,如下,选择适合自己的版本:
下载完这两个文件后:
pipinstallwhl文件
注意:如果安装失败,可能是系统里没有c++环境导致的,则需要先安装C++,提供C环境。
OpenGL库及函数简介
我第一次接触OpenGL的GL/GLU/GLUT的时候,一下就被这些长得像孪生兄弟的库名字给整懵圈了,要不是内心强大,也许就跟OpenGL说再见了。
时间久了才发现,OpenGL的库及函数命名规则非常合理,便于查找、记忆。
OpenGL函数的命名格式如下:
常见的库前缀有gl、glu、glut、aux、wgl、glx、agl等。
库前缀表示该函数属于OpenGL哪一个开发库。
从函数名后面中还可以看出需要多少个参数以及参数的类型。
I代表int型,f代表float型,d代表double型,u代表无符号整型。
例如glColor3f()表示了该函数属于gl库,参数是三个浮点数。
OpenGL函数库相关的API有核心库(gl)、实用库(glu)、实用工具库(glut)、辅助库(aux)、窗口库(glx、agl、wgl)和扩展函数库等。
gl是核心,glu是对gl的部分封装。
glut是为跨平台的OpenGL程序的工具包,比aux功能强大。
glx、agl、wgl是针对不同窗口系统的函数。
扩展函数库是硬件厂商为实现硬件更新利用OpenGL的扩展机制开发的函数。
本文仅对常用的四个库做简单介绍。
OpenGL核心库GL
核心库包含有115个函数,函数名的前缀为gl。
这部分函数用于常规的、核心的图形处理。
此函数由gl.dll来负责解释执行。
由于许多函数可以接收不同数以下几类。
据类型的参数,因此派生出来的函数原形多达300多个。
核心库中的函数主要可以分为以下几类函数:
绘制基本几何图元的函数:
glBegain()、glEnd()、glNormal*()、glVertex*()
矩阵操作、几何变换和投影变换的函数:
如矩阵入栈函数glPushMatrix(),矩阵出栈函数glPopMatrix(),装载矩阵函数glLoadMatrix(),矩阵相乘函数glMultMatrix(),当前矩阵函数glMatrixMode()和矩阵标准化函数glLoadIdentity(),几何变换函数glTranslate*()、glRotate*()和glScale*(),投影变换函数glOrtho()、glFrustum()和视口变换函数glViewport()
颜色、光照和材质的函数:
如设置颜色模式函数glColor*()、glIndex*(),设置光照效果的函数glLight*()、glLightModel*()和设置材质效果函数glMaterial()
显示列表函数:
主要有创建、结束、生成、删除和调用显示列表的函数glNewList()、glEndList()、glGenLists()、glCallList()和glDeleteLists()
纹理映射函数:
主要有一维纹理函数glTexImage1D()、二维纹理函数glTexImage2D()、设置纹理参数、纹理环境和纹理坐标的函数glTexParameter*()、glTexEnv*()和glTetCoord*()
特殊效果函数:
融合函数glBlendFunc()、反走样函数glHint()和雾化效果glFog*()
光栅化、象素操作函数:
如象素位置glRasterPos*()、线型宽度glLineWidth()、多边形绘制模式glPolygonMode(),读取象素glReadPixel()、复制象素glCopyPixel()
选择与反馈函数:
主要有渲染模式glRenderMode()、选择缓冲区glSelectBuffer()和反馈缓冲区glFeedbackBuffer()
曲线与曲面的绘制函数:
生成曲线或曲面的函数glMap*()、glMapGrid*(),求值器的函数glEvalCoord*()glEvalMesh*()
状态设置与查询函数:
glGet*()、glEnable()、glGetError()
OpenGL实用库GLU
包含有43个函数,函数名的前缀为glu。
OpenGL提供了强大的但是为数不多的绘图命令,所有较复杂的绘图都必须从点、线、面开始。
Glu为了减轻繁重的编程工作,封装了OpenGL函数,Glu函数通过调用核心库的函数,为开发者提供相对简单的用法,实现一些较为复杂的操作。
此函数由glu.dll来负责解释执行。
OpenGL中的核心库和实用库可以在所有的OpenGL平台上运行。
主要包括了以下几种:
辅助纹理贴图函数:
gluScaleImage()、gluBuild1Dmipmaps()、gluBuild2Dmipmaps()
坐标转换和投影变换函数:
定义投影方式函数gluPerspective()、gluOrtho2D()、gluLookAt(),拾取投影视景体函数gluPickMatrix(),投影矩阵计算gluProject()和gluUnProject()
多边形镶嵌工具:
gluNewTess()、gluDeleteTess()、gluTessCallback()、gluBeginPolygon()、gluTessVertex()、gluNextContour()、gluEndPolygon()
二次曲面绘制工具:
主要有绘制球面、锥面、柱面、圆环面gluNewQuadric()、gluSphere()、gluCylinder()、gluDisk()、gluPartialDisk()、gluDeleteQuadric()
非均匀有理B样条绘制工具:
主要用来定义和绘制Nurbs曲线和曲面,包括gluNewNurbsRenderer()、gluNurbsCurve()、gluBeginSurface()、gluEndSurface()、gluBeginCurve()、gluNurbsProperty()
错误反馈工具:
获取出错信息的字符串gluErrorString()
OpenGL工具库GLUT
包含大约30多个函数,函数名前缀为glut。
glut是不依赖于窗口平台的OpenGL工具包,由MarkKLilgrad在SGI编写(现在在Nvidia),目的是隐藏不同窗口平台API的复杂度。
函数以glut开头,它们作为aux库功能更强的替代品,提供更为复杂的绘制功能,此函数由glut.dll来负责解释执行。
由于glut中的窗口管理函数是不依赖于运行环境的,因此OpenGL中的工具库可以在X-Window,WindowsNT,OS/2等系统下运行,特别适合于开发不需要复杂界面的OpenGL示例程序。
对于有经验的程序员来说,一般先用glut理顺3D图形代码,然后再集成为完整的应用程序。
这部分函数主要包括:
窗口操作函数:
窗口初始化、窗口大小、窗口位置函数等glutInit()、glutInitDisplayMode()、glutInitWindowSize()、glutInitWindowPosition()
回调函数:
响应刷新消息、键盘消息、鼠标消息、定时器函数GlutDisplayFunc()、glutPostRedisplay()、glutReshapeFunc()、glutTimerFunc()、glutKeyboardFunc()、glutMouseFunc()
创建复杂的三维物体:
这些和aux库的函数功能相同
菜单函数:
创建添加菜单的函数GlutCreateMenu()、glutSetMenu()、glutAddMenuEntry()、glutAddSubMenu()和glutAttachMenu()
程序运行函数:
glutMainLoop()
Windows专用库WGL
针对windows平台的扩展,包含有16个函数,函数名前缀为wgl。
这部分函数主要用于连接OpenGL和Windows,以弥补OpenGL在文本方面的不足。
Windows专用库只能用于Windows环境中。
这类函数主要包括以下几类:
绘图上下文相关函数:
wglCreateContext()、wglDeleteContext()、wglGetCurrentContent()、wglGetCurrentDC()、wglDeleteContent()
文字和文本处理函数:
wglUseFontBitmaps()、wglUseFontOutlines()
覆盖层、地层和主平面层处理函数:
wglCopyContext()、wglCreateLayerPlane()、wglDescribeLayerPlane()、wglReakizeLayerPlatte()
其他函数:
wglShareLists()、wglGetProcAddress()
OpenGL基本图形的绘制
设置颜色
设置颜色的函数有几十个,都是以glColor开头,后面跟着参数个数和参数类型。
参数可以是0到255之间的无符号整数,也可以是0到1之间的浮点数。
三个参数分别表示RGB分量,第四个参数表示透明度(其实叫不透明度更恰当)。
以下最常用的两个设置颜色的方法:
glColor3f(1.0,0.0,0.0)#设置当前颜色为红色
glColor4f(0.0,1.0,1.0,1.0)#设置当前颜色为青色,不透明度
glColor3ub(0,0,255)#设置当前颜色为蓝色
glColor也支持将三个或四个参数以向量方式传递,例如:
glColor3fv([0.0,1.0,0.0])#设置当前颜色为绿色
特别提示:OpenGL是使用状态机模式,颜色是一个状态变量,设置颜色就是改变这个状态变量并一直生效,直到再次调用设置颜色的函数。
除了颜色,OpenGL还有很多的状态变量或模式。
在任何时间,都可以查询每个状态变量的当前值,还可以用glPushAttrib()或glPushClientAttrib()把状态变量的集合保存起来,必要的时候,再用glPopAttrib()或glPopClientAttrib()恢复状态变量。
设置顶点
顶点(vertex)是OpengGL中非常重要的概念,描述线段、多边形都离不开顶点。
和设置颜色类似,设置顶点的函数也有几十个,都是以glVertex开头,后面跟着参数个数和参数类型,同样也支持将多个以向量方式传递。
两个参数的话,分别表示xy坐标,三个参数则分别表示xyz坐标。
如有第四个参数,则表示该点的齐次坐标w;否则,默认w=1。
至于什么是齐次坐标,显然超出了初中数学的范畴,在此不做探讨。
glVertex2f(1.0,0.5)#xoy平面上的点,z=0
glVertex3f(0.5,1.0,0.0)#三维空间中的点
绘制基本图形
仅仅设置颜色和顶点,并不能画出来什么。
我们可以在任何时候改变颜色,但所有的顶点设置,都必须包含在glBegin()和glEnd()之间,而glBegin()的参数则指定了将这些顶点画成什么。
以下是glBegin()可能的参数选项:
参数说明:
GL_POINTS 绘制一个或多个顶点
GL_LINES 绘制线段
GL_LINE_STRIP 绘制连续线段
GL_LINE_LOOP 绘制闭合的线段
GL_POLYGON 绘制多边形
GL_TRIANGLES 绘制一个或多个三角形
GL_TRIANGLE_STRIP 绘制连续三角形
GL_TRIANGLE_FAN 绘制多个三角形组成的扇形
GL_QUADS 绘制一个或多个四边形
GL_QUAD_STRIP 绘制连续四边形
第一个OpenGL程序
通常,我们使用工具库(GLUT)创建OpenGL应用程序。
为啥不用GL或者GLU库呢?画画之前总得先有一块画布吧,不能直接拿起画笔就开画。
前文说过,工具库主要提供窗口相关的函数,有了窗口,就相当于有了画布,而核心库和实用库,就好比各式各样的画笔、颜料。
使用工具库(GLUT)创建OpenGL应用程序只需要四步(当然,前提是你需要先准备好绘图函数,并给它取一个合适的名字):
初始化glut库
创建glut窗口
注册绘图的回调函数
进入glut主循环
OK,铺垫了这么多之后,我们终于开始第一个OpenGL应用程序了:绘制三维空间的世界坐标系,在坐标原点的后方(z轴的负半区)画一个三角形。
代码如下:
#-*-coding:utf-8-*-
#-------------------------------------------
#quidam_01.py三维空间的世界坐标系和三角形
#-------------------------------------------
fromOpenGL.GLimport*
fromOpenGL.GLUTimport*
defdraw():
#---------------------------------------------------------------
glBegin(GL_LINES)#开始绘制线段(世界坐标系)
#以红色绘制x轴
glColor4f(1.0,0.0,0.0,1.0)#设置当前颜色为红色不透明
glVertex3f(-0.8,0.0,0.0)#设置x轴顶点(x轴负方向)
glVertex3f(0.8,0.0,0.0)#设置x轴顶点(x轴正方向)
#以绿色绘制y轴
glColor4f(0.0,1.0,0.0,1.0)#设置当前颜色为绿色不透明
glVertex3f(0.0,-0.8,0.0)#设置y轴顶点(y轴负方向)
glVertex3f(0.0,0.8,0.0)#设置y轴顶点(y轴正方向)
#以蓝色绘制z轴
glColor4f(0.0,0.0,1.0,1.0)#设置当前颜色为蓝色不透明
glVertex3f(0.0,0.0,-0.8)#设置z轴顶点(z轴负方向)
glVertex3f(0.0,0.0,0.8)#设置z轴顶点(z轴正方向)
glEnd()#结束绘制线段
#---------------------------------------------------------------
glBegin(GL_TRIANGLES)#开始绘制三角形(z轴负半区)
glColor4f(1.0,0.0,0.0,1.0)#设置当前颜色为红色不透明
glVertex3f(-0.5,-0.366,-0.5)#设置三角形顶点
glColor4f(0.0,1.0,0.0,1.0)#设置当前颜色为绿色不透明
glVertex3f(0.5,-0.366,-0.5)#设置三角形顶点
glColor4f(0.0,0.0,1.0,1.0)#设置当前颜色为蓝色不透明
glVertex3f(0.0,0.5,-0.5)#设置三角形顶点
glEnd()#结束绘制三角形
#---------------------------------------------------------------
glFlush()#清空缓冲区,将指令送往硬件立即执行
if__name__=="__main__":
glutInit()#1.初始化glut库
glutCreateWindow('QuidamOfOpenGL')#2.创建glut窗口
glutDisplayFunc(draw)#3.注册回调函数draw()
glutMainLoop()#4.进入glut主循环
fromOpenGL.GLimport*
fromOpenGL.GLUimport*
fromOpenGL.GLUTimport*
defDraw():
glClear(GL_COLOR_BUFFER_BIT)
glRotatef(0.5,0,1,0)
glutWireTeapot(0.5)
glFlush()
glutInit()
glutInitDisplayMode(GLUT_SINGLE|GLUT_RGBA)
glutInitWindowSize(400,400)
glutCreateWindow("test")
glutDisplayFunc(Draw)
glutIdleFunc(Draw)
glutMainLoop()
ViewCode
运行代码,我这里显示结果如下面左图所示。
如果尝试运行这段代码出错的话,我猜应该是pyopengl安装出现了问题,首先登录https://www.opengl.org/resources/libraries/glut/,下载下图箭头所指的文件:
解压缩,如下图所示:
将:glut.dll和glut32.dll放在C:\WINDOWS中的SysWOW64(win10 64位操作系统),然后重新运行代码。
短暂的激动之后,你可能会尝试画一些其他的线段,变换颜色或者透明度,甚至绘制多边形。
很快你会发现,我们的第一个程序有很多问题,比如:
窗口的标题不能使用中文,否则会显示乱码
窗口的初始大小和位置无法改变
改变窗口的宽高比,三角形宽高比也会改变(如上面右图所示)
三角形不应该遮挡坐标轴
改变颜色的透明度无效
不能缩放旋转
没关系,除了第1个问题我不知道怎么解决(貌似无解),其他问题都不是事儿。
和我们的代码相比,一个真正实用的OpenGL程序,还有许多工作要做:
设置初始显示模式
初始化画布
绘图函数里面需要增加:
清除屏幕及深度缓存
投影设置
模型试图设置
绑定鼠标键盘的事件函数
设置初始显示模式
初始化glut库的时候,我们一般都要用glutInitDisplayMode()来设置初始的显示模式,它的参数可以是下表中参数的组合。
参数说明:
GLUT_RGB 指定RGB颜色模式的窗口
GLUT_RGBA 指定RGBA颜色模式的窗口
GLUT_INDEX 指定颜色索引模式的窗口
GLUT_SINGLE 指定单缓存窗口
GLUT_DOUBLE 指定双缓存窗口
GLUT_ACCUM 窗口使用累加缓存
GLUT_ALPHA 窗口的颜色分量包含alpha值
GLUT_DEPTH 窗口使用深度缓存
GLUT_STENCIL 窗口使用模板缓存
GLUT_MULTISAMPLE 指定支持多样本功能的窗口
GLUT_STEREO 指定立体窗口
GLUT_LUMINANCE 窗口使用亮度颜色模型
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
使用双缓存窗口,可以避免重绘时产生抖动的感觉。
我一般选择GLUT_DOUBLE|GLUT_ALPHA|GLUT_DEPTH作为参数来设置初始的显示模式。
初始化画布
开始绘图之前,需要对画布做一些初始化工作,这些工作只需要做一次。
比如:
glClearColor(0.0,0.0,0.0,1.0)#设置画布背景色。
注意:这里必须是4个参数
glEnable(GL_DEPTH_TEST)#开启深度测试,实现遮挡关系
glDepthFunc(GL_LEQUAL)#设置深度测试函数(GL_LEQUAL只是选项之一)
如有必要,还可以开启失真校正(反走样)、开启表面剔除等。
清除屏幕及深度缓存
每次重绘之前,需要先清除屏幕及深度缓存。
这项操作一般放在绘图函数的开头。
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
设置投影
投影设置也是每次重绘都需要的步骤之一。
glOrtho()用来设置平行投影,glFrustum()用来设置透视投影。
这两个函数的参数相同,都是视景体的left/right/bottom/top/near/far六个面。
视景体的left/right/bottom/top四个面围成的矩形,就是视口。
near就是投影面,其值是投影面距离视点的距离,far是视景体的后截面,其值是后截面距离视点的距离。
far和near的差值,就是视景体的深度。
视点和视景体的相对位置关系是固定的,视点移动时,视景体也随之移动。
我个人认为,视景体是OpengGL最重要、最核心的概念,它和视口、视点、投影面、缩放、漫游等概念密切关联。
只有正确理解了视景体,才能正确设置它的六个参数,才能呈现出我们期望的效果。
为了在窗口宽高比改变时,绘制的对象仍然保持固定的宽高比,一般在做投影变换时,需要根据窗口的宽高比适当调整视景体的left/right或者bottom/top参数。
假设view是视景体,width和height是窗口的宽度和高度,在投影变换之前,需要先声明是对投影矩阵的操作,并将投影矩阵单位化:
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
ifwidth>height:
k=width/height
glFrustum(view[0]*k,view[1]*k,view[2],view[3],view[4],view[5])
else:
k=height/width
glFrustum(view[0],view[1],view[2]*k,view[3]*k,view[4],view[5])
设置视点
视点是和视景体关联的概念。
设置视点需要考虑眼睛在哪儿、看哪儿、头顶朝哪儿,分别对应着eye,lookat和eye_up三个向量。
gluLookAt(
eye[0],eye[1],eye[2],
look_at[0],look_at[1],look_at[2],
eye_up[0],eye_up[1],eye_up[2]
)
设置视口
视口也是和视景体关联的概念,相对简单一点。
glViewport(0,0,width,height)
设置模型视图
模型平移、旋转、缩放等几何变换,需要切换到模型矩阵:
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
glScale(1.0,1.0,1.0)
捕捉鼠标事件、键盘事件和窗口事件
GLUT库提供了几个函数帮我们捕捉鼠标事件、键盘事件和窗口事件:
glutMouseFunc()
该函数捕捉鼠标点击和滚轮操作,返回4个参数给被绑定的事件函数:键(左键/右键/中键/滚轮上/滚轮下)、状态(1/0)、x坐标、y坐标
glutMotionFunc()该函数捕捉有一个鼠标键被按下时的鼠标移动给被绑定的事件函数,返回2个参数:x坐标、y坐标
glutPassiveMotionFunc()该函数捕捉鼠标移动,返回2个参数给被绑定的事件函数:x坐标、y坐标
glutEntryFunc()该函数捕捉鼠标离开或进入窗口区域,返回1个参数给被绑定的事件函数:GLUT_LEFT或者GLUT_ENTERED
glutKeyboardFunc(keydown)该函数捕捉键盘按键被按下,返回3个参数给被绑定的事件函数:被按下的键,x坐标、y坐标
glutReshapeFunc()该函数捕捉窗口被改变大小,返回2个参数给被绑定的事件函数:窗口宽度、窗口高度
如果我们需要捕捉这些事件,只需要定义事件函数,注册相应的函数就行:
defreshape(width,height):
pass
defmouseclick(button,state,x,y):
pass
defmousemotion(x,y):
pass
defkeydown(key,x,y):
pass
glutReshapeFunc(reshape)#注册响应窗口改变的函数reshape()
glutMouseFunc(mouseclick)#注册响应鼠标点击的函数mouseclick()
glutMotionFunc(mousemotion)#注册响应鼠标拖拽的函数mousemotion()
glutKeyboardFunc(keydown)#注册键盘输入的函数keydown()
综合应用
是时候把我们上面讲的这些东西完整的演示一下了。
下面的代码还是画了世界坐标系,并在原点前后各画了一个三角形。
鼠标可以拖拽视点绕参考点旋转(二者距离保持不变),滚轮可以缩放模型。
敲击退格键或回车键可以让视点远离或接近参考点。
敲击x/y/z可以减小参考点对应的坐标值,敲击X/Y/Z可以增大参考点对应的坐标值。
敲击空格键可以切换投影模式。
fromOpenGL.GLimport*
fromOpenGL.GLUimport*
fromOpenGL.GLUTimport*
importnumpyasnp
IS_PERSPECTIVE=True#透视投影
VIEW=np.array([-0.8,0.8,-0.8,0.8,1.0,20.0])#视景体的left/right/bottom/top/near/far六个面
SCALE_K=np.array([1.0,1.0,1.0])#模型缩放比例
EYE=np.array([0.0,0.0,2.0])#眼睛的位置(默认z轴的正方向)
LOOK_AT=np.array([0.0,0.0,0.0])#瞄准方向的参考点(默认在坐标原点)
EYE_UP=np.array([0.0,1.0,0.0])#定义对观察者而言的上方(默认y轴的正方向)
WIN_W,WIN_H=640,480#保存窗口宽度和高度的变量
LEFT_IS_DOWNED=False#鼠标左键被按下
MOUSE_X,MOUSE_Y=0,0#考察鼠标位移量时保存的起始位置
defgetposture():
globalEYE,LOOK_AT
dist=np.sqrt(np.power((EYE-LOOK_AT),2).sum())
ifdist>0:
phi=np.arcsin((EYE[1]-LOOK_AT[1])/dist)
theta=np.arcsin((EYE[0]-LOOK_AT[0])/(dist*np.cos(phi)))
else:
phi=0.0
theta=0.0
returndist,phi,theta
DIST,PHI,THETA=getposture()#眼睛与观察目标之间的距离、仰角、方位角
definit():
glClearColor(0.0,0.0,0.0,1.0)#设置画布背景色。
注意:这里必须是4个参数
glEnable(GL_DEPTH_TEST)#开启深度测试,实现遮挡关系
glDepthFunc(GL_LEQUAL)#设置深度测试函数(GL_LEQUAL只是选项之一)
defdraw():
globalIS_PERSPECTIVE,VIEW
globalEYE,LOOK_AT,EYE_UP
globalSCALE_K
globalWIN_W,WIN_H
#清除屏幕及深度缓存
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
#设置投影(透视投影)
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
ifWIN_W>WIN_H:
ifIS_PERSPECTIVE:
glFrustum(VIEW[0]*WIN_W/WIN_H,VIEW[1]*WIN_W/WIN_H,VIEW[2],VIEW[3],VIEW[4],VIEW[5])
else:
glOrtho(VIEW[0]*WIN_W/WIN_H,VIEW[1]*WIN_W/WIN_H,VIEW[2],VIEW[3],VIEW[4],VIEW[5])
else:
ifIS_PERSPECTIVE:
glFrustum(VIEW[0],VIEW[1],VIEW[2]*WIN_H/WIN_W,VIEW[3]*WIN_H/WIN_W,VIEW[4],VIEW[5])
else:
glOrtho(VIEW[0],VIEW[1],VIEW[2]*WIN_H/WIN_W,VIEW[3]*WIN_H/WIN_W,VIEW[4],VIEW[5])
#设置模型视图
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
#几何变换
glScale(SCALE_K[0],SCALE_K[1],SCALE_K[2])
#设置视点
gluLookAt(
EYE[0],EYE[1],EYE[2],
LOOK_AT[0],LOOK_AT[1],LOOK_AT[2],
EYE_UP[0],EYE_UP[1],EYE_UP[2]
)
#设置视口
glViewport(0,0,WIN_W,WIN_H)
#---------------------------------------------------------------
glBegin(GL_LINES)#开始绘制线段(世界坐标系)
#以红色绘制x轴
glColor4f(1.0,0.0,0.0,1.0)#设置当前颜色为红色不透明
glVertex3f(-0.8,0.0,0.0)#设置x轴顶点(x轴负方向)
glVertex3f(0.8,0.0,0.0)#设置x轴顶点(x轴正方向)
#以绿色绘制y轴
glColor4f(0.0,1.0,0.0,1.0)#设置当前颜色为绿色不透明
glVertex3f(0.0,-0.8,0.0)#设置y轴顶点(y轴负方向)
glVertex3f(0.0,0.8,0.0)#设置y轴顶点(y轴正方向)
#以蓝色绘制z轴
glColor4f(0.0,0.0,1.0,1.0)#设置当前颜色为蓝色不透明
glVertex3f(0.0,0.0,-0.8)#设置z轴顶点(z轴负方向)
glVertex3f(0.0,0.0,0.8)#设置z轴顶点(z轴正方向)
glEnd()#结束绘制线段
#---------------------------------------------------------------
glBegin(GL_TRIANGLES)#开始绘制三角形(z轴负半区)
glColor4f(1.0,0.0,0.0,1.0)#设置当前颜色为红色不透明
glVertex3f(-0.5,-0.366,-0.5)#设置三角形顶点
glColor4f(0.0,1.0,0.0,1.0)#设置当前颜色为绿色不透明
glVertex3f(0.5,-0.366,-0.5)#设置三角形顶点
glColor4f(0.0,0.0,1.0,1.0)#设置当前颜色为蓝色不透明
glVertex3f(0.0,0.5,-0.5)#设置三角形顶点
glEnd()#结束绘制三角形
#---------------------------------------------------------------
glBegin(GL_TRIANGLES)#开始绘制三角形(z轴正半区)
glColor4f(1.0,0.0,0.0,1.0)#设置当前颜色为红色不透明
glVertex3f(-0.5,0.5,0.5)#设置三角形顶点
glColor4f(0.0,1.0,0.0,1.0)#设置当前颜色为绿色不透明
glVertex3f(0.5,0.5,0.5)#设置三角形顶点
glColor4f(0.0,0.0,1.0,1.0)#设置当前颜色为蓝色不透明
glVertex3f(0.0,-0.366,0.5)#设置三角形顶点
glEnd()#结束绘制三角形
#---------------------------------------------------------------
glutSwapBuffers()#切换缓冲区,以显示绘制内容
defreshape(width,height):
globalWIN_W,WIN_H
WIN_W,WIN_H=width,height
glutPostRedisplay()
defmouseclick(button,state,x,y):
globalSCALE_K
globalLEFT_IS_DOWNED
globalMOUSE_X,MOUSE_Y
MOUSE_X,MOUSE_Y=x,y
ifbutton==GLUT_LEFT_BUTTON:
LEFT_IS_DOWNED=state==GLUT_DOWN
elifbutton==3:
SCALE_K*=1.05
glutPostRedisplay()
elifbutton==4:
SCALE_K*=0.95
glutPostRedisplay()
defmousemotion(x,y):
globalLEFT_IS_DOWNED
globalEYE,EYE_UP
globalMOUSE_X,MOUSE_Y
globalDIST,PHI,THETA
globalWIN_W,WIN_H
ifLEFT_IS_DOWNED:
dx=MOUSE_X-x
dy=y-MOUSE_Y
MOUSE_X,MOUSE_Y=x,y
PHI+=2*np.pi*dy/WIN_H
PHI%=2*np.pi
THETA+=2*np.pi*dx/WIN_W
THETA%=2*np.pi
r=DIST*np.cos(PHI)
EYE[1]=DIST*np.sin(PHI)
EYE[0]=r*np.sin(THETA)
EYE[2]=r*np.cos(THETA)
if0.5*np.pi
延伸文章資訊
- 1PyOpenGl的基本使用- 码迷-wjz - 博客园
Python 旗下,影响较大的三维库有pyOpenGl / VT. ... 我第一次接触OpenGL 的GL / GLU / GLUT 的时候,一下就被这些长得像孪生兄弟的库名字给整懵圈 ...
- 2一篇就够的超良心pyOpenGL入门教程,不香喷我! - CSDN博客
OpenGL (python版本)OpenGL是用于渲染2D、3D矢量图形的扩语言、跨平台的应用程序编程接口。这个接口由近350个不同的函数调用组成,用来从简单的图形 ...
- 3Python安裝設定OpenGL環境的全過程記錄 - IT145.com
下載與自己Python版本合適的,執行命令:pip install XXX.whl 即可正常使用pyopengl環境。 測試環境程式碼. from OpenGL.GL import * from...
- 4PyOpenGL基礎 - MahalJsp
本篇討論在Python中使用PyQt5 視窗環境,將OpenGL運算後的資料, ... gl.glViewport(0, 0, width, height) def processInput(w...
- 5Brief Introduction to OpenGL in Python with PyOpenGL
The easiest way to install OpenGL using Python is through the pip package manager. ... GL import ...