用最简单方式打造Three.js 3D汽车展示厅 您所在的位置:网站首页 汽车3d图片怎么做好看 用最简单方式打造Three.js 3D汽车展示厅

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

2024-04-20 04:42| 来源: 网络整理| 查看: 265

前言

在上一篇文章简单粗略的描述了开发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

需要了解这几个概念

笔者用舞台表演来比如:

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

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

创建场景

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

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) 创建相机

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

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} = defaultMap camera = new PerspectiveCamera(60, innerWidth / innerHeight, 1, 1000) camera.position.set(x, y, z) } //初始化所有函数 const init = () => { setScene() setCamera() } //用vue钩子函数调用 onMounted(init) 引入特斯拉模型

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,dhelper let 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} = defaultMap camera = 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 = load if (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,hHelper let 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} = defaultMap camera = 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 = load if (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,controls let 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} = defaultMap camera = 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 = load if (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 / 2 controls.enableZoom = true controls.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模板

{{parseInt(loadingWidth)}}% x : {{x}} y:{{y}} z :{{z}} 转动车 停止 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} = defaultMap camera = 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 / 2 controls.enableZoom = true controls.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 = load if (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) 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; }

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

最后

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



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有