当前位置:   article > 正文

three.js实现3D汽车展厅效果展示_threejs 停车场进出效果

threejs 停车场进出效果

项目搭建

本案例还是借助框架书写three项目,借用vite构建工具搭建vue项目,搭建完成之后,用编辑器打开该项目,在终端执行 npm i 安装一下依赖,安装完成之后终端在安装 npm i three 即可。

因为我搭建的是vue3项目,为了便于代码的可读性,所以我将three.js代码单独抽离放在一个组件当中,在App根组件中进入引入该组件。具体如下:

  1. <template>
  2. <!-- 3D汽车展厅 -->
  3. <CarShowroom></CarShowroom>
  4. </template>
  5. <script setup>
  6. import CarShowroom from './components/CarShowroom.vue';
  7. </script>
  8. <style lang="less">
  9. *{
  10. margin: 0;
  11. padding: 0;
  12. }
  13. </style>

初始化three.js代码

three.js开启必须用到的基础代码如下:

导入three库

import * as THREE from 'three'

初始化场景

const scene = new THREE.Scene()

初始化相机

  1. // 创建相机
  2. const camera = new THREE.PerspectiveCamera(40,window.innerWidth / window.innerHeight,0.1,1000)
  3. camera.position.set(4.25,1.4,-4.5)

初始化渲染器

  1. // 创建渲染器
  2. const renderer = new THREE.WebGLRenderer({ antialias: true })
  3. renderer.setSize(window.innerWidth,window.innerHeight)
  4. document.body.appendChild(renderer.domElement)

监听屏幕大小的改变,修改渲染器的宽高和相机的比例

  1. window.addEventListener("resize",()=>{
  2. renderer.setSize(window.innerWidth,window.innerHeight)
  3. camera.aspect = window.innerWidth/window.innerHeight
  4. camera.updateProjectionMatrix()
  5. })

导入轨道控制器

  1. // 添加轨道控制器
  2. import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
  3. // 添加控制器
  4. const controls = new OrbitControls(camera,renderer.domElement)
  5. controls.enableDamping = true // 设置控制阻尼

设置渲染函数

  1. // 设置渲染函数
  2. const render = (time) =>{
  3. controls.update()
  4. renderer.render(scene,camera)
  5. requestAnimationFrame(render)
  6. }
  7. render()

ok,写完基础代码之后,接下来开始具体的Demo实操。

加载汽车模型

通过使用模型加载器GLTFLoader,然后使用DRACOLoader加载Draco压缩过的模型可以显著减小模型文件体积,从而加快加载速度和提高用户体验。代码如下:

  1. import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
  2. import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
  3. // 加载汽车模型
  4. const loader = new GLTFLoader()
  5. const dracoLoader = new DRACOLoader()
  6. dracoLoader.setDecoderPath("/draco/")
  7. loader.setDRACOLoader(dracoLoader)
  8. loader.load("/public/model/Lamborghini.glb",(gltf)=>{
  9. scene.add(gltf.scene)
  10. })
复制代码模型加载完成,画面如下:

因为没有灯光,所以我们需要给一个灯光让模型展现出来,这里设置一下环境光源:

  1. // 设置环境光源
  2. const ambientLight = new THREE.AmbientLight('#fff',0.5)
  3. scene.add(ambientLight)

设置展厅效果

这里通过three库中自带的一些模型来实现展厅的效果,如下:

设置地板样式

  1. // 设置地板样式
  2. const floorGeometry = new THREE.PlaneGeometry(20,20)
  3. const floormaterial = new THREE.MeshPhysicalMaterial({
  4. side: THREE.DoubleSide,
  5. color: 0x808080,
  6. metalness: 0, // 设置金属度
  7. roughness: 0.1, // 设置粗糙度
  8. wireframe: false // 关闭网格线
  9. })
  10. const mesh = new THREE.Mesh(floorGeometry,floormaterial)
  11. mesh.rotation.x = Math.PI / 2
  12. scene.add(mesh)

底部样式设置完,设置一个圆柱体将整个地板进行包裹:

  1. // 设置圆柱体模拟展厅
  2. const cylinder = new THREE.CylinderGeometry(12,12,20,32)
  3. const cylindermaterial = new THREE.MeshPhysicalMaterial({
  4. color: 0x6c6c6c,
  5. side: THREE.DoubleSide
  6. })
  7. const cylinderMesh = new THREE.Mesh(cylinder,cylindermaterial)
  8. scene.add(cylinderMesh)

接下来在圆柱体中设置一个聚光灯,让聚光灯偏垂直照射汽车模型,如下:

  1. // 设置聚光灯(让汽车更具有立体金属感)
  2. const spotLight = new THREE.SpotLight('#fff',2)
  3. spotLight.angle = Math.PI / 8 // 散射角度,和水平线的夹角
  4. spotLight.penumbra = 0.2 // 横向,聚光锥的半影衰减百分比
  5. spotLight.decay = 2 // 纵向,沿着光照距离的衰减量
  6. spotLight.distance = 30
  7. spotLight.shadow.radius = 10
  8. spotLight.shadow.mapSize.set(4096,4096)
  9. spotLight.position.set(-5,10,1)
  10. spotLight.target.position.set(0,0,0) // 光照射的方向
  11. spotLight.castShadow = true
  12. scene.add(spotLight)

