obj文件基本结构及读取 - 计算机图形学
读取Obj格式的模型文件(Dx10) - [Tutorial]
引言:
最近开始用DirectX 10了,感觉和Dx9还是有一些变化的。虽然还不能完全理解这些变化所带来的优势,但是基本还是适应了。
唯一觉得不适应的就是在Dx10的接口里面,D3DX库里面已经没有对于.x模型文件的直接支持了。就是说不能通过D3DX来读取.x模型文件了,Dx10提供了一种新的模型格式sdkmesh。但是为了读取这种模型,或者自己写解析器,可能会很复杂,或者用DXUT的接口,但是如果仅仅为了读取模型文件就用了这个庞大的接口多少显得有些累赘。所以我自己也弄了一个读取Obj格式的模型文件的Demo,对于想读取自己自定义格式的模型文件的朋友,可以看看。希望能有一点帮助。
正文:
Obj是一种应用广泛的模型格式。在很多三维建模软件里面,都可以找到Obj格式的导出插件。本文简单解析了Obj的最基本的格式,可以读取由三角形组成的模型。
为了简单起见,这里分析的Obj是ASCII码格式的,这样用户可以用wordpad查看obj文件里面的内容,配合程序的调试,可以更好的理解文件解析的过程。其实这个模型格式可以很简单的描述一个Mesh。下面我们简单看看一些关键字:
# 这个就相当于C++代码里面的//,如果一行开始时#,那么就可以理解为这一行完全是注释,解析的时候可以无视
g 这个应该是geometry的缩写,代表一个网格,后面的是网格的名字。
v v是Vertex的缩写,很简单,代表一个顶点的局部坐标系中的坐标,可以有三个到四个分量。我之分析了三个分量,因为对于正常的三角形的网格来说,第四个分量是1,可以作为默认情况忽略。如果不是1,那可能这个顶点是自由曲面的参数顶点,这个我们这里就不分析了,因为大部分的程序都是用三角形的。
vn 这个是Vertex Normal,就是代表法线,这些向量都是单位的,我们可以默认为生成这个obj文件的软件帮我们做了单位化。
vt 这个是Vertex Texture Coordinate,就是纹理坐标了,一般是两个,当然也可能是一个或者三个,这里我之分析两个的情况。
mtllib <matFileName> 这个代表后面的名字是一个材质描述文件的名字,可以根据后面的名字去找相应的文件然后解析材质。
usemtl <matName> 这里是说应用名字为matName的材质,后面所有描述的面都是用这个材质,直到下一个usemtl。
f 这里就是face了,真正描述面的关键字。后面会跟一些索引。一般索引的数量是三个,也可能是四个(OpenGL里面可以直接渲染四边形,Dx的话只能分成两个三角形来渲染了)。每个索引数据中可能会有顶点索引,法线索引,纹理坐标索引,以/分隔。
上面就是描述一个简单的Obj模型文件的基本内容了,下面贴上一个例子
#
# Wavefront OBJ file
# Converted by the DEEP Exploration 2.1.12.1218
# Right Hemisphere, LTD
#mtllib cup.mtl
g insideShape
v -0.395825 0.436485 9.94025e-009
v 0.395911 0.436485 0.0264705
v -0.0391825 0.436485 0.394319
v -0.414339 0.479981 1.25328e-008
v -0.0410357 0.479981 0.41274
v -0.325067 0.479981 0.257087
v -0.23434 -0.406736 -4.03196e-008
v -0.225996 -0.406736 0.0620398
v -0.232244 -0.406736 0.0312992
v -0.116927 -0.406736 -4.03196e-008
…………………………vt 1 0.978723
vt 0.941176 0.978723
vt 1 1
vt 1 0.468085
vt 0.941176 0.468085
vt 1 0.489362
vt 1 0.212766
vt 0.941176 0.212766
vt 1 0.234043
vt 0.470588 0.212766
vt 0.411765 0.212766
…………………………vn 0.303793 -0.951539 0.0477825
vn 0.1201 -0.992691 0.0118188
vn 0.307451 -0.951539 0.00686204
vn -0.299922 -0.95154 -0.0679646
vn -0.119043 -0.992691 -0.0198105
vn -0.306307 -0.951539 -0.027376
vn 0.077944 -0.951538 -0.297489
vn 0.0237771 -0.992691 -0.118317
vn 0.037599 -0.951538 -0.305224
vn -0.0473055 0.97514 0.21648
vn 0.0390879 0.988496 -0.146111
vn -0.0180342 0.975141 0.220851
vn -0.188498 0.97514 0.116491
…………………………usemtl inside
f 1/3/3 800/2/2 799/1/1
f 2/6/6 407/5/5 406/4/4
f 3/9/9 204/8/8 203/7/7
f 5/12/12 98/11/11 97/10/10
f 6/15/15 51/14/14 50/13/13
f 8/18/18 11/17/17 9/16/16
………………………………
基本上就是这些部分就可以描述一个静态的模型了,其实有很多数据,这里用省略号略过了。下面看下mtl文件,就是材质文件。这个比较简单清晰,先贴上一个好了:
#
# Wavefront material file
# Converted by the DEEP Exploration 2.1.12.1218
# Right Hemisphere, LTD
#
newmtl inside
Ka 0.4 0.4 0.4
Kd 0.587609 0.587609 0.587609
Ks 0.071744 0.071744 0.071744
Ns 32
newmtl outside
Ka 0 0 0
Kd 1 1 1
Ks 0.384296 0.194061 0.174387
Ns 64
map_Kd cup.jpg前面也是一些注释,简单介绍下关键字这些东西就很好看了:
newmtl:New Material,没啥好说的。
Ka:Ambient Color
Kd:Diffuser Color
Ks:Specular Color
Ns:Shininess
map_Kd:Diffuse的纹理贴图。
其实不介绍上面的东西也很直接了。那么一个最简单的Obj格式的文件基本就是这么多东西了,虽然内容很简单,很少,但是这种格式足够描述任意多的静态多边形,写一些简单的程序也足够了。
下面的内容就和Obj格式没什么关系了,是这个Demo的另一部分,简单的介绍下怎样把一些自定义的数据弄到Dx10的ID3DX10Mesh里面,以及一些Dx10的注意事项。
在上面的解析中,我们单纯的把所有的v,vt,vn分别push到三个vector里面。然后利用索引分别拷贝相应的数据就好了,这里面我们需要三个缓冲:顶点缓冲,其中每个顶点包括了位置,法线,纹理坐标等属性;索引缓冲,描述每个三角形的索引,这个索引是指向上面的顶点缓冲区中的;Attribute缓冲,这里面应该是描述一些顶点的材质属性的。根据解析出的Obj的数据,生成这三个缓冲应该没什么大问题。但是这里有一点需要注意下,如果我们单纯对于每个Obj里面的索引去拷贝数据的话,可能会有一些顶点在顶点缓冲中重复。例如,两个三角形公用一个顶点,那么简单的加入数据,共享的顶点就会被加入到顶点缓冲两次。一个简单的办法是用hash表来查找要加入的顶点是否存在于现有的顶点缓冲中(这部分工作我的Demo没有做)。
有了上述三个缓冲,可以利用ID3DX10Mesh的三个接口:SetVertexData , SetIndexData , SetAttributeData 把数据Fill到接口中,在数据写入后,要调用一次CommitDevice,否则是无法渲染的。完成了这些工作,对于写入的Mesh就可以正常对待了。模型文件也可以正常渲染了。当然我们还要自己写Shader来渲染,但是实现一个简单的Phong光照模型并没什么难度,所以这里也不介绍了。
最后,有一点需要特殊注意的。用Dx10渲染一些文字的时候,我的Demo里面用的是ID3DX10FONT,但是在它的DrawText接口被调用后,它会改变ZCompareFunction,会导致下一帧的三维图形渲染的错误。所以一定要在DrawText之后回复状态。可以通过device->SetDepthStencilState( NULL , 0 ) 恢复到默认状态。也可以在Shader里面加一些参数,强制在渲染模型的时候切换渲染状态。例如:
//depth state
DepthStencilState DepthState
{
DepthEnable = TRUE;
DepthWriteMask = ALL;
};
//raster stae
RasterizerState RasterState
{
CullMode = NONE;
};//the technique
technique10 DefaultTec
{
pass DefaultPass
{
SetVertexShader( CompileShader( vs_4_0 , DefaultVertexShader() ) );
SetGeometryShader( NULL );
SetPixelShader( CompileShader( ps_4_0 , DefaultPixelShader() ) );//enable depth
SetDepthStencilState( DepthState, 0 );//Disable culling
SetRasterizerState( RasterState );
}
}
上面的代码是我的Demo里面的一部分,在这个Shader进行渲染的时候强制了一些渲染状态,个人比较推荐后者的解决方案,因为无论是编码还是运行起来,后面的效率要更高一些,而且更有针对性。
好了,这次的东西其实就是体力活而已,没什么想法的东西,只要注意一些细节就可以搞出来了,代码在下面:
http://filer.blogbus.com/4730079/resource_47300791260195369m.rar
开发公司:Alias|Wavefront公司
典型应用:
软件:
(1)Advanced Visualizer(Wavefront)
(2)Poser
其他应用:
所有dcc/cad/cam都支持该格式
问题提出:
3D软件模型之间的互导时出现的错误,比如如果Maya自身的模型出错,也可以先转成OBJ格式,修改之后
再导回Maya。
################----OBJ文件 -- 概念----#######################
OBJ文件有2种基本格式:
ASCII格式(.obj)。
binary格式(.mod),该专利尚未公开.
文档版本:v2.11\v3.0。
#############----OBJ文件 -- 特点----#######################
(1)OBJ是一种3D模型文件,支持法线和贴图坐标,但是不包含动画、动力学、粒子等信息。
(2)OBJ3.0格式支持多边形(Polygon),直线(Lines),表面(Surfaces),和5种自由形态曲线(Free-form
Curves)。包括那些基于Bezier\B-spline\Cardinal\Taylor equations曲线。
注意:
各三维软件由于多方面原因,对obj格式的容纳能力不尽相同,比如Maya导出的OBJ文件只支持多边形。
(3)OBJ文件支持三角面。
很多其它的模型文件格式只支持三角面,所以我们导入Maya的模型经常被三角化了,这对于我们对模型
的再加工甚为不利。
################----基本结构 File structure----#####################
Syntax | Specifying | |
顶点数据(Vertex data) | v(v x y z w) | Geometric vertices |
vt(u v w) | Texture vertices | |
vn(i j k) | Vertex normals | |
vp(u v w) | curve/surface attributes (Parameter space vertices) | |
自由形态曲线(Free-form curve)/表面属性(surface attributes) | deg(degu degv) | Degree |
bmat(u matrix) (v matrix) | Basis matrix | |
step(stepu stepv) | Step size | |
cstype | Curve or surface type | |
元素(Elements) | p(v1 v2 v3 ……) | -Point |
l (v1/vt1 v2/vt2 v3/vt3 ……) | -Line | |
f (v1/vt1/vn1 v2/vt2/vn2 v3/vt3/vn3 ……) | Face | |
curv(u0 u1 v1 v2 ……) | Curve | |
curv2 (vp1 vp2 vp3……) | 2D curve | |
surf(s0 s1 t0 t1 v1/vt1/vn1 v2/vt2/vn2……) | Surface | |
自由形态曲线(Free-form curve)/表面主体陈述(surface body statements) | parm (u p1 p2 p3……) (v p1 p2 p3……) | Parameter values |
trim(u0 u1 curv2d u0 u1 curv2d ……) | Outer trimming loop | |
hole(u0 u1 curv2d u0 u1 curv2d …… ) | Inner trimming loop | |
scrv(u0 u1 curv2d u0 u1 curv2d……) | Special curve | |
sp( vp1 vp……) | special point | |
end | End statement | |
自由形态表面之间的连接(Connectivity between free-form surfaces) | con(surf_1 q0_1 q1_1 curv2d_1 surf_2 q0_2 q1_2 curv2d_2) | Connect |
成组(Grouping) | g(group_name1 group_name2 ……) | Group name |
s(group_number) | Smoothing group | |
mg(group_number res) | Merging group | |
o(object_name) | object name | |
显示(Display)/渲染属性(render attributes) | bevel(on/off) | Bevel interpolation |
c_interp(on/off) | Color interpolation | |
d_interp(on/off) | issolve interpolation | |
lod(level) | Level of detail | |
maplib(filename1 filename2……) | ||
usemap(map_name/off) | ||
usemtl( material_name) | Material name | |
mtllib(filename1 filename2 ……) | Material library | |
shadow_obj(filename) | Shadow casting | |
trace_obj(filename) | Ray tracing | |
ctech(cparm res) (cspace maxlength) (curv maxdist maxangle) | Curve approximation technique | |
stech(cparma ures vres) (cparmb uvres) (cspace maxlength) (curv maxdist maxangle ) | Surface approximation technique |
OBJ文件不包含面的颜色定义信息,不过可以引用材质库,材质库信息储存在一个后缀是".mtl"的独立文件中。关键字"mtllib"即材质库的意思。
材质库中包含材质的漫射(diffuse),环境(ambient),光泽(specular)的RGB(红绿蓝)的定义值,以及反射(specularity),折射(refraction),透明度(transparency)等其它特征。
-------------------****基本结构*****-------------------------------
###########################################################
最优秀的模型格式01----DAE
最优秀的模型格式02----FBX
maya读取dae/fbx,可能需要自己安装插件!!
maya导入(import)obj等等模型后,可能需要做几个工作:
(1)cleanup清除重叠面
polygons>cleanup>lamina faces。
(2)qudrangulate 转为4边面
polygons>qudrangulate
(3)translation 调整大小等
(4)重新指定贴图
maya有一个bug,没有赋予材质的mtl文件不能正确读取,但是如果该mtl文件被正确的赋予了材质的话,就可以正确读取了
参考文献:
(1)http://www.martinreddy.net/gfx/3d/OBJ.spec
(2)http://en.wikipedia.org/wiki/Obj
(3)http://www.alias.com/eng/index.shtml, The Alias web site.
(4)http://people.sc.fsu.edu/~burkardt/data/obj/obj.html
你问我生命中还有什么可追寻?