【可视化学习】44-元宇宙基础学习(三)
发表于:2023-09-14 |

前言

本文将讲解之后的综合项目的基础知识,和之前两篇文章一样为之后的元宇宙的项目搭建理论基础

性能优化

前面我们讲过使用lod来提升性能,今天再分享两种提升性能的方法

实例化物体提升性能

在游戏中,我们经常会遇到大量相同的物体,比如树木,草地,石头等等,这些物体都是相同的,只是位置不同,如果我们每个物体都创建一个mesh,那么会占用大量的内存,这时候我们就可以使用实例化的方法来提升性能,实例化的方法就是将一个mesh实例化多次,这样就可以节省大量的内存,下面我们来看一下实例化的方法

普通状态

首先我们创建一个没有实例化的情况

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
<template>
<div id="container"></div>
</template>

<script setup>
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import { onMounted } from "vue";

onMounted(() => {
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x88ccee);
const camera = new THREE.PerspectiveCamera(
70,
window.innerWidth / window.innerHeight,
0.001,
1000
);
camera.position.set(0, 5, 10);

const container = document.getElementById("container");

const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.VSMShadowMap;
renderer.toneMapping = THREE.ACESFilmicToneMapping;
container.appendChild(renderer.domElement);

const controls = new OrbitControls(camera, renderer.domElement);
controls.target.set(0, 0, 0);

console.log(renderer.info);

function animate() {
controls.update();
renderer.render(scene, camera);
requestAnimationFrame(animate);
}

// 创建一个平面
const planeGeometry = new THREE.PlaneGeometry(20, 20, 1, 1);
const planeMaterial = new THREE.MeshBasicMaterial({
color: 0xffffff,
side: THREE.DoubleSide,
});
const plane = new THREE.Mesh(planeGeometry, planeMaterial);
plane.receiveShadow = true;
plane.rotation.x = -Math.PI / 2;
scene.add(plane);

// 形状和材质必须是相同的
const geometry = new THREE.TorusKnotGeometry(1, 0.3, 100, 16);
const material = new THREE.MeshBasicMaterial({ color: 0xffff00 });
const torusKnot = new THREE.Mesh(geometry, material);
torusKnot.position.set(0, 2, 0);
scene.add(torusKnot);

for (let i = 0; i < 1000; i++) {
const torusKnot = new THREE.Mesh(geometry, material);
torusKnot.position.set(
Math.random() * 100 - 50,
Math.random() * 100 - 50,
Math.random() * 100 - 50
);
scene.add(torusKnot);
}
animate();
});
</script>

<style>
* {
margin: 0;
padding: 0;
}
#container {
width: 100vw;
height: 100vh;
}
</style>

这里我们打印了renderer的info,可以看我们的渲染信息
占用资源图

使用实例

我们使用实例化的方法来创建物体
我先先将创建物体这一段注释掉

1
2
3
4
5
6
7
8
9
10
11
12
// torusKnot.position.set(0, 2, 0);
// scene.add(torusKnot);

// for (let i = 0; i < 1000; i++) {
// const torusKnot = new THREE.Mesh(geometry, material);
// torusKnot.position.set(
// Math.random() * 100 - 50,
// Math.random() * 100 - 50,
// Math.random() * 100 - 50
// );
// scene.add(torusKnot);
// }

接下来使用我们的实例化方法

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
const instanceMesh = new THREE.InstancedMesh(geometry, material, 10000);
for (let i = 0; i < 1000; i++) {
const position = new THREE.Vector3(
Math.random() * 100 - 50,
Math.random() * 100 - 50,
Math.random() * 100 - 50
);
const rotation = new THREE.Euler(
Math.random() * Math.PI * 2,
Math.random() * Math.PI * 2,
Math.random() * Math.PI * 2
);
// 四元数用于表示旋转
const quaternion = new THREE.Quaternion().setFromEuler(rotation);
const scale = new THREE.Vector3(
Math.random() * 0.5 + 0.5,
Math.random() * 0.5 + 0.5,
Math.random() * 0.5 + 0.5
);
let matrix = new THREE.Matrix4();
matrix.compose(position, quaternion, scale);

instanceMesh.setMatrixAt(i, matrix);
}