为了不让展厅穿帮,这里将控制器的缩放以及旋转角度进行一个限制,让其只能在展厅中灵活查看而不能跑到展厅外面去:

  1. controls.maxDistance = 10 // 最大缩放距离
  2. controls.minDistance = 1 // 最小缩放距离
  3. controls.minPolarAngle = 0 // 最小旋转角度
  4. controls.maxPolarAngle = 85 / 360 * 2 * Math.PI // 最大旋转角度

设置GUI面板动态控制车身操作

这里我使用three.js库中自带的gui库,来动态的改变车身相关操作,因为我仅仅是控制车身材质和玻璃材质相关的数据操作,这里就线设置一下其相关的材质:

  1. // 车身材质
  2. let bodyMaterial = new THREE.MeshPhysicalMaterial({
  3. color: 'red',
  4. metalness: 1,
  5. roughness: 0.5,
  6. clearcoat: 1.0,
  7. clearcoatRoughness: 0.03
  8. })
  9. // 玻璃材质
  10. let glassMaterial = new THREE.MeshPhysicalMaterial({
  11. color: '#793e3e',
  12. metalness: 0.25,
  13. roughness: 0,
  14. transmission: 1.0 // 透光性
  15. })

在glb模型中,通过traverse函数遍历场景中的所有对象(包括Mesh、Group、Camera、Light等),并对这些对象进行相应操作或处理(这里的门操作后面会讲解到):

  1. loader.load("/public/model/Lamborghini.glb",(gltf)=>{
  2. const carModel = gltf.scene
  3. carModel.rotation.y = Math.PI
  4. carModel.traverse((obj)=>{
  5. if(obj.name === 'Object_103' || obj.name === 'Object_64' || obj.name === 'Object_77'){
  6. // 车身
  7. obj.material = bodyMaterial
  8. }else if(obj.name === 'Object_90'){
  9. // 玻璃
  10. obj.material = glassMaterial
  11. }else if(obj.name === 'Empty001_16' || obj.name === 'Empty002_20'){
  12. //
  13. // doors.push(obj)
  14. }else{
  15. return true
  16. }
  17. })
  18. scene.add(gltf.scene)
  19. })

最后得到的结果如下:

接下来通过控制面板来动态的监视汽车模型的车身和玻璃材质:

  1. // 设置gui模板控制
  2. // 修改默认面板名称
  3. gui.domElement.parentNode.querySelector('.title').textContent = '3D汽车动态操作'
  4. const bodyChange = gui.addFolder("车身材质设置")
  5. bodyChange.close() // 默认关闭状态
  6. bodyChange.addColor(bodyMaterial,'color').name('车身颜色').onChange(value=>{
  7. bodyMaterial.color.set(value)
  8. })
  9. bodyChange.add(bodyMaterial,'metalness',0,1).name('金属度').onChange(value=>{
  10. bodyMaterial.metalness = value
  11. })
  12. bodyChange.add(bodyMaterial,'roughness',0,1).name('粗糙度').onChange(value=>{
  13. bodyMaterial.roughness = value
  14. })
  15. bodyChange.add(bodyMaterial,'clearcoat',0,1).name('清漆强度').onChange(value=>{
  16. bodyMaterial.clearcoat = value
  17. })
  18. bodyChange.add(bodyMaterial,'clearcoatRoughness',0,1).name('清漆层粗糙度').onChange(value=>{
  19. bodyMaterial.clearcoatRoughness = value
  20. })
  21. const glassChange = gui.addFolder("玻璃设置")
  22. glassChange.close() // 默认关闭状态
  23. glassChange.addColor(glassMaterial,'color').name('玻璃颜色').onChange(value=>{
  24. glassMaterial.color.set(value)
  25. })
  26. glassChange.add(glassMaterial,'metalness',0,1).name('金属度').onChange(value=>{
  27. glassMaterial.metalness = value
  28. })
  29. glassChange.add(glassMaterial,'roughness',0,1).name('粗糙度').onChange(value=>{
  30. glassMaterial.roughness = value
  31. })
  32. glassChange.add(glassMaterial,'transmission',0,1).name('透光性').onChange(value=>{
  33. glassMaterial.transmission = value
  34. })

车门操作与车身视角展示

这里依然用GUI控制面板来动态实现开关车门以及车内车外视角动态切换的操作,如下:

  1. var obj = { carRightOpen,carLeftOpen,carRightClose,carLeftClose,carIn,carOut }
  2. // 设置车身动态操作
  3. const doChange = gui.addFolder("车身动态操作设置")
  4. doChange.close() // 默认关闭状态
  5. doChange.add(obj, "carLeftOpen").name('打开左车门')
  6. doChange.add(obj, "carRightOpen").name('打开右车门')
  7. doChange.add(obj, "carLeftClose").name('关闭左车门')
  8. doChange.add(obj, "carRightClose").name('关闭右车门')
  9. doChange.add(obj, "carIn").name('车内视角')
  10. doChange.add(obj, "carOut").name('车外视角')

