OBJ文件格式
obj文件是3D模型文件格式。由Alias|Wavefront公司为3D建模和动画软件"Advanced Visualizer"开发的一种标准,适合用于3D软件模型之间的互导,也可以通过Maya读写。OBJ文件是一种文本文件,可以直接用写字板打开进行查看和编辑修改。
文件特点
OBJ3.0文件格式支持直线(Line)、多边形(Polygon)、表面(Surface)和自由形态曲线(Free-form Curve)。直线和多边形通过它们的点来描述,曲线和表面则根据它们的控制点和依附于曲线类型的额外信息来定义,这些信息支持规则和不规则的曲线,包括那些基于贝塞尔曲线(Bezier)、B样条(B-spline)、基数(Cardinal/Catmull-Rom)和泰勒方程(Taylor equations)的曲线。其他特点如下:
- OBJ文件是一种3D模型文件。不包含动画、材质特性、贴图路径、动力学、粒子等信息。
- OBJ文件主要支持多边形(Polygons)模型。虽然也支持曲线(Curves)、表面(Surfaces)、点组材质(Point Group aterials),但Maya导出的OBJ文件并不包括这些信息。
- OBJ文件支持三个点以上的面,这一点很有用。很多其它的模型文件格式只支持三个点的面,所以导入Maya的模型经常被三角化了,这对于我们对模型进行再加工甚为不利。
- OBJ文件支持法线和贴图坐标。在其它软件中调整好贴图后,贴图坐标信息可以存入OBJ文件中,这样文件导入Maya后只需指定一下贴图文件路径就行了,不需要再调整贴图坐标。
基本结构
OBJ文件不需要任何种文件头(File Header),尽管经常使用几行文件信息的注释作为文件的开头。OBJ文件由一行行文本组成,注释行以符号“#”为开头,空格和空行可以随意加到文件中以增加文件的可读性。有字的行都由一两个标记字母也就是关键字(Keyword)开头,关键字可以说明这一行是什么样的数据。多行可以逻辑地连接在一起表示一行,方法是在每一行最后添加一个连接符()。注意连接符()后面不能出现空格或Tab格,否则将导致文件出错。
下列关键字可以在OBJ文件使用。在这个列表中, 关键字根据数据类型排列,每个关键字有一段简短描述。
顶点数据(Vertex data):
- v 几何体顶点(Geometric vertices)
- vt 贴图坐标点(Texture vertices)
- vn 顶点法线(Vertex normals)
- vp 参数空格顶点 (Parameter space vertices)
自由形态曲线(Free-form curve)/表面属性(surface attributes): - deg 度(Degree)
- bmat 基础矩阵(Basis matrix)
- step 步尺寸(Step size)
- cstype 曲线或表面类型 (Curve or surface type)
元素(Elements): - p 点(Point)
- l 线(Line)
- f 面(Face)
- curv 曲线(Curve)
- curv2 2D曲线(2D curve)
- surf 表面(Surface)
自由形态曲线(Free-form curve)/表面主体陈述(surface body statements): - parm 参数值(Parameter values )
- trim 外部修剪循环(Outer trimming loop)
- hole 内部整修循环(Inner trimming loop)
- scrv 特殊曲线(Special curve)
- sp 特殊的点(Special point)
- end 结束陈述(End statement)
自由形态表面之间的连接(Connectivity between free-form surfaces): - con 连接 (Connect)
成组(Grouping): - g 组名称(Group name)
- s 光滑组(Smoothing group)
- mg 合并组(Merging group)
- o 对象名称(Object name)
显示(Display)/渲染属性(render attributes): - bevel 导角插值(Bevel interpolation)
- c_interp 颜色插值(Color interpolation)
- d_interp 溶解插值(Dissolve interpolation)
- lod 细节层次(Level of detail)
- usemtl 材质名称(Material name)
- mtllib 材质库(Material library)
- shadow_obj 投射阴影(Shadow casting)
- trace_obj 光线跟踪(Ray tracing)
- ctech 曲线近似技术(Curve approximation technique)
- stech 表面近似技术 (Surface approximation technique)
OBJ里的面的索引可正可负,为正数时是指顶点的绝对索引,为负时(比如:f -a -b -c),表示从该面位置开始,倒数的第a,b,c个顶点,vn,vt索引也一样。
OBJ文件不包含面的颜色定义信息,不过可以引用材质库,材质库信息储存在一个后缀是".mtl"的独立文件中。关键字"mtllib"即材质库的意思。
材质库中包含材质的漫射(diffuse),环境(ambient),光泽(specular)的RGB(红绿蓝)的定义值,以及反射(specularity),折射(refraction),透明度(transparency)等其它特征。
"usemtl"指定了材质之后,以后的面都是使用这一材质,直到遇到下一个"usemtl"来指定新的材质。
正常情况下顶点数据中每一行表示一个XYZ的坐标点(x,y,z);
有自定义的OBJ文件,会在坐标点后,紧跟3个RGB值,可能是浮点型(0.0-1.0),可能是0-255;
如果使用VTK中的vtkOBJReader来解析OBJ文件,只会得到与坐标相关的坐标点集合;
VTK中对OBJ文件的操作
vtkOBJReader
加载常规OBJ文件很简单,直接使用vtkOBJReader即可;
vtkSmartPointer<vtkOBJReader> reader = vtkSmartPointer<vtkOBJReader>::New();
reader->SetFileName(filename.c_str());
reader->Update();
vtkOBJReader对外提供了输出流;
reader->GetOutput();
reader->GetOutputPort()
由于vtkOBJReader类只读取了坐标信息,对于自定义的OBJ文件每行中的v 后三个坐标位置之后信息未使用;
如果需要对后面紧跟信息进行解析使用,就需要对 vtkOBJReader源码进行修改,或者在使用vtkOBJReader读取OBJ文件后,再次解析 OBJ文件,使用vtkUnsignedCharArray对OBJ中的几何数据增加标量数据,以标量数据来表示颜色值;
在《VTK图形图像进阶》中,讲到颜色可以直接作为一种标量属性数据,设置到相应的点或者单元数据中,这也是最直接的一种图形着色方式。
vtkSmartPointer<vtkOBJReader> reader = vtkSmartPointer<vtkOBJReader>::New();
reader->SetFileName(filename.c_str());
reader->Update();
int numPts = reader->GetOutput()->GetPoints()->GetNumberOfPoints();
vtkSmartPointer<vtkUnsignedCharArray> pointColors = vtkSmartPointer<vtkUnsignedCharArray>::New();
pointColors->SetNumberOfComponents(3);
for (int i = 0; i < numPts; ++i)
{
pointColors->InsertNextTuple3(255, 0, 0);
}
// 设置标量属性数据;
reader->GetOutput()->GetPointData()->SetScalars(pointColors);
之前书本上使用了:
unsigned char red[3] = {255,0,0};
pointColors->InsertNextTupleValue(red);
InsertNextTupleValue函数已经不再使用;
可以使用:
pointColors->InsertNextTuple3(255, 0, 0);
或者
double red[3] = { 255.0,0.0,0.0 };
pointColors->InsertNextTuple(red);
对于自定义i的OBJ文件,有的会在v 的XYZ坐标后,紧跟RGB的颜色值;
如果使用VTK提供的vtkOBJReader,只能获取到无颜色属性的poly数据;
需要我们自己从OBJ文件中获取到颜色值,生成vtkUnsignedCharArray数据,设置reader的几何点的标量数据;
vector<tuple<double, double, double>> GetColorRGBFromOBJ(string obj_file_path)
{
vector<tuple<double, double, double>> color_sets;
FILE *in = fopen(obj_file_path.c_str(), "r");
if (in == nullptr)
{
cout << "File " << obj_file_path << " not found" << endl;
return color_sets;
}
const int MAX_LINE = 1024 * 256;
char rawLine[MAX_LINE] = { 0 };
while (!feof(in) && fgets(rawLine, MAX_LINE, in) != nullptr)
{
char *pLine = rawLine;
char *pEnd = rawLine + strlen(rawLine);
if (*(pEnd - 1) != '\n' && !feof(in))
{
break;
}
while (isspace(*pLine) && pLine < pEnd) { pLine++; }
const char *cmd = pLine;
if (cmd[0] == '#')
{
memset(rawLine, 0, MAX_LINE);
continue;
}
while (!isspace(*pLine) && pLine < pEnd) { pLine++; }
// terminate command
if (pLine < pEnd)
{
*pLine = '\0';
pLine++;
}
if (strcmp(cmd, "v") == 0)
{
float xyz[3] = { 0 };
float rgb[3] = { 0 };
if (sscanf(pLine, "%f %f %f %f %f %f", xyz, xyz + 1, xyz + 2, rgb, rgb + 1, rgb + 2) == 6)
{
color_sets.push_back(make_tuple(rgb[0], rgb[1], rgb[2]));
}
}
else
{
break;
}
memset(rawLine, 0, MAX_LINE);
}
fclose(in);
return color_sets;
}
将GetColorRGBFromOBJ生成的RGB颜色集合;生成vtkUnsignedCharArray;
int numPts = reader->GetOutput()->GetPoints()->GetNumberOfPoints();
vtkSmartPointer<vtkUnsignedCharArray> pointColors = vtkSmartPointer<vtkUnsignedCharArray>::New();
pointColors->SetNumberOfComponents(3);
for (auto it = colors.begin(); it != colors.end(); it++)
{
pointColors->InsertNextTuple3(std::get<0>(*it) * 255.0, std::get<1>(*it) * 255.0, std::get<2>(*it) * 255.0);
}
reader->GetOutput()->GetPointData()->SetScalars(pointColors);
这样reader中的数据就有了标量数据,可以用来带有颜色的图形;
vtkOBJWriter
vtkOBJExporter
vtkOBJImporter
引用资料
1.obj文件