OpenGL:打开PLY文件及定点绘图

一、Ply简介

  • 本次实验用opengl读取.ply文件,个人理解,ply里面存储的是多边形模型的数据,包括一些点、面、材质、颜色等属性。

  • 比较严谨的解释:

    • PLY作为一种多边形模型数据格式,不同于三维引擎中常用的场景图文件格式和脚本文件,每个PLY文件只用于描述一个多边形模型对象(Object),该模型对象可以通过诸如顶点、面等数据进行描述,每一类这样的数据被称作一种元素(Element)。相比于现代的三维引擎中所用到的各种复杂格式,PLY实在是种简单的不能再简单的文件格式,但是如果仔细研究就会发现,就像设计者所说的,这对于绝大多数的图形应用来说已经是足够用了。

二、Ply中的结构

1、PLY的文件结构:

 文件头加上元素数据列表。其中文件头中以行为单位描述文件类型、格式与版本、元素类型、元素的属性等,然后就根据在文件头中所列出元素类型的顺序及其属性,依次记录各个元素的属性数据。

2、 典型的PLY文件结构:

  • 1、 头部
  • 2、顶点列表
  • 3、面片列表
  • 4、 其他元素列表

3、简单举例

  • 1) 头部

    • a.头部是一系列以回车结尾的文本行,用来描述文件的剩余部分。
    • b. 头部包含一个对每个元素类型的描述,包括元素名(如“边”),元素的数量,以及一个与这个元素关联的不同属性的列表。
    • c.头部还说明这个文件是二进制的或者是ASCII的。
    • d.头部后面的是一个每个元素类型的元素列表,按照在头部中描述的顺序出现。
    • e.文件中的注释一般在 “comment”开始的关键词定义行里。
  • 2) 举例:下面是一个立方体的完整ASCII描述。大括号中的注释不是文件的一部分,它们是这个例子的注解。

    • a.头部的每个部分都是一个以关键词开头,以回车结尾的ASCII串。
    • b.”ply”是文件的头四个字符。
    • c.关键词“format”及其后面的是一个特定的ASCII或者二进制的格式,接下来是一个版本号。
    • d.end_header 头文件结束
    • f.再下面是多边形文件中每个元素的描述,在每个元素里还有多属性的说明。一般元素以下面的格式描述: 
      element <元素名> <在文件中的个数> 
      property <数据类型> <属性名-1> 
      property <数据类型> <属性名-2> 
      property <数据类型> <属性名-3>
ply 
format   ascii   1.0   {   ascii/二进制,格式版本数   } 
comment   made   by   anonymous   {   注释关键词说明,像其他行一样   } 
comment   this   file   is   a   cube 
element   vertex   8   {   定义“vertex”(顶点)元素,在文件中有8个   } 
property   float32   x   {   顶点包含浮点坐标“x”} 
property   float32   y   {   y   坐标同样是一个顶点属性   } 
property   float32   z   {   z   也是坐标   } 
element   face   6   {   在文件里有6个“face”(面片)   } 
property   list   uint8   int32   vertex_index   {   “vertex_indices”(顶点素引)是一列整数   } 
end_header   {   划定头部结尾   } 
0   0   0   {   顶点列表的开始   } 
0   0   1 
0   1   1 
0   1   0 
1   0   0 
1   0   1 
1   1   1 
1   1   0 
4   0   1   2   3   {   面片列表开始   } 
4   7   6   5   4 
4   0   4   5   1 
4   1   5   6   2 
4   2   6   7   3 
4   3   7   4   0 
  • 3) 其他说明 
    • 属性罗列在“element”(元素)行后面定义,既包含属性的数据类型,也包含属性在每个元素中出现的次序。一个属性可以有三种数据类型:标量,字符串和列表。属性可能具有的标量数据类型列表如下。
    • 这些字节计数很重要,而且在实现过程中不能修改以使这些文件可移植。
    • 使用列表数据类型的属性定义有一种特殊的格式:property list <数值类型> <数值类型> <属性名> ,这种格式,一个非负字符表示在属性里包含多少索引,接下来是一个列表包含许多整数。在这个边长列表里的每个整数都是一个顶点的索引。
          名称      类型           字节数 
         ------------------------------- 
         int8        字符          1 
         uint8      非负字符        1 
         int16      短整型          2 
         uint16    非负短整型       2 
         int32      整型           4 
         uint32    非负整型         4 
         float32   单精度浮点数     4 
         float64   双精度浮点数     8