每个操作都对应一个函数,如下:

  1. // 打开左车门
  2. const carLeftOpen = () => {
  3. setAnimationDoor({ x: 0 }, { x: Math.PI / 3 }, doors[1])
  4. }
  5. // 打开右车门
  6. const carRightOpen = () => {
  7. setAnimationDoor({ x: 0 }, { x: Math.PI / 3 }, doors[0])
  8. }
  9. // 关闭左车门
  10. const carLeftClose = () => {
  11. setAnimationDoor({ x: Math.PI / 3 }, { x: 0 }, doors[1])
  12. }
  13. // 关闭右车门
  14. const carRightClose = () => {
  15. setAnimationDoor({ x: Math.PI / 3 }, { x: 0 }, doors[0])
  16. }
  17. // 车内视角
  18. const carIn = () => {
  19. setAnimationCamera({ cx: 4.25, cy: 1.4, cz: -4.5, ox: 0, oy: 0.5, oz: 0 }, { cx: -0.27, cy: 0.83, cz: 0.60, ox: 0, oy: 0.5, oz: -3 });
  20. }
  21. // 车外视角
  22. const carOut = () => {
  23. setAnimationCamera({ cx: -0.27, cy: 0.83, cz: 0.6, ox: 0, oy: 0.5, oz: -3 }, { cx: 4.25, cy: 1.4, cz: -4.5, ox: 0, oy: 0.5, oz: 0 });
  24. }

这里使用了补间动画tween.js,其github网址为 github.com/tweenjs/twe… ,终端安装其第三方插件之后,直接引入即可,如下(这里不再过多介绍该库的使用,想学习的可以自行寻找其官方文档学习):

接下来借助tween.js库实现补间动画,如下:

  1. // 设置补间动画
  2. const setAnimationDoor = (start, end, mesh) => {
  3. const tween = new TWEEN.Tween(start).to(end, 1000).easing(TWEEN.Easing.Quadratic.Out)
  4. tween.onUpdate((that) => {
  5. mesh.rotation.x = that.x
  6. })
  7. tween.start()
  8. }
  9. const setAnimationCamera = (start, end) => {
  10. const tween = new TWEEN.Tween(start).to(end, 3000).easing(TWEEN.Easing.Quadratic.Out)
  11. tween.onUpdate((that) => {
  12. // camera.postition 和 controls.target 一起使用
  13. camera.position.set(that.cx, that.cy, that.cz)
  14. controls.target.set(that.ox, that.oy, that.oz)
  15. })
  16. tween.start()
  17. }

最终实现的效果如下:

点击查看车内视角的话,画面如下:

设置手动点击打开关闭车门

通过设置监听点击事件函数来动态实现打开关闭车门:

  1. // 设置点击打开车门的动画效果
  2. window.addEventListener('click', onPointClick);
  3. function onPointClick(event) {
  4. let pointer = {}
  5. pointer.x = (event.clientX / window.innerWidth) * 2 - 1;
  6. pointer.y = - (event.clientY / window.innerHeight) * 2 + 1;
  7. var vector = new THREE.Vector2(pointer.x, pointer.y)
  8. var raycaster = new THREE.Raycaster()
  9. raycaster.setFromCamera(vector, camera)
  10. let intersects = raycaster.intersectObjects(scene.children);
  11. intersects.forEach((item) => {
  12. if (item.object.name === 'Object_64' || item.object.name === 'Object_77') {
  13. if (!carStatus || carStatus === 'close') {
  14. carLeftOpen()
  15. carRightOpen()
  16. } else {
  17. carLeftClose()
  18. carRightClose()
  19. }
  20. }
  21. })
  22. }

然后给每个车门设置汽车状态,如下:

设置图片背景

为了让展厅更具有视觉效果,接下来设置一个画面背景让其更具有画面感,如下:

  1. // 创建聚光灯函数
  2. const createSpotlight = (color) => {
  3. const newObj = new THREE.SpotLight(color, 2);
  4. newObj.castShadow = true;
  5. newObj.angle = Math.PI / 6;;
  6. newObj.penumbra = 0.2;
  7. newObj.decay = 2;
  8. newObj.distance = 50;
  9. return newObj;
  10. }
  11. // 设置图片背景
  12. const spotLight1 = createSpotlight('#ffffff');
  13. const texture = new THREE.TextureLoader().load('src/assets/imgs/奥特曼.jpg')
  14. spotLight1.position.set(0, 3, 0);
  15. spotLight1.target.position.set(-10, 3, 10)
  16. spotLight1.map = texture
  17. const lightHelper = new THREE.SpotLightHelper(spotLight1);
  18. scene.add(spotLight1);

最终呈现的效果如下:

原文链接:
https://juejin.cn/post/7307146429004333094

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/133634
推荐阅读
相关标签
  

闽ICP备14008679号