【可视化学习】50-汽车展示与选配
发表于:2023-10-13 |

前言

今天更新个简单的汽车展示与选配功能。准备工作还是创建vite,安装three,按照之前的文章创建即可。这里就不再赘述了。

测试three是否可用

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
<template>
<div class="home">
</div>
</template>
<script setup>
import * as THREE from "three";
import { onMounted } from "vue";

onMounted(()=>{
// 创建场景
const scene = new THREE.Scene();
// 创建相机
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
// 创建渲染器
const renderer = new THREE.WebGLRenderer();
// 设置渲染器的大小为窗口的内宽度,也就是内容区的宽度,并将其添加到文档中
renderer.setSize(window.innerWidth, window.innerHeight);
// 将渲染器添加到页面中
document.body.appendChild(renderer.domElement);
// 创建一个立方体,并将立方体添加到场景中
const geometry = new THREE.BoxGeometry();
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
camera.position.z = 5;
// 创建一个渲染器的循环函数,这样可以保证在每一帧都能够渲染场景
const animate = function () {
requestAnimationFrame(animate);
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
renderer.render(scene, camera);
};
animate();
})
</script>
<style>
body{
width:100vw;
height:100vh;
position: fixed;
top:0;
left:0;
}
</style>

测试three是否可用

转移目录

这里我使用的是glb格式的模型,所以需要将node_modules中three文件夹下的examples文件夹中的jsm文件夹中的libs中的draco文件夹复制到public文件夹下,这样方便引入我们的glb文件
测试three是否可用

导入模型并调整参数

接下来我们导入模型,修改下一些配置,让显示更加舒服

调整相机

1
2
// 调整相机位置
camera.position.set(0, 2, 6);

初始化渲染器,渲染背景

1
2
3
4
// 初始化渲染器,渲染背景
renderer.setClearColor("#000");
scene.background = new THREE.Color("#ccc");
scene.environment = new THREE.Color("#ccc");

导入控制器

1
2
3
4
5
6
7
8
9
10
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
// 初始化控制器
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
// 创建一个渲染器的循环函数,这样可以保证在每一帧都能够渲染场景
const animate = function () {
renderer.render(scene, camera);
controls.update();
requestAnimationFrame(animate);
};

添加抗锯齿

1
2
3
4
5
// 创建渲染器
const renderer = new THREE.WebGLRenderer({
// 抗锯齿
antialias: true,
});

添加模型

1
2
3
4
5
6
7
8
9
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
// 添加gltf汽车模型
const loader = new GLTFLoader();
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath("./draco/gltf/");
loader.setDRACOLoader(dracoLoader);
loader.load("./model/bmw01.glb", (gltf) => {
scene.add(gltf.scene);
})

添加光

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
// 添加灯光
const light1 = new THREE.DirectionalLight(0xffffff, 1);
light1.position.set(0, 0, 10);
scene.add(light1);
const light2 = new THREE.DirectionalLight(0xffffff, 1);
light2.position.set(0, 0, -10);
scene.add(light2);
const light3 = new THREE.DirectionalLight(0xffffff, 1);
light3.position.set(10, 0, 0);
scene.add(light3);
const light4 = new THREE.DirectionalLight(0xffffff, 1);
light4.position.set(-10, 0, 0);
scene.add(light4);
const light5 = new THREE.DirectionalLight(0xffffff, 1);
light5.position.set(0, 10, 0);
scene.add(light5);
const light6 = new THREE.DirectionalLight(0xffffff, 0.3);
light6.position.set(5, 10, 0);
scene.add(light6);
const light7 = new THREE.DirectionalLight(0xffffff, 0.3);
light7.position.set(0, 10, 5);
scene.add(light7);
const light8 = new THREE.DirectionalLight(0xffffff, 0.3);
light8.position.set(0, 10, -5);
scene.add(light8);
const light9 = new THREE.DirectionalLight(0xffffff, 0.3);
light9.position.set(-5, 10, 0);
scene.add(light9);

添加光

添加网格地面

1
2
3
4
5
// 添加网格地面
const gridHelper = new THREE.GridHelper(10, 10);
gridHelper.material.opacity = 0.2;
gridHelper.material.transparent = true;
scene.add(gridHelper);

添加网格地面

定义初始化材质

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
let wheels = [];
let carBody, frontCar, hoodCar, glassCar;
// 创建材质
const bodyMaterial = new THREE.MeshPhysicalMaterial({
color: 0xff0000,
metalness: 1,
roughness: 0.5,
clearcoat: 1,
clearcoatRoughness: 0,
});

const frontMaterial = new THREE.MeshPhysicalMaterial({
color: 0xff0000,
metalness: 1,
roughness: 0.5,
clearcoat: 1,
clearcoatRoughness: 0,
});
const hoodMaterial = new THREE.MeshPhysicalMaterial({
color: 0xff0000,
metalness: 1,
roughness: 0.5,
clearcoat: 1,
clearcoatRoughness: 0,
});
const wheelsMaterial = new THREE.MeshPhysicalMaterial({
color: 0xff0000,
metalness: 1,
roughness: 0.1,
});
const glassMaterial = new THREE.MeshPhysicalMaterial({
color: 0xffffff,
metalness: 0,
roughness: 0,
transmission: 1,
transparent: true,
});