scene.add(instanceMesh);

占用资源图
可以明显看到,占用的资源减少了很多。

合并物体提升性能

前面的实例化要求是物体形状和材质都是一样的,如果只是材质一样,物体形状不一样,我们可以使用合并物体的方法来提升性能

合并物体

这里我换了一种方法来让物体进行旋转平移,大家可以和上面那种方法进行比对学习

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
import * as BufferGeometryUtils from "three/examples/jsm/utils/BufferGeometryUtils.js";
const material = new THREE.MeshBasicMaterial({ color: 0xffff00 });
// 形状不一样和材质必须是相同的

let geometries = [];
for (let i = 0; i < 1000; i++) {
const geometry = new THREE.TorusKnotGeometry(
0.5 + Math.random() * 0.5,
0.3,
50 + parseInt(Math.random() * 50),
16
);
const matrix = new THREE.Matrix4();
matrix.makeRotationX(Math.random() * Math.PI);
matrix.makeRotationY(Math.random() * Math.PI);
matrix.makeRotationZ(Math.random() * Math.PI);
matrix.makeScale(
Math.random() * 0.5 + 0.5,
Math.random() * 0.5 + 0.5,
Math.random() * 0.5 + 0.5
);
matrix.makeTranslation(
Math.random() * 100 - 50,
Math.random() * 100 - 50,
Math.random() * 100 - 50
);

geometry.applyMatrix4(matrix);
geometries.push(geometry);
}
let mergeGeometry = BufferGeometryUtils.mergeBufferGeometries(geometries);
let mesh = new THREE.Mesh(mergeGeometry, material);
scene.add(mesh);

占用资源图

物理材质和光效

这个其实在前面的一篇文章中阐述过了,因为是之后综合项目要用到这一块,我就重新讲一下

载入hdr

1
2
3
4
5
6
7
8
import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader";
const hdrLoader = new RGBELoader();
hdrLoader.load("./hdr/023.hdr", (texture) => {
texture.mapping = THREE.EquirectangularReflectionMapping;
texture.format = THREE.RGBAFormat;
scene.background = texture;
scene.environment = texture;
});

hdr图

添加小球

1
2
3
4
5
6
7
8
 const sphereGeometry = new THREE.SphereGeometry(1, 32, 32);
const sphereMaterial = new THREE.MeshPhysicalMaterial({
color: 0xffffff,
});
sphereMaterial._iridescence = 1;
const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
sphere.position.set(0, 0, 0);
scene.add(sphere);

小球图

设置透光度

1
2
3
4
5
6
const sphereMaterial = new THREE.MeshPhysicalMaterial({
color: 0xffffff,
// 透光程度
transmission: 1,
transparent: true,
});

透光小球图

设置粗糙度和反射

1
2
3
4
5
6
7
8
const sphereMaterial = new THREE.MeshPhysicalMaterial({
color: 0xffffff,
// 透光程度
transmission: 1,
transparent: true,
roughness: 0,
reflectivity: 1,
});

反射小球图

设置虹膜

1
2
3
4
5
6
7
8
9
10
11
const sphereMaterial = new THREE.MeshPhysicalMaterial({
color: 0xffffff,
// 透光程度
transmission: 1,
transparent: true,
roughness: 0,
reflectivity: 1,
ior: 2.33,
iridescenceIOR: 1.33,
});
sphereMaterial._iridescence = 1;

虹膜小球图

动态世界环境光

普通光

我们添加一个立方体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 创建球
const sphereGeometry = new THREE.SphereGeometry(1, 32, 32);
let sphereMaterial = new THREE.MeshPhysicalMaterial({
color: 0xffffff,
// 透光程度
transparent: true,
roughness: 0,
metalness: 1,
});

const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
sphere.position.set(0, 0, 0);
scene.add(sphere);

// 创建立方体
const boxGeometry = new THREE.BoxGeometry(1, 1, 1);
const boxMaterial = new THREE.MeshPhysicalMaterial({
color: 0xffffff,
});
const box = new THREE.Mesh(boxGeometry, boxMaterial);
box.position.set(3, 0, 0);
scene.add(box);

没有世界环境光
我们可以看到我们的小球只反射了环境贴图,并没有对我们的立方体进行反射,这时候我们就需要添加世界环境光

