这是3D可视化教程系列的文章,如果第一次阅读请先阅读《3D可视化教程导读》
  这篇文章可以 快速略过 ,当你觉得“需要研究一下”时再深入了解。

源码及3D项目文件

  源码及工程项目都放到github上。
  源码:threejs-example

OBJ格式

 现在我们先研究一下最简单的3D文件数据是长什么样的,长宽高各为1米的立方体并导出OBJ格式:
导出OBJ

 导出能看到一个.obj文件与.mtl文件。 根据OBJ文件格式的官方文档定义,.obj文件保存的是3D模型的几何结构数据与其它属性数据(每个顶点的位置、UV位置、法线,以及组成面(多边形)的顶点列表等数据),.mtl文件(Material library files官方文档)保存的是材质数据(颜色、纹理、贴图、反光等等用于描述物体外表的参数,3D模型创建时会使用默认材质。)。在windows10下直接点击.obj文件,会使用3D Viewer查看3D模型:
3Dviewer

 用vscode打开这些文件,就能看到文件里的内容。这里我添加了注释,一起看看.obj里的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# Blender v2.82 (sub 7) OBJ File: ''
# www.blender.org
# mtllib 材质库.mtl。
# 以下这句 代表 引用了untitled.mtl文件,注意这包含了路径。
mtllib untitled.mtl
# o 对象名
o Cube
# v(vertex)数据段: 模型顶点列表,下面就代表了立方体的八个点位置
v 1.000000 1.000000 -1.000000
v 1.000000 -1.000000 -1.000000
v 1.000000 1.000000 1.000000
v 1.000000 -1.000000 1.000000
v -1.000000 1.000000 -1.000000
v -1.000000 -1.000000 -1.000000
v -1.000000 1.000000 1.000000
v -1.000000 -1.000000 1.000000
# vt(vertex texture)数据段:模型顶点的纹理坐标列表,用于UV贴图时对应的坐标
vt 0.625000 0.500000
vt 0.875000 0.500000
vt 0.875000 0.750000
vt 0.625000 0.750000
vt 0.375000 0.750000
vt 0.625000 1.000000
vt 0.375000 1.000000
vt 0.375000 0.000000
vt 0.625000 0.000000
vt 0.625000 0.250000
vt 0.375000 0.250000
vt 0.125000 0.500000
vt 0.375000 0.500000
vt 0.125000 0.750000
# vn(vertex normal)数据段:顶点法线列表,法线常用于计算光线效果
vn 0.0000 1.0000 0.0000
vn 0.0000 0.0000 1.0000
vn -1.0000 0.0000 0.0000
vn 0.0000 -1.0000 0.0000
vn 1.0000 0.0000 0.0000
vn 0.0000 0.0000 -1.0000
# usemtl 材质名
usemtl Material
# s 平滑组
s off
# f(face):模型面对应的点,
# 每一行定义1个面,这里1个面有4个点,1个点具有“顶点/纹理坐标/法线”3个索引值
# f v/vt/vn v/vt/vn v/vt/vn v/vt/vn
f 1/1/1 5/2/1 7/3/1 3/4/1
f 4/5/2 3/4/2 7/6/2 8/7/2
f 8/8/3 7/9/3 5/10/3 6/11/3
f 6/12/4 2/13/4 4/5/4 8/14/4
f 2/13/5 1/1/5 3/4/5 4/5/5
f 6/11/6 5/10/6 1/1/6 2/13/6

GLTF/GLB格式

 同理,导出GLTF格式(gltf官方文档,GLB是二进制格式形式,更节省空间。),gltf格式是可以将二进制的图片内嵌到一个文件里,不像OBJ格式会拆分成很多个文件。但由于顶点数据以二进制的方式存到buffers,并不方便分析,不多讲,自己看着图来分析就行了。
gltfOverview-2.0.0b

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
{
"asset" : {
"generator" : "Khronos glTF Blender I/O v1.1.45",
"version" : "2.0"
},
"scene" : 0,
"scenes" : [
{
"name" : "Scene",
"nodes" : [
0
]
}
],
"nodes" : [
{
"mesh" : 0,
"name" : "Cube"
}
],
"materials" : [
{
"doubleSided" : true,
"emissiveFactor" : [
0,
0,
0
],
"name" : "Material",
"pbrMetallicRoughness" : {
"baseColorFactor" : [
0.800000011920929,
0.800000011920929,
0.800000011920929,
1
],
"metallicFactor" : 0,
"roughnessFactor" : 0.4000000059604645
}
}
],
"meshes" : [
{
"name" : "Cube",
"primitives" : [
{
"attributes" : {
"POSITION" : 0,
"NORMAL" : 1,
"TEXCOORD_0" : 2
},
"indices" : 3,
"material" : 0
}
]
}
],
"accessors" : [
{
"bufferView" : 0,
"componentType" : 5126,
"count" : 24,
"max" : [
1,
1,
1
],
"min" : [
-1,
-1,
-1
],
"type" : "VEC3"
},
{
"bufferView" : 1,
"componentType" : 5126,
"count" : 24,
"type" : "VEC3"
},
{
"bufferView" : 2,
"componentType" : 5126,
"count" : 24,
"type" : "VEC2"
},
{
"bufferView" : 3,
"componentType" : 5123,
"count" : 36,
"type" : "SCALAR"
}
],
"bufferViews" : [
{
"buffer" : 0,
"byteLength" : 288,
"byteOffset" : 0
},
{
"buffer" : 0,
"byteLength" : 288,
"byteOffset" : 288
},
{
"buffer" : 0,
"byteLength" : 192,
"byteOffset" : 576
},
{
"buffer" : 0,
"byteLength" : 72,
"byteOffset" : 768
}
],
"buffers" : [
{
"byteLength" : 840,
"uri" : "data:application/octet-stream;base64,AACAPwAAgD8AAIA...."
}
]
}

说明

索引

 模型数据的特点是使用 索引 ,这样做的好处是可以共用数据。比如说,有10个立方体,他们某个顶点都是位于(1,2,3),那么他们的顶点都指向这个顶点,而不需要给每个立方体都保存一份(1,2,3)的数据,原本需要十个顶点数据,现在只需要保存一个顶点数据,节约了存储空间。特别是材质,很多情况下相同的物体都是使用相同的材质,假设10个立方体都使用了红色的材质A,当把材质A的颜色改为绿色时,那么这10个立方体都会变成绿色。
 如果想给这10个立方体都要显示不同的颜色,那就得需要10个不同颜色的材质。在编程时就需要注意,你修改的这个物体材质时,会不会其它物体也在使用,会不会影响到其它物体?如果不想影响到其它物体,就需要将当前材质克隆一份出来,赋予该物体新的材质,再去修改。

顶点坐标

 顶点相连组成线,线线相连组成面,面面相连组成物体,所以可以看到几何图形数据主要是顶点坐标(vertex)。

UV贴图

 当需要将一张图片贴到一个物体上时,就需要UV贴图。UV贴图的本质就是,设置点坐标与图片的横纵坐标一一对应。

three.js数据结构

 一个拥有几何结构(geometry)与材质(material)的3D物体被称为Mesh(被翻译成网格),three.js从3D文件里读取数据会换成其对应的数据,常用属性如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
Mesh (extends Object3D) 继承Object3D对象
- geometry:Geometry/BufferGeometry 记录点线面的数据
- material:Material 记录材质数据

Geometry 几何图形
- vertices:Array 存放几何数据

BufferGeometry 更高效的Geometry
- attributes:BufferAttribute 存放几何数据

Material 材质
- opacity:Float 透明度(0.0-1.0 ,transparent为true时才会表现出透明效果)
- side:Integer 渲染面(前/后/双面 渲染:THREE.FrontSide,THREE.BackSide,THREE.DoubleSide)
- color:Color 颜色
- emissive:Color 发光色
- transparent:Boolean 是否透明

Object3D 3D对象
- children:Object3D[] 子对象
- matrix:Matrix4 相对坐标矩阵(相对于父级)
- matrixWorld:Matrix4 世界坐标矩阵
- name:String 名称
- parent:Object3D 上级对象
- position:Vector3 位置坐标(x,y,z)
- renderOrder:Integer 渲染顺序
- rotation:Euler 角度
- scale:Vector3 缩放倍数
- userData:Object 放自定义数据的地方
- uuid:String 唯一ID
- visible:Boolean 是否可见

Vector3 3维向量
- x:Float x值
- y:Float y值
- z:Float z值

 我经常在console里打印数据出来分析:
控制台打印数据
 有很多东西在前期会搞不懂,直到读完《交互式计算机图形学——基于WebGL的自顶向下方法(第七版)》搞懂很多原理之后,就好多了。前期主要是参考模仿代码做几个效果,直到有一种强烈的欲望想去了解原理时,再去了解原理。

其它

  • obj文件格式比较简单,很多如不动点(pivot)、分层等数据是不能保存下来的。
  • gltf 与 glb格式可以使用工具相互转换。(有对应的vscode插件可以实现)
  • 网上有很多工具可以将不同格式的文件转换成gltf/glb格式,但并不保证能完全兼容,转换出来的效果不一定能完全符合预算。
  • blender并不能把所有的数据导出来。(你可以试一下,使用blender导出的3D文件,再重新导入,会发现有可能不一样,那是因为blender没有导出对应的数据(比如alphaMap贴图)或该3D文件格式不支持那些数据。)

附录