loader.load("./model/bmw01.glb", (gltf) => {
const bmw = gltf.scene;
// console.log(gltf);
bmw.traverse((child) => {
if (child.isMesh) {
console.log(child.name);
}
// 判断是否是轮毂
if (child.isMesh && child.name.includes("轮毂")) {
child.material = wheelsMaterial;
wheels.push(child);
}
// 判断是否是车身
if (child.isMesh && child.name.includes("Mesh002")) {
carBody = child;
carBody.material = bodyMaterial;
}
// 判断是否是前脸
if (child.isMesh && child.name.includes("前脸")) {
child.material = frontMaterial;
frontCar = child;
}
// 判断是否是引擎盖
if (child.isMesh && child.name.includes("引擎盖_1")) {
child.material = hoodMaterial;
hoodCar = child;
}
// 判断是否是挡风玻璃
if (child.isMesh && child.name.includes("挡风玻璃")) {
child.material = glassMaterial;
glassCar = child;
}
});
scene.add(bmw);
})

定义修改方法

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

let colors = ["red", "blue", "green", "gray", "orange", "purple"];
let selectColor = (index) => {
bodyMaterial.color.set(colors[index]);
frontMaterial.color.set(colors[index]);
hoodMaterial.color.set(colors[index]);
wheelsMaterial.color.set(colors[index]);
// glassMaterial.color.set(colors[index]);
};

let materials = [
{ name: "磨砂", value: 1 },
{ name: "冰晶", value: 0 },
];
let selectMaterial = (index) => {
bodyMaterial.clearcoatRoughness = materials[index].value;
frontMaterial.clearcoatRoughness = materials[index].value;
hoodMaterial.clearcoatRoughness = materials[index].value;
};

修改HTML,CSS

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
<template>
<div class="home">
<div class="canvas-container" ref="canvasDom"></div>

<div class="home-content">
<div class="home-content-title">
<h1>汽车展示与选配</h1>
</div>
<h2>选择车身颜色</h2>
<div class="select">
<div
class="select-item"
v-for="(item, index) in colors"
:key="index"
@click="selectColor(index)"
>
<div
class="select-item-color"
:style="{ backgroundColor: item }"
></div>
</div>
</div>

<h2>选择贴膜材质</h2>
<div class="select">
<div
class="select-item"
v-for="(item, index) in materials"
:key="index"
@click="selectMaterial(index)"
>
<div class="select-item-text">{{ item.name }}</div>
</div>
</div>
</div>
</div>
</template>
<style>
body{
width:100vw;
height:100vh;
position:fixed;
top:0;
left:0;
}

.home-content {
position: fixed;
top: 0;
right: 20px;
}

.select-item-color {
width: 50px;
height: 50px;
border: 1px solid #ccc;
margin: 10px;
display: inline-block;
cursor: pointer;
border-radius: 10px;
}
.select {
display: flex;
}
</style>

完整代码

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
<template>
<div class="home">
<div class="canvas-container" ref="canvasDom"></div>

<div class="home-content">
<div class="home-content-title">
<h1>汽车展示与选配</h1>
</div>
<h2>选择车身颜色</h2>
<div class="select">
<div
class="select-item"
v-for="(item, index) in colors"
:key="index"
@click="selectColor(index)"
>
<div
class="select-item-color"
:style="{ backgroundColor: item }"
></div>
</div>
</div>

<h2>选择贴膜材质</h2>
<div class="select">
<div
class="select-item"
v-for="(item, index) in materials"
:key="index"
@click="selectMaterial(index)"
>
<div class="select-item-text">{{ item.name }}</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import * as THREE from "three";
import { onMounted } from "vue";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";


// 定义初始化材质
let wheels = [];
let carBody, frontCar, hoodCar, glassCar;

// 创建材质
const bodyMaterial = new THREE.MeshPhysicalMaterial({
color: 0xff0000,
metalness: 1,
roughness: 0.5,
clearcoat: 1,
clearcoatRoughness: 0,
});

const frontMaterial = new THREE.MeshPhysicalMaterial({
color: 0xff0000,
metalness: 1,
roughness: 0.5,
clearcoat: 1,
clearcoatRoughness: 0,
});
const hoodMaterial = new THREE.MeshPhysicalMaterial({
color: 0xff0000,
metalness: 1,
roughness: 0.5,
clearcoat: 1,
clearcoatRoughness: 0,
});
const wheelsMaterial = new THREE.MeshPhysicalMaterial({
color: 0xff0000,
metalness: 1,
roughness: 0.1,
});
const glassMaterial = new THREE.MeshPhysicalMaterial({
color: 0xffffff,
metalness: 0,
roughness: 0,
transmission: 1,
transparent: true,
});