世界环境光

创建cubeTarget

1
2
3
// 创建cubeTarget
const cubeRenderTarget = new THREE.WebGLCubeRenderTarget(512);
const cubeCamera = new THREE.CubeCamera(0.1, 1000, cubeRenderTarget);

hdr加载完将材质添加到cubeTarget上

1
2
3
4
5
6
7
 hdrLoader.load("./hdr/023.hdr", (texture) => {
texture.mapping = THREE.EquirectangularReflectionMapping;
texture.format = THREE.RGBAFormat;
scene.background = texture;
scene.environment = texture;
sphereMaterial.envMap = cubeRenderTarget.texture;
});

animate进行实时渲染

1
2
3
4
5
6
7
8
9
function animate() {

stats.update();
controls.update();

cubeCamera.update(renderer, scene);
renderer.render(scene, camera);
requestAnimationFrame(animate);
}

有世界环境光
这时我们可以看到我们的小球也反射了立方体

镜面反射

普通平面

1
2
3
4
5
6
7
8
const planeGeometry = new THREE.PlaneGeometry(10, 10);
const planeMaterial = new THREE.MeshPhysicalMaterial({
color: 0xffffff,
});
const plane = new THREE.Mesh(planeGeometry, planeMaterial);
plane.position.set(0, -1, 0);
plane.rotation.x = -Math.PI / 2;
scene.add(plane);

普通平面

反射平面

注释掉下面这段代码

1
2
3
4
// const planeMaterial = new THREE.MeshPhysicalMaterial({
// color: 0xffffff,
// });
// const plane = new THREE.Mesh(planeGeometry, planeMaterial);

添加以下代码

1
2
3
4
5
const plane = new Reflector(planeGeometry, {
textureWidth: 1024,
textureHeight: 1024,
color: 0xffffff,
});

镜面平面

canvas绘制3d聊天框

添加canvas

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 创建canvas对象
const canvas = document.createElement("canvas");
canvas.width = 1080;
canvas.height = 1080;
canvas.style.position = "absolute";
canvas.style.top = "0px";
canvas.style.left = "0px";
canvas.style.zIndex = "1";
canvas.style.transformOrigin = "0 0";
canvas.style.transform = "scale(0.1)";
const context = canvas.getContext("2d");

var image = new Image();
image.src = "./textures/chat.png";
image.onload = function () {
context.drawImage(image, 0, 0, 1080, 1080);
context.textAlign = "center";
context.textBaseline = "middle";
context.font = "bold 100px Arial";
context.fillStyle = "rgba(0,255,255,1)";
context.fillText("Hello World", canvas.width / 2, canvas.height / 2);
};
document.body.appendChild(canvas);

效果图

创建一个平面

1
2
3
4
5
6
7
8
9
10
// 创建一个平面
const planeGeometry = new THREE.PlaneGeometry(2, 2, 1, 1);
const planeMaterial = new THREE.MeshBasicMaterial({
color: 0xffffff,
side: THREE.DoubleSide,
transparent: true,
depthWrite: false,
});
const plane = new THREE.Mesh(planeGeometry, planeMaterial);
scene.add(plane);

效果图

canvas作为纹理贴图给平面

1
2
3
4
5
6
7
8
9
10
11
12
image.onload = function () {
context.drawImage(image, 0, 0, 1080, 1080);
context.textAlign = "center";
context.textBaseline = "middle";
context.font = "bold 100px Arial";
context.fillStyle = "rgba(0,255,255,1)";
context.fillText("Hello World", canvas.width / 2, canvas.height / 2);
let texture = new THREE.CanvasTexture(canvas);
plane.material.map = texture;
plane.material.alphaMap = texture;
plane.material.needsUpdate = true;
};

效果图

设置alpha填充图片

1
2
let alphaMap = new THREE.TextureLoader().load("./textures/chat_alpha.png");
plane.material.alphaMap=alphaMap

效果图

视频纹理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 添加视频纹理
const video = document.createElement("video");
video.src = "./video/keji1.mp4";
// 如果想要视频能够自动播放,那么就设置为静音
video.muted = true;
video.loop = true;
video.play();
const texture = new THREE.VideoTexture(video);

