用最简单方式打造Three.js 3D汽车展示厅

news/2025/2/1 13:00:36/

前言

在上一篇文章简单粗略的描述了开发3D汽车展厅,笔者想再写一篇比较详细的教程。对于笔者来说Three.js说难不难,说简单也不简单。说简单因为他简化了对三维知识的理解,简化了很多操作。说难是因为api很多,要用熟也不是一朝半夕的时间。笔者在这给大家介绍一下以最简单方式打造一个3D汽车展示厅。这个3D汽车展厅实现出来也不算完整,主要想让同学们找找感觉,找些成就感,有感觉自己也有学下去的动力。_

简单粗略了解三维

在2D里只有两个坐标,分别是X轴,和Y轴。在3D就多了一个Z轴。相信刚学3D的同学对X轴和Y轴都比较熟悉,Z轴是比较陌生,笔者建议大家可以上 three编辑器的网站尝试创建一些几何物体,找找对3D理解。

image.png

完整效果

屏幕录制2021-07-08 下午1.39.03.gif

需要了解这几个概念

笔者用舞台表演来比如:

  1. 场景 Sence 相当于在一个舞台,在这里是布置场景物品和表演者表演的地方
  2. 相机 Carma 相当于观众的眼睛去观看
  3. 几何体 Geometry 相当于舞台的表演者
  4. 灯光 light 相当于舞台灯光照射
  5. 控制 Controls 相当于这出舞台剧的总导演

既然知道这几个概念,我们就根据这几大概念以函数形式区分,就很好理解。在这个three程序我分别创建了:
setScenesetCarmaloadfilesetLightsetControls分别对应以上几个概念

创建场景

首先我们还是用vue3的setup方式编写,npm安装three包, 引入 SceneWebGLRenderer 两个对象,创建两个变量 scenerenderer并赋值,这样就简单搭建了一个场景,场景背景默认是黑色。创建一个init初始化函数,并在onMounted调用

<script setup>import {onMounted} from 'vue'import { Scene,WebGLRenderer,PerspectiveCamera} from 'three'let scene,renderer//创建场景const setScene = ()=>{scene = new Scene()renderer = new WebGLRenderer()renderer.setSize(innerWidth, innerHeight)document.querySelector('.boxs').appendChild(renderer.domElement)}//初始化所有函数 const init = () => {setScene()}//用vue钩子函数调用onMounted(init)
</script>

创建相机

有了场景就要加相机,相机相当于人的眼睛去观察几何物体,引入PerspectiveCamera
参数有4个,具体可以看看官网文档。然后通过实例方法position.set设置相机坐标

<script setup>import { Scene,WebGLRenderer,PerspectiveCamera} from 'three'let scene,renderer,camera//相机的默认坐标const defaultMap = {x: 510,y: 128,z: 0,}//创建场景const setScene = ()=>{scene = new Scene()renderer = new WebGLRenderer()renderer.setSize(innerWidth, innerHeight)document.querySelector('.boxs').appendChild(renderer.domElement)}//创建相机  const setCamera = () => {const {x, y, z} = defaultMapcamera = new PerspectiveCamera(60, innerWidth / innerHeight, 1, 1000)camera.position.set(x, y, z)}//初始化所有函数 const init = () => {setScene()setCamera()}//用vue钩子函数调用onMounted(init)
</script>

引入特斯拉模型

