• COLLADA DOM Tutorial


    引言

      COLLADA是一个开放的标准,最初用于3D软件数据交换,由SCEA发起,现在则被许多著名厂家支持如Autodesk、XSI等。COLLADA不仅仅可以用于建模工具之间交换数据之用,也可以作为场景描述语言用于小规模的实时渲染。因为COLLADA DOM拥有丰富的内容用于表现场景中的各种元素,从多边形几何体到摄像机无所不包。我们可以通过COLLADA DOM库来进行场景文件的读取与处理操作。

    提示

      COLLADA DOM的编程方式类似COM

    苏醒

      从这里下载COLLADA DOM

      http://sourceforge.net/projects/collada-dom/

      准备好你的IDE/编译器,Windows平台下推荐Visual Studio 8,LINUX/UNIX平台下看各路英豪自己的了。

      推荐下载安装包,会省掉不必要的重新编译的工作。我向来最讨厌重新编译别人的库,一来是时间宝贵,编译的时候自己不可能看到任何有意义的东西,二来很多时候编写这些库的时候引用了特定版本的其它库,导致自己还需要去下载其它的库,非常麻烦。

      安装好后记得在VC的工程目录加入COLLADA的头文件和库文件文件夹路径,否则什么都找不到。

    开始

      首先在C++源文件中加入COLLADA DOM所需要的头文件

    #include <dae.h>
    #include 
    <dom/domCOLLADA.h>
      下面写代码,打开一个DAE XML文件。
    int main(int argc, char** argv)
    {
        DAE 
    *collada_dom = new DAE();//创建一个DOM解析器
        daeInt error = collada_dom->load("file:///C:/Test/colladaDocument.dae");//打开一个放在C盘Test文件夹下一个名为colladaDocument.dae的文档
        error = collada_com->unload();//关闭刚才打开的文档
        return 0;//程序返回
    }
    一切都是很简单的。载入文档,获得一个根指针,而后一切的操作都是从这个指针开始逐级的向下遍历、转换。为什么load函数中不是我们所想象的"C:\\Test\\colladaDocument",而是加了个file前缀。COLLADA DOM支持在处理DAE的时候使用URI直接定位到资源,详细的可以看附带的文档。

      现在来点复杂的,读取一个几何体。在实际编码前,我们需要理解一个概念,就是Shape与Instance的区别。假如场景中有10000个立方体,那么我们其实只需要储存8个顶点、向量、三角形索引,然后我们指定这10000个立方体各自的变换、Shader参数就可以了。使用COLLADA DOM处理场景中几何体的思路就是,先获得Geometry(也就是我们所知道的Shape),而后获得Instance。在对unload()的调用前增加下面一行代码,

    int geometryElementCount = (int)(collada_dom->getDatabase()->getElementCount(NULL, "geometry", NULL));

      这个时候我们就获得了几何体的确切数目,然后遍历获得各自的数据。再添加一个循环,

    for(int currentGeometry=0;currentGeometry<geometryElementCount;currentGeometry++)
    {
        domGeometry 
    *thisGeometry = 0;
        m_dae
    ->getDatabase()->getElement((daeElement**)&thisGeometry,currentGeometry,NULL, "geometry");
        domMesh 
    *thisMesh = thisGeometry->getMesh();
    }

      先不要继续添加代码,先最好定义一种我们的程序要使用的物体格式。比如,可以这样,

    struct CObject
    {
        string m_sName;
        size_t m_iVertexNum;
        size_t m_iNormalNum;
        float* m_pVertices;
        float* m_pNormals;
        size_t m_iTriangleNum;
    };

      我们就可以直接调用glDrawArrays去绘制这个物体。以后为了提高效率甚至可以把所有顶点都上传到Vertex Buffer Object中,这样就不需要每次绘制的时候把顶点、向量、纹理坐标都上传一遍了。下面继续补全代码,

    std::vector<CObject*> ObjectShapes;
    for(int currentGeometry=0;currentGeometry<geometryElementCount;currentGeometry++)
    {
        CObject
    * pShape = new CObject;
        domGeometry 
    *thisGeometry = 0;
        m_dae
    ->getDatabase()->getElement((daeElement**)&thisGeometry,currentGeometry,NULL, "geometry"); //逐个的找到每个Geometry Shape
        domMesh *thisMesh = thisGeometry->getMesh();//取得Mesh
        domListOfFloats vertexArray = thisMesh->getSource_array()[0]->getFloat_array()->getValue();//取得储存顶点的数组
        domListOfFloats normalArray = thisMesh->getSource_array()[1]->getFloat_array()->getValue();//取得储存向量的数组
        domListOfUInts indexArray = thisMesh->getTriangles_array()[0]->getP()->getValue();//取得三角形索引
        pShape->m_iTriangleNum = indexArray.getCount() / 6;//看下面的解释
        pShape->m_iVertexNum = vertexArray.getCount() / 3;//每个顶点由3个数字组成
        pShape->m_iNormalNum = normalArray.getCount() / 3;//每个向量也由3个数字组成
        printf("%u %u %u\n", pShape->m_iTriangleNum, pShape->m_iVertexNum, pShape->m_iNormalNum);//再次打印一下
        ObjectShapes.push_back(pShape);
    }

    Exporter

      我们知道从MAYA导出的OBJ格式可以不是三角形,通过COLLADA插件导出的物体也一样,我们可以选择三角化或者保持原样。假如我们不选择三角化,那么对于一个简单的CUBE来说,它的表示可能是这样的,

    <polylist material="initialShadingGroup" count="6">
      
    <input semantic="VERTEX" source="#pCubeShape1-vertices" offset="0"/>
      
    <input semantic="NORMAL" source="#pCubeShape1-normals" offset="1"/>
      
    <vcount>4 4 4 4 4 4</vcount>
      
    <p>0 0 1 1 3 2 2 3 2 4 3 5 5 6 4 7 4 8 5 9 7 10 6 11 6 12 7 13 1 14 0 15 1 16 7 17 5 18 3 19 6 20 0 21 2 22 4 23</p>
    </polylist>

      这里vcount的意思是每个POLYGON由多少个顶点向量对组成,列表可以让大家明白的更容易一些,

    Polygon Vertex Index Normal Index
    0 0 1 3 2 0 1 2 3
    1 2 3 5 4 4 5 6 7

      也就是说,索引数值遵照“顶点 向量 顶点 向量”这样的顺序排列,即使有了UV也一样。

    <triangles material="initialShadingGroup" count="12">
      
    <input semantic="VERTEX" source="#pCubeShape1-vertices" offset="0"/>
      
    <input semantic="NORMAL" source="#pCubeShape1-normals" offset="1"/>
      
    <p>0 0 1 1 2 3 1 1 3 2 2 3 2 4 3 5 4 7 3 5 5 6 4 7 4 8 5 9 6 11 5 9 7 10 6 11 6 12 7 13 0 15 7 13 1 14 0 15 1 16 7 17 3 19 7 17 5 18 3 19 6 20 0 21 4 23 0 21 2 22 4 23</p>
    </triangles>

      三角化后一切看似都变多了,其实原理依旧,

    Triangle Vertex Index Normal Index
    0 0 1 2 0 1 3
    1 1 3 2 1 2 3

      了解了这个之后,让我们再次把代码补全,将所有三角化后几何体按照顺序储存到数组里去让OpenGL直接渲染。

    std::vector<CObject*> ObjectShapes;

    for(int currentGeometry=0;currentGeometry<geometryElementCount;currentGeometry++)
    {
        CObject
    * pShape = new CObject;
        domGeometry 
    *thisGeometry = 0;
        m_dae
    ->getDatabase()->getElement((daeElement**)&thisGeometry,currentGeometry,NULL, "geometry"); //逐个的找到每个Geometry Shape
        domMesh 
    *thisMesh = thisGeometry->getMesh();//取得Mesh
        domListOfFloats vertexArray 
    = thisMesh->getSource_array()[0]->getFloat_array()->getValue();//取得储存顶点的数组
        domListOfFloats normalArray 
    = thisMesh->getSource_array()[1]->getFloat_array()->getValue();//取得储存向量的数组
        domListOfUInts indexArray 
    = thisMesh->getTriangles_array()[0]->getP()->getValue();//取得三角形索引

        pShape
    ->m_iTriangleNum = indexArray.getCount() / 6;//看下面的解释
        pShape->m_iVertexNum = vertexArray.getCount() / 3;//每个顶点由3个数字组成
        pShape->m_iNormalNum = normalArray.getCount() / 3;//每个向量也由3个数字组成
        printf("%u %u %u\n", pShape->m_iTriangleNum, pShape->m_iVertexNum, pShape->m_iNormalNum);//再次打印一下
        pShape->m_pVertices = new float[pShape->m_iTriangleNum*3*3];
        pShape
    ->m_pNormals = new float[pShape->m_iTriangleNum*3*3];

        ObjectShapes.push_back(pShape);

        size_t _V[
    3],_N[3];
       
    for( size_t i = 0; i < cube.m_iTriangleNum; i++ ){
            size_t offset 
    = i*6;
            _V[
    0= indexArray.get(offset+0);
            _N[
    0= indexArray.get(offset+1);
            _V[
    1= indexArray.get(offset+2);
            _N[
    1= indexArray.get(offset+3);
            _V[
    2= indexArray.get(offset+4);
            _N[
    2= indexArray.get(offset+5);

            offset 
    = i*3*3;
            for( size_t j=0; j < 3; j++ ){
                pShape
    ->m_pVertices[offset+0= vertexArray.get(_V[0]*3+0);
                pShape
    ->m_pVertices[offset+1= vertexArray.get(_V[0]*3+1);
                pShape
    ->m_pVertices[offset+2= vertexArray.get(_V[0]*3+2);
                pShape
    ->m_pVertices[offset+3= vertexArray.get(_V[1]*3+0);
                pShape
    ->m_pVertices[offset+4= vertexArray.get(_V[1]*3+1);
                pShape
    ->m_pVertices[offset+5= vertexArray.get(_V[1]*3+2);
                pShape
    ->m_pVertices[offset+6= vertexArray.get(_V[2]*3+0);
                pShape
    ->m_pVertices[offset+7= vertexArray.get(_V[2]*3+1);
                pShape
    ->m_pVertices[offset+8= vertexArray.get(_V[2]*3+2);
                pShape
    ->m_pNormals[offset+0= normalArray.get(_N[0]*3+0);
                pShape
    ->m_pNormals[offset+1= normalArray.get(_N[0]*3+1);
                pShape
    ->m_pNormals[offset+2= normalArray.get(_N[0]*3+2);
                pShape
    ->m_pNormals[offset+3= normalArray.get(_N[1]*3+0);
                pShape
    ->m_pNormals[offset+4= normalArray.get(_N[1]*3+1);
                pShape
    ->m_pNormals[offset+5= normalArray.get(_N[1]*3+2);
                pShape
    ->m_pNormals[offset+6= normalArray.get(_N[2]*3+0);
                pShape
    ->m_pNormals[offset+7= normalArray.get(_N[2]*3+1);
                pShape
    ->m_pNormals[offset+8= normalArray.get(_N[2]*3+2);
            }
        }
    }

      这样,我们就可以使用OpenGL渲染了,

    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_NORMAL_ARRAY);
    forint i=0; i<ObjectShapes.size(); i++ ){
        glVertexPointer(
    3,GL_FLOAT,0,ObjectShapes[i]->m_pVertices);
        glNormalPointer(GL_FLOAT,
    0,ObjectShapes[i]->m_pNormals);
        glDrawArrays(GL_TRIANGLES,
    0,ObjectShapes[i]->m_iTriangleNum*3);
    }
    glDisableClientState(GL_VERTEX_ARRAY);
    glDisableClientState(GL_NORMAL_ARRAY);

      在这里可能会有疑问,为什么不使用索引的方式绘制,而是把所有的三角形全部分开,因为导出的场景向量与顶点的数目、位置都不统一,导致索引“顾此失彼”全然无序,虽然说可以修正,但是那样代码量就多了起来,而且无法应用OOCSX的方法简化复杂几何体。

    关于调试方法

      COLLADA DOM在操作过程中几乎都是与指针打交道,在开始不熟悉的情况下频频访问违规出错等等是很正常的,只要注意老老实实的调用getElementName()、getTypeName()、getCount()查看当前操作对象的名称和元素数据,而后逐步的找到自己需要的资源。

    性能建议

      COLLADA DOM的底层使用的是SAX进行XML文件的访问操作,构建于LibXML2库之上,所以我推荐从DAE文件头开始依次处理Geometry、Visual Scene等等,减少运行库在来回搜索的损耗。默认COLLADA DOM是静态库,导致链接后的程序着实非常巨大,所以推荐使用动态链接。

  • 相关阅读:
    jquery 实现 html5 placeholder 兼容password密码框
    php返回json的结果
    使用PHP读取远程文件
    Sharepoint 自定义字段
    Sharepoint 中新增 aspx页面,并在页面中新增web part
    【转】Sharepoint 2010 配置我的站点及BLOG
    JS 实现 Div 向上浮动
    UserProfile同步配置
    【转】Import User Profile Photos from Active Directory into SharePoint 2010
    Sharepoint 2010 SP1升级后 FIMSynchronizationService 服务无法开启
  • 原文地址:https://www.cnblogs.com/Jedimaster/p/979256.html
Copyright © 2020-2023  润新知