let colors = ["red", "blue", "green", "gray", "orange", "purple"];

let selectColor = (index) => {
bodyMaterial.color.set(colors[index]);
frontMaterial.color.set(colors[index]);
hoodMaterial.color.set(colors[index]);
wheelsMaterial.color.set(colors[index]);
// glassMaterial.color.set(colors[index]);
};

let materials = [
{ name: "磨砂", value: 1 },
{ name: "冰晶", value: 0 },
];
let selectMaterial = (index) => {
bodyMaterial.clearcoatRoughness = materials[index].value;
frontMaterial.clearcoatRoughness = materials[index].value;
hoodMaterial.clearcoatRoughness = materials[index].value;
};

onMounted(()=>{
// 创建场景
const scene = new THREE.Scene();
// 创建相机
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
// 调整相机位置
camera.position.set(0, 2, 6);
// 创建渲染器
const renderer = new THREE.WebGLRenderer({
// 抗锯齿
antialias: true,
});
// 设置渲染器的大小为窗口的内宽度,也就是内容区的宽度,并将其添加到文档中
renderer.setSize(window.innerWidth, window.innerHeight);
// 将渲染器添加到页面中
document.body.appendChild(renderer.domElement);

// 初始化控制器
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;

// 初始化渲染器,渲染背景
renderer.setClearColor("#000");
scene.background = new THREE.Color("#ccc");
scene.environment = new THREE.Color("#ccc");


// 添加gltf汽车模型
const loader = new GLTFLoader();
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath("./draco/gltf/");
loader.setDRACOLoader(dracoLoader);
loader.load("./model/bmw01.glb", (gltf) => {
const bmw = gltf.scene;
// console.log(gltf);
bmw.traverse((child) => {
if (child.isMesh) {
console.log(child.name);
}
// 判断是否是轮毂
if (child.isMesh && child.name.includes("轮毂")) {
child.material = wheelsMaterial;
wheels.push(child);
}
// 判断是否是车身
if (child.isMesh && child.name.includes("Mesh002")) {
carBody = child;
carBody.material = bodyMaterial;
}
// 判断是否是前脸
if (child.isMesh && child.name.includes("前脸")) {
child.material = frontMaterial;
frontCar = child;
}
// 判断是否是引擎盖
if (child.isMesh && child.name.includes("引擎盖_1")) {
child.material = hoodMaterial;
hoodCar = child;
}
// 判断是否是挡风玻璃
if (child.isMesh && child.name.includes("挡风玻璃")) {
child.material = glassMaterial;
glassCar = child;
}
});
scene.add(bmw);
})
// 创建一个渲染器的循环函数,这样可以保证在每一帧都能够渲染场景
const animate = function () {
renderer.render(scene, camera);
controls.update();
requestAnimationFrame(animate);
};
animate();

// 添加灯光
const light1 = new THREE.DirectionalLight(0xffffff, 1);
light1.position.set(0, 0, 10);
scene.add(light1);
const light2 = new THREE.DirectionalLight(0xffffff, 1);
light2.position.set(0, 0, -10);
scene.add(light2);
const light3 = new THREE.DirectionalLight(0xffffff, 1);
light3.position.set(10, 0, 0);
scene.add(light3);
const light4 = new THREE.DirectionalLight(0xffffff, 1);
light4.position.set(-10, 0, 0);
scene.add(light4);
const light5 = new THREE.DirectionalLight(0xffffff, 1);
light5.position.set(0, 10, 0);
scene.add(light5);
const light6 = new THREE.DirectionalLight(0xffffff, 0.3);
light6.position.set(5, 10, 0);
scene.add(light6);
const light7 = new THREE.DirectionalLight(0xffffff, 0.3);
light7.position.set(0, 10, 5);
scene.add(light7);
const light8 = new THREE.DirectionalLight(0xffffff, 0.3);
light8.position.set(0, 10, -5);
scene.add(light8);
const light9 = new THREE.DirectionalLight(0xffffff, 0.3);
light9.position.set(-5, 10, 0);
scene.add(light9);

// 添加网格地面
const gridHelper = new THREE.GridHelper(10, 10);
gridHelper.material.opacity = 0.2;
gridHelper.material.transparent = true;
scene.add(gridHelper);
})

</script>
<style>
body{
width:100vw;
height:100vh;
position:fixed;
top:0;
left:0;
}

.home-content {
position: fixed;
top: 0;
right: 20px;
}

.select-item-color {
width: 50px;
height: 50px;
border: 1px solid #ccc;
margin: 10px;
display: inline-block;
cursor: pointer;
border-radius: 10px;
}
.select {
display: flex;
}
</style>

效果

结语

本篇文章就到这里,更多内容敬请期待~

上一篇:
获取屏幕比例
下一篇:
基于keep-alive动态跳转白屏