image.png
在three我们除了可以通过api创建几何物体,还可以引入第三方3d模型,具体可以上 sketchfab ,国外一个3d模型下载网站,里面有很多免费的模型下载,这次用特斯拉汽车模型为例,下载一个gltf格式的3D模型。引入GLTFLoader 创建一个loadfile函数并通过Promise返回模型数据,在init函数加上async调用loadfile得到返回模型数据并添加到场景scene

  import {GLTFLoader} from 'three/examples/jsm/loaders/GLTFLoader'import { Scene,WebGLRenderer,PerspectiveCamera} from 'three'let scene,renderer,camera,directionalLight,dhelperlet isLoading = ref(true)let loadingWidth = ref(0)//相机的默认坐标const defaultMap = {x: 510,y: 128,z: 0,}//创建场景const setScene = ()=>{scene = new Scene()renderer = new WebGLRenderer()renderer.setSize(innerWidth, innerHeight)document.querySelector('.boxs').appendChild(renderer.domElement)}//创建相机  const setCamera = () => {const {x, y, z} = defaultMapcamera = new PerspectiveCamera(60, innerWidth / innerHeight, 1, 1000)camera.position.set(x, y, z)}//通过Promise处理一下loadfile函数const loadFile = (url) => {return new Promise(((resolve, reject) => {loader.load(url,(gltf) => {resolve(gltf)}, ({loaded, total}) => {let load = Math.abs(loaded / total * 100)loadingWidth.value = loadif (load >= 100) {setTimeout(() => {isLoading.value = false}, 1000)}console.log((loaded / total * 100) + '% loaded')},(err) => {reject(err)})}))}//初始化所有函数 const init = async() => {const gltf =await loadFile('src/assets/3d/tesla_2018_model_3/scene.gltf')setScene()setCamera()scene.add(gltf.scene)}//用vue钩子函数调用onMounted(init) 

创建灯光

汽车模型还看不见,所以我们要给它设置灯光,引入DirectionalLight,DirectionalLightHelper,HemisphereLight,HemisphereLightHelper,并设置灯光的参数,使模型可见,并有些反射光面,阴影的效果,然后也在init函数调用 setLight,再增加loop函数,使场景、照相机、模型不停循环调用。然后车模型就能看见啦,看到车出现的那一刻,好像自己的新买的一样,_

image.png

  import {GLTFLoader} from 'three/examples/jsm/loaders/GLTFLoader'import { Scene,WebGLRenderer,PerspectiveCamera,   DirectionalLight,DirectionalLightHelper,HemisphereLight,HemisphereLightHelper} from 'three'let scene,renderer,camera,directionalLight,hemisphereLight,dhelper,hHelperlet isLoading = ref(true)let loadingWidth = ref(0)//相机的默认坐标const defaultMap = {x: 510,y: 128,z: 0,}//创建场景const setScene = ()=>{scene = new Scene()renderer = new WebGLRenderer()renderer.setSize(innerWidth, innerHeight)document.querySelector('.boxs').appendChild(renderer.domElement)}//创建相机  const setCamera = () => {const {x, y, z} = defaultMapcamera = new PerspectiveCamera(60, innerWidth / innerHeight, 1, 1000)camera.position.set(x, y, z)}//通过Promise处理一下loadfile函数const loadFile = (url) => {return new Promise(((resolve, reject) => {loader.load(url,(gltf) => {resolve(gltf)}, ({loaded, total}) => {let load = Math.abs(loaded / total * 100)loadingWidth.value = loadif (load >= 100) {setTimeout(() => {isLoading.value = false}, 1000)}console.log((loaded / total * 100) + '% loaded')},(err) => {reject(err)})}))}// 设置灯光const setLight = () => {directionalLight = new DirectionalLight(0xffffff, 0.5)directionalLight.position.set(-4, 8, 4)dhelper = new DirectionalLightHelper(directionalLight, 5, 0xff0000)hemisphereLight = new HemisphereLight(0xffffff, 0xffffff, 0.4)hemisphereLight.position.set(0, 8, 0)hHelper = new HemisphereLightHelper(hemisphereLight, 5)scene.add(directionalLight)scene.add(hemisphereLight)}//初始化所有函数 const init = async() => {const gltf = await loadFile('src/assets/3d/tesla_2018_model_3/scene.gltf')setScene()setCamera()setLight()scene.add(gltf.scene)loop()}//使场景、照相机、模型不停调用const loop = () => {requestAnimationFrame(loop)renderer.render(scene, camera)}//用vue钩子函数调用onMounted(init) 

控制模型

屏幕录制2021-07-08 下午12.04.36.gif