三、读取.ply文件

读取的方法: 
1、在头文件信息中读取顶点数量和面数量(也可同时确定每个顶点的属性种类和数量)。 
2、在文件的数据部分读取所有顶点信息,及面信息。 
3、由顶点信息绘制图形。

PlyLoader.cpp

#include "PLYLoader.h"

CPLYLoader::CPLYLoader()
{
    this->m_totalConnectedQuads = 0;
    this->m_totalConnectedPoints = 0;
    m_ModelData.iTotalConnectedTriangles = 0;
}

int CPLYLoader::LoadModel(char* filename)
{
    //Loading hint
    printf("Loading %s...\n",filename);
    char* pch = strstr(filename,".ply");

    //if file isn's null, go on reading
    if (pch != NULL)
    {
        FILE* file = fopen(filename,"r");
        if (!file)
        {
            printf("load PLY file %s failed\n",filename);
            return false;
        }
        //comfirm the size of the file
        fseek(file,0,SEEK_END);
        long fileSize = ftell(file);

        try
        {
            mp_vertexXYZ = (float*) malloc (ftell(file));
            mp_vertexNorm = (float*) malloc (ftell(file));
            mp_vertexRGB = (float*) malloc(ftell(file));
        }
        catch (char* )
        {
            return -1;
        }
        if (mp_vertexXYZ == NULL) return -1;
        if (mp_vertexNorm == NULL) return -2;
        if (mp_vertexRGB == NULL) return -3;

        //go to the begining of the file
        fseek(file,0,SEEK_SET); 

        if (file)
        {
            int i = 0;   
            int temp = 0;
            int quads_index = 0;
            int triangle_index = 0;
            int normal_index = 0;
            int colorIndex = 0;
            char buffer[1000];

            //read a line once
            fgets(buffer,300,file);         


            // READ HEADER
            // -----------------

            // Find number of vertexes
            while (  strncmp( "element vertex", buffer,strlen("element vertex")) != 0  )
            {
                //printf("%s\n", buffer);

                fgets(buffer,300,file);         
            }
            strcpy(buffer, buffer+strlen("element vertex"));

            //the second parament is like regular expression.
            //%i can automately translate the octonary number into the decimal number.
            sscanf(buffer,"%i", &this->m_totalConnectedPoints);


            //print test
            //printf("totalPoints:%d\n", this->m_totalConnectedPoints);

            // Find number of face
            fseek(file,0,SEEK_SET);
            while (  strncmp( "element face", buffer,strlen("element face")) != 0  )
            {
                //printf("%s\n", buffer);

                fgets(buffer,300,file);         // format
            }
            strcpy(buffer, buffer+strlen("element face"));
            sscanf(buffer,"%i", &this->m_totalFaces);

            //printf("totalQuads:%d\n", this->m_totalFaces);

            // go to end_header
            while (  strncmp( "end_header", buffer,strlen("end_header")) != 0  )
            {
                fgets(buffer,300,file);         
            }

            //----------------------


            // read vertices
            i =0;
            for (int iterator = 0; iterator < this->m_totalConnectedPoints; iterator++)
            {
                char tmp[1];
                fgets(buffer,300,file);
                //有的顶点元素可能存在3个属性,分别为坐标,法向量、颜色,但是最基本的,如上述正方形,仅有坐标属性
                sscanf(buffer,"%f %f %f %f %f %f %c %f %f %f", &mp_vertexXYZ[i], &mp_vertexXYZ[i+1], &mp_vertexXYZ[i+2],
                    &mp_vertexNorm[i], &mp_vertexNorm[i+1], &mp_vertexNorm[i+2],
                    tmp,
                    &mp_vertexRGB[i], &mp_vertexRGB[i+1],  &mp_vertexRGB[i+2]);

                //print test

                //printf("%f %f %f %f %f %f %c %f %f %f\n", mp_vertexXYZ[i], mp_vertexXYZ[i+1], mp_vertexXYZ[i+2],
                //  mp_vertexNorm[i], mp_vertexNorm[i+1], mp_vertexNorm[i+2],
                //  tmp,
                //  mp_vertexRGB[i], mp_vertexRGB[i+1],  mp_vertexRGB[i+2]);
                //
                //system("pause");

                i += 3;
            }

            // read faces
            i =0;
            for (int iterator = 0; iterator < this->m_totalFaces; iterator++)
            {
                fgets(buffer,300,file);

                //在面片部分,第一个数字表示此面有几个顶点构成,buffer[0] == ‘3’表示由3个顶点构成
                if (buffer[0] == '3')
                {
                    int vertex1 = 0, vertex2 = 0, vertex3 = 0;
                    buffer[0] = ' ';
                    //读取构成面的三个顶点的索引
                    sscanf(buffer,"%i%i%i", &vertex1,&vertex2,&vertex3 );//number of vertex eg:5,7,6

                    //printf("%d, %d, %d\n", vertex1, vertex2, vertex3);
//按照面的三个顶点的顺序,依次把每个面的顶点数据存储到m_ModelData中,方便后面绘制顶点
                                    m_ModelData.vecFaceTriangles.push_back( mp_vertexXYZ[3*vertex1]);
                    m_ModelData.vecFaceTriangles.push_back( mp_vertexXYZ[3*vertex1+1]);
                    m_ModelData.vecFaceTriangles.push_back( mp_vertexXYZ[3*vertex1+2]);
                    m_ModelData.vecFaceTriangles.push_back( mp_vertexXYZ[3*vertex2]);
                    m_ModelData.vecFaceTriangles.push_back( mp_vertexXYZ[3*vertex2+1]);
                    m_ModelData.vecFaceTriangles.push_back( mp_vertexXYZ[3*vertex2+2]);
                    m_ModelData.vecFaceTriangles.push_back( mp_vertexXYZ[3*vertex3]);
                    m_ModelData.vecFaceTriangles.push_back( mp_vertexXYZ[3*vertex3+1]);
                    m_ModelData.vecFaceTriangles.push_back( mp_vertexXYZ[3*vertex3+2]);

                    //方法二
                    //vecFaceIndex.push_back(vertex1);
                    //vecFaceIndex.push_back(vertex2);
                    //vecFaceIndex.push_back(vertex3);


                    m_ModelData.vecFaceTriangleColors.push_back( mp_vertexRGB[3*vertex1]  / 255.0f);
                    m_ModelData.vecFaceTriangleColors.push_back( mp_vertexRGB[3*vertex1+1]/ 255.0f);
                    m_ModelData.vecFaceTriangleColors.push_back( mp_vertexRGB[3*vertex1+2]/ 255.0f);
                    m_ModelData.vecFaceTriangleColors.push_back( mp_vertexRGB[3*vertex2]  / 255.0f);
                    m_ModelData.vecFaceTriangleColors.push_back( mp_vertexRGB[3*vertex2+1]/ 255.0f);
                    m_ModelData.vecFaceTriangleColors.push_back( mp_vertexRGB[3*vertex2+2]/ 255.0f);
                    m_ModelData.vecFaceTriangleColors.push_back( mp_vertexRGB[3*vertex3]  / 255.0f);
                    m_ModelData.vecFaceTriangleColors.push_back( mp_vertexRGB[3*vertex3+1]/ 255.0f);
                    m_ModelData.vecFaceTriangleColors.push_back( mp_vertexRGB[3*vertex3+2]/ 255.0f);

                    m_ModelData.vecNormals.push_back( mp_vertexNorm[3*vertex1]);
                    m_ModelData.vecNormals.push_back( mp_vertexNorm[3*vertex1+1]);
                    m_ModelData.vecNormals.push_back( mp_vertexNorm[3*vertex1+2]);
                    m_ModelData.vecNormals.push_back( mp_vertexNorm[3*vertex2]);
                    m_ModelData.vecNormals.push_back( mp_vertexNorm[3*vertex2+1]);
                    m_ModelData.vecNormals.push_back( mp_vertexNorm[3*vertex2+2]);
                    m_ModelData.vecNormals.push_back( mp_vertexNorm[3*vertex3]);
                    m_ModelData.vecNormals.push_back( mp_vertexNorm[3*vertex3+1]);
                    m_ModelData.vecNormals.push_back( mp_vertexNorm[3*vertex3+2]);

                    triangle_index += 9;
                    m_ModelData.iTotalConnectedTriangles += 3;
                }


                i += 3;
            }

            fclose(file);
            printf("%s Loaded!\n",filename);

        }

        else 
        { 
            printf("File can't be opened\n"); 
        }
    }   
    else 
    {
        printf("File does not have a .PLY extension. ");    
    }   

    return 0;
}

