three.js实现3D汽车展厅效果展示

项目搭建

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

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

<template>
  <!-- 3D汽车展厅 -->
  <CarShowroom></CarShowroom>
</template>
 
<script setup>
import CarShowroom from './components/CarShowroom.vue';
</script>
 
<style lang="less">
  *{
    margin: 0;
    padding: 0;
  }
</style>

初始化three.js代码

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

导入three库

import * as THREE from 'three'

初始化场景

const scene = new THREE.Scene()

初始化相机

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

初始化渲染器

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

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

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

导入轨道控制器

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

设置渲染函数

// 设置渲染函数
const render = (time) =>{ 
  controls.update()
  renderer.render(scene,camera)
  requestAnimationFrame(render)
}
render()

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

加载汽车模型

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

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

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

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

设置展厅效果

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

设置地板样式

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

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

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

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

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

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

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

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

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

// 车身材质
let bodyMaterial = new THREE.MeshPhysicalMaterial({
  color: 'red',
  metalness: 1,
  roughness: 0.5,
  clearcoat: 1.0,
  clearcoatRoughness: 0.03
})
// 玻璃材质
let glassMaterial = new THREE.MeshPhysicalMaterial({
  color: '#793e3e',
  metalness: 0.25,
  roughness: 0,
  transmission: 1.0 // 透光性
})

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

loader.load("/public/model/Lamborghini.glb",(gltf)=>{
  const carModel = gltf.scene
  carModel.rotation.y = Math.PI
  carModel.traverse((obj)=>{
    if(obj.name === 'Object_103' || obj.name === 'Object_64' || obj.name === 'Object_77'){
      // 车身
      obj.material = bodyMaterial
    }else if(obj.name === 'Object_90'){
      // 玻璃
      obj.material = glassMaterial
    }else if(obj.name === 'Empty001_16' || obj.name === 'Empty002_20'){
      // 门
      // doors.push(obj)
    }else{
      return true
    }
  })
  scene.add(gltf.scene)
})

最后得到的结果如下:

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

// 设置gui模板控制
// 修改默认面板名称
gui.domElement.parentNode.querySelector('.title').textContent = '3D汽车动态操作'
 
const bodyChange = gui.addFolder("车身材质设置")
bodyChange.close() // 默认关闭状态
bodyChange.addColor(bodyMaterial,'color').name('车身颜色').onChange(value=>{
  bodyMaterial.color.set(value)
})
bodyChange.add(bodyMaterial,'metalness',0,1).name('金属度').onChange(value=>{
  bodyMaterial.metalness = value
})
bodyChange.add(bodyMaterial,'roughness',0,1).name('粗糙度').onChange(value=>{
  bodyMaterial.roughness = value
})
bodyChange.add(bodyMaterial,'clearcoat',0,1).name('清漆强度').onChange(value=>{
  bodyMaterial.clearcoat = value
})
bodyChange.add(bodyMaterial,'clearcoatRoughness',0,1).name('清漆层粗糙度').onChange(value=>{
  bodyMaterial.clearcoatRoughness = value
})
const glassChange = gui.addFolder("玻璃设置")
glassChange.close() // 默认关闭状态
glassChange.addColor(glassMaterial,'color').name('玻璃颜色').onChange(value=>{
  glassMaterial.color.set(value)
})
glassChange.add(glassMaterial,'metalness',0,1).name('金属度').onChange(value=>{
  glassMaterial.metalness = value
})
glassChange.add(glassMaterial,'roughness',0,1).name('粗糙度').onChange(value=>{
  glassMaterial.roughness = value
})
glassChange.add(glassMaterial,'transmission',0,1).name('透光性').onChange(value=>{
  glassMaterial.transmission = value
})

车门操作与车身视角展示

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

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

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

// 打开左车门
const carLeftOpen = () => { 
  setAnimationDoor({ x: 0 }, { x: Math.PI / 3 }, doors[1])
}
// 打开右车门
const carRightOpen = () => { 
  setAnimationDoor({ x: 0 }, { x: Math.PI / 3 }, doors[0])
}
// 关闭左车门
const carLeftClose = () => { 
  setAnimationDoor({ x: Math.PI / 3 }, { x: 0 }, doors[1])
}
// 关闭右车门
const carRightClose = () => { 
  setAnimationDoor({ x: Math.PI / 3 }, { x: 0 }, doors[0])
}
 
// 车内视角
const carIn = () => {
  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 });
}
// 车外视角
const carOut = () => {
  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 });
}

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

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

