通过前面三节的学习,我们已经知道three.js
的基本应用结构,也能做出一个自适应的简单demo来。从这一篇开始,我们将从细节入手,去深入理解three.js
的技术细节。
在hello three.js中,我们做了一个不停转动的立方体。在了解threejs的基本概念中我们知道,three.js
的基本应用但愿Mesh
是由设计图Geometry
和原材料material
组成,我们做立方体的设计图叫BoxGeometry
。类似BoxGeometry
的设计图在three.js
中有很多,之前为了方便理解,我们叫它们设计图,实际上通用的叫法图元
。
现在我们凑着hello three.js的东风,首先学习第一个图元BoxGeometry
盒子。
开始前的准备
在开始之前,还是按照前面的做法,首先新建一个文件learnAboutBoxGeometry.html
,然后将responseThree.html
中的内容都复制过去,重命名文件中的title为‘了解盒子图元BoxGeometry’
了解盒子图元BoxGeometry
之所以从BoxGeometry
开始学习,首先是因为它足够简单,然后就是因为我们首先接触到了它。
BoxGeometry
是用来创建立方体的。生活中我们都知道,立方体由长宽高组成,因此在创建立方体图元的时候,也必须传入长宽高三个参数才可以。如下:
1 | const width = 1; |
代码中width
代表X轴上的距离长度,height
代表Y轴上的距离长度,depth
则代表Z轴上的距离长度。为了便于一些空间感不强的同学理解,我们可以这么理解,width
就是生活中一个立方体物体的宽度,height
是高度,depth
则可以当作长度。有了立方体的设计图(图元),再加上相应的材料Material
,配合Mesh
,我们就能做出一个完整的立方体来了。可以在hello three.js中查看完整案例。
除了长宽高,BoxGeometry
还能接受三个参数,widthSegments — (可选)宽度的分段数,默认值是1。
heightSegments — (可选)高度的分段数,默认值是1,depthSegments — (可选)深度的分段数,默认值是1。
看字面量,分别在长宽高三个方向上的切面。必入说将heightSegments
设置为2,那么就是在高这个方向上将立方体分成2个。看案例:
基于threejs响应式设计开发的案例,添加分段线进去看看情况
1 | const boxWidth = 4; |
这时候我们看到的内容好像和之前没什么变化,也没看到线,如下:
其实很简单,要想看到具体分割的样式,我们还需要在原材料上做一个简单的设置,将
1 | const material = new THREE.MeshPhongMaterial({color: 0x44aa88}); |
改成
1 | const material = new THREE.MeshPhongMaterial({color: 0x44aa88, wireframe: true}); |
现在,我们就能看到线框形式渲染的图形了,如下:
这样,有线条分割的立体线框图就出来了,可以根据自己的需要,去改变不同位置的分割线条数来看看不同的结果。将动画停下来,去数一下分割线分割出来的区块。
在这里需要补充说明的是图元基本都是由三角形组成的,因此在数分割线的时候,请忽略掉对应方向的斜线。
以分割线的方式的方式显示,虽然我们做的东西显示出来了,但是实体没了。美观上还是不够的,所以还有一种既能显示实体,又能显示分割线的方式。
用WireframeGeometry
来显示线框
除了将材料的wireframe
设置为true
用来显示图元的框架外,我们还可以利用WireframeGeometry
来实现同时显示实体和分割线的样式。
因为涉及到新的知识点和调整,首先将显示虚线应用代码先全部复制过来,我们再进行代码改造。
1 | function main () { |
恢复显示完整实体的代码
在先有的代码中,我们是通过将const material = new THREE.MeshPhongMaterial({color: 0x44aa88, wireframe: true});
中的wireframe
设置为true来实现显示立方体的边框,因此,我们首先将wireframe
设置为false或者直接删除掉,改成下面的样子:1
2const material = new THREE.MeshPhongMaterial({color: 0x44aa88});
增加一个
group
群组
关于群组的概念,可以这样理解,群组就是一个整体,这个整体需要不同的零件来组装,然后就可以协调运行。就像现实生活中的汽车一样,汽车是一个整体,但是它需要由发动机,车外壳,内饰,轮胎等组装才能成为一个整体。代码进行如下的改造
1
2
3
4
5// cube.position.z = -8 注释掉设置立方体位置的代码
// scene.add(cube) 注释掉将立方体直接放入场景中的代码
const group = new THREE.group() // 增加群组
group.position.z = -8 // 修改显示群组位置的代码
scene.add(group)另外,在render函数中做如下修改
1
2
3
4// cube.rotation.x = time 注释掉改变立方体位置的代码
// cube.rotation.y = time 注释掉改变立方体位置的代码
group.rotation.x = time // 改成显示群体位置的代码
group.rotation.x = time // 改成显示群体位置的代码
现在,刷新代码,发现啥都没变是不是?
没变就对了,我们接着走下面的步骤:
增加切片线段
之前我们说过,要想显示一个东西,就必须有设计图和材料及用来组合它们粘合剂才行,要想显示切片线段,我们也得首先准备好材料const lineMaterial = new THREE.LineBasicMaterial( { color: 0x00FF7F} );
然后我们要准备好设计图,因为咱们要显示的是切片线段,因此,这里使用特殊的
WireframeGeometry
图元,它是用来以线框的形式显示图元,希望显示什么东西的切线图,就将对应的图元传进去就行,因此以后咱们要显示其它的切片线段,也离不开它。const lineGeometry = new THREE.WireframeGeometry( geometry );
设计图和原材料都有了,接下来进行粘合,然后添加到群组中1
2const line = new THREE.Line(lineGeometry, lineMaterial)
group.add(line)
刷新你的代码,看看效果是否和我的一样
这里涉及到群组,会用就行,后面会深入学习。如果实在纠结,可以线下去查查。
为了方便校对代码,我将最终完成版的代码贴到下面,供大家参考。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<script>
function main () {
const canvas = document.querySelector('#three')
const renderer = new THREE.WebGLRenderer({canvas});
const fov = 75;
const aspect = 2; // 默认值
const near = 0.1;
const far = 200;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
// const material = new THREE.MeshPhongMaterial({color: 0x44aa88, wireframe: true});
const material = new THREE.MeshBasicMaterial({color: 0x44aa88})
const boxWidth = 4;
const boxHeight = 4;
const boxDepth = 4;
const geometry = new THREE.BoxGeometry(boxWidth, boxHeight, boxDepth, 4, 1, 1);
const cube = new THREE.Mesh(geometry, material);
// cube.position.z = -8
const scene = new THREE.Scene();
// scene.add(cube)
const group = new THREE.Group();
group.position.z = -8
const lineMaterial = new THREE.LineBasicMaterial( { color: 0xffffff} );
const lineGeometry = new THREE.WireframeGeometry( geometry );
const line = new THREE.Line(lineGeometry, lineMaterial)
group.add(cube)
group.add(line)
scene.add(group)
{
const lightColor = 0xffffff; // 灯光颜色
const intensity = 1; // 灯光强度
const light = new THREE.DirectionalLight(lightColor, intensity);
light.position.set(-1, 2, 4);
scene.add(light);
}
function render (time) {
if (resizeRenderSizeToDisplaySize(canvas)) {
const pixelRatio = window.devicePixelRatio;
const width = canvas.clientWidth * pixelRatio
const height = canvas.clientHeight * pixelRatio
renderer.setSize(width, height, false)
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}
time *= 0.001
group.rotation.x = time
group.rotation.y = time
renderer.render(scene, camera)
requestAnimationFrame(render)
}
requestAnimationFrame(render)
}
function resizeRenderSizeToDisplaySize (canvas) {
const pixelRatio = window.devicePixelRatio;
const displayWidth = canvas.clientWidth * pixelRatio
const displayHeight = canvas.clientHeight * pixelRatio
const renderWidth = canvas.width;
const renderHeight = canvas.height;
const needResize = displayWidth !== renderWidth || displayHeight !== renderHeight
return needResize
}
window.onload = main
</script>