3D文件就是用来存储3D模型信息,一般用文本或二进制进行存储。目前,3D文件的格式有很多,像stl、step、obj、fbx(Autodesk)、3ds、dae、gltf等等,那么这些文件存储又有何差异呢?
通常情况下,一个3D模型文件存储的主要信息是什么?
一、geometry
geometry汉译为几何,主要是用来描述模型本身的形状。一个3D模型,最基本的组成就是它的形状,所以,geometry是所有3D文件最基本功能,每种3D文件都支持这一点,否则,它们将不被视为3D文件格式。
那么,3D文件中,geometry又是用什么数据形式来表示呢?让我们先来了解一下顶点。
1、顶点
按我们已有的认知,点成线,线成面,面成体。在3D文件,也是如此,我们可以先来看一个3D模型文件。
仔细看,我们不难发现,上面的3D模型,可以划分成一个个三角面,每3个点构成一个三角面,而这些点,就是我们所说的顶点
利用顶点的位置信息,我们可以绘制出我们想要的3D模型。
这里我们用obj来进行测试(因为它的格式是最容易理解的了,但并不是最简单的格式,后面会说明),保存下列代码,命名为《三角形.obj》,mac用户对着文件按空格就可以看到它的形状。
v -0.500000 -0.500000 0.50000
v 0.500000 -0.500000 0.50000
v -0.500000 0.500000 0.50000
f 1 2 3
这里v就代表顶点,后面跟着这个obj的3d坐标(xyz)。f 代表面,后面跟着形成该面顶点对应的索引,就是这么简单,obj格式是不是很好理解啊。
我们可以再加一个点,画2个三角形,就形成一个四边形。
v -0.500000 -0.500000 0.50000
v 0.500000 -0.500000 0.50000
v -0.500000 0.500000 0.50000
v 0.500000 0.500000 0.50000
f 1 2 3
f 3 2 4
修改一下第四个点的z坐标,就可以变成立体的,可以试一下。
到这里,我们已经了解到一个3D模型文件最基本的组成——顶点,我们通过构建顶点的位置,构建出一个个三角面,就可以形成我们想要3D形状。
STL文件格式就是只存储了顶点位置信息的一种格式,也是我目前了解到最简单的一种文件格式之一,用一个个三角形网格,来表现3D CAD模型,后面我会用其他文章来介绍这个文件格式的。
讲到这里,对于有接触3D的朋友,在听到顶点的时候,一般也会接触到顶点法线和顶点纹理坐标,那这些又是什么呢?
2、法线
首先,我们先来了解一下,什么是法线。法线,并不是3D行业特有的名词,在物理光学中,其实我们早学过这个概念。我们先来温习一下,如何根据入射光,计算一个反射光
伟大的物理老师曾经告诉我们,求一个面的反射光,先作平面的垂直线,根据入射角和反射角相等,画出反射线,而这条垂直于反射平面的直线,就是法线
在3D世界中,除了具有形状的模型,一般是有光源,对模型进行光照,我们才能看到模型,以及看到模型的效果。(像上面用mac查看obj,没有场景,没有光源,但其实系统是给了默认的光源)
那么模型接收到光照时,有多少光照信息反射给我们,这就由我们的法线决定了。一般是包含面法线和顶点法线
(1)面法线
看回我们上面的例子,三角形.obj,我们可以根据构成的三角面,求出面所处的法线,我们称之为面法线。也是在没有定义顶点法线时,模型中默认含有的法线信息。
(2)顶点法线
什么是顶点法线呢?其实也就是顶点所在平面的法线,反之,定义了顶点法线,相当于定义了顶点所在的平面。
而我们的面上点的法线,可以通过构成面的顶点的法线的均值来计算。
在接触过3D的童鞋不难发现,相对于面法线,顶点法线是我们用的更多,这又是为啥呢?
我们可以看下面一个两个在空间中互相垂直,且共享一条边的三角形,我们画出默认的“面法线”
在有一束光进来的时候,我们会发现,由于两个面法线互相垂直,当一个面很亮(反射光较多,接近90度入射时),另一个面会很暗(反射光较少,接近0度入射),那么两个面就会出现很强烈的“边缘感”。
而如果我们用的是顶点法线,两个面上的点的法线是由2个共享顶点的法线以及1个不同顶点的法线求均值来,所以两面的反射光照信息会更接近,边缘会更“光滑”。
所以,在模型文件中,我们看见的更多是用顶点法线来存储法线信息
接下来,我们继续看下obj文件是怎么存储法线信息的,我们给我们原来的加上法线信息,vn代表法线,f postion_index/tex_index/normal_index,这里我们先不设置纹理坐标
v -0.500000 -0.500000 0.50000
v 0.500000 -0.500000 0.50000
v -0.500000 0.500000 0.50000
vn 0.000000 0.000000 1.000000
vn 0.000000 0.000000 1.000000
vn 0.000000 0.000000 1.000000
f 1//1 2//2 3//3
3、顶点纹理坐标(uv坐标)
纹理,其实就是在模型上的贴图(图片),可以简单理解为,我们用图片给模型套上一层外观。由于涉及到材质,这里我们只是简单的说下顶点uv坐标的原理。
先看看上面的模型(百度随便找的图),它是一个人的模型,我们给它贴上了一张UV图,那么它的身体就会变成紫色,裤子就会变成蓝色,是不是很神奇?
这时候,你就会发现一个问题,你完全看不懂UV图的规律,为啥UV图长这个鬼样子,贴给一个模型,它为什么就能自动把裤子,身体,四肢自动的填充对应的纹理呢?(一开始接触我也以为随便找张图贴上去就对了)
这就得归功于我们顶点的纹理坐标了:3d模型顶点的position信息,决定了模型在三维位置的位置,而贴图是二维坐标。这时候我们只需要在每个顶点,附上一个二维坐标,那么三个顶点在空间构成的三角面,就可以在贴图上找到对应的三角面,形成映射,就可以完成纹理的填充了。
在obj中,vt代表uv坐标,一个含有的顶点位置、顶点法线、顶点uv坐标的模型格式是长这样的。有编辑器,可以上传该模型,然后贴个贴图看看效果。
4、mesh
mesh,网格,一般是由顶点组成的一个个三角面构成。mesh有点类似分组的概念,将我们模型分成一个个mesh,例如上面人的模型,我们可以分成头部、身体、左腿、右腿、双手几个mesh,这样子我们就可以对这几个Mesh进行不同的处理,当然也可以把整个人合并成一个mesh。一个模型所有的mesh组成了我们一开始所说的geometry。
形成geometry的mesh有三种方式,分别是:
- approximate mesh
- precise mesh
- constructive solid geometry
(1)approximate mesh
就是我们上面所说的用无数个三角面,近似我们想要的3d模型形状,目前我们接触到的大部分3d文件都是用这个方式,我们整篇文章也主要是以这种方式进行解释。
(2)precise mesh
approximate mesh 近似网格只适用精度要求不是很高的模型应用场景,但是有一些需要高精度的应用场景(像构建大型飞机零部件,尤其是曲面部分),用近似网格,就会产生棱角,不够光滑。
precise mesh会采用Non-Uniform Rational B-Spline patches技术而不是三角网络来实现高精度模型,简单来讲,就是通过控制点和一些控制曲面的弯曲度,从而实现更高精度的面结构。
(3)constructive solid geometry
还有一种是完全没用到mesh(网络)的形式,而是用一个个最原始的形状去拼接,被称为constructive solid geometry 。像拿两个球体+一个圆柱体,就可以形成一个杠铃
这种方式操作简单,对于一些简单模型更容易实现。
二、Apperance
1、texture贴图
在上面,我们在解释顶点的uv坐标,其实我们已经有提到纹理贴图。简单再说一次,我们给模型顶点设置一组(u,v)的坐标,就可以将贴图每个像素二维的位置映射到模型顶点的三维坐标上,将贴图“贴”上模型。
贴图包括纹理坐标,并不像模型的顶点位置,是模型的必备信息,所以你会发现,像ply/stl这些模型格式是没办法存储这些信息的。而我们的obj,为了支持纹理包括下面要提到的材质信息(其实纹理贴图一般是会关联到材质信息的某个属性),定义了一个扩展文件,叫做mtl。
这里我们可以拿上面的,有uv信息的obj,关联个mtl文件和一张贴图,看下效果。
mtllib 200.mtl
usemtl diffuse_texture_material_name
v -0.500000 -0.500000 0.50000
v 0.500000 -0.500000 0.50000
v -0.500000 0.500000 0.50000
vn 0.000000 0.000000 1.000000
vn 0.000000 0.000000 1.000000
vn 0.000000 0.000000 1.000000
vt 0.000000 0.000000
vt 0.000000 1.000000
vt 1.000000 0.000000
f 1/1/1 2/2/2 3/3/3
newmtl diffuse_texture_material_name
map_Ka 200.jpeg
map_Kd 200.jpeg
obj文件中mtlib代表关联mtl文件名,usemtl 表示下面的face用该材质,mtl中newmtl关联材质名,下面是材质属性,分别是环境光贴图和漫反射贴图,200.jpeg可以是任意一张贴图,并和obj和mtl文件置于同个文件。这里建议用xcode打开,mac自带的obj预览是无法自动关联。
2、material材质
三、动画
3D模型动画的基本原理:让模型的各顶点位置随时间变化而变化。一般模型动画会分成Morph动画、关节动画和骨骼蒙皮动画 。
这3种动画的基本原理都是基于关键帧的方式,并对动画进行帧补间(其他帧通过插值)的方式实现,只不过他们关键帧存储数据的方式不同而已。
这里我们可以看下一个行人动画的关键帧(图是网上随便找的),这个动画是由3个关键帧数据组成(人站着,人蹲下,人站起来),引擎读取到数据后,知道模型在t0,t6,t12的帧动画,但如果直接显示这3张图片,你会发现动画会闪过3张图,毫无流畅性可言。
所以引擎会在t1-t5,t7-t11通过插帧的方式,补足动画(图中的虚线部分),让动画流程起来,当然插帧的方式有很多,最简单就是对每个顶点的位置数据取平均值。
Morph(渐变,变形)动画是直接指定动画每一帧的顶点位置,其动画关键中存储的是Mesh全部顶点在关键帧相应时刻的位置。
关节动画是将模型分成非常多部分(Mesh),通过一个父子层次结构将这些分散的Mesh组织在一起,父Mesh带动其下子Mesh的运动,各Mesh中的顶点坐标定义在自己的坐标系中,这样各个Mesh是作为一个总体參与运动的。
关节动画的问题是,各部分Mesh中的顶点是固定在其Mesh坐标系中的,这样在两个Mesh结合处就可能产生裂缝。骨骼蒙皮动画的出现攻克了关节动画的裂缝问题。
骨骼动画的基本原理可概括为:在骨骼控制下,通过顶点混合动态计算蒙皮网格的顶点,而骨骼的运动相对于其父骨骼,并由动画关键帧数据驱动。
四、场景信息
除了我们上面提到模型的形状、材质和动画,我们在一些高级的模型格式,像fbx、gltf,是有scene(场景)这个概念。场景主要是描绘了模型、灯光、摄像机以及其他模型之间的层次关系,我们可以看下Gltf的场景结构:
像早先的模型格式,像stl/ply,它们就只有mesh信息,而obj一些较高级的模型数据格式就会引入了material、texture,他们会用group等概念来分组管理,再高级点的像fbx、gltf,会引入骨骼、动画、摄像机,同时用scene和node的方式来管理模型内的资源。