void CPLYLoader::DrawByLine()
{
    if (m_ModelData.vecFaceTriangleColors.empty())
    {
        cout << "model data is null"<<endl;
        exit(-1);
    }

    glEnableClientState(GL_VERTEX_ARRAY);   
    glEnableClientState(GL_NORMAL_ARRAY);
    glVertexPointer(3,GL_FLOAT, 0,m_ModelData.vecFaceTriangles.data()); 
    glNormalPointer(GL_FLOAT, 0, m_ModelData.vecNormals.data());
    glDrawArrays(GL_LINES, 0, m_ModelData.iTotalConnectedTriangles);
    glDisableClientState(GL_VERTEX_ARRAY);    
    glDisableClientState(GL_NORMAL_ARRAY);
}

void CPLYLoader::DrawByPoint()
{
    if (m_ModelData.vecFaceTriangleColors.empty())
    {
        cout << "model data is null"<<endl;
        exit(-1);
    }

    glEnableClientState(GL_VERTEX_ARRAY);   
    glEnableClientState(GL_NORMAL_ARRAY);
    glVertexPointer(3,GL_FLOAT, 0,m_ModelData.vecFaceTriangles.data()); 
    glNormalPointer(GL_FLOAT, 0, m_ModelData.vecNormals.data());
    glDrawArrays(GL_POINTS, 0, m_ModelData.iTotalConnectedTriangles);
    glDisableClientState(GL_VERTEX_ARRAY);    
    glDisableClientState(GL_NORMAL_ARRAY);
}