// 创建一个平面
const planeGeometry = new THREE.PlaneGeometry(2, 2, 1, 1);
const planeMaterial = new THREE.MeshBasicMaterial({
color: 0xffffff,
side: THREE.DoubleSide,
transparent: true,
blending: THREE.AdditiveBlending,
depthWrite: false,
map: texture,
alphaMap: texture,
});
const plane = new THREE.Mesh(planeGeometry, planeMaterial);
scene.add(plane);

视频结合canvas纹理

代码

创建canvas和视频,把视频当作图片绘制在canvas里面,然后canvas当作texture给我们的平面然后在animate中调用

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
// 创建canvas对象
const canvas = document.createElement("canvas");
canvas.width = 1080;
canvas.height = 1080;
canvas.style.position = "absolute";
canvas.style.top = "0px";
canvas.style.left = "0px";
canvas.style.zIndex = "1";
canvas.style.transformOrigin = "0 0";
canvas.style.transform = "scale(0.1)";
const context = canvas.getContext("2d");

const video = document.createElement("video");
video.src = "./video/ui_chat.mp4";
// 如果想要视频能够自动播放,那么就设置为静音
video.muted = true;
video.loop = true;
video.play();
const texture = new THREE.CanvasTexture(canvas);

// 创建一个平面
const planeGeometry = new THREE.PlaneGeometry(2, 2, 1, 1);
const planeMaterial = new THREE.MeshBasicMaterial({
color: 0xffffff,
side: THREE.DoubleSide,
map: texture,
alphaMap: texture,
transparent: true,
blending: THREE.AdditiveBlending,
depthWrite: false,
});
const plane = new THREE.Mesh(planeGeometry, planeMaterial);
scene.add(plane);

function drawVideoText() {
context.clearRect(0, 0, canvas.width, canvas.height);
context.drawImage(video, 0, 0, canvas.width, canvas.height);

context.textAlign = "center";
context.textBaseline = "middle";
context.font = "bold 100px Arial";
context.fillStyle = "rgba(255,255,255,1)";
context.fillText("Hello World", canvas.width / 2, canvas.height / 2);

texture.needsUpdate = true;
planeMaterial.needsUpdate = true;
}
document.body.appendChild(canvas);

animate使用

1
2
3
4
5
6
7
function animate() {
drawVideoText();
stats.update();
controls.update();
renderer.render(scene, camera);
requestAnimationFrame(animate);
}

音频远近声音

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
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
<template>
<div id="container"></div>
</template>

<script setup>
import * as THREE from "three";
import Stats from "three/examples/jsm/libs/stats.module.js";

import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";

import { Octree } from "three/examples/jsm/math/Octree.js";
import { OctreeHelper } from "three/examples/jsm/helpers/OctreeHelper.js";

import { Capsule } from "three/examples/jsm/math/Capsule.js";

import { GUI } from "three/examples/jsm/libs/lil-gui.module.min.js";

import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";

import { reactive, onMounted, ref } from "vue";

