anda 内部数据结构的高级操作
这一章讲Panda内部如何描述顶点和可渲染的几何体,以及如何直接读取、操控这些数据。本章属于比较高级的知识,是为高级用户准备的,普通的模型渲染和动画并不会用到这方面的知识。
 
Panda3D 如果存储顶点和几何数据
本节主要讲述Panda内部的顶点和几何数据对象的结构及联系。在学习怎样用程序生成几何数据之前,应该仔细阅读本节,全面了解Panda的数据结构。
GeomVertexData
在panda里,用于存储顶点信息的基本对象为 GeomVertexData,它以表格为数据组织形式存储顶点序列。表格的每一行表示一个顶点,每一列表示该顶点的某种数据。例如,下面的表格定义了4个顶点,每个顶点都有自己的位置、法线向量、颜色和纹理坐标:
 
vertex
normal
color
texcoord
0
(1, 0, 0)
(0, 0, 1)
(0, 0, 1, 1)
(1, 0)
1
(1, 1, 0)
(0, 0, 1)
(0, 0, 1, 1)
(1, 1)
2
(0, 1, 0)
(0, 0, 1)
(0, 0, 1, 1)
(0, 1)
3
(0, 0, 0)
(0, 0, 1)
(0, 0, 1, 1)
(0, 0)
顶点编号通常从0开始,一直计数到表格的最后一行(至少是1)。
不是全部GeomVertexData对象都得像上图那样包含4列,列数可以多一些也可以少一些。除了存储顶点位置的“vertex”列,别的列都是可选的。
各列的顺序无关紧要,但列的名字非常关键。Panda保留了若干个列的名字,用于指示每一列的含义。例如,顶点位置列一般命名为“vertex”,如果有法线列,必须名为“normal”。全部的保留名字请参考GeomVertexFormat部分。
用户可以自定义表格的列。如果Panda不能识别列的名字,它将不对该列进行处理,但仍然会把它们传送给显卡。当然,接下来就看你的了,你自己编写顶点着色器(vertex shdaer)来操作这些列的数据。
可以把GeomVertexData分解成几个数组。一个 GeomVertexArray就是一个在内存里连续的存储顶点数据的块。一般一个GeomVertexData由一个数组构成。但我们可以把数据分开,在一个数组里存储某些列,在另一个数组里存储其余的列:
 
vertex
texcoord
 
normal
color
0
(1, 0, 0)
(1, 0)
 
(0, 0, 1)
(0, 0, 1, 1)
1
(1, 1, 0)
(1, 1)
 
(0, 0, 1)
(0, 0, 1, 1)
2
(0, 1, 0)
(0, 1)
 
(0, 0, 1)
(0, 0, 1, 1)
3
(0, 0, 0)
(0, 0)
 
(0, 0, 1)
(0, 0, 1, 1)
有 时候你可能得这么做。例如,对于不同区段的顶点,某些列的数据总是一样的,你可以把这些列放到一个数组里,让不同的GeomVertexData对象共用 这个数组。把一个GeomVertexData分成多少个数组没有限制,你可以把每一列单独作为一个数组。(这样做可能会对性能产生一些影响。某些显卡对 连续的数据块——单个数组——处理更快,而某些显卡可能更适合处理多个数组。但这种性能上的差异很小)
 
GeomVertexFormat
GeomVertexFormat对象描述GeomVertexData的列如何排序和命名,以及存储在每一列的是什么样的数据。每个GeomVertexData都由一个相应的GeomVertexFormat来描述它其中的数据是怎样存储的。
正如GeomVertexData对象实际上是一个或多个GeomVertexArrayData对象组成的列表,GeomVertexFormat对象也是一个或多个 GeomVertexArrayFormat对象组成,它与数组一一对应,定义数组的结构。每个GeomVertexArrayFormat由一个 GeomVertexColumn对象列表构成,和数组的每一列一一对应。举个例子,一个有着6个列,分别在3个数组的GeomVertexData的格式如下:
GeomVertexFormat
GeomVertexArrayFormat
GeomVertexColumn
GeomVertexColumn
GeomVertexColumn
 