想用鼠标自由旋转,或者自动旋转,就要引用 OrbitControls对象,创建setControls函数也是在init调用,通过绑定change还可以监听坐标变化,另外也要在loop函数增加
controls.update()才可以更新位置变化

  import { Scene,WebGLRenderer,PerspectiveCamera} from 'three'import {GLTFLoader} from 'three/examples/jsm/loaders/GLTFLoader'import {OrbitControls} from 'three/examples/jsm/controls/OrbitControls.js'let scene,renderer,camera,directionalLight,hemisphereLight,dhelper,hHelper,controlslet isLoading = ref(true)let loadingWidth = ref(0)//相机的默认坐标const defaultMap = {x: 510,y: 128,z: 0,}//创建场景const setScene = ()=>{scene = new Scene()renderer = new WebGLRenderer()renderer.setSize(innerWidth, innerHeight)document.querySelector('.boxs').appendChild(renderer.domElement)}//创建相机  const setCamera = () => {const {x, y, z} = defaultMapcamera = new PerspectiveCamera(60, innerWidth / innerHeight, 1, 1000)camera.position.set(x, y, z)}//通过Promise处理一下loadfile函数const loadFile = (url) => {return new Promise(((resolve, reject) => {loader.load(url,(gltf) => {resolve(gltf)}, ({loaded, total}) => {let load = Math.abs(loaded / total * 100)loadingWidth.value = loadif (load >= 100) {setTimeout(() => {isLoading.value = false}, 1000)}console.log((loaded / total * 100) + '% loaded')},(err) => {reject(err)})}))}// 设置灯光const setLight = () => {directionalLight = new DirectionalLight(0xffffff, 0.5)directionalLight.position.set(-4, 8, 4)dhelper = new DirectionalLightHelper(directionalLight, 5, 0xff0000)hemisphereLight = new HemisphereLight(0xffffff, 0xffffff, 0.4)hemisphereLight.position.set(0, 8, 0)hHelper = new HemisphereLightHelper(hemisphereLight, 5)scene.add(directionalLight)scene.add(hemisphereLight)}// 设置模型控制const setControls = () => {controls = new OrbitControls(camera, renderer.domElement)controls.maxPolarAngle = 0.9 * Math.PI / 2controls.enableZoom = truecontrols.addEventListener('change', render)}const render = () => {map.x = Number.parseInt(camera.position.x)map.y = Number.parseInt(camera.position.y)map.z = Number.parseInt(camera.position.z)}//初始化所有函数 const init = async() => {const gltf =await loadFile('src/assets/3d/tesla_2018_model_3/scene.gltf')setScene()setCamera()setLight()setControls()scene.add(gltf.scene)}//使场景、照相机、模型不停调用和更新位置数据const loop = () => {requestAnimationFrame(loop)renderer.render(scene, camera)controls.update()}//用vue钩子函数调用onMounted(init) 

改变车身颜色

到这里基础的已经搭建好了,接下来我们再加一个功能改变汽车车身颜色,也是展厅展示一个比较基础的功能.创建一个setColor 这里说一下实例scene 有一个traverse函数,它回调了所有模型的子模型信息,只要我们找到对应name属性,就可以更改颜色,和增加贴图等等,
因为对模型结构不怎熟悉,所以根据名字来猜了一下 找到door_前序的名字大概应该就车身的套件。当然如果细分到我只想改引擎盖的颜色就要找出引擎盖套件。当然要很熟悉这个模型结构了

 //设置车身颜色const setCarColor = (index) => {const currentColor = new Color(colorAry[index])scene.traverse(child => {if (child.isMesh) {console.log(child.name)if (child.name.includes('door_')) {child.material.color.set(currentColor)}}})}

上完整代码

其他操作都是交给vue控制,包括设置车身颜色、是否自动转动等等。大家运行以下代码的时候记得用vite打包工具创建vue3模板

