three.js学习之基础网格材质MeshBasicMaterial

three.js学习之立体圆环及立体扭结圆环中,我们学习了立体图像的最后一部分内容,今天开始,我们要学习material的内容了,在开始之前,让我们将思路回到threejs学习第一课:了解threejs的基本概念,复习一下相关的内容。

threejs学习第一课:了解threejs的基本概念中,我们知道一个three.js应用由rendererCameraScene三部分中组成,而Scene中除了light光源外,顺着节点往下看,几乎所有东西都是由Mesh组成的,Mesh的基本组成单元,则是geometry图元(为了便于理解,我们也曾称它为设计图)和Material材料(为了便于理解我们也叫它原材料)组成。在前面的章节中,我们已经学习了geometry中的一些基本图元,从这一节开始,我们就要学习一些基础的Material了。

在正式开始前,three.js学习之立体圆环及立体扭结圆环中的代码,删除除了扭结圆环外的所有代码

基础网格材质MeshBasicMaterial用法学习

在现实生活中,想找到一个不受光照影响的材料,简直太难了。但在three.js中,这种材料是我们要学习的最简单,最基本的的材料。

MeshBasicMaterial是three.js中最基本的原材料,它的展示不受光照的影响,永远都是一种状态。

在使用MeshBasicMaterial构建材料时,和我们之前构建图元的方法有些微小的差别,在构建图元时,我们是把参数分开一个个的传进去,而在构建材料时,我们是将所有参数放到一个对象中一次性传入。

原材料的构建其实特别简单,只要没有特殊需求,在构建时传入一个空对象即可,这时three.js会按照系统默认的情况绘制图元,绘制出来的图形如下显示:

  • MeshBasicMaterial构建时传入一个空对象示例

    这里我们将three.js学习之立体圆环及立体扭结圆环中的扭结圆环代码复制过来,然后改造一下
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    {
    // 圆环扭结集合体
    const radius = 2;
    const tube = 0.3;
    const radialSegments = getSearchObj('radialSegments') || 10;
    const tubularSegments = getSearchObj('tubularSegments') || 100
    const p = getSearchObj('p') || 5
    const q = getSearchObj('q') || 4
    const torusKnotGeometry = new THREE.TorusKnotGeometry(radius, tube, tubularSegments, radialSegments, p, q)

    // const material = new THREE.MeshPhongMaterial({ color: 0x00F5FF }) 这段代码注释掉
    const material = new THREE.MeshBasicMaterial({}) // 替换成这段代码

    const tirusKnot = new THREE.Mesh(torusKnotGeometry, material)
    tirusKnot.position.y = 3
    const torusKnotLineGeometry = new THREE.WireframeGeometry(torusKnotGeometry)
    const torusKnotLine = new THREE.Line(torusKnotLineGeometry, lineMaterial)
    tirusKnot.position.x = -2
    torusKnotLine.position.x = 2
    torusKnotLine.position.y = 3
    if (search.includes('tirusKnot') || !search) {
    group.add(tirusKnot)
    }
    }

如果恰巧你运气很好,设计师说,哇哦,宝贝,这就是我想要的结果。那么,一切就该结束了。

可惜啊,很不幸啊。产品看了一会说,猿猿啊,我们换个这个颜色吧!

  • 通过MeshBasicMaterial修改图元(3D形状)颜色

对这种随时修改需求的行为,早已习以为常的猿猿,只说了一个字:“好”。然后默默的

1
const material = new THREE.MeshBasicMaterial({})

修改成了

1
2
3
4
const material = new THREE.MeshBasicMaterial({
color: '#' + getSearchObj('color') || 0xFFE4C4
})

题外话:getSearchObj函数用来获取url上附带的值,如下。

1
2
3
4
5
6
7
8
9
10
11
function getSearchObj (key) {
const search = window.location.search.replace('?', '')
const list = search.split('&')
const obj = {}
for (let i = 0; i < list.length; i++) {
const eachList = list[i].split('=')
obj[eachList[0]] = eachList[1]
}
return obj[key]
}

