PLY格式介绍与读取
一、格式介绍
PLY是一种电脑档案格式,全名为多边形档案(Polygon File Format)或 斯坦福三角形档案(Stanford Triangle Format)。 史丹佛大学的 The Digital Michelangelo Project计划采用PLY格式储存极高分辨率之米开朗基罗的作品"大卫"雕塑。
该格式主要用以储存立体扫描结果的三维数值,透过多边形片面的集合描述三维物体,与其他格式相较之下这是较为简单的方法。它可以储存的资讯包含颜色、透明度、表面法向量、材质座标与资料可信度,并能对多边形的正反两面设定不同的属性。
在档案内容的储存上PLY有两种版本,分别是纯文字(ASCII)版本与二元码(binary)版本,其差异在储存时是否以ASCII编码表示元素资讯。
档案格式
(本文并未提供完整的格式描述,以下仅介绍PLY的基本概念与格式)
每个PLY档都包含档头(header),用以设定网格模型的“元素”与“属性”,以及在档头下方接着一连串的元素“数值资料”。一般而言,网格模型的“元素”就是顶点(vertices)、面(faces),另外还可能包含有边(edges)、深度图样本(samples of range maps)与三角带(triangle strips)等元素。无论是纯文字与二元码的PLY档,档头资讯都是以ASCII编码编写,接续其后的数值资料才有编码之分。PLY档案以此行:
ply
|
开头作为PLY格式的识别。接着第二行是版本资讯,目前有三种写法:
format
ascii 1.0
format
binary_little_endian 1.0
format
binary_big_endian 1.0
|
其中ascii, binary_little_endian, binary_big_endian是档案储存的编码方式,而1.0是遵循的标准版本(现阶段仅有PLY 1.0版)。在档头中可使用"comment"作为一行的开头以编写注解,例如:
comment
This is a
comment!
|
描述元素及属性,必须使用"element"及"property"的关键字,一般的格式为element下方接着属性列表,例如:
element
<element name> <number in file>
property
<data_type> <property name 1>
property
<data_type> <property name 2>
property
<data_type> <property name 3>
|
"property"不仅定义了资料的型态,其出现顺序亦定义了资料的顺序。内定的资料形态有两种写法:一种是char uchar short ushort int uint float double,另外一种是具有位元长度的int8 uint8 int16 uint16 int32 uint32 float32 float64。 例如,描述一个包含12个顶点的物体,每个顶点使用3个单精度浮点数 (x,y,z)代表点的座标,使用3个unsigned char代表顶点颜色,颜色顺序为 (B, G, R),则档头的写法为:
element
vertex 12
property float x
property float y
property float z
property
uchar blue
property
uchar green
property
uchar red
|
其中vertex是内定的元素类型,接续的6行property描述构成vertex元素的数值字段顺序代表的意义,及其资料形态。
另一个常使用的元素是面。由于一个面是由3个以上的顶点所组成,因此使用一个“顶点列表”即可描述一个面, PLY格式使用一个特殊关键字"property list"定义之。 例如,一个具有10个面的物体,其PLY档头可能包含:
element
face 10
property
list uchar int vertex_indices
|
"property list"表示该元素face的特性是由一行的顶点列表来描述。列表开头以uchar型态的数值表示列表的项目数,后面接着资料型态为int的顶点索引值(vertex_indices),顶点索引值从0开始。
最后,标头必须以此行结尾:
end_header
|
档头后接着的是元素资料(端点座标、拓朴连结等)。在ASCII格式中各个端点与面的资讯都是以独立的一行描述,而二元编码格式则连续储存这些资料,加载时须以"element"定义的元素数目以及"property"中设定的资料形态计算各笔字段的长度。
范例
一个典型的PLY档案结构分成三部分:
档头
(从ply开始到end_header)
顶点元素列表
面元素列表
|
其中的顶点元素列表一般以x y z方式排列,形态如档头所定义;而面元素列表是以下列格式表示。
<組成面的端點數N>
<端點#1的索引> <端點#2的索引> ... <端點#N的索引>
|
例如画出一个有4个顶点,4个面的四面体,档案内容为:
ply
format
ascii 1.0
comment這是一個正四面體
element
vertex 4
property float x
property float y
property float z
element
face 4
property
list uchar int vertex_index
end_header
0
3 0
2.449
-1.0 -1.414
0
-1 2.828
-2.449
-1.0 -1.414
3
0 1 3
3
0 2 1
3
0 3 2
3
1 2 3
|
其中1~10行是档头, 11~14行是顶点元素列表, 15~18行则是面元素列表。
其中: 0 3 0是顶点
历史
PLY格式发展于90年代中期,在史丹佛大学图学实验室的Marc Levoy教授指导下,由Greg Turk及其他成员开发出来。PLY格式受Wavefront .obj格式的启发,但改进了Obj格式所缺少的对任意属性及群组的扩充性。因此PLY格式发明了"property"及"element"这两个关键词,来概括“顶点、面、相关资讯、群组”的概念。
注意
ply文件不支持中文格式的文件名字,所以在使用过程中避免使用中文来命名。
二、C语言读取PLY文件
// Try to read a plyfile, returning vertices and faces bool read_ply(const char *plyfile, int &numleaves, QTree_Node * &leaves, int &numfaces, face * &faces, bool &have_colors, std::string &comments) { bool have_faces=false, have_tstrips=false; int tstripdatalen=0, *tstripdata = NULL; int other_prop_len, color_offset; char buf[255]; int i, result; have_colors = false; numleaves = numfaces = 0; leaves = NULL; faces = NULL; FILE *f = fopen(plyfile, "r"); if (!f) { fprintf(stderr, "Can"t open plyfile %s ", plyfile); return false; } printf("Reading %s... ", plyfile); // Read header if (!fgets(buf, 255, f) || strncmp(buf, "ply", 3)) { fprintf(stderr, "Not a ply file. "); return false; } #define GET_LINE() if (!fgets(buf, 255, f)) goto plyreaderror #define LINE_IS(text) !strncasecmp(buf, text, strlen(text)) GET_LINE(); if (!LINE_IS("format binary_big_endian 1.0")&&!LINE_IS("format binary_little_endian 1.0")) { fprintf(stderr, "Can only read binary ply files. "); } while (1) { GET_LINE(); if (LINE_IS("obj_info")) { continue; } else if (LINE_IS("comment")) { comments += buf+8; continue; } else { break; } } result = sscanf(buf, "element vertex %d ", &numleaves); if (result != 1) { fprintf(stderr, "Expected "element vertex" "); goto plyreaderror; } GET_LINE(); if (!LINE_IS("property float x")) { fprintf(stderr, "Expected "property float x" "); goto plyreaderror; } GET_LINE(); if (!LINE_IS("property float y")) { fprintf(stderr, "Expected "property float y" "); goto plyreaderror; } GET_LINE(); if (!LINE_IS("property float z")) { fprintf(stderr, "Expected "property float z" "); goto plyreaderror; } other_prop_len = 0; GET_LINE(); while (LINE_IS("property")) { if (LINE_IS("property char") || LINE_IS("property uchar")) { other_prop_len += 1; } else if (LINE_IS("property int") || LINE_IS("property uint") || LINE_IS("property float")) { other_prop_len += 4; } else { fprintf(stderr, "Unsupported vertex property: %s ", buf); goto plyreaderror; } if (LINE_IS("property uchar diffuse_red")) { have_colors = true; color_offset = other_prop_len - 1; } GET_LINE(); } result = sscanf(buf, "element face %d", &numfaces); if (result == 1) { have_faces = true; GET_LINE(); if (!LINE_IS("property list uchar int vertex_indices")) goto plyreaderror; GET_LINE(); } else if (LINE_IS("element tristrips 1")) { have_tstrips = true; GET_LINE(); if (!LINE_IS("property list int int vertex_indices")) goto plyreaderror; GET_LINE(); } if (!LINE_IS("end_header")) { fprintf(stderr, "Expected "end_header" "); goto plyreaderror; } // OK, we think we"ve parsed the header. Slurp in the actual data... leaves = new QTree_Node[numleaves]; printf(" Reading %d vertices... ", numleaves); fflush(stdout); for (i=0; i < numleaves; i++) { if (!fread((void *)&(leaves[i].pos[0]), 12, 1, f)) goto plyreaderror; if (other_prop_len && !fread((void *)buf, other_prop_len, 1, f)) goto plyreaderror; if (have_colors) { memcpy((void *)&(leaves[i].col[0]), buf + color_offset, sizeof(color)); } } printf("Done. "); if (have_tstrips) { printf(" Reading triangle strips... "); fflush(stdout); if (!fread((void *)&tstripdatalen, 4, 1, f)) goto plyreaderror; FIX_LONG(tstripdatalen); tstripdata = new int[tstripdatalen]; if (!fread((void *)tstripdata, 4*tstripdatalen, 1, f)) goto plyreaderror; for (int t=0; t < tstripdatalen; t++) FIX_LONG(tstripdata[t]); } else if (have_faces) { printf(" Reading %d faces... ", numfaces); fflush(stdout); faces = new face[numfaces]; for (i=0; i < numfaces; i++) { if (!fread((void *)buf, 1, 1, f)) goto plyreaderror; if (buf[0] != 3) { fprintf(stderr, "Non-triangle found in mesh. "); } if (!fread((void *)faces[i], 12, 1, f)) goto plyreaderror; } } printf("Done. "); if (tstripdatalen) { unpack_tstrips(tstripdatalen, tstripdata, numfaces, faces); delete [] tstripdata; } fgets(buf, 2, f); if (!feof(f)) { fprintf(stderr, "Warning: ignored excess garbage at end of ply file. "); } fclose(f); return true; plyreaderror: fclose(f); fprintf(stderr, "Error reading plyfile. "); if (leaves) delete [] leaves; if (faces) delete [] faces; if (tstripdata) delete [] tstripdata; return false; }