OpenGL显示3DS模型若干问题的研究
2010-07-07胡平平刘建明王晶杰
胡平平, 刘建明, 王晶杰
(1. 北京信息科技大学,北京 100192;2. 国网信息通信有限公司,北京 100761)
OpenGL是一个著名的开放式三维图形开发函数库,它提供的函数不仅可以创建基本的 3D物体原形还能够很方便地对 3D物体进行各种操作和变换,得到逼真的 3D显示效果,这使它成为一个非常流行的 3D图形浏览开发工具。但OpenGL却不适于创建复杂的不规则 3D物体。3DS是Autodesk公司存储3D模型数据的一种文件格式,许多流行的 3D建模软件和图形格式转换工具都能够生成 3D模型的 3DS文件,将OpenGL技术和3DS模型数据结合实现对3D物体的浏览便成为一种广泛使用的方法。
由于3DS文件的数据格式没有官方的正式说明文档,尽管很多文献[1-3]对用OpenGL读取、显示和控制3DS模型数据的方法进行了介绍,但都侧重于OpenGL编程实现方法,只涉及了基本的静态3DS模型数据的使用。各种资源上能够得到的读取3DS数据的方法基本上一样,都没有对动态数据进行处理,也没有考虑数据转换的性能。用这些方法显示含动画信息的3DS模型时会出现物体位置错位的现象,即使是静态数据,当模型复杂且数据量较大时,其处理速度常常慢到不能实用的地步。
本文从OpenGL显示3D模型的数据处理过程出发,结合3DS文件数据的组织结构,扼要介绍了如何由3DS文件构造OpenGL显示数据的方法。重点讨论了法线向量在OpenGL显示中的两种应用效果和由3DS数据生成法线向量的方法,给出了一种高效的法线向量求解算法,该算法的速度比现有方法快一至两个数量级,极大地提高了数据转换的性能。本文还介绍了3DS模型中和动画有关的关键帧数据的使用方法,解决了现有方法在显示带动画信息的3DS模型时物体位置不正确的问题。
1 OpenGL和3DS数据的关系
1.1 OpenGL的数据处理过程
OpenGL显示三维物体的一般过程是:保存当前变换矩阵、设置新的视景体和新的变换矩阵、设置每一个三维物体的数据、恢复当前变换矩阵。设置三维物体数据的过程是由一系列嵌入在glBegin()和glEnd()调用之间的OpenGL函数调用构成的,其过程如下所示:
对每一个三维物体:
若该物体没有纹理或各面有相同纹理映射则glBegin();
对物体的每一个面:
若该面有纹理映射则设置纹理;
若该物体不同面有不同的纹理则glBegin();
设置以面为单位的面法线向量;
设置纹理或材质的颜色;
对每一个面的所有顶点:
设置以顶点为单位的顶点法线向量;
若该面有纹理则设置顶点的纹理坐标;
设置顶点坐标;
若该物体不同面有不同的纹理则glEnd();
若该物体没有纹理或各面有相同纹理映射glEnd();
在上面的过程中,以面为单位的法线向量设置和以顶点为单位的法线向量设置是两种不同的方式,下文将详细介绍。如果物体没有纹理映射或各面的纹理映射相同,则 glBegin()和 glEnd()以物体为单位,否则应该以每一个面为单位,这样不同的面才能显示不同的纹理。
1.2 3DS文件数据的组织结构
关于3DS文件的格式,文献[4]和文献[5]有较详细的介绍,此处仅列出和OpenGL显示数据有关部分的组织结构:
3DS文件数据:
文件版本数据(ID=0x0002);
编辑数据(ID=0x3D3D):
. 材质数据(ID=0xAFFF);
. . 材质名称数据(ID=0xAFFF);
. . 材质颜色数据(ID=0xA010/20/30);
. . 材质纹理数据(ID=0xA300);
. 物体数据(ID=0x4000):
. 物体名称数据;
. 物体顶点数据(ID=0x4110);
. 物体面数据(ID=0x4120);
. . 面材质数据(ID=0x4130);
. . 面材质名称;
. . 同材质面索引数据;
. 物体转换矩阵数据(ID=0x4160);
关键帧数据(ID=0xB000):
关键帧名称(ID=0xB00A);
关键帧起止帧数据(ID=0xB008);
物体关键帧数据(ID=0xB002);
物体关键帧标识数据(ID=0xB030);
物体关键帧名称和层次数据
(ID=0xB010/11);
物体关键帧支点坐标数据(ID=0xB013);
物体关键帧移动数据(ID=0xB020);
物体关键帧转动数据(ID=0xB021);
物体关键帧缩放数据(ID=0xB022);
1.3 OpenGL显示数据和3DS数据的关系
从上面的介绍可知,OpenGL显示处理是以物体为单位进行的,3DS的数据则按照类型组织,对应于OpenGL中同一个物体的数据要从3DS文件中不同类型的数据获得,具体方法是:
物体的顶点和面数据:直接来自3DS的物体顶点数据(x,y,z坐标值)和物体面数据(该面所用顶点的索引号和方向)。
物体的材质数据:由3DS物体面数据的面材质名称数据得到材质名称,再由该材质名称到3DS材质数据中找到对应的材质颜色和纹理数据。而物体面数据的同材质面索引数据则指出哪些面使用这个材质。
物体的位置数据:该数据由物体顶点数据、物体转换矩阵数据和关键帧数据共同决定,前两种数据从物体数据中直接得到,后一个则来自物体对应的关键帧数据。具体生成方法,在本文后面介绍。
物体的法线向量数据:由上述得到的物体顶点和面数据通过计算得到,具体方法见下文。
2 OpenGL中法线向量的作用和求解方法
2.1 法向量的作用和效果
OpenGL显示三维物体时,需要为每一个顶点设置法线向量,法线向量不仅为OpenGL光照处理确定每一个面的正反提供依据,而且还影响到物体的显示效果[6-7]。OpenGL法向量的设置有两种方法:以面为单位和以顶点为单位,前一种方法每个面的所有顶点使用相同的法向量,后一种方法每个面的不同顶点有不同的法向量(又称平均法向量)。图1和图2是同一个3D模型用两种法向量设置方法的显示效果图。
从上面的效果图可以看出,以顶点为单位的法向量方法显示的物体比较平滑,这也是OpenGL显示3DS模型时最常用的方法。
图1 法向量以面为单位的显示效果
图2 法向量以顶点为单位的显示效果
2.2 现有的法向量求解方法
3DS模型并没有直接提供OpenGL显示用的法向量数据,这些数据要从3DS模型的面数据中求解得到。3DS模型的面是由3个顶点和一个方向码构成的三角面片,读入3DS模型的面数据后,首先要统一各个面的方向(必要时要根据方向码调整3个顶点的顺序),然后按特点顺序由三角形的两个边向量的叉积求出该三角形的法向量。如果采用以面为单位的法向量方法,则构成三角形的3个顶点的法向量就是该三角形的法向量;如果采用以顶点为单位的法向量方法,由于同一个顶点可能为不同的三角形共用,此时一个顶点的法向量就是使用该顶点的所有三角形法向量的平均向量。现有的顶点法向量求解方法如下所示:
对每一个三维物体:
对物体的每一个面:
计算该面的法向量并归一化;
对物体的每一个顶点:
在物体所有面中寻找用到该顶点的面并将该面的法向量叠加;
叠加法向量平均并归一化后作为该顶点的法向量;
该方法在求某一个顶点的法向量时,要遍历物体所有面的3个顶点,其运算量是:顶点数量X面数量X3,由于是先求出所有面的法向量然后再使用,因此,计算过程中还要为每一个面设置一个法向量存储空间。
2.3 改进的法向量求解方法
3D物体模型中,一个顶点可能被多个面使用,但用到某一个顶点的面占全部面的比例往往很小。一般情况下,简单模型的比例最大约10%,复杂大型模型的比例最大约1%,可见现有算法为求某一个顶点的法向量而遍历所有面的处理大部分是不必要的。鉴于此,可以对顶点法向量的求解方法改进为如下所示:
对每一个三维物体:
将该物体所有顶点的法向量设置为0;
对物体的每一个面:
计算该面的法向量;
将该面的法向量叠加到该面 3个顶点的法向量中;
对物体的每一个顶点:
将顶点的法向量除以其叠加次数并归一化;
上述新方法在求顶点的法向量时避免了对不使用该顶点的面的判断处理,且新方法不用为每一个面设置一个法向量存储空间,只需为每一个顶点设置一个叠加计数器变量,由于计数器变量所使用的空间只是法向量的1/6〔计数器为两字节整数,法向量为3个浮点数,用12字节〕,而顶点的数量最多是面数量的3倍,故新方法使用的变量空间最多是现有方法的1/2。
2.4 两种法向量求解方法的比较
新顶点法向量的求解方法在使用变量空间上至少节省一半,而计算量的节省情况则和 3D模型的具体情况有关。只有当使用每一个顶点的面的数量和面的总数量相等时,现有算法才不存在无用的处理,此时两种算法的处理量几乎相等,但该情况意味着每一个顶点被所有的面共用,这是不可能存在的,因此新算法的处理量总是小于现有方法。
表1是两种处理方法对几种典型模型的实际处理时间对比,可以看出,模型的数据量越大,尤其是顶点被共用的面的数量相对于面的总数量越小,新方法的改进效果就越明显。对于一般模型数据,新方法的速度平均是现有方法的几倍至几十倍,而对大型模型,速度提高的效果非常明显,约为几百倍。
表1 两种法向量求解方法的计算时间比较
3 3DS模型关键帧数据的使用方法
不带动画信息的3DS模型中各物体的顶点数据可以直接作为这些物体的实际位置坐标数据,但当3DS模型带动画信息时,模型中各物体的实际位置一般由顶点数据和关键帧数据共同决定,如果仅按顶点数据显示物体,则模型中的物体可能会显示在错误的位置上。
3DS模型中每一个物体的关键帧信息由物体的名称或关键帧的层次结构决定。关键帧的层次关系是一个树状结构,当某一个物体是根节点或根节点的叶节点时,其位置信息仅由它自己的关键帧数据决定;否则该物体的位置信息要由它自己的关键帧数据和它所有父节点的关键帧数据共同决定。
3DS模型中各种关键帧数据块的结构在文献[4]和文献[5]中有一定的介绍,但这些数据的具体含义和使用方法都没有介绍。下面仅对与物体位置有关的关键帧数据含义和使用方法做一个简单介绍,至于如何利用关键帧数据在OpenGL环境中显示可以运动的3DS模型,由于篇幅所限,此处不做介绍。
3.1 物体关键帧数据的确定方法
3DS模型中每一个关键帧数据都有自己的名称,该名称在名称层次块(0xB010)中,如果其中的名称不是“DUMMY”,则该关键帧数据就属于名称与之匹配的物体,否则,该关键帧数据属于一个虚物体,该物体是一个父节点,它的实际名称在后面的虚名称块(0xB011)中,而该父节点的关键帧数据为其所有子节点共用,它所含子节点的关键帧数据位于其后,由子节点关键帧数据的层次号决定。
在关键帧数据中,每一个物体的关键帧数据(0xB002)都有一个位于名称层次块(0xB010)中的层次号,层次号是由该关键帧的层次关系和它在关键帧数据中出现的位置决定的,其编排方法是:根关键帧的层次号为-1,其它关键帧的层次号由0开始递增或保持不变。层次号保持不变,说明这些关键帧属于同一个层次;层次号增加则表示该关键帧是上一个节点的子节点;层次号减小,则说明该关键帧是一个新的父节点,该节点的层次同前面与之层次号相同的节点一样,每一个父节点之后新子节点关键帧的层次号就是该关键帧数据出现的实际位置的顺序号(新子节点关键帧的层次号一定是一个没有出现过的值)。以后各关键帧的层次号依上述规律编排,直到所有的关键帧数据编排完毕。
3.2 物体位置和关键帧数据的关系
3DS模型中物体的位置由以下数据决定:物体的顶点坐标数据(0x4110)、转换矩阵数据(0x4160)中的源点坐标和物体关键帧数据中的支点坐标数据(0xB013)、移动数据(0xB020)、旋转数据(0xB021)及缩放数据(ID=0xB022)。
3DS模型中不带动画信息的物体也有一帧关键帧数据,只不过其关键帧数据的支点坐标和旋转关键帧数据都是0,缩放数据是1。带动画信息的物体则按它具有关键帧的多少带有指定数量的关键帧数据。物体转换矩阵数据中的源点坐标表示该物体当前位置和它被创建时原始位置的位移,该数据和物体关键帧数据第0帧的移动数据相同。物体的顶点坐标实际上就是由物体被创建时的初始位置和大小按照其第0帧关键帧数据做变换(平移、旋转和缩放)生成的。而物体关键帧数据中的支点坐标则是顶点坐标表示的位置到当前位置的平移量,因此,为了显示物体正确的当前位置,应该将物体的顶点坐标进行调整,方法是:将物体各顶点坐标减去其关键帧数据的支点坐标。
4 结 论
本文给出的由3DS模型数据求解顶点法向量的新方法和关键帧数据的使用均成功地应用到作者开发的多屏同步 3D显示系统中,不仅实现了大型3DS模型的快速装入和带动画信息的3DS模型物体位置的正确显示,还实现了带动画信息的3DS模型物体的运动显示,效果良好。
[1]赵文广, 李仲学, 李翠平. 面向工程可视化仿真的VC++, OpenGL与3DS集成技术[J]. 北京科技大学学报, 2001, 23(6): 563-565.
[2]殷素峰, 高雪强, 杨胜强. 在 OpenGL环境下开发3DS文件浏览器[J]. 工程图学学报, 2005, 26(6):22-25.
[3]尹士伟, 张光年, 郭新宇. 一种控制3DS模型的新方法的研究与实现[J]. 微计算机信息, 2007, 23(3-2):307-308.
[4]Martin van Velsen.3D-Studio File Format(.3ds) [EB/OL].http://www.the-labs.com/Blender/3dsspec.html,1998.10.5.
[5]Jeff Lewis.The Unofficial 3DStudio 3DS File Format[EB/OL].http://www.the-labs.com/Blender/3DS-details.html,1998.10.5.
[6]乔 林, 费广正, 林 杜, 等. OpenGL程序设计[M].北京: 清华大学出版社, 2000. 194-202.
[7][美]Dave Shrieiner, Mason Woo等. OpenGL编程指南(第 4版)[M]. 邓郑祥译.北京:人民邮电出版社,2005. 44-45.