GeomVertexArrayFormat
GeomVertexColumn
 
GeomVertexArrayFormat
GeomVertexColumn
GeomVertexColumn
GeomVertexColumn有很多属性:
getNumComponents()
定义列的数据有多少个数值分量。例如,典型的顶点位置为一个(X, Y, Z)3元组,有X、Y、Z 3个分量。纹理坐标一般有2个分量(U, V),但某些情况下有3个(U, V, W)。
getNumericType()
定义分量的数据类型。必须是以下这几种符号:
Geom.NTFloat32
每个分量都是一个32位浮点数,目前为止最常用的类型。
Geom.NTUint8
每个分量都是一个8位整数,大小从0到255。OpenGL的RGBA颜色值使用4个8位整型表示,顺序为R、G、B、A。
Geom.NTUint16
每个分量都是一个16位整数,大小从0到65535。
Geom.NTUint32
每个分量都是一个32位整数,大小从0到4294967295。
Geom.NTPackedDcba
每 个分量都是一个32位字(word),包含4个8位的整数索引值,排序为(D, C, B, A),DirectX形式。通常用在只有一个分量的列(因为一个分量就已经包含了4个值)。DirectX在顶点动画中使用该格式存储变换表的4个索引。 (GeomVertexReader 和 GeomVertexWriter类自动把DirectX的D、C、B、A顺序重新排列成A、B、C、D)
Geom.NTPackedDabc
每 个分量都是一个32位字(word),包含4个8位的整数索引值,ARGB的排列为(D, A, B, C)。同上,通常用在只有一个分量的列。DirectX用这个格式来表示RGBA颜色值。(GeomVertexReader 和 GeomVertexWriter类自动把DirectX的A、R、G、B顺序重新排列成R、G、B、A)
getContents()
一般地定义了列中数据的语意含义。当应用变换矩阵或纹理矩阵时,Panda根据它来决定数据怎样变化。它还控制着列数据的默认值,以及数据存储和获取的方式。下面是具体的几种符号:
Geom.CPoint
数 据代表物体坐标系中的点,可以是3维坐标(3个分量)或者4维齐次坐标(4个分量)。当一个变换矩阵被应用到顶点数据时,这列中的数据被当成一个点来变 换。如果一个4元组存储到3元列里,第4个分量将被视为齐次坐标,隐含成为前3个分量的分母。如果数据从3元组读入4元组,则第4个分量隐含的值为1。
Geom.CClipPoint
数据代表已经变换到裁剪空间的点;也就是说这些点已经做好渲染前的准备。渲染时Panda将不会再对它们进行坐标变换。裁剪空间坐标应为一个4维齐次坐标,因此通常有4个分量。
Geom.CVector
数据代表3维向量,例如物体空间中的法线向量、切向量或副法线。当一个变换矩阵被应用到顶点数据时,这列中的数据被当成一个向量来变换。(也就是,忽略矩阵的平移成员)
Geom.CTexcoord
数据代表纹理坐标,可以2维也可以3维。当一个纹理矩阵(不是变换矩阵)被应用到顶点数据时,这列中的数据被当成一个点来变换。
Geom.CColor
数据代表RGBA颜色值。如果用浮点数来读取或写入一个整数颜色分量,将自动从0.0-1.0范围放大成整数。颜色列的默认值为(1, 1, 1, 1),而其他列的默认值都是0。
Geom.CIndex
数据代表到某个表的整数索引。
Geom.CMorphDelta
数据代表在动画时应用到其他列的偏移值。
Geom.COther
数据代表其他用户定义的意思。不要对它进行变换。
getName()
列的名字是对Panda最重要的信息,它告诉Panda列中数据的具体含义。名字也是列的一个独一无二的句柄。在一个GeomVertexFormat里不能出现同名的两列。
Panda的列名都有其特定的含义:
vertex
顶点的位置,通常为3维坐标(x, y, z)。它是渲染唯一强制的列;其他列都是可选的。顶点一般是Geom.NTFloat32,Geom.CPoint, 3元组。
normal
顶点的表面法线,用于光照效果计算;与碰撞系统没有任何关系,后者有自己定义的表面法线。如果打开光照效果的话应该提供一个法线列。如果没有,则物体在光照下会显得很奇怪。法线一般应为Geom.NTFloat32, Geom.CVertex,3元组。
texcoord
顶 点的U、V纹理坐标,默认的纹理。对几何体应用纹理时(除非你使用TexGenAttrib)必须要有这一列。通常是2维坐标,但当使用3维纹理或 cube map时,它是3维的U、V、W坐标。纹理坐标应为Geom.NTFloat32,Geom.CTexcoord,2或3元组。
texcoord.foo
名为“foo”(foo代表任何名字)的纹理的U、V坐标。用于在同一个几何体上应用多个不同的纹理。同上,也是2或3元组。
tangent
binormal
这两列总在一起,连同法线列,目的是得到法线图(normal map)(或称bump map)。它们在每个顶点定义法线图空间。和法线一样,它们应为Geom.NTFloat32, Geom.CVertex,3元组。
tangent.foo
binormal.foo
这两列定义名为“foo”的纹理坐标的切向量和副法线。
color
定 义RGBA颜色值。如果没有这一列,默认的顶点颜色为白色(除非调用nodePath.setColor()设置成另外的颜色)。OpenGL和 DirectX内部使用的颜色不同,前者的格式是Geom.NTUint8(或 Geom.NTFloat32),Geom.CColor,4元组;后者的格式为Geom.NTPackedDabc, Geom.CColor,1元组。实际上,你可以使用任何一种格式,Panda将自动进行必要的转换。
rotate
size
aspect_ratio
这3 列用于精灵(sprite)渲染(也就是,GeomPoints应用 nodePath.setRenderModeThickness()的效果)。如果存在,它们将分别控制旋转(逆时针,以度为单位),顶点的 thickness、square的纵横比。3者都应为Geom.NTFloat32,Geom.COther,1元组。
余下的名字只在进行顶点动画(vertex animation)时才有意义。例如实现Actor时。虽然下面也列出这些名字,但顶点动画作为Panda顶点表示法的高级特性,我们建议让Panda来设置顶点动画表,而不是你自己创建。
transform_blend
用 于控制顶点分配到1个或更多动画变换。本列中的值是对与GeomVertexData关联的TransformBlendTable的整数索引; TransformBlendTable的每个入口定义一种变换权重组合,因此通过索引,你可以为每个顶点找到不同的变换权重组合。
transform_weight
transform_index
这2 列总在一起,功能与transform_blend类似,但索引的是与GeomVertexData关联的TransformTable,而不是 TransformBlendTable。尤其适用于把顶点传到OpenGL或DirectX中进行动画,而不在CPU上运行动画。
column.morph.slider
有这样名字的列定义一个浮点的变形偏移(morph offset),被名为“ slider”的变形滑动条缩放后,加到名为“ column”的列上( slider column可以是任何名字 。用于在CPU上进行顶点动画。
列可以取任何名字(但在一个GeomVertexFormat内不同有重名)。如果出现本表以外的名字,Panda将不对该列采取任何处理,但将把它传给顶点着色器,顶点着色器使用vtx_ columnname参数来请求该数据。请参考顶点着色器输入列表。
GeomVertexColumn另外一些属性,在每一行数据里决定精确的偏移和字节对齐。除非要设计一个跟已存在的数据块相匹配的GeomVertexFormat,其他时候你不必担心这个问题。请参考自动生成的API说明。
 
GeomPrimitive
为了使用 GeomVertexData 中的顶点来渲染图形, Panda 需要提供某些类型的 GeomPrimitive ,它们对顶点表进行索引,指示 Panda 怎样把顶点连接成线、三角形或单独的点。
Panda 提供几种不同的GeomPrimitive对象,每一种表示一种图元。每个GeomPrimitive对象实际上存储几种不同的图元,每种图元由一个顶点 编号列表表示,索引存储在相关GeomVertexData里的顶点。对某些类型的GeomPrimitive,如GeomTriangles,所有图元 都必须有固定的顶点数(GeomTriangles顶点数为3);而其他类型,如GeomTristrips,每个图元都可以包含不同数量的顶点。
例如,一个包含3个三角形的GeomTriangles对象和一个包含2个三角形带的GeomTristrips如下图所示:
GeomTriangles
 
GeomTristrips
0
 
0
1
 
2
2
 
3
 
 
5
2
 
6
1
 
1
3
 
 
 
 
5
0
 
1
5
 
3
6
 
2
注意,GeomPrimitive对象本身并不包含顶点数据,它们包含的只是顶点的索引编号,用于在GeomVertexData对象里查找真正的顶点数据。索引和数据分开存放。
GeomTriangles (三角形)
最常用的GeomPrimitive类型。这种图元存储任意数量的连接的或不连接的三角形。当然,每个三角形必须只有3个顶点。从三角形正面看,每个三角形的顶点以逆时针顺序排列。
GeomTristrips (三角形带)
这种图元存储连接的三角形列,在某种排列下成为一个三角形带。在一个GeomTristrips对象里可以存储任意多个三角形带,每个三角形带可以有任意多个顶点(至少3个)。
三角形带的前三个顶点确定一个三角形,顶点以逆时针顺序排列。因此,每增加一个顶点即增加一个三角形,由一个新顶点和它前面的2个顶点构成。顶点来回地走之字形路线。
注意,三角形带的第2个三角形顶点顺序为顺时针,第3个变回逆时针,第4个又顺时针,如此往复。
某些硬件,尤其是老的SGI和一些游戏主机,都把三角形带用作减少少图形管线顶点数量的重要手段,因为大多数三角形(除了第一个)都可以只用1个顶点来定义。
现代PC显卡更喜欢通过重复顶点和合并三角形的方法把一组三角形带连接成一个很长的三角形带。Panda会自动完成该任务,但你必须保证每个三角形带的顶点数都为偶数。
此外,因为现代的PC显卡都集成了顶点高速缓存,所以能够快速渲染单独的、索引的三角形,丝毫不必三角形带慢。因此,三角形带的作用现在不再那么明显了。除非你有很好的理由使用GeomTristrips,否则请使用更简便的GeomTriangles。
GeomTrifans (三角形扇)
它 与GeomTristrips类似,可以包含任意多个三角形扇,每个三角形扇可以包含任意多个顶点。三角形扇的前三个顶点(逆时针顺序)确定一个三角形, 每增加一个顶点确定一个新的三角形。但是,它不使用之前的2个顶点来构造新三角形,而是用最早的和第一个顶点,也就是说所有三角形都出自一个点,如图:
和三角形带一样,三角形扇对某些硬件也是重要的优化手段。但在现在的PC上用它只能招来祸害,因为不可能在一个批处理里发送多于一个的三角形扇。所以不能在PC上使用三角形扇。使用GeomTriangles或GeomTristrips代替。
GeomLines (线段)
这种GeomPrimitive存储任意多条连接或不连接的线段。与GeomTriangles类似,只是线段代替了三角形。每条线段有且只有2个顶点。
默认条件下,线段的宽度为一个像素,无论离摄影机多远都保持不变。可以使用 nodePath.setRenderModeThickness()来改变线宽。如果给定一个大于1的数,线条将被渲染成指定像素宽的粗线。但线宽对于摄影机的距离仍然是保持不变的。
DirectX的渲染器不支持粗线,宽度参数将被忽略。
GeomLinestrips (线段带)
它与GeomTristrips对象相似:可以存储任意多条线段带,每条线段带可以包含任意多个顶点。线段带的头2个顶点定义了一条线段,接着每增加一个顶点就定义一条新的线段,与前一条线段首位相接。线段带可以用来近似地绘制曲线,弯曲很容易。
GeomPoints (点)
最简单的GeomPrimitive类型;存储许多个独立的点。每个点只有一个顶点。
默认情况下,每个点被渲染成一个像素。可以使用 nodePath.setRenderModeThickness()来改变点的大小。如果指定一个大于1的宽度,点将被渲染成正方形(始终朝向摄影机),正方形的中心位于顶点坐标处,边长为你指定的像素数。无论离摄影机多远,全部的点都保持一样大小。不像线段,DirectX支持粗点。
除了大小始终不变的常规粗点,你也可以使用 nodePath.setRenderModePerspective()让点随着摄像机的距离远近变大缩小,看起来更像3维场景里的物体。这一点在渲染精灵多边形时非常有用,例如用于粒子效果。事实上,Panda的 SpriteParticleRenderer 就是利用了这个渲染模式。(这个模式只适用与点,对线段没有效果。)
尽管精灵多边形被渲染成正方形,请记住它们实际上由一个顶点确定,每个顶点只提供一个 UV 坐标。意味着,每个精灵的整个多边形只有一个 UV 坐标。如果想在精灵表明应用纹理,使用 nodePath.setTexGen() TexGenAttrib.MPointSprite 模式。将为每个多边形生成 (0, 0) (1, 1) 的纹理坐标。然后,你可以对纹理坐标进行变换,如果你愿意,可以使用 nodePath.setTexOffset() setTexScale() 等方法。
 
Geom
Geom对象把一个GeomVertexData和一个或多个GeomPrimitive集中到一起组成一个可渲染的集合体。实际上,Geom就是Panda把场景细分后得到的最小的渲染单位。在任何一帧画面,要么渲染整个Geom,要么不渲染,它不可再分开。
幸好,Geom非常简单。它包含指向一个GeomVertexData的指针,以及一个或多个不同的GeomPrimitives组成的列表。全部GeomPrimitives都索引同一个GeomVertexData的数据。
Geom
GeomVertexData
 
GeomTriangles
GeomTriangles
GeomTristrips
每个Geom的GeomVertexData指针可以不同,多个Geom也可以共用一个GeomVertexData(每个Geom使用的顶点数据都是不同的子集)。而且,尽管通常每个Geom的GeomPrimitive对象都不同,它们可以在不同的Geom间共享。
虽 然一个Geom可以有任意多个GeomPrimitive,但所有的GeomPrimitive必须是同一种基本类型的图元:三角形、线段或点。一个 Geom可以包含GeomTriangles、GeomTristrips和 GeomTrifans;或者包含GeomLines和GeomLinestrips;或者包含GeomPoints。但就是不能包含不同的基本类型。你 可以调用 geom.getPrimitiveType()检查这个Geom的基本图元类型。
 
GeomNode
GeomNode是把Geom粘贴到scene graph的胶水。一个GeomNode包含一个或多个Geom。
GeomNode
Geom
RenderState
Geom
RenderState
Geom
RenderState
GeomNode类从PandaNode中继承而来,所以想其他节点一样它直接连接到scene graph,而且也会从父节点继承变换和渲染状态(render state)。这些变换和状态应用到节点的每个Geom上。
此外,GeomNode为每个Geom存储一个额外的渲染状态。这就使GeomNode内的每个Geom都可以有自己独一无二的状态。例如,每个Geom应用不同的纹理。
当 从egg文件载入一个模型时,渲染所需的全部状态通常都将存储在每个Geom的状态里,而不是在GeomNode这一层面存储。这些单个的Geom状态将 覆盖继承自scene graph的渲染状态,除非scene graph的状态拥有比默认的0更高的优先级。(这就是为什么如果你想换掉egg文件里应用到模型的纹理,就必须在调用 nodePath.setTexture()时指定第二个参数是1的原因)
 
To be continued
 
Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