<template><div class="boxs"><div class="maskLoading" v-if="isLoading"><div class="loading"><div :style="{width : loadingWidth +'%' }"></div></div><div style="padding-left: 10px;">{{parseInt(loadingWidth)}}%</div></div><div class="mask"><p>x : {{x}} y:{{y}} z :{{z}}</p><button @click="isAutoFun">转动车</button><button @click="stop">停止</button><div class="flex"><div @click="setCarColor(index)" v-for="(item,index) in colorAry":style="{backgroundColor : item}"></div></div></div></div>
</template><script setup>import {onMounted, reactive, ref, toRefs} from 'vue'import {Color,DirectionalLight,DirectionalLightHelper,HemisphereLight,HemisphereLightHelper,PerspectiveCamera,Scene,WebGLRenderer} from 'three'import {OrbitControls} from 'three/examples/jsm/controls/OrbitControls.js'import {GLTFLoader} from 'three/examples/jsm/loaders/GLTFLoader'//车身颜色数组const colorAry = ["rgb(216, 27, 67)", "rgb(142, 36, 170)", "rgb(81, 45, 168)", "rgb(48, 63, 159)", "rgb(30, 136, 229)", "rgb(0, 137, 123)","rgb(67, 160, 71)", "rgb(251, 192, 45)", "rgb(245, 124, 0)", "rgb(230, 74, 25)", "rgb(233, 30, 78)", "rgb(156, 39, 176)","rgb(0, 0, 0)"] // 车身颜色数组 const loader = new GLTFLoader() //引入模型的loader实例const defaultMap = {x: 510,y: 128,z: 0,}// 相机的默认坐标const map = reactive(defaultMap)//把相机坐标设置成可观察对象const {x, y, z} = toRefs(map)//输出坐标给模板使用let scene, camera, renderer, controls, floor, dhelper, hHelper, directionalLight, hemisphereLight // 定义所有three实例变量let isLoading = ref(true) //是否显示loading  这个load模型监听的进度let loadingWidth = ref(0)// loading的进度//创建灯光const setLight = () => {directionalLight = new DirectionalLight(0xffffff, 0.5)directionalLight.position.set(-4, 8, 4)dhelper = new DirectionalLightHelper(directionalLight, 5, 0xff0000)hemisphereLight = new HemisphereLight(0xffffff, 0xffffff, 0.4)hemisphereLight.position.set(0, 8, 0)hHelper = new HemisphereLightHelper(hemisphereLight, 5)scene.add(directionalLight)scene.add(hemisphereLight)}// 创建场景const setScene = () => {scene = new Scene()renderer = new WebGLRenderer()renderer.setSize(innerWidth, innerHeight)document.querySelector('.boxs').appendChild(renderer.domElement)}// 创建相机const setCamera = () => {const {x, y, z} = defaultMapcamera = new PerspectiveCamera(60, innerWidth / innerHeight, 1, 1000)camera.position.set(x, y, z)}// 设置模型控制const setControls = () => {controls = new OrbitControls(camera, renderer.domElement)controls.maxPolarAngle = 0.9 * Math.PI / 2controls.enableZoom = truecontrols.addEventListener('change', render)}//返回坐标信息const render = () => {map.x = Number.parseInt(camera.position.x)map.y = Number.parseInt(camera.position.y)map.z = Number.parseInt(camera.position.z)}// 循环场景 、相机、 位置更新const loop = () => {requestAnimationFrame(loop)renderer.render(scene, camera)controls.update()}//是否自动转动const isAutoFun = () => {controls.autoRotate = true}//停止转动const stop = () => {controls.autoRotate = false}//设置车身颜色const setCarColor = (index) => {const currentColor = new Color(colorAry[index])scene.traverse(child => {if (child.isMesh) {console.log(child.name)if (child.name.includes('door_')) {child.material.color.set(currentColor)}}})}const loadFile = (url) => {return new Promise(((resolve, reject) => {loader.load(url,(gltf) => {resolve(gltf)}, ({loaded, total}) => {let load = Math.abs(loaded / total * 100)loadingWidth.value = loadif (load >= 100) {setTimeout(() => {isLoading.value = false}, 1000)}console.log((loaded / total * 100) + '% loaded')},(err) => {reject(err)})}))}//初始化所有函数const init = async () => {setScene()setCamera()setLight()setControls()const gltf = await loadFile('src/assets/3d/tesla_2018_model_3/scene.gltf')scene.add(gltf.scene)loop()}//用vue钩子函数调用onMounted(init) 
</script><style>body {margin: 0;}.maskLoading {background: #000;position: fixed;display: flex;justify-content: center;align-items: center;top: 0;left: 0;bottom: 0;right: 0;z-index: 1111111;color: #fff;}.maskLoading .loading {width: 400px;height: 20px;border: 1px solid #fff;background: #000;overflow: hidden;border-radius: 10px;}.maskLoading .loading div {background: #fff;height: 20px;width: 0;transition-duration: 500ms;transition-timing-function: ease-in;}canvas {width: 100%;height: 100%;margin: auto;}.mask {color: #fff;position: absolute;bottom: 0;left: 0;width: 100%;}.flex {display: flex;flex-wrap: wrap;padding: 20px;}.flex div {width: 10px;height: 10px;margin: 5px;cursor: pointer;}
</style>

屏幕录制2021-07-08 下午1.39.03.gif

最后

在这个3D汽车展示厅笔者只是简单创建了一些基础功能,还有很多功能可以增加,比如创建背景、地板、一些阴影、定点显示车套件位置信息等等。掌握套路,这些功能实现也不难。如果喜欢就给小弟点个赞,谢谢啦 _


http://www.ppmy.cn/news/356849.html

相关文章

three.js实现一个汽车3D实景(颜色交互)

问题来源&#xff1a; 和导师交流后决定以这个来做毕业设计&#xff0c;于是就开始了three.js的学习&#xff0c;经过半个多月的基础学习&#xff0c;终于入门&#xff0c;下面来看看成果。 代码演示&#xff1a; html部分 <!DOCTYPE html> <html lang"en&…

Three.js实现汽车3D展示/开关门/变色/运动/视角切换/波动热点/汽车模型

1&#xff0c;介绍 该示例使用Three.js库 r141版本。 主要实现功能&#xff1a;使用Three.js实现引入汽车模型&#xff0c;汽车3D展示&#xff0c;开门关门动画&#xff0c;运动&#xff0c;变色&#xff0c;视角切换&#xff0c;显示波动热点标签。 效果图如下&#xff1a; 参…

东风日产到访CASAIM,双方联合开展运用高精度3D打印技术制造汽车产线相关的工装夹具、检具及治具的技术应用研究

3月中旬&#xff0c;东风日产乘用车有限公司&#xff08;简称&#xff1a;东风日产&#xff09;制造技术部一行到访CASAIM&#xff0c;CASAIM副主任、产品总监刘颖鹏对东风日产一行到访表示热烈欢迎。会上&#xff0c;CASAIM副主任、产品总监刘颖鹏围绕3D打印技术工艺、材料及创…

目标检测YOLO实战应用案例100讲-网联自动驾驶中感知图像隐私目标分类与检测方法

目录 前言 基础知识 2.1 深度学习知识 2.1.1 卷积神经网络 2.1.2 残差神经网络模型

爬虫:requests+pymysql+BeautifulSoup+re

爬虫网站限于学习&#xff01;&#xff01; 功能实现&#xff1a;扫描站点目录、按目录分页扫描页面【提取数据并正则过滤特殊符号】、数据保存到数据库&#xff0c;可按多线程、多进程进行爬取内容 import requests,os,xml,pymysql,threading,time,queue,_thread from bs4 im…

python爬虫快速添加请求头、随机生成user-agent

一、快速添加请求头 import re # 下方引号内添加替换掉请求头内容 headers_str """ accept: text/html,application/xhtmlxml,application/xml;q0.9,image/avif,image/webp,image/apng,*/*;q0.8,application/signed-exchange;vb3;q0.9 accept-encoding: gzip…

百度指数python爬虫的简单应用

这个是“关键词”爬取&#xff0c;如果mysql数据库不会改&#xff0c;建议把mysql有关的代码注释或删除。 注意&#xff0c;时间为我自己添加上去的&#xff0c;如果时间跨度较长&#xff0c;百度指数时间会变为一个星期一次数据&#xff0c;而我添加的为每天&#xff0c;需自…