// 设置补间动画
const setAnimationDoor = (start, end, mesh) => {
  const tween = new TWEEN.Tween(start).to(end, 1000).easing(TWEEN.Easing.Quadratic.Out)
  tween.onUpdate((that) => {
    mesh.rotation.x = that.x
  })
  tween.start()
}
const setAnimationCamera = (start, end) => {
  const tween = new TWEEN.Tween(start).to(end, 3000).easing(TWEEN.Easing.Quadratic.Out)
  tween.onUpdate((that) => {
      //  camera.postition  和 controls.target 一起使用
      camera.position.set(that.cx, that.cy, that.cz)
      controls.target.set(that.ox, that.oy, that.oz)
  })
  tween.start()
}

最终实现的效果如下:

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

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

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

// 设置点击打开车门的动画效果
window.addEventListener('click', onPointClick);
function onPointClick(event) {
  let pointer = {}
  pointer.x = (event.clientX / window.innerWidth) * 2 - 1;
  pointer.y = - (event.clientY / window.innerHeight) * 2 + 1;
  var vector = new THREE.Vector2(pointer.x, pointer.y)
  var raycaster = new THREE.Raycaster()
  raycaster.setFromCamera(vector, camera)
  let intersects = raycaster.intersectObjects(scene.children);
  intersects.forEach((item) => {
    if (item.object.name === 'Object_64' || item.object.name === 'Object_77') {
      if (!carStatus || carStatus === 'close') {
        carLeftOpen()
        carRightOpen()
      } else {
        carLeftClose()
        carRightClose()
      }
    }
  })
}

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

设置图片背景

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

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

最终呈现的效果如下:

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/274224.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

理解 Go Mod Init

初始化Go模块和管理依赖的全面指南 go mod init 是Go编程语言&#xff08;通常称为Golang&#xff09;中用于初始化新Go模块的命令。在Go中&#xff0c;一个模块是一组相关的Go包&#xff0c;它们作为一个单元一起进行版本控制。通常&#xff0c;在项目目录的根目录下使用 go m…

NLP论文阅读记录 - 01 | 2021 神经抽象摘要方法及摘要事实一致性综述