onMounted(() => {
const clock = new THREE.Clock();

const scene = new THREE.Scene();
scene.background = new THREE.Color(0x88ccee);
scene.fog = new THREE.Fog(0x88ccee, 0, 50);

const camera = new THREE.PerspectiveCamera(
70,
window.innerWidth / window.innerHeight,
0.001,
1000
);
camera.position.set(0, 5, 10);

const backCamera = new THREE.PerspectiveCamera(
70,
window.innerWidth / window.innerHeight,
0.001,
1000
);
camera.position.set(0, 5, -10);

const container = document.getElementById("container");

const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.VSMShadowMap;
renderer.outputEncoding = THREE.sRGBEncoding;
renderer.toneMapping = THREE.ACESFilmicToneMapping;
container.appendChild(renderer.domElement);

const stats = new Stats();
stats.domElement.style.position = "absolute";
stats.domElement.style.top = "0px";
container.appendChild(stats.domElement);

// const controls = new OrbitControls(camera, renderer.domElement);
// controls.target.set(0, 0, 0);
let activeCamera = camera;

function animate() {
let delta = clock.getDelta();
// console.log(delta);
controlPlayer(delta);
updatePlayer(delta);
resetPlayer();
stats.update();
// controls.update();
// 更新动作
if (mixer) {
mixer.update(delta);
}
renderer.render(scene, activeCamera);
requestAnimationFrame(animate);
}

// 创建一个平面
const planeGeometry = new THREE.PlaneGeometry(20, 20, 1, 1);
const planeMaterial = new THREE.MeshBasicMaterial({
color: 0xffffff,
side: THREE.DoubleSide,
});
const plane = new THREE.Mesh(planeGeometry, planeMaterial);
plane.receiveShadow = true;
plane.rotation.x = -Math.PI / 2;

// 创建立方体叠楼梯的效果
for (let i = 0; i < 10; i++) {
const boxGeometry = new THREE.BoxGeometry(1, 1, 0.15);
const boxMaterial = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const box = new THREE.Mesh(boxGeometry, boxMaterial);
box.position.y = 0.15 + i * 0.15;
box.position.z = i * 0.3;
plane.add(box);
}

// 创建一个octree

const group = new THREE.Group();
group.add(plane);
scene.add(group);
const worldOctree = new Octree();
worldOctree.fromGraphNode(group);

// 创建一个octreeHelper
// const octreeHelper = new OctreeHelper(worldOctree);
// scene.add(octreeHelper);

// 创建一个人的碰撞体
const playerCollider = new Capsule(
new THREE.Vector3(0, 0.35, 0),
new THREE.Vector3(0, 1.35, 0),
0.35
);

console.log(playerCollider.getCenter(new THREE.Vector3()));
console.log(worldOctree);

// 创建一个平面
// const capsuleBodyGeometry = new THREE.PlaneGeometry(1, 0.5, 1, 1);
// const capsuleBodyMaterial = new THREE.MeshBasicMaterial({
// color: 0x0000ff,
// side: THREE.DoubleSide,
// });
// const capsuleBody = new THREE.Mesh(capsuleBodyGeometry, capsuleBodyMaterial);
// capsuleBody.position.set(0, 0.5, 0);

// 添加半球光源
const hemisphereLight = new THREE.HemisphereLight(0xffffff, 0xffffff, 1);
scene.add(hemisphereLight);

// 加载机器人模型
const loader = new GLTFLoader();
// 设置动作混合器
let mixer = null;
let actions = {};
// 设置激活动作
let activeAction = null;
loader.load("./models/RobotExpressive.glb", (gltf) => {
const robot = gltf.scene;
robot.scale.set(0.5, 0.5, 0.5);
robot.position.set(0, -0.88, 0);
console.log(gltf);
capsule.add(robot);
mixer = new THREE.AnimationMixer(robot);
for (let i = 0; i < gltf.animations.length; i++) {
let name = gltf.animations[i].name;
actions[name] = mixer.clipAction(gltf.animations[i]);
if (name == "Idle" || name == "Walking" || name == "Running") {
actions[name].clampWhenFinished = false;
actions[name].loop = THREE.LoopRepeat;
} else {
actions[name].clampWhenFinished = true;
actions[name].loop = THREE.LoopOnce;
}
}
activeAction = actions["Idle"];
activeAction.play();
console.log(actions);
});

// 创建一个胶囊物体
// const capsuleGeometry = new THREE.CapsuleGeometry(0.35, 1, 32);
// const capsuleMaterial = new THREE.MeshBasicMaterial({
// color: 0xff0000,
// side: THREE.DoubleSide,
// });
// const capsule = new THREE.Mesh(capsuleGeometry, capsuleMaterial);
const capsule = new THREE.Object3D();
capsule.position.set(0, 0.85, 0);

// 将相机作为胶囊的子元素,就可以实现跟随
camera.position.set(0, 2, -5);
camera.lookAt(capsule.position);
backCamera.position.set(0, 2, 5);
backCamera.lookAt(capsule.position);
// controls.target = capsule.position;
// 控制旋转上下的空3d对象
const capsuleBodyControl = new THREE.Object3D();
capsuleBodyControl.add(camera);
capsuleBodyControl.add(backCamera);
capsule.add(capsuleBodyControl);

// capsule.add(capsuleBody);

scene.add(capsule);

// 设置重力
const gravity = -9.8;
// 玩家的速度
const playerVelocity = new THREE.Vector3(0, 0, 0);
// 方向向量
const playerDirection = new THREE.Vector3(0, 0, 0);
// 键盘按下事件
const keyStates = {
KeyW: false,
KeyA: false,
KeyS: false,
KeyD: false,
Space: false,
isDown: false,
};

// 玩家是否在地面上
let playerOnFloor = false;

function updatePlayer(deltaTime) {
let damping = -0.05;
if (playerOnFloor) {
playerVelocity.y = 0;

keyStates.isDown ||
playerVelocity.addScaledVector(playerVelocity, damping);
} else {
playerVelocity.y += gravity * deltaTime;
}

// console.log(playerVelocity);
// 计算玩家移动的距离
const playerMoveDistance = playerVelocity.clone().multiplyScalar(deltaTime);
playerCollider.translate(playerMoveDistance);
// 将胶囊的位置进行设置
playerCollider.getCenter(capsule.position);

// 进行碰撞检测
playerCollisions();

// console.log(Math.abs(playerVelocity.x) + Math.abs(playerVelocity.z));
// 如果有水平的运动,则设置运动的动作
if (
Math.abs(playerVelocity.x) + Math.abs(playerVelocity.z) > 0.1 &&
Math.abs(playerVelocity.x) + Math.abs(playerVelocity.z) <= 3
) {
fadeToAction("Walking");
} else if (Math.abs(playerVelocity.x) + Math.abs(playerVelocity.z) > 3) {
fadeToAction("Running");
} else {
fadeToAction("Idle");
}
}

function playerCollisions() {
// 人物碰撞检测
const result = worldOctree.capsuleIntersect(playerCollider);
// console.log(result);
playerOnFloor = false;
if (result) {
playerOnFloor = result.normal.y > 0;
playerCollider.translate(result.normal.multiplyScalar(result.depth));
}
}

function resetPlayer() {
if (capsule.position.y < -20) {
playerCollider.start.set(0, 2.35, 0);
playerCollider.end.set(0, 3.35, 0);
playerCollider.radius = 0.35;
playerVelocity.set(0, 0, 0);
playerDirection.set(0, 0, 0);
}
}

// 根据键盘按下的键来更新键盘的状态
document.addEventListener(
"keydown",
(event) => {
console.log(event.code);
keyStates[event.code] = true;
keyStates.isDown = true;
},
false
);
document.addEventListener(
"keyup",
(event) => {
keyStates[event.code] = false;
keyStates.isDown = false;
if (event.code === "KeyV") {
activeCamera = activeCamera === camera ? backCamera : camera;
}
if (event.code === "KeyT") {
// 打招呼
fadeToAction("Wave");
}
},
false
);

function fadeToAction(actionName) {
let prevAction = activeAction;
activeAction = actions[actionName];
if (prevAction != activeAction) {
prevAction.fadeOut(0.3);
activeAction
.reset()
.setEffectiveTimeScale(1)
.setEffectiveWeight(1)
.fadeIn(0.3)
.play();

mixer.addEventListener("finished", (e) => {
let prevAction = activeAction;
activeAction = actions["Idle"];
prevAction.fadeOut(0.3);
activeAction
.reset()
.setEffectiveTimeScale(1)
.setEffectiveWeight(1)
.fadeIn(0.3)
.play();
});
}
}
document.addEventListener(
"mousedown",
(event) => {
// 锁定鼠标指针
document.body.requestPointerLock();
sound1.play();
sound2.play();
},
false
);

// 根据键盘状态更新玩家的速度
function controlPlayer(deltaTime) {
if (keyStates["KeyW"]) {
playerDirection.z = 1;
//获取胶囊的正前面方向
const capsuleFront = new THREE.Vector3(0, 0, 0);
capsule.getWorldDirection(capsuleFront);
// console.log(capsuleFront);
// 计算玩家的速度
playerVelocity.add(capsuleFront.multiplyScalar(deltaTime * 5));
}
if (keyStates["KeyS"]) {
playerDirection.z = 1;
//获取胶囊的正前面方向
const capsuleFront = new THREE.Vector3(0, 0, 0);
capsule.getWorldDirection(capsuleFront);
// console.log(capsuleFront);
// 计算玩家的速度
playerVelocity.add(capsuleFront.multiplyScalar(-deltaTime));
}
if (keyStates["KeyA"]) {
playerDirection.x = 1;
//获取胶囊的正前面方向
const capsuleFront = new THREE.Vector3(0, 0, 0);
capsule.getWorldDirection(capsuleFront);

// 侧方的方向,正前面的方向和胶囊的正上方求叉积,求出侧方的方向
capsuleFront.cross(capsule.up);
// console.log(capsuleFront);
// 计算玩家的速度
playerVelocity.add(capsuleFront.multiplyScalar(-deltaTime));
}
if (keyStates["KeyD"]) {
playerDirection.x = 1;
//获取胶囊的正前面方向
const capsuleFront = new THREE.Vector3(0, 0, 0);
capsule.getWorldDirection(capsuleFront);

// 侧方的方向,正前面的方向和胶囊的正上方求叉积,求出侧方的方向
capsuleFront.cross(capsule.up);
// console.log(capsuleFront);
// 计算玩家的速度
playerVelocity.add(capsuleFront.multiplyScalar(deltaTime));
}
if (keyStates["Space"]) {
playerVelocity.y = 15;
}
}

// 根据鼠标在屏幕移动,来旋转胶囊
window.addEventListener(
"mousemove",
(event) => {
capsule.rotation.y -= event.movementX * 0.003;

capsuleBodyControl.rotation.x += event.movementY * 0.003;
if (capsuleBodyControl.rotation.x > Math.PI / 8) {
capsuleBodyControl.rotation.x = Math.PI / 8;
} else if (capsuleBodyControl.rotation.x < -Math.PI / 8) {
capsuleBodyControl.rotation.x = -Math.PI / 8;
}
},
false
);

// 多层次细节展示
const material = new THREE.MeshBasicMaterial({
color: 0xff0000,
wireframe: true,
});
let lod = new THREE.LOD();
for (let i = 0; i < 5; i++) {
const geometry = new THREE.SphereGeometry(1, 22 - i * 5, 22 - i * 5);

const mesh = new THREE.Mesh(geometry, material);

lod.addLevel(mesh, i * 5);
}
let mesh = new THREE.Mesh(new THREE.PlaneGeometry(1, 1), material);
mesh.visible = false;
lod.addLevel(mesh, 25);
lod.position.set(10, 0, 10);
scene.add(lod);

// 创建2个小球
const sphereGeometry = new THREE.SphereGeometry(0.5, 32, 32);
const sphereMaterial = new THREE.MeshBasicMaterial({
color: 0xff0000,
});
const sphere1 = new THREE.Mesh(sphereGeometry, sphereMaterial);
sphere1.position.set(-10, 0, 10);
scene.add(sphere1);

const sphereMaterial2 = new THREE.MeshBasicMaterial({
color: 0x0000ff,
});
const sphere2 = new THREE.Mesh(sphereGeometry, sphereMaterial2);
sphere2.position.set(10, 0, -10);
scene.add(sphere2);

// 创建positionAudio

const listener1 = new THREE.AudioListener();
camera.add(listener1);

const sound1 = new THREE.PositionalAudio(listener1);
const audioLoader = new THREE.AudioLoader();
audioLoader.load("./audio/child.mp3", (buffer) => {
sound1.setBuffer(buffer);
sound1.setRefDistance(1);
});
sphere1.add(sound1);

const listener2 = new THREE.AudioListener();
camera.add(listener2);
const sound2 = new THREE.PositionalAudio(listener2);
audioLoader.load("./audio/gyz.mp3", (buffer) => {
sound2.setBuffer(buffer);
sound2.setRefDistance(1);
});
sphere2.add(sound2);

animate();
});
</script>

<style>
* {
margin: 0;
padding: 0;
}
#container {
width: 100vw;
height: 100vh;
}
</style>

全损音质,就听个乐哈,设置了距离相机的距离,就会有远近的声音

结语

本篇文章就到这里了,下一篇文章我们将会讲解如何使用three.js创建一个简单的元宇宙游戏,敬请期待!

上一篇:
【项目配置】6-router-permission结合ts封装
下一篇:
【可视化学习】43-元宇宙基础学习(二)