修改完代码后,猿猿直接将https://www.91yqz.com/learnThree/learnThreeMeshBasicMaterial.html?show=[tirusKnot1]&color=7FFFD4链接发给了颤颤,告诉他:”想要什么颜色,将16进制颜色代码的后6位放到color后面刷新就可以了”

颤颤试着改了几个颜色试了一下,便自己去玩了。

看着颤颤离得不远的背影,猿猿知道,新的需求马上又要来了。

  • 通过MeshBasicMaterial改动wireframe用线条显示图元(3D形状)

    为了完成颤颤即将到来的新需求,猿猿决定先行一步。
    他将原来的代码
    1
    2
    3
    4
    const material = new THREE.MeshBasicMaterial({
    color: '#' + getSearchObj('color') || 0xFFE4C4
    })

    改成了
    1
    2
    3
    4
    5
    const material = new THREE.MeshBasicMaterial({
    color: '#' + getSearchObj('color') || 0xFFE4C4,
    wireframe: getSearchObj('wireframe') === 'true' ? true : false
    })

果然,没过几分钟时间,颤颤又来了。
不等颤颤开售,猿猿率先将刚才的改动讲了起来。
“我刚才在传入的对象中添加了一个参数,他叫wireframe”。每次在使用MeshBasicMaterial构造原材料时,它都会检查有没有传入一个叫wireframe的参数,如果没有,会默认将它设置成false,然后啥也不干。在构建时明确传入wireframetrueMeshBasicMaterial会将图元用线框显示出来。”
“你看,下面这个就是我将wireframe设置为true的形式,您可以通过修改https://www.91yqz.com/learnThree/learnThreeMeshBasicMaterial.html?show=[tirusKnot1]&color=7FFFD4&wireframe=false”中wireframe后面的参数为false或者来控制要不要以线框的形式来显示3D图形。

“我本来想要一个线框的圈圈,但是现在你既然做出来了,那我想要一个线框加实体在一起的那种。”颤颤表示。

“是不是类似下面这样的?”,猿猿问。


“是的是的”颤颤答。

“这种我不做,之前讲geometry时天天做,都做吐了。你想要的话自己去看看three.js学习之盒子–用WireframeGeometry来显示线框,然后自己做”猿猿不悦的说。

“那好吧,线框包裹的不做了,做一个可以控制透明度的立方体总可以吧?”颤颤问。

“要做可以控制透明度的立方体,其实是比较简单的,听我先给你讲讲再做。”

### three.js通过修改MeshBasicMaterial的构建参数transparentopacity来控制图元(3d形状)的透明性
在使用MeshBasicMaterial构造函数创建基础材料时,如果需要控制立体图形的透明度,需要对下面两个参数尽心修改:
transparent 决定材料是否透明,如果设置为true那这个材料就是透明的,你后面对它进行设置才能有效,如果是false你再去设置它的透明度,累死个人还不出活。
* opacity 决定材质的透明度到底是多少。取值范围从0.1到1.0。它生效的前提是提前把transparent设置成了true。

不管颤颤听懂与否,猿猿已经拿起键盘敲了起来。

1
2
3
4
5
const material = new THREE.MeshBasicMaterial({
color: '#' + getSearchObj('color') || 0xFFE4C4,
wireframe: getSearchObj('wireframe') === 'true' ? true : false
})


改成

1
2
3
4
5
6
const material = new THREE.MeshBasicMaterial({
color: '#' + getSearchObj('color') || 0xffe4c4,
wireframe: getSearchObj('wireframe') === 'true' ? true : false,
transparent: getSearchObj('transparent') === 'true' ? true : false,
opacity: getSearchObj('opacity') || 1
})

因为颤颤要求做一个立方体,因此我们新建一个图元,如果对于建造立方体不熟悉的,可以看three.js学习之盒子
1
2
3
4
5
6
7
8
const boxWidth = 2
const boxHeight = 2
const boxDepth = 2

