three.js学习之盒子

通过前面三节的学习,我们已经知道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
2
3
4
const width = 1;
const height = 1;
const depth = 1;
const cube = new THREE.BoxGeometry(width, height, depth)

代码中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
2
3
4
5
6
7
const boxWidth = 4;
const boxHeight = 4;
const boxDepth = 4;
const widthSegments = 1;
const heightSegments = 1;
const depthSegments = 1;
const geometry = new THREE.BoxGeometry(boxWidth, boxHeight, boxDepth, widthSegments, heightSegments, depthSegments);

这时候我们看到的内容好像和之前没什么变化,也没看到线,如下:

其实很简单,要想看到具体分割的样式,我们还需要在原材料上做一个简单的设置,将

1
const material = new THREE.MeshPhongMaterial({color: 0x44aa88});

改成

1
const material = new THREE.MeshPhongMaterial({color: 0x44aa88,  wireframe: true});

现在,我们就能看到线框形式渲染的图形了,如下:

这样,有线条分割的立体线框图就出来了,可以根据自己的需要,去改变不同位置的分割线条数来看看不同的结果。将动画停下来,去数一下分割线分割出来的区块。

在这里需要补充说明的是图元基本都是由三角形组成的,因此在数分割线的时候,请忽略掉对应方向的斜线。

以分割线的方式的方式显示,虽然我们做的东西显示出来了,但是实体没了。美观上还是不够的,所以还有一种既能显示实体,又能显示分割线的方式。

WireframeGeometry来显示线框

除了将材料的wireframe设置为true用来显示图元的框架外,我们还可以利用WireframeGeometry来实现同时显示实体和分割线的样式。

因为涉及到新的知识点和调整,首先将显示虚线应用代码先全部复制过来,我们再进行代码改造。

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
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 scene = new THREE.Scene();
const material = new THREE.MeshPhongMaterial({color: 0x44aa88, wireframe: true});

const boxWidth = 4;
const boxHeight = 4;
const boxDepth = 4;
const geometry = new THREE.BoxGeometry(boxWidth, boxHeight, boxDepth, 2, 2, );

const cube = new THREE.Mesh(geometry, material);
cube.position.z = -8
const scene = new THREE.Scene();
scene.add(cube)

{
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
cube.rotation.x = time
cube.rotation.y = time
renderer.render(scene, camera)
requestAnimationFrame(render)
}
requestAnimationFrame(render)
}
  • 恢复显示完整实体的代码
    在先有的代码中,我们是通过将const material = new THREE.MeshPhongMaterial({color: 0x44aa88, wireframe: true});中的wireframe设置为true来实现显示立方体的边框,因此,我们首先将wireframe设置为false或者直接删除掉,改成下面的样子:

    1
    2
    const 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
    2
    const 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>