文章目录 前言0、论文摘要一、Introduction二.背景2.1自动总结任务2.2 数据集DUC-2004Gigaword [Graff et al., 2003, Napoles et al., 2012]CNN/DailyMail [Nallapati 等人&#xff0c;2016]XSum [Narayan 等人&#xff0c;2018] 2.3 摘要系统的评估2.3.1 Rouge [Lin, 2004] 三…

用编程解决习题【计算机图像处理】

用编程解决习题【计算机图像处理】 前言版权第三章 03采样量化与像素间关系作业编程 第六章 06图像的直方图变换作业编程 第七章 07图像的噪声抑制作业编程 第十章 10二值图像的分析作业编程 最后 前言 2023-12-27 21:11:27 以下内容源自《【计算机图像处理】》 仅供学习交流…

CSS3用户界面弹性盒子

CSS3用户界面 resize 该CSS3属性用于定义元素是否应该调整大小&#xff0c;如果需要调整大小&#xff0c;那么以哪个轴进行调整。 语法&#xff1a;resize:both | horizontal | none | vertical 注意&#xff1a;该属性仅应用在overflow值而不是visible的元素上。通常而言&am…

《国货之光》-粗粮八宝粉

国潮正当时&#xff0c;好物当自强。赋能国货品牌&#xff0c;打造行业爆品。今天为大家带来的国货好物是老磨坊纯杂粮八宝粉 小时候&#xff0c;经常听妈妈说&#xff0c;要多吃杂粮&#xff0c;对身体好。然后每天不重样的给我做杂粮饭菜。不知道大家有没有和我相同的经历。…

Windows搭建FTP服务器教学以及计算机端口介绍

目录 一. FTP服务器介绍 FTP服务器是什么意思&#xff1f; 二.Windows Service 2012 搭建FTP服务器 1.开启防火墙 2.创建组 ​编辑3.创建用户 4.用户绑定组 5.安装ftp服务器 ​编辑6.配置ftp服务器 7.配置ftp文件夹的权限 8.连接测试 三.计算机端口介绍 什么是网络…

单集群400TB,OceanBase稳定支撑快手核心业务场景

一款日均超过千万人访问的短视频 App 快手&#xff0c;面对高并发流量如何及时有效地处理用户请求&#xff1f;通过在后端配置多套 MySQL 集群来支撑高流量访问&#xff0c;以解决大数据量存储和性能问题&#xff0c;这种传统的 MySQL 分库分表方案有何问题&#xff1f;快手对分…

c语言结构体数组

使用结构体变量建立数据类型后&#xff0c;我们就可以利用这个数据类型创建数组&#xff0c;就比如创建整形数组就可以写为 int arr[ ],那么创建一个自己建立的数据类型的数组就可以写为&#xff1a; 声明的结构体类型名 数组名。 例如我们声明一个结构体类型struct my后&am…

技术扫盲:如何优雅的使用 java -jar

java -jar xxx.jar java -jar 是一个用于在命令行界面中执行 Java 可执行 JAR 文件的命令。它的语法如下&#xff1a; java -jar <JAR 文件路径> [参数]其中&#xff1a; java 是 Java 运行时环境的可执行文件。-jar 是一个选项&#xff0c;表示要执行的文件是一个 JA…

文件IO

文章目录 文章目录 前言 一 . 文件 文件路径 文件类型 Java中操作文件 File 概述 属性 构造方法 方法 createNewFile mkdir 二 . 文件内容的读写 - IO InputStream 概述 FileInputStream 概述 利用 Scanner 进行字符读取 OutputStream 概述 PrintWriter封装O…

【教学类-42-03】20231225 X-Y 之间加法题判断题3.0(确保错误题有绝对错误的答案)

背景需求&#xff1a; 根据需求&#xff0c;0-5以内的判断是21题正确&#xff0c;21题错误&#xff0c;但由于错误答案是随机数抽取&#xff0c;有可能恰好是正确的&#xff0c;所以会出现每套题目的正确数和错误数不一样的情况 优化思路一&#xff1a; 设置如果错误答案与正…

14 UVM sequencer

sequencer是在sequence和driver之间建立连接的中介。最终&#xff0c;它将transactions或sequence items传递给driver&#xff0c;以便将其驱动到DUT。 1. uvm_sequencer class hierarchy uvm_sequencer class declaration: class uvm_sequencer #( type REQ uvm_sequence_i…

美国某金融公司遭遇网络攻击,130 万民众受影响

The Record 网站披露&#xff0c;美国最大的产权保险公司富达国民金融&#xff08;Fidelity National Financial&#xff08;"FNF"&#xff09;&#xff09;子公司向所在州监管机构报告了一起数据泄露事件&#xff0c;并指出有 1316938 人的数据信息被入侵其母公司的…

ActiveMQ漏洞合集

目录 介绍CVE-2015-5254&#xff1a;Apache ActiveMQ任意代码执行漏洞漏洞介绍 & 环境准备漏洞发现Nuclei❌Vulmap✅漏洞验证漏洞利用 CVE-2016-3088&#xff1a;Apache ActiveMQ Fileserver远程代码执行漏洞漏洞发现Nuclei✅Vulmap✅MSF✅第三方工具1&#xff08;漏洞探测…

【SAM系列】I-MedSAM: Implicit Medical Image Segmentation with Segment Anything

论文链接&#xff1a;https://arxiv.org/abs/2311.17081 比较有趣的点&#xff1a;frequency adapter

中间件系列 - Redis入门到实战(原理篇)

前言 学习视频&#xff1a; 黑马程序员Redis入门到实战教程&#xff0c;深度透析redis底层原理redis分布式锁企业解决方案黑马点评实战项目 中间件系列 - Redis入门到实战 本内容仅用于个人学习笔记&#xff0c;如有侵扰&#xff0c;联系删除 学习目标 Redis数据结构Redis网…

若依common模块解读,Java小白入门(四)

● 若依springboot版本后端&#xff0c;有两个核心模块&#xff0c;一个是ruoyi-common,一个是ruoyi-framewok,今天我们来看第一个模块ruoyi-common, 一些基类&#xff0c;工具等框架底座&#xff0c;分别来看其包含哪些基本内容&#xff1a; 1. annotation 注解&#xff0c;…

K8S 全局架构图 —— 筑梦之路

kube-apiserver&#xff1a; Kubernetes API 服务器验证并配置 API 对象的数据&#xff0c; 这些对象包括 pods、services、replicationcontrollers 等。API 服务器为 REST 操作提供服务&#xff0c;并为集群的共享状态提供前端&#xff0c; 所有其他组件都通过该前端进行交互。…

传感器原理与应用复习--电感式传感器

文章目录 上一篇自感式电感传感器差动变压器式传感器电涡流式传感器下一篇 上一篇 传感器原理与应用–传感器基本特性与应变式传感器 自感式电感传感器 将线圈通入电流产生磁场&#xff0c;而间隙的大小将影响磁场的大小&#xff0c;从而进行检测 变气隙式电感传感器&#xf…