const boxGeometry = new THREE.BoxGeometry(boxWidth, boxHeight, boxDepth);

const box = new THREE.Mesh(boxGeometry, material)
group.add(box)


另外,猿猿还修改了一下Scene的背景颜色,来保证调整完透明度后能看明确的看出来。
将原先代码中的
1
const scene = new THREE.Scene()

改成了
1
2
3
4
const scene = new THREE.Scene()
if (search.includes('box')) {
scene.background = new THREE.Color(0x90EE90)
}

这样,当我们要显示透明立方体时,就将场景的北京颜色改成绿的。

做完了这些工作,猿猿擦了擦身上的汗,一如既往的祭出了一个链接,让颤颤自己去修改https://www.91yqz.com/learnThree/learnThreeMeshBasicMaterial.html?show=[box]&transparent=true&opacity=0.2
下面三个分别是将opacity设置成0.3,0.6,0.9的状态。

  • three.js通过修改MeshBasicMaterial的构建参数side来控制图元(3D形状)的显示方式

趁着颤颤玩透明的功夫,猿猿自己改起来了代码来。
将代码

1
2
3
4
5
6
const material = new THREE.MeshBasicMaterial({
color: '#' + getSearchObj('color') || 0xffe4c4,
wireframe: getSearchObj('wireframe') === 'true' ? true : false,
transparent: getSearchObj('transparent') === 'true' ? true : false,
opacity: getSearchObj('opacity') || 1
})

改成了
1
2
3
4
5
6
7
const material = new THREE.MeshBasicMaterial({
color: '#' + getSearchObj('color') || 0xffe4c4,
wireframe: getSearchObj('wireframe') === 'true' ? true : false,
transparent: getSearchObj('transparent') === 'true' ? true : false,
opacity: getSearchObj('opacity') || 1,
side: THREE[getSearchObj('side') ? getSearchObj('side') : 'DoubleSide'],
})

在新修改的代码中,猿猿新加入了一个叫side的参数。这个side可以用来控制显示图元(3D形状)的那个一面来,他有三个参数,分别如下:

  • THREE.FrontSide 只显示表面
  • THREE.BackSIde 只显示内面,也就是内部。这部分只有在将一个立体形状展开的情况下才能看到
  • THREE.DoubleSide 物体的内外两面都显示出来。

为了便于理解一会给颤颤讲解,猿猿特意将立方体改成了切开的球形,球形部分代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

const material = new THREE.MeshBasicMaterial({
color: '#' + getSearchObj('color') || 0xffe4c4,
wireframe: getSearchObj('wireframe') === 'true' ? true : false,
transparent: getSearchObj('transparent') === 'true' ? true : false,
opacity: getSearchObj('opacity') || 1,
side: THREE[getSearchObj('side') ? getSearchObj('side') : 'DoubleSide'],
})

const lineMaterial = new THREE.LineBasicMaterial({ color: 0xffffff, linecap:'round' })

const sphereGeometry = new THREE.SphereGeometry(3, 15, 20, 0, 1 * Math.PI, 0, 1 * Math.PI)
const sphere = new THREE.Mesh(sphereGeometry, material)
const sphereLineGeometry = new THREE.WireframeGeometry(sphereGeometry)
const sphereLine = new THREE.LineSegments(sphereLineGeometry, lineMaterial)
if (search.includes('sphere')) {
group.add(sphere, sphereLine)
}


THREE.DoubleSide
THREE.BackSIde
THREE.FrontSide

对比三个案例,就能发现对side设置成不同的值后的具体展示形态。

  • three.js通过修改MeshBasicMaterial的构建参数needsUpdatematerial.setValues和来实时修改图元(3D形状)材料的属性