void CPLYLoader::DrawByPolygon() //implemented in GLPainter, not called again
{
    if (m_ModelData.vecFaceTriangleColors.empty())
    {
        cout << "model data is null"<<endl;
        exit(-1);
    }
    //需要根据你的.ply文件每个顶点是否存在以下3个属性来绘图
    //假如你没有颜色属性,则读取的时候颜色相关数据为空,执行glColorPointer后可能导致绘制的图形无法显示

    glEnableClientState(GL_VERTEX_ARRAY);   
    glEnableClientState(GL_NORMAL_ARRAY);
    //glEnableClientState(GL_COLOR_ARRAY);
    //每3个值为一组(代表一个顶点)
    glVertexPointer(3,GL_FLOAT, 0,m_ModelData.vecFaceTriangles.data()); 
    //glColorPointer(3,GL_FLOAT,0,m_ModelData.vecFaceTriangleColors.data());
    glNormalPointer(GL_FLOAT, 0, m_ModelData.vecNormals.data());
    //以GK_TRIANGLES方式,即每3个顶点画一个面
    glDrawArrays(GL_TRIANGLES, 0, m_ModelData.iTotalConnectedTriangles);
    glDisableClientState(GL_VERTEX_ARRAY);    
    glDisableClientState(GL_NORMAL_ARRAY);
    //glDisableClientState(GL_COLOR_ARRAY);


    //方法二:
    //glEnableClientState(GL_VERTEX_ARRAY);
    //glVertexPointer(3, GL_FLOAT, 0, mp_vertexXYZ);
    //glDrawElements(GL_TRIANGLES, m_totalFaces * 3, GL_UNSIGNED_INT, vecFaceIndex.data());
}

PlyLoader.h

#ifndef PLYREADER_H_
#define PLYREADER_H_

#include <vector>
#include <iostream>
#include <GL/glut.h>
#include <GL/glu.h>
#include <GL/gl.h>
using namespace std;

struct SModelData
{
    vector <float> vecFaceTriangles; // = face * 9
    vector <float> vecFaceTriangleColors; // = face * 9
    vector <float> vecNormals; // = face * 9
    int iTotalConnectedTriangles;
};

class CPLYLoader 
{
public:
    CPLYLoader();
    int LoadModel(char *filename);
    void DrawByPoint();
    void DrawByLine();
    void DrawByPolygon();

private:
    float* mp_vertexXYZ;
    float* mp_vertexNorm;
    float* mp_vertexRGB;
    int m_totalConnectedQuads;  
    int m_totalConnectedPoints;
    int m_totalFaces;
    SModelData m_ModelData;

    vector <int> vecFaceIndex;   //顶点索引
};

#endif

说明:

  1. 不同的.ply文件,每个顶点包含的属性值可能不一样,较简单的就只包含x y z值,复杂的还包含rgb 和法向量的值,所以绘制的时候要注意。
  2. 绘制的时候采用顶点数组的方式绘制,具体用法见3链接。

猜你喜欢

转载自blog.csdn.net/qq_25241325/article/details/80580429