panda3d 入门.doc
PANDA3D入门-古道天马一、 前言这个是我自学的总结。因为刚开始看PANDA3D的教程,发现在看天书,静下心来学后,感觉其实是教程不够深入浅出,没有照顾我们这些一点基础都没有的初学者。因此,把我自学的一点心得记录下来,便于自己及他人参考。学习PANDA3D的目的是编制一个三维的设备管理程序。嫌C#运行效率低下,C+的语言不够简练,看好了PYTHON编程。百度了一下,PYTHON的3D图形库有PYGLET,PANDA3D,BLENDER等,最初是想用BLENDER,但是BELNDER侧重于建模,于我的用途不太符合。改用PYGLET的,看中它也是很简练的库,后来发现PYGLET缺乏维护,教程也少。因此转向PANDA3D,PANDA3D的教程以及维护要完善的多。但是按网上的说法,学习曲线是比较陡峭的。果然,刚开始时一头雾水,经过查阅官方教程后,又通过自己一点点的实验和摸索,稍微有点头绪了。顺便提一下,我的PYTHON和PANDA3D是同步学的,都在初级阶段。这里侧重写PANDA3D的特点,PYTHON的略微提到些。注意:这不是手册,很多进阶的东东请查官网手册。二、 安装我的系统是WIN7 64位,安装PYTHON的2.7.6版本32位版,https:/www.python.org/ftp/python/2.7.6/python-2.7.6.msiPANDA3D的1.8.1版本(自带2.7.3版本的PYTHON)。http:/www.panda3d.org/download/panda3d-1.8.1/Panda3D-1.8.1.exe打开“开始”菜单,运行PANDA3D下的范例文件,第一个范例是ASTEROIDS,小行星。点击“Run Asteroids”,出现游戏界面这说明PANDA3D内含的PYTHON2.7.3已经可以运行了。现在,大家肯定都迫不及待的要看看源代码了吧。点击View Source Code出现文件夹点击Tut-Asteroids.py,结果出现提示没有找到模块,说明你的PYTHON2.7.6还没找到PANDA3D的模块。那么按下面的方法做。在C:Python27Libsite-packages的目录下,建一个PANDA.PTH的文件,用写字本添加下列文本(这里的文件路径是默认的,如果你修改过的话,根据实际情况调整)C:Panda3D-1.8.1C:Panda3D-1.8.1/direct/src/actor C:Panda3D-1.8.1/direct/src/cluster C:Panda3D-1.8.1/direct/src/controls C:Panda3D-1.8.1/direct/src/directbase C:Panda3D-1.8.1/direct/src/directdevices C:Panda3D-1.8.1/direct/src/directnotify C:Panda3D-1.8.1/direct/src/directscripts C:Panda3D-1.8.1/direct/src/directtools C:Panda3D-1.8.1/direct/src/directutil C:Panda3D-1.8.1/direct/src/distributed C:Panda3D-1.8.1/direct/src/extensions C:Panda3D-1.8.1/direct/src/extensions_native C:Panda3D-1.8.1/direct/src/ffi C:Panda3D-1.8.1/direct/src/fsm C:Panda3D-1.8.1/direct/src/gui C:Panda3D-1.8.1/direct/src/interval C:Panda3D-1.8.1/direct/src/leveleditor C:Panda3D-1.8.1/direct/src/motiontrail C:Panda3D-1.8.1/direct/src/particles C:Panda3D-1.8.1/direct/src/physics C:Panda3D-1.8.1/direct/src/pyinst C:Panda3D-1.8.1/direct/src/showbase C:Panda3D-1.8.1/direct/src/showutil C:Panda3D-1.8.1/direct/src/task C:Panda3D-1.8.1/direct/src/tkpanels C:Panda3D-1.8.1/direct/src/tkwidgets C:Panda3D-1.8.1/bin再次点击Tut-Asteroids.py,看看是否成功运行。三、 正式开始现在安装工作已经完成,我们可以正式开始了。右键Tut-Asteroids.py文件,用IDLE打开,我们可以看到源程序,按F5可以调试运行。不过拿这个源程序作为我们的开端,显然是不合适。3.1 第一个PANDA程序现在我们开始编写第一个PANDA3D程序。第一个程序,当然要简单粗暴些。新建一个TEST.PY(注:PANDA3D似乎不支持中文目录,所以你的程序不要放在中文目录下),用IDLE编辑(原则上你也可以用其它文本编辑器编辑)输入以下两行import direct.directbase.DirectStartrun()CTRL-S保存,F5运行(顺便提一下,PYTHON2.7以前的IDLE是不支持右键复制黏贴的,不用快捷键会很蛋疼,2.7的版本是懒人的福音)出现一个灰色的空窗口,比较简陋些,不过作为第一个程序已经够了。根据官网的解释,第一句import direct.directbase.DirectStart,建立ShowBase的实例第二句run(),循环运行ShowBase实例,监视键盘鼠标输入,并反馈。(试试:如果删了RUN()会咋样?)老版本的PANDA的语法是这样写的。from direct.showbase.ShowBase import ShowBasep3dApp = ShowBase()p3dApp.run()这里的ShowBase是显式的,新的语法里都简化了。有兴趣的话,你可以打开C:Panda3D-1.8.1directdirectbase看看里面的源程序。不过,本着初学者够用就行的态度,咱们就不用深究这些了,只要记住这两句的作用如下:import direct.directbase.DirectStart # 建立舞台run() # 演出开始了!# 是PYTHON的注释方法保存时,程序会提示你有中文注释,要加 # -*- coding: cp936 -*- ,同意它修改就好。3.2 加料灰色窗口太不起眼了,需要加点料,事实上只要加两行就能让它大变样。# -*- coding: cp936 -*-import direct.directbase.DirectStart # 建立舞台 environ = loader.loadModel("models/environment")environ.reparentTo(render)run() # 演出开始了!F5出现哈哈,两行代码就有这么大变化,:-O,厉害的!还有更厉害的,试着按住鼠标左右键拖动看看!默认的鼠标控制方式如下:(当然,这个控制方式很不人道)KeyAction鼠标左键左右进行平移鼠标右键前后移动鼠标中键围绕程序的坐标原点进行旋转鼠标右键和中键围绕视野中心轴进行滚动对这两句代码的解释如下environ = loader.loadModel("models/environment") # 导入模型environ.reparentTo(render) # 将模型加入渲染列表第一句代码:environ, 是我们自行规定的一个变量,这个变量现在代表了我们在等号后面导入的模型。loader.loadModel( ),它的作用是导入一个模型。(注意:PYTHON对大小写敏感)这句代码稀奇的是,我们并没有新建这个模型,可是却可以导入这个模型。那么这个模型在哪里呢?原来,这个模型在C:Panda3D-1.8.1models 下,文件名为 environment.egg.pz。(注意:在代码里面的文件路径用的是LINUX的斜杠”/ ”,和WIN下的反斜杠” ”不一样) 这个*.pz文件,是一种压缩格式,后面会提到。这里我们只要理解了,程序在 默认的文件夹下找到了 environment的模型文件。当然,这个文件有可能是environment.egg.pz或者environment.egg或者environment.bam。(试试,将C:Panda3D-1.8.1models 下的environment.egg.pz改名或者挪个位置)事实上,如果我们在程序所在文件夹下新建一个models文件夹并将environment.egg.pz拷贝在此文件夹下,程序同样可以找到模型。(想想,如果有两个同名模型会咋样?)第二句代码:environ.reparentTo(render), 意思是将environ模型置于render节点下。节点是个术语,是为了更好的组织各个模型。模型只有置于节点下,才能被程序渲染。整个结构是个树型表。有个比较容易理解的比喻是,将节点看成文件夹。 Render就是根目录C:,模型就是文件,我们要看到模型,就必须把文件拷到C:(关于节点的用法,我们后面详述)。当然,为了方便管理,我们可以在C:盘下建立目录,以及子目录,这种目录叫做空节点四、 创建模型上面那个environment模型,超出了我们的理解能力。现在,我们试着自行建立一个模型。在你程序所在文件夹下,创建一个models文件夹。4.1 建立正方形在models文件夹下,新建一个square.egg模型文件。用记事本编辑square.egg,并拷入下列代码。<CoordinateSystem> Z-up <Group> <VertexPool> Cube <Vertex> 0 1.0 1.0 -1.0 <Vertex> 1 1.0 -1.0 -1.0 <Vertex> 2 -1.0 -1.0 -1.0 <Vertex> 3 -1.0 1.0 -1.0 <Polygon> <VertexRef> 0 3 2 1 <Ref> Cube 现在将我们的TEST.PY修改为# -*- coding: cp936 -*-import direct.directbase.DirectStart # 建立舞台 box = loader.loadModel("models/square") # 导入模型box.reparentTo(render) # 将模型加入渲染列表run() # 演出开始了!F5运行,咦,还是没东东啊。我们还需要加代码。# -*- coding: cp936 -*-import direct.directbase.DirectStart # 建立舞台 box = loader.loadModel("models/square") # 导入模型box.reparentTo(render) # 将模型加入渲染列表box.setPos(0,20,-3) #设置模型原点的位置run() # 演出开始了!box.setPos(0,20,-3),含义是将模型按其原点(0,0,0)对应PANDA3D坐标的(0,20,-3)的对应关系放置。一个梯形。如果你按住鼠标左键移动的话,会发现,原来是个一个水平放置的正方形。现在我们得了解一下PANDA3D的坐标系以及EGG文件了。YZX下面是PANDA3D的坐标系ZYX从屏幕上看,就是X方向是屏幕的宽,Z方向就是屏幕的高,Y方向就是屏幕的深了。简单解释一下square.egg文件代码的含义<CoordinateSystem> Z-up /Z方向 向上,这和PANDA的坐标系是一致的。 (你可以试试X方向或者Y方向向上会如何)<Group> /给顶点集和多边形编组 <VertexPool> Cube /顶点集,我们将这个集命名为CUBE <Vertex> 0 / CUBE顶点集下的第1个点,编号为0,你也可以从别的序号开始编 1.0 1.0 -1.0 / 顶点的 X Y Z坐标 <Vertex> 1 1.0 -1.0 -1.0 <Vertex> 2 -1.0 -1.0 -1.0 <Vertex> 3 -1.0 1.0 -1.0 <Polygon> /多边形 <VertexRef> 0 3 2 1 <Ref> Cube /多边形的顶点是顶点集CUBE下的4个点。 上面点的坐标是相对模型空间的原点(0,0,0)的值,原点也是setPos( )函数操作的点。box.setPos(0,20,-3) #设置模型原点的位置这句的意思就是将box模型按照模型空间原点(0,0,0)对应屏幕空间点(0,20,-3)的方式放置。实践:1)调整程序文件中的setPos()的参数,看看有什么变化。 2)模型文件中的增加或者减少多边型的点,改变点的坐标,改变多边形点的排列顺序,看看能否找到什么规律(注意,一次最好只改一项)刚才我们在用鼠标移动那个水平放置的正方形时,会发现正方形移动到屏幕上方会消失。这说明这个正方形是有方向型的,它的反面是不可见的。模型的方向是右手性的。右手螺旋系当然,PANDA的坐标系也是右手性的。反面不可见是为了减少计算量,所以我们要确保多边形在正确的方向上,以免看不到。4.2 建立正方体现在我们可以通过修改EGG文件来创建一个正方体模型了。在MODELS文件夹下新建CUBE.EGG文件。小心调整方向后,我们得到下面这些代码。<CoordinateSystem> Z-up <Group> <VertexPool> Cube <Vertex> 0 1.0 1.0 -1.0 <Vertex> 1 1.0 -1.0 -1.0 <Vertex> 2 -1.0 -1.0 -1.0 <Vertex> 3 -1.0 1.0 -1.0 <Vertex> 4 1.0 1.0 1.0 <Vertex> 5 1.0 -1.0 1.0 <Vertex> 6 -1.0 -1.0 1.0 <Vertex> 7 -1.0 1.0 1.0 <Polygon> <VertexRef> 0 1 2 3 <Ref> Cube <Polygon> <VertexRef> 4 7 6 5 <Ref> Cube <Polygon> <VertexRef> 0 4 5 1 <Ref> Cube <Polygon> <VertexRef> 1 5 6 2 <Ref> Cube <Polygon> <VertexRef> 2 6 7 3 <Ref> Cube <Polygon> <VertexRef> 0 3 7 4 <Ref> Cube 因为模型名字变了,所以在我们的TEST.PY中将模型改为cubebox = loader.loadModel("models/cube") # 导入模型4.3 增加颜色一个纯白的正方体,太朴素了,我们给它加点颜色修改cube.egg文件,在顶点1的属性中加入一行颜色。 <Vertex> 1 1.0 -1.0 -1.0 <RGBA> 1.0 0.0 0.0 1.0 我们给 顶点1 变成红色, <RGBA> 1.0 0.0 0.0 1.0 红、绿、蓝、不透明度(阿尔法值)现在我们得到一个有一个红角的正方体。注意中间的粉色,是通过对4个顶点的颜色插值得到的。试着修改其它点的颜色吧。注意白色 是<RGBA> 1.0 1.0 1.0 1.0 这是我得到一个混色正方体。4.4 调整比例在PANDA3D中我们可以随意设置模型的三个方向的比例尺。# -*- coding: cp936 -*-import direct.directbase.DirectStart # 建立舞台 box = loader.loadModel("models/cube") # 导入模型box.reparentTo(render) # 将模型加入渲染列表box.setPos(0,20,-3) #设置BOX的原点位置box.setScale(0.5, 0.5, 0.5) #设置BOX的三个方向的比例尺,对模型放大缩小run() # 演出开始了!你可以试试通过setScale( )把正方体变成长方体。五、 镜头首先我们可以想象我们是通过一个镜头在观察模型。事实上,PANDA3D中确实有这个镜头。如果你对镜头的具体数据不关心的话,你可以跳到5.3。5.1 近距我们知道,如果模型在镜头之后(Y<0),显然是不可见的。但是模型在镜头之前就必定被渲染了么?下面我们做个试验来测试一下。我们设计一个正方形,它在模型空间的Y值为0, 放置于屏幕空间就会平行于屏幕。Square.egg文件如下:<CoordinateSystem> Z-up <Group> <VertexPool> Cube <Vertex> 0 1.0 0.0 1.0 <RGBA> 1.0 0.0 0.0 1.0 <Vertex> 1 1.0 0.0 -1.0 <RGBA> 1.0 1.0 1.0 1.0 <Vertex> 2 -1.0 0.0 1.0 <RGBA> 0.0 1.0 0.0 1.0 <Vertex> 3 -1.0 0.0 -1.0 <RGBA> 1.0 1.0 1.0 1.0 <Polygon> <VertexRef> 0 2 3 1 <Ref> Cube Test.py如下# -*- coding: cp936 -*-import direct.directbase.DirectStart box = loader.loadModel("models/square")box.reparentTo(render)box.setPos(0,16,0) run()下面我们调整setPos( ) 的Y值注意当Y值变化时,这个2*2的正方形的视觉变化; Y=16 Y=8 Y=4 Y=2.9 Y= 1.0000001 Y=1从这个试验,我们大致 可以得到两个结论:1)Y<=1时,模型不会被渲染(如果模型的部分区域Y<=1,则这部分会被裁减,其余部分仍可见)2)Y>1时,模型会被渲染(跟据我的两台电脑,双显卡应该是Y>1.00000018左右,单显卡Y>1.00000006,当然这个值意义不大)3)Y=1.0000001时,正方形出现惊人的效果,一半可见,一半不可见(这大概是因为双显卡交火的问题,我在笔记本上未发现此现象)。Y=1,这个参数叫做 镜头的近距(near distance),PANDA默认比这个距离更贴近镜头的物体区域是不可见的。5.2 视场在上面那个实验中,Y在2.8左右时,2*2的正方形差不多撑满窗口屏幕宽度(默认窗口模式(4:3),非全屏)。按照三角公式Tan(/2)=1/2.8计算出大约为39.3度。这个角度叫做 镜头的视场(field of view)。PANDA3D官方给的数据是默认为40度。大致是全画幅的60mm镜头。当然,如果显示比例为16:9,或者16:10,视场也扩大问题:在非常规窗口比例中,视场为多少?我们可以将Y=3.8,然后用鼠标对窗口进行变形。 4:3 近似16:9 1:2可以看出PANDA将宽和高的视场限制为 不小于约30度。也就是默认的宽视场。就上面的例子而言,得到的效果就是,无论你如何变换窗口,都无法遮住正方形,而且正方形也不会产生形变。5.3 镜头的位置这个是我们最常用的镜头参数。现在我们来显式的控制我们的镜头。回到我们的立方体的例子在TEST.PY里,我们首先禁止鼠标控制镜头# -*- coding: cp936 -*-import direct.directbase.DirectStart # 建立舞台base.disableMouse() # 禁止鼠标控制镜头box = loader.loadModel("models/cube") # 导入模型box.reparentTo(render) # 将模型加入渲染列表box.setPos(0,20,0) #设置BOX的原点位置run() # 演出开始了!现在,镜头不能动了。目的是为了减少控制冲突,两个鼠标一起动的感觉你懂的。这下看不到立方体的上面了,咋办呢。把镜头挪挪吧。# -*- coding: cp936 -*-import direct.directbase.DirectStart # 建立舞台base.disableMouse() # 禁止鼠标控制镜头box = loader.loadModel("models/cube") # 导入模型box.reparentTo(render) # 将模型加入渲染列表box.setPos(0,20,0) #设置BOX的原点位置camera.setPos(0,0,3) # 设置镜头的位置run() # 演出开始了!六、任务下面现在我们要设计一段代码,让我们的镜头动起来。基本思路是隔一秒,镜头进1。注意: 根据PYTHON的规定,缩进是很讲究的,如果下面的程序拷到test.PY后运行出错,一般是因为缩进不符合PYTHON的规定。请用TAB键而不是空格键来控制缩进。# -*- coding: cp936 -*-import timeimport direct.directbase.DirectStart # 建立舞台base.disableMouse() # 禁止鼠标控制镜头box = loader.loadModel("models/cube") # 导入模型box.reparentTo(render) # 将模型加入渲染列表box.setPos(0,20,0) #设置BOX的原点位置for i in range(10):time.sleep(1)camera.setPos(0,i,3)run() # 演出开始了!在10秒黑屏后突兀出现了最后的画面。离我们的理想有差距啊。问题在哪里呢我们的镜头变换循环并不和渲染同步进行。镜头变换完后才进行的渲染。现在我们需要引入一个工具。Task 任务# -*- coding: cp936 -*-import direct.directbase.DirectStart # 建立舞台from direct.task import Task # 建立任务表base.disableMouse() # 禁止鼠标控制镜头box = loader.loadModel("models/cube") # 导入模型box.reparentTo(render) # 将模型加入渲染列表box.setPos(0,20,0) #设置BOX的原点位置CamY = 0def Task1(task):# 建立一个叫Task1的任务global CamYcamera.setPos(0,CamY,3) CamY += 1task.delayTime = 1# 任务的延迟时间为1秒return Task.again # 返回,延迟delayTime(1秒)后再次启动任务taskMgr.add(Task1, "GOGOGO") # 将Task1任务加入任务列表,以便执行run() # 演出开始了!上面这个办法比较笨的实现了镜头的前进。一跳一跳的。为了顺畅的运行,我将步进改为0.01秒, 同时规定镜头移动10后停止。# -*- coding: cp936 -*-import direct.directbase.DirectStart # 建立舞台from direct.task import Task # 建立任务表base.disableMouse() # 禁止鼠标控制镜头box = loader.loadModel("models/cube") # 导入模型box.reparentTo(render) # 将模型加入渲染列表box.setPos(0,20,0) #设置BOX的原点位置CamY = 0def Task1(task):global CamYcamera.setPos(0,CamY,3)CamY = CamY +0.01 # 步进改为0.01task.delayTime = 0.01 # 延迟改为0.01秒if CamY > 10:return Task.done # 任务停止else:return Task.again #延迟0.01秒后继续任务taskMgr.add(Task1, "GOGOGO") # 将Task1任务 任务代号GOGOGO 加入任务列表,以便执行run() # 演出开始了!看着不错。不过有个问题,有点慢,预计10秒就该完成的任务用了17秒。咋回事呢?为了解决这个问题,我们换个方式# -*- coding: cp936 -*-import direct.directbase.DirectStart # 建立舞台from direct.task import Task #建立任务表base.disableMouse() # 禁止鼠标控制镜头base.setFrameRateMeter(True) # 显示帧速box = loader.loadModel("models/cube") # 导入模型box.reparentTo(render) # 将模型加入渲染列表box.setPos(0,20,0) #设置BOX的原点位置camera.setPos(0,0,3) # def Task1(task):CamY = task.time #task.time从任务开始时计时,以秒为单位,camera.setY(CamY)if CamY > 10:return Task.done # 任务停止else:return Task.cont #下一帧继续taskMgr.add(Task1, "GOGOGO")run() # 演出开始了!注意右上角的帧数与setY类似的有setX(),setY(),setZ(),setH(),setP(),setR()等等详见:http:/panda3d.noie.name/manual/%E5%B8%B8%E7%94%A8%E7%8A%B6%E6%80%81%E6%9B%B4%E6%94%B9建议对每个函数都用一下,熟悉他们的用法。将上个test.py中的Task1任务,旋转方向做调整,也可以将旋转对象改为 box.def Task1(task):CamY = task.time #task.time从任务开始时计时,以秒为单位,box.setH(CamY)if CamY > 100:return Task.done # 任务停止else:return Task.cont #下一帧继续看看效果。后记暂时没有时间更新。以后再说吧。O(_)O29 ) (吧再 新间入后果继帧下 停务# 00 ( 位为以计务 # ) ( 改象将,做方,任 法用悉下都个 % % % % % / % % % % . : 见等( ( ( , ,( 似 帧的右!出#(" . 继继 停任 0 位为,时务 . . ) #)0( 位点 置 00 列列加将) 模导 ( 帧显# ( 镜镜止#) 务立 . 舞建# . * 方换我个这呢事。 务的就 ,点问不错了始演#执执表务 任任 将 " " 任继 0# 停任 0 为 .0.进步 + ( . ) 位点 设)0, 列渲入 # ( 模导# / " =镜控止#) 务立 . 舞建 . * : 止后动镜定 0.步我运的跳一的了的笨个了开演)执以表入加 将 " 任启再 延返 间延任 . ( 任的 一)( = 位点 ), 列列型将 模入 / . = 镜控止#) 务立建 舞立 . *- -*任 具工引们染的行换镜。步渲环变里哪啊距理面的了兀屏了开 ) ,( .) 位位 置 00 列染入模) 模导 ) " 镜镜止#( 舞建 * 进制键是键 请的 符缩般错运后 到序下果的进缩的 据 进秒一来动的们代一要下任了开 位的镜 ,0 . 位原 0 .列染入 ) .模入 " " 镜镜鼠 ) 舞立 * -吧挪呢咋了体到的你的动标,控少了能镜了了 )位原 设 00( 列渲入将) .模入 ) / ( = 镜制止# 舞建 . * -*镜镜止禁我 例体的头镜们式们数参镜们位位变变会方而形住无窗何你是就得而的场视认也0 不为视宽 : 近 形变窗鼠用, 以少少,例窗在扩也, 者: 示如头头0的致大0认的官 ) 视做度度 ./=公角三) 式认度幕满不形 *左.在实视 的可不物头更个认 ) 近镜做个)现发本笔,火卡为概见半一一,人现正 00大义值然 00>显, 00> 显,电据染会模>见仍余,会这 <分型如渲不模时<论个得以们验= 000 .= = 化觉视方 ,变 ( 调(), . " / " . * 如. .0 .0 > .-.0 . 000> . 0. 0 .0 > <.-0 . .0 . > . 00 > 下件 .幕幕平空于放为 间模形个下一测个么染渲前之模但见不) 后在果,近 以可心据体镜头个实 上事观镜一们想们镜体方体把( 过试了始演 缩大模例比方 设# 0.( .位点 置)- ( .列渲入将) 模导#) ( =舞建# . . * 尺比方三模意们 比比体方混一 > <吧吧点改的得色点个通色间体正红有到值