虽然猿猿很像立即将前面的那三个球讲给颤颤,奈何颤颤不想听他说。而是提出了新的要求,要每秒修改一次前面出现的扭结圆环的颜色。这个当然难不倒猿猿了。只需要在构建时将needsUpdate设置为true,后面便可以使用material.setValues灵活的修改材料的属性了。material.setValues是一个方法,接受一个对象,对象中在构建时传入的参数,都可以进行修改。

代码修改如下:

1
2
3
4
5
6
7
8
 const material = new THREE.MeshBasicMaterial({
color: '#' + getSearchObj('color') || 0xffe4c4,
wireframe: getSearchObj('wireframe') === 'true' ? true : false,
transparent: getSearchObj('transparent') === 'true' ? true : false,
opacity: getSearchObj('opacity') || 1,
side: THREE[getSearchObj('side') ? getSearchObj('side') : 'DoubleSide'],
needsUpdate: getSearchObj('needsUpdate') === 'true' ? true : false,
})

然后因为要修改的是颜色,增加下面的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
if (needsUpdate && search.includes('tirusKnot1')){
function getNum (min, max) {
return Math.floor(Math.random() * (max - min)) + min
}
function setMaterailColor () {
console.log('进来了请456鱼')
const r = getNum(0, 255)
const g = getNum(0, 255)
const b = getNum(0, 255)
material.setValues({
color: `rgb(${r}, ${g}, ${b})`
})
setTimeout(() => {
setMaterailColor()
}, 1000)
}
setMaterailColor()
}

下面的getNum函数可以用来获取0到255的随机数

1
2
3
function getNum (min, max) {
return Math.floor(Math.random() * (max - min)) + min
}

获取到随机数后我们将颜色每一秒修改一次就行了。

最终效果如下哦

