ltaoo's web

Three.js 渲染自定义模型

Three.js 虽然内置了一些模型,但在实际业务中往往需要导入外部模型,导入的模型可以视为自定义模型。

首先要知道的是模型本质上来说是「三维坐标的集合」,最简单的一个模型可以用三个坐标和连接这三个坐标的「面」来表示。

模型就是数字

1
2
3
4
5
6
7
8
const vertices = [
[0, 0, 0],
[0, 100, 0],
[100, 0, 0],
];
const faces = [
[0, 1, 2],
];

这段代码表示有三个坐标分别为 0, 0,00, 100, 0100, 0, 0,并且这三个点在 vertices 数组中的下标依次为 012,依次连接我们就可以得到一个三角形「面」,所以 [0, 1, 2] 表示的就是这个三角形的面。

image-20200102162447524

当然现在只有三个点所以很简单,但如果再多一个点,哪几个点连接才能变成我们期望的那个形状呢,所以 faces 的意义就在这里,模型有几个面,这个面是由哪三个点连接而成的。

image-20200102162959641

1
2
3
4
5
6
7
8
9
const vertices = [
[0, 0, 0], // 0
[0, 100, 0], // 1
[100, 0, 0], // 2
[100, 100, 0],// 3
];
const faces = [
[0, 1, 2],
];

所以看到的三角形是

image-20200102163622488

并且,直角在右上角的面可以用 [1, 3, 2] 表示;直角在左上角的可以用 [0, 1, 3] 表示;直角在右下角的可以用 [0, 2, 3] 表示;

可能在这里有疑问,这不是一个平面吗,不是模型呀,模型应该是立体的。

其实模型就是多个面组合在一起,又因为面其实就是点,所以开始才说「模型就是三维坐标的集合」。如果想生成一个普遍意义上的模型,可以再增加 1 个三维坐标,和已有的三个坐标组成四个面。

image-20200102150210912

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
const vertices = [
[0, 0, 0], // 0
[0, 100, 0], // 1
[100, 0, 0], // 2
[0, 0, 100], // 3
];
const faces = [
[0, 1, 2],
[0, 1, 3],
[0, 2, 3],
[1, 2, 3],
];

// three.js 代码
const geometry = new THREE.Geometry();
geometry.vertices = [
new THREE.Vector3(0, 0, 0),
new THREE.Vector3(0, 100, 0),
new THREE.Vector3(100, 0, 0),
new THREE.Vector3(0, 0, 100),
];
geometry.faces = [
new THREE.Face3(0, 1, 2),
new THREE.Face3(0, 1, 3),
new THREE.Face3(0, 2, 3),
new THREE.Face3(1, 2, 3),
];
const material = new THREE.MeshBasicMaterial({
side: THREE.DoubleSide,
color: 'red',
});
const mesh = new THREE.Mesh(geometry, material);

这是我理解的模型的本质。

手写 obj 文件渲染模型

再以 obj 格式的模型文件来说明,我们可以手写一个 obj 文件导入到任意支持 obj 格式的软件中,也能渲染出我们预期的模型,仍然拿上面的例子说明。

使用记事本创建一个文本,写入下面内容后将后缀保存为 example.obj

1
2
3
4
5
6
7
8
9
10
o Mesh
v 0 0 0
v 0 100 0
v 100 0 0
v 0 0 100

f 1 2 3
f 1 2 4
f 1 3 4
f 2 3 4

第一行 o Mesh 表示开始一个模型,名字为 Mesh;第二至第五行 v 开头表示四个三维坐标;第七至十行 f 开头表示四个面,这里需要注意的是下标不是从 0 开始,而是从 1 开始

使用 blender 导入该文件,能正确渲染我们预期的模型。

给自定义模型贴图

贴图的本质就是把面上的点对应到贴图上,现在有这样一张贴图

image-20200102152933444

需要把这三个三角形贴到之前的模型的三个等边直角三角形上,只要修改 geometry.faceVertexUvs[0] 即可,对应的值是每个面顶点在贴图上的相对位置。有点难描述,geometry 第一个面是 0, 1, 2 ,我们想给这个面贴上绿色的这个三角形,其实就是把 0, 1, 2 这三个点「放到」贴图上

image-20200102154500201

1
2
3
4
5
6
7
8
9
10
11
geometry.faceVertexUvs[0] = [
// 第一个面上三个点对应贴图上的相对位置
[
// 0 这个点,即 0, 0, 0 对应贴图上 0, 0.5 这个点
[0, 0.5],
// 1 这个点,即 0, 100, 0 对应贴图上 0, 1 这个点
[0, 1],
// 2 这个点,即 100, 0, 0 对应贴图上 0.5, 0.5 这个点
[0.5, 0.5],
],
];

这里还是用 blender 来展示效果,当 blender 加载 example.obj 文件时,会加载相同目录下的同名 mtl 文件,所以再创建一个 example.mtl 文件

1
2
3
4
5
6
7
8
9
10
newmtl material
Ns 323.999994
Ka 1.000000 1.000000 1.000000
Kd 0.800000 0.800000 0.800000
Ks 0.500000 0.500000 0.500000
Ke 0.0 0.0 0.0
Ni 1.450000
d 1.000000
illum 2
map_Kd texture.png

最后一行 map_Kd texture.png 指定了贴图文件路径,可以使用网络地址和绝对路径;以及修改 example.obj 文件,指定「相对位置」和面上的点和相对位置的对应关系。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 这行必须,指定贴图文件
mtllib example.mtl
o Mesh
v 0 0 0
v 0 100 0
v 100 0 0
v 0 0 100
# 新增的「相对位置」
vt 0 0.5
vt 0 1
vt 0.5 0.5
# 使用贴图,这行也是必须的
usemtl material
# 指定 v 和 vt 的对应关系
f 1/1 2/2 3/3
f 1 2 4
f 1 3 4
f 2 3 4

example.objexample.mtltexture.png 放在同一目录下,使用 blender 重新导入 example.obj,点击上方 Shading 菜单,即可看到贴图后的效果。

image-20200102160336959

这是后下方的视角。

要给其他面贴图同理。

总结

虽然上面的例子很简单,但理解了这个例子,再复杂的模型也是一样的原理。并且知道了模型的本质,也能帮助理解模型的变换,甚至可以实现 three.js 不提供的更复杂的功能。

刚开始使用 three.js 时,three.js就像一个黑盒一样,输入一个obj` 文件,输出一个渲染好的模型,这中间发生了什么一直不清楚,遇到问题也不知道怎么解决,甚至于定位问题都做不到,不过后来慢慢摸索,总算是理解了一部分,材质那部分到现在还不是很理解,需要再花些时间去探索。