初试PyOpenGL一 (Python+OpenGL)
很早就一直想学Python,看到一些书都有介绍,不管是做为游戏的脚本语言,还是做为开发项目的主要语言都有提及(最主要的CUDA都开始支持Python,CUDA后面一定要学),做为先熟悉一下Python,本文用PyOpenGL实现一些基本的显示效果,一个网格,一个球体,加一个能切换第一与第三人称的摄像机。
PyOpenGL是一个用Python实现的多平台的OpenGL的API,为了学习Python与PyOpengl,本文也是用的Python,而不是.net版本的IronPython.
先看一下,相关环境的搭建:
首先我们需要下载Python: http://www.python.org/getit/
然后是PyOpenGL库:https://pypi.python.org/pypi/PyOpenGL
和PyOpenGL库常连在一起用的二个库,一个库numpy,提供常用的科学计算包含矩阵运算,数组转换与序列化,还有一个是3D常用的图片处理库:Python Imaging Library (PIL)。
numpy下载:http://sourceforge.net/projects/numpy/files/ 简介:http://sebug.net/paper/books/scipydoc/numpy_intro.html
Python Imaging Library (PIL)下载:http://www.pythonware.com/products/pil/
当上面环境安装完成后,我们先来实现一个基本的球体VBO实现,代码请参考前面的WebGL 利用FBO完成立方体贴图中的球的代码:
1 #common.py 2 import math 3 from OpenGL.GL import * 4 from OpenGL.arrays import vbo 5 from OpenGL.GLU import * 6 from OpenGL.GLUT import * 7 #import OpenGL.GLUT as glut 8 import numpy as ny 9 #Python Imaging Library (PIL) 10 class common: 11 bCreate = False 12 13 #球的实现 14 class sphere(common): 15 def __init__(this,rigns,segments,radius): 16 this.rigns = rigns 17 this.segments = segments 18 this.radius = radius 19 def createVAO(this): 20 vdata = [] 21 vindex = [] 22 for y in range(this.rigns): 23 phi = (float(y) / (this.rigns - 1)) * math.pi 24 for x in range(this.segments): 25 theta = (float(x) / float(this.segments - 1)) * 2 * math.pi 26 vdata.append(this.radius * math.sin(phi) * math.cos(theta)) 27 vdata.append(this.radius * math.cos(phi)) 28 vdata.append(this.radius * math.sin(phi) * math.sin(theta)) 29 vdata.append(math.sin(phi) * math.cos(theta)) 30 vdata.append(math.cos(phi)) 31 vdata.append(math.sin(phi) * math.sin(theta)) 32 for y in range(this.rigns - 1): 33 for x in range(this.segments - 1): 34 vindex.append((y + 0) * this.segments + x) 35 vindex.append((y + 1) * this.segments + x) 36 vindex.append((y + 1) * this.segments + x + 1) 37 vindex.append((y + 1) * this.segments + x + 1) 38 vindex.append((y + 0) * this.segments + x + 1) 39 vindex.append((y + 0) * this.segments + x) 40 #this.vboID = glGenBuffers(1) 41 #glBindBuffer(GL_ARRAY_BUFFER,this.vboID) 42 #glBufferData (GL_ARRAY_BUFFER, len(vdata)*4, vdata, GL_STATIC_DRAW) 43 #this.eboID = glGenBuffers(1) 44 #glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,this.eboID) 45 #glBufferData (GL_ELEMENT_ARRAY_BUFFER, len(vIndex)*4, vIndex, 46 #GL_STATIC_DRAW) 47 this.vbo = vbo.VBO(ny.array(vdata,'f')) 48 this.ebo = vbo.VBO(ny.array(vindex,'H'),target = GL_ELEMENT_ARRAY_BUFFER) 49 this.vboLength = this.segments * this.rigns 50 this.eboLength = len(vindex) 51 this.bCreate = True 52 def drawShader(this,vi,ni,ei): 53 if this.bCreate == False: 54 this.createVAO() 55 #glBindBuffer(GL_ARRAY_BUFFER,this.vboID) 56 #glVertexAttribPointer(vi,3,GL_FLOAT,False,24,0) 57 #glEnableVertexAttribArray(vi) 58 #glVertexAttribPointer(ni,3,GL_FLOAT,False,24,12) 59 #glEnableVertexAttribArray(ni) 60 #glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,this.eboID) 61 #glDrawElements(GL_TRIANGLES,this.eboLength,GL_UNSIGNED_INT,0) 62 this.vbo.bind() 63 def draw(this): 64 if this.bCreate == False: 65 this.createVAO() 66 #glBindBuffer(GL_ARRAY_BUFFER,this.vboID) 67 #glInterleavedArrays(GL_N3F_V3F,0,None) 68 #glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,this.eboID) 69 #glDrawElements(GL_TRIANGLES,this.eboLength,GL_UNSIGNED_INT,None) 70 this.vbo.bind() 71 glInterleavedArrays(GL_N3F_V3F,0,None) 72 this.ebo.bind() 73 glDrawElements(GL_TRIANGLES,this.eboLength,GL_UNSIGNED_SHORT,None)
这段代码画球,不同于我最开始用的每个三角形分成四个三角形的代码,基本思想是和地球仪上的经纬线一样,画出经纬线的点(顶点位置),然后有顺序的连接在一起(顶点索引)就可以了。
这里先说下python,和我之前接触的语言来看,我发现这个出乎意料的最和F#比较接近,虽然他们一个是动态语言,一个是静态语言,但是他们首先都是强类型语言,并且都支持多范式(对象式,过程式,函数式),同做为强类型语言,默认都不需要声明类型,不知Python是否和F#一样,是用的类型推导,有个比较明显的地方和F#一样的地方就是,在这里def __init__(this,rigns,segments,radius),首先righs,segments,radius开始鼠标移上去都是unknow type,但是在别的地方调用common.sphere(16,16,1)后,他就能推断出righs,segments,radius都为int.并且和F#一样,声明类的方法时,都需要带一个表示自己的参数,且都和C#不一样(限定this)可以自定义这个参数的名称.当然还有最大的共同点,他们都是用缩进来控制语言块(满分),现在写C#代码有些不爽的地方就是缩进。基于以上这些,写python感觉很亲切,也很爽,和F#一样,能写出很简洁的代码,相信一个学习过F#的人来写python,肯定也有此类感觉。当然python做为动态语言,比F#,C#来说,开发效率更高,比如,在上面一段中,this.vboLength = this.segments * this.rigns,这里直接动态声明一个属性vboLength,而不需要和F#与C#一样来先声明一个这样的属性,当然,net4.0中的DLR来说,也是能实现这种效果,但是用起来感觉就不一样了。
大家如果有兴趣了解各编程语言,强烈推荐郑晖大神的冒号课堂系列文章 第一篇冒号课堂§1.1:开班发言
下面是网格的代码,代码也可以参考前面柏林噪声实践(一) 海波,一样是生成网格上所有的x,z点,然后组织索引,看代码:
1 class plane(common): 2 def __init__(this,xres,yres,xscale,yscale): 3 this.xr,this.yr,this.xc,this.yc = xres - 1,yres - 1,xscale,yscale 4 def createVAO(this): 5 helfx = this.xr * this.xc * 0.5 6 helfy = this.yr * this.yc * 0.5 7 vdata = [] 8 vindex = [] 9 for y in range(this.yr): 10 for x in range(this.xr): 11 vdata.append(this.xc * float(x) - helfx) 12 vdata.append(0.) 13 vdata.append(this.yc * float(y) - helfy) 14 for y in range(this.yr - 1): 15 for x in range(this.xr - 1): 16 vindex.append((y + 0) * this.xr + x) 17 vindex.append((y + 1) * this.xr + x) 18 vindex.append((y + 0) * this.xr + x + 1) 19 vindex.append((y + 0) * this.xr + x + 1) 20 vindex.append((y + 1) * this.xr + x) 21 vindex.append((y + 1) * this.xr + x + 1) 22 print len(vdata),len(vindex) 23 this.vbo = vbo.VBO(ny.array(vdata,'f')) 24 this.ebo = vbo.VBO(ny.array(vindex,'H'),target = GL_ELEMENT_ARRAY_BUFFER) 25 this.eboLength = len(vindex) 26 this.bCreate = True 27 def draw(this): 28 if this.bCreate == False: 29 this.createVAO() 30 this.vbo.bind() 31 glInterleavedArrays(GL_V3F,0,None) 32 this.ebo.bind() 33 glDrawElements(GL_TRIANGLES,this.eboLength,GL_UNSIGNED_SHORT,None)
哈哈,大家发现了,我都是把以前写的javascripe,F#代码拿来改写的,毕竟我也只是一个python新手,把别的语言拿来改写我认为是最快熟悉一门语言的方法。同样,下面第一,第三人称漫游代码也是我前面Opengl绘制我们的小屋(四)第三人称漫游,Opengl绘制我们的小屋(二)第一人称漫游里的代码改写的,具体思路请转至这二篇文章。
1 class camera: 2 origin = [0.0,0.0,0.0] 3 length = 1. 4 yangle = 0. 5 zangle = 0. 6 __bthree = False 7 def __init__(this): 8 this.mouselocation = [0.0,0.0] 9 this.offest = 0.01 10 this.zangle = 0. if not this.__bthree else math.pi 11 def setthree(this,value): 12 this.__bthree = value 13 this.zangle = this.zangle + math.pi 14 this.yangle = -this.yangle 15 def eye(this): 16 return this.origin if not this.__bthree else this.direction() 17 def target(this): 18 return this.origin if this.__bthree else this.direction() 19 def direction(this): 20 if this.zangle > math.pi * 2.0 : 21 this.zangle < - this.zangle - math.pi * 2.0 22 elif this.zangle < 0. : 23 this.zangle < - this.zangle + math.pi * 2.0 24 len = 1. if not this.__bthree else this.length if 0. else 1. 25 xy = math.cos(this.yangle) * len 26 x = this.origin[0] + xy * math.sin(this.zangle) 27 y = this.origin[1] + len * math.sin(this.yangle) 28 z = this.origin[2] + xy * math.cos(this.zangle) 29 return [x,y,z] 30 def move(this,x,y,z): 31 sinz,cosz = math.sin(this.zangle),math.cos(this.zangle) 32 xstep,zstep = x * cosz + z * sinz,z * cosz - x * sinz 33 if this.__bthree : 34 xstep = -xstep 35 zstep = -zstep 36 this.origin = [this.origin[0] + xstep,this.origin[1] + y,this.origin[2] + zstep] 37 def rotate(this,z,y): 38 this.zangle,this.yangle = this.zangle - z,this.yangle + y if not this.__bthree else -y 39 def setLookat(this): 40 ve,vt = this.eye(),this.target() 41 #print ve,vt 42 glLoadIdentity() 43 gluLookAt(ve[0],ve[1],ve[2],vt[0],vt[1],vt[2],0.0,1.0,0.0) 44 def keypress(this,key, x, y): 45 if key in ('e', 'E'): 46 this.move(0.,0.,1 * this.offest) 47 if key in ('f', 'F'): 48 this.move(1 * this.offest,0.,0.) 49 if key in ('s', 'S'): 50 this.move(-1 * this.offest,0.,0.) 51 if key in ('d', 'D'): 52 this.move(0.,0.,-1 * this.offest) 53 if key in ('w', 'W'): 54 this.move(0.,1 * this.offest,0.) 55 if key in ('r', 'R'): 56 this.move(0.,-1 * this.offest,0.) 57 if key in ('v', 'V'): 58 #this.__bthree = not this.__bthree 59 this.setthree(not this.__bthree) 60 if key == GLUT_KEY_UP: 61 this.offest = this.offest + 0.1 62 if key == GLUT_KEY_DOWN: 63 this.offest = this.offest - 0.1 64 def mouse(this,x,y): 65 rx = (x - this.mouselocation[0]) * this.offest * 0.1 66 ry = (y - this.mouselocation[1]) * this.offest * -0.1 67 this.rotate(rx,ry) 68 print x,y 69 this.mouselocation = [x,y]
代码很简单,当然,做为一个类来说,其实setLookat,keypress,mouse这三个方法应该分离出去的,不过用了使用方便,也便于放在一起理解。其中keypress与mouse实现键盘启用常用的基本漫游,其中EDSF前后左右移动,WR分别向上与向下,鼠标右键加移动鼠标控制方向,V切换第一人称与第三人称。UP与DOWN切换前面操作的移动幅度。其中len = 1. if not this.__bthree else this.length if 0. else 1.这个解释下,在不是第三人称漫游下,长度为1,否则在第三人称漫游下,长度取length的长度,如果length为0,则取1的长度.其实就是相当二个三元运算符,但是感觉理解起来更方便.
下面来看具体调用并显示的代码:
1 from OpenGL.GL import * 2 from OpenGL.GLUT import * 3 from OpenGL.GLU import * 4 5 import common 6 import sys 7 8 window = 0 9 sph = common.sphere(16,16,1) 10 camera = common.camera() 11 plane = common.plane(12,12,1.,1.) 12 def InitGL(width,height): 13 glClearColor(0.1,0.1,0.5,0.1) 14 glClearDepth(1.0) 15 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE) 16 glMatrixMode(GL_PROJECTION) 17 glLoadIdentity() 18 gluPerspective(45.0,float(width)/float(height),0.1,100.0) 19 camera.move(0.0,3.0,-5) 20 21 def DrawGLScene(): 22 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) 23 glMatrixMode(GL_MODELVIEW) 24 camera.setLookat() 25 plane.draw() 26 glTranslatef(-1.5,0.0,0.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 glTranslatef(3.0, 0.0, 0.0) 34 sph.draw() 35 glutSwapBuffers() 36 37 def mouseButton( button, mode, x, y ): 38 if button == GLUT_RIGHT_BUTTON: 39 camera.mouselocation = [x,y] 40 41 def ReSizeGLScene(Width, Height): 42 glViewport(0, 0, Width, Height) 43 glMatrixMode(GL_PROJECTION) 44 glLoadIdentity() 45 gluPerspective(45.0, float(Width)/float(Height), 0.1, 100.0) 46 glMatrixMode(GL_MODELVIEW) 47 48 def main(): 49 global window 50 glutInit(sys.argv) 51 glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH) 52 glutInitWindowSize(640,400) 53 glutInitWindowPosition(800,400) 54 window = glutCreateWindow("opengl") 55 glutDisplayFunc(DrawGLScene) 56 glutIdleFunc(DrawGLScene) 57 glutReshapeFunc(ReSizeGLScene) 58 glutMouseFunc( mouseButton ) 59 glutMotionFunc(camera.mouse) 60 glutKeyboardFunc(camera.keypress) 61 glutSpecialFunc(camera.keypress) 62 InitGL(640, 480) 63 glutMainLoop() 64 65 main()
代码很简单,把球,网络,漫游摄像机应用进去。注意glutMouseFunc( mouseButton )与glutMotionFunc(camera.mouse)组合用才能达到原来OpenTK提供的鼠标检测功能,因为glutmousefunc只检测鼠标的按下等动作,意思你一直按下移动他是不会引用的,在这引用的是glutmotionfunc,这个大家可以自己去试验。
下面放出效果图:
效果很简单,主要是为了下文先做一个基本的效果,同时也是用pyOpengl对前面的一点总结。
代码下载:代码 和上面说的一样,其中EDSF前后左右移动,WR分别向上与向下,鼠标右键加移动鼠标控制方向,V切换第一人称与第三人称。UP与DOWN切换前面操作的移动幅度。