本篇完整源代码如下,包括所有注释:

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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
<script>
function getSearchObj (key) {
const search = window.location.search.replace('?', '')
const list = search.split('&')
const obj = {}
for (let i = 0; i < list.length; i++) {
const eachList = list[i].split('=')
obj[eachList[0]] = eachList[1]
}
return obj[key]
}
function main () {
const search = location.search
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 group = new THREE.Group()
group.position.z = -10
const lineMaterial = new THREE.LineBasicMaterial({ color: 0xffffff, linecap:'round' })


{
// 圆环扭结集合体
const radius = 2;
const tube = 0.3;
const radialSegments = getSearchObj('radialSegments') || 10;
const tubularSegments = getSearchObj('tubularSegments') || 100
const p = getSearchObj('p') || 5
const q = getSearchObj('q') || 4
const torusKnotGeometry = new THREE.TorusKnotGeometry(radius, tube, tubularSegments, radialSegments, p, q)
const needsUpdate = getSearchObj('needsUpdate') === 'true' ? true : false
const material = new THREE.MeshBasicMaterial({
color: '#' + getSearchObj('color') || 0xFFE4C4,
wireframe: getSearchObj('wireframe') === 'true' ? true : false,
needsUpdate: true,
})



const tirusKnot = new THREE.Mesh(torusKnotGeometry, material)
tirusKnot.position.y = 3
const torusKnotLineGeometry = new THREE.WireframeGeometry(torusKnotGeometry)
const torusKnotLine = new THREE.Line(torusKnotLineGeometry, lineMaterial)
tirusKnot.position.x = -2
torusKnotLine.position.x = 2
torusKnotLine.position.y = 3
if (search.includes('tirusKnot1')) {
group.add(tirusKnot)
if (search.includes('tirusKnot1')) {
tirusKnot.position.x = 0
tirusKnot.position.y = 0
torusKnotLine.position.x = 0
torusKnotLine.position.y = 0
}
if (needsUpdate && search.includes('tirusKnot1')){
function getNum (min, max) {
return Math.floor(Math.random() * (max - min)) + min
}
function setMaterailColor () {
console.log('进来了请456鱼')
const r = getNum(0, 255)
const g = getNum(0, 255)
const b = getNum(0, 255)
material.setValues({
color: `rgb(${r}, ${g}, ${b})`
})
setTimeout(() => {
setMaterailColor()
}, 500)
}
setMaterailColor()
}
}
}

{
const radius = 2;
const tube = 0.3;
const radialSegments = getSearchObj('radialSegments') || 10;
const tubularSegments = getSearchObj('tubularSegments') || 100
const p = getSearchObj('p') || 5
const q = getSearchObj('q') || 4
const torusKnotGeometry = new THREE.TorusKnotGeometry(radius, tube, tubularSegments, radialSegments, p, q)

const material = new THREE.MeshBasicMaterial({
color: '#' + getSearchObj('color') || 0xFFE4C4
})

const lineGeometry = new THREE.WireframeGeometry(torusKnotGeometry)
const line = new THREE.Line(lineGeometry, lineMaterial)

const tirusKnot = new THREE.Mesh(torusKnotGeometry, material)
tirusKnot.position.y = 3
const torusKnotLineGeometry = new THREE.WireframeGeometry(torusKnotGeometry)
const torusKnotLine = new THREE.Line(torusKnotLineGeometry, lineMaterial)
tirusKnot.position.x = -2
torusKnotLine.position.x = 2
torusKnotLine.position.y = 3
if (search.includes('tirusKnot2')) {
group.add(tirusKnot, line)
if (search.includes('tirusKnot2')) {
tirusKnot.position.x = 0
tirusKnot.position.y = 0
torusKnotLine.position.x = 0
torusKnotLine.position.y = 0
}
}
}

{
const boxWidth = 2;
const boxHeight = 2;
const boxDepth = 2;
const boxGeometry = new THREE.BoxGeometry(boxWidth, boxHeight, boxDepth)
const material = new THREE.MeshBasicMaterial({
color: '#' + getSearchObj('color') || 0xffffff,
wireframe: getSearchObj('wireframe') === 'true' ? true : false,
transparent: getSearchObj('transparent') === 'true' ? true : false,
opacity: getSearchObj('opacity') || 1
})

const box = new THREE.Mesh(boxGeometry, material)
if (search.includes('box')) {
group.add(box)
}
}

{
const sphereGeometry = new THREE.SphereGeometry(3, 15, 20, 0, 1 * Math.PI, 0, 1 * Math.PI)
const searchSide = getSearchObj('side')
const side = THREE[searchSide ? searchSide : 'DoubleSide']
const material = new THREE.MeshBasicMaterial({
color: 0x00F5FF,
wireframe: false,
// transparent: true,
// opacity: 0.2,
side: side,
needsUpdate: true
})

// setTimeout(() => {
// console.log('时间到')
// material.setValues({
// color: 0xe74478,
// side: THREE.DoubleSide
// })
// }, 1000 * 30)
const sphere = new THREE.Mesh(sphereGeometry, material)


const sphereLineGeometry = new THREE.WireframeGeometry(sphereGeometry)
const sphereLine = new THREE.LineSegments(sphereLineGeometry, lineMaterial)
if (search.includes('sphere')) {
group.add(sphere, sphereLine)
}
}

const scene = new THREE.Scene()
if (search.includes('box')) {
scene.background = new THREE.Color(0x90EE90)
}
{
const lightColor = 0xffffff; // 灯光颜色
const intensity = 1; // 灯光强度
const light = new THREE.DirectionalLight(lightColor, intensity);
light.position.set(-1, 2, 4);
scene.add(light);
}
scene.add(group)

function resizeRenderSizeToDisplaySize () {
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
}
function render (time) {
if (resizeRenderSizeToDisplaySize()) {
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['children'].forEach((each, index) => {
if (each.position.y === 0) {
each.rotation.y = -time
} else {
each.rotation.y = -time
}
// if (index % 2 === 0) {
// each.rotation.z = -time
// } else {
// each.rotation.z = time
// }
})

// group.position.y = time
// group.position.x = time
renderer.render(scene, camera)
requestAnimationFrame(render)
}
requestAnimationFrame(render)
// render()
}
main()
</script>