Threejs项目实战之二:产品三维爆炸图效果展示

目录

  • 最终效果
  • 1. 实现原理
  • 2. 创建项目
  • 3. 编写代码

最终效果

今天我们来实现如何使用ThreeJS实现产品的三维爆炸图分解与组合的效果,先看下最终项目完成后的效果展示动画
在这里插入图片描述

1. 实现原理

要实现这种爆炸图的分解与组合效果,其实原理很简单,就是找到模型中各个组成部分对应的Mesh,然后通过修改对应Mesh的Position坐标来实现产品的分解与组合效果,为了使分解和组合效果看起来更丝滑,在修改对应Position位置的时候,设置一个动画效果,使其开起来过渡更自然,我这里使用的是GSAP动画库,这个动画库非常强大,感兴趣的小伙伴可以看我之前写的一篇关于GSAP动画库使用的博客,这里只介绍具体的使用,就不讲解各个参数的定义了。

2. 创建项目

  • 在D盘新建vite-vue-Valve文件夹,鼠标右键点击新建的文件夹,使用vscode打开;
  • 在vscode中使用快捷键Ctrl+Shift+~打开终端,在终端中使用vite构建工具创建项目,输入pnpm create vite bmw-app --template vue创建项目
  • 创建成功后,在终端中输入cd bmw-app进入文件夹
  • 输入pnpm i 安装依赖包
  • 安装完成后,输入pnpm run div 启动项目,打开浏览器,可以看到系统默认的页面,说明项目环境搭建成功
  • 安装ThreeJS库,在终端中输入pnpm i three安装threejs插件
  • 安装GSAP库,在终端中输入 pnpm i gsap安装GSAP库
  • 删除vite构建工具为我们创建的HelloWord.vue文件和style.css中的样式,删除App.vue中的样式
  • 在components文件夹下新建ValveView.vue文件
  • 在App.vue的Template模板中调用 ValveView.vue
    App.vue中代码如下
    <template><ValveView></ValveView>
    </template><script setup>
    import ValveView from './components/ValveView.vue'; 
    </script>
    <style scoped>
    </style>
    

style.css中的样式代码如下:
*{ margin: 0; padding: 0; list-style: none; }

3. 编写代码

  • 在ValveView.vue的template模板中添加一个div,id设置为scene,作为承载Threejs的容器;再增加一个div,设置class=“control”,在这个div中添加两个button,并给两个button添加点击事件,用于控制产品模型的分解与组合
    template模板中代码如下:
    <template><div id="scene"> </div><div class="control"><button @click="split(true)">分解</button><button @click="split(false)">组合</button></div>  
    </template>
    
  • 设置ValveView.vue中元素的样式,默认情况想,我们上面设置的id为scene的div和class为control的div是一列排列的,我们需要将control中的两个button设置为页面右上角的位置,在style代码片段中设置类名为control的样式和button的样式代码如下
    <style scoped>
    .control{display: flex;position: fixed;top:50px;right: 50px;
    }
    .control button {width: 100px;height: 30px;margin: 10px;
    }
    </style>
    
  • 在script标签中引入threejs
    import * as THREE from 'three'
  • 这里我们选择的产品模型是gltf格式的文件,因此,我们需要引入threejs为我们提供的GLTFLoader加载器
    import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
  • 由于我们需要对物体进行鼠标旋转缩放控制,因此我们需要引入threejs为我们提供的OrbitControls控制器
    import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
  • 引入GSAP库,实现产品分解、组合时的动画效果
    import { gsap } from 'gsap'
  • 引入vue的生命周期onMounted
    import { onMounted } from 'vue'
  • 创建init函数,用于初始化threejs相关设置
    const init = () => {}
  • 为了便于后期代码的维护,我这里将创建threejs场景、相机、灯光、渲染器及控制器等各个部分进行了分别的封装,这样便于后期的代码维护与修改。
  • 首先设置全局变量scene, camera, loader, renderer, controls和model
    let scene, camera, loader, renderer, controls, model
  • 创建初始化场景函数,在初始化场景函数中,调用new THREE.Scene()方法初始化场景,具体代码如下
    // 初始化场景
    const initScene = () => {scene = new THREE.Scene()scene.background = new THREE.Color(0xcccccc)scene.environment = new THREE.Color(0xcccccc)
    }
    
  • 创建初始化相机函数,在初始化相机函数中,调用 new THREE.PerspectiveCamera()方法创建相机并设置相机的位置,具体代码如下:
    // 添加相机
    const initCamera = () => {camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.01, 1)camera.position.set(0.08, 0.08, 0.15)
    }
    
  • 创建初始化灯光函数,在初始化灯光函数中,添加环境光、自然光、聚光灯等各种灯光效果,代码如下:
    // 添加灯光
    const initLight = () => {// 设置环境光scene.add(new THREE.AmbientLight(0xffffff, 0.5))// 添加球光源const hesLight = new THREE.HemisphereLight(0xffffff, 0x444444)hesLight.intensity = 0.6scene.add(hesLight)// 自然光const dirLight = new THREE.DirectionalLight()dirLight.position.set(0, 0, 15)scene.add(dirLight)const dirLight2 = new THREE.DirectionalLight()dirLight2.position.set(0, 0, -15)scene.add(dirLight2)const dirLight3 = new THREE.DirectionalLight()dirLight3.position.set(15, 0, 0)scene.add(dirLight3)const dirLight4 = new THREE.DirectionalLight()dirLight4.position.set(-15, 0, 0)scene.add(dirLight4)const dirLight5 = new THREE.DirectionalLight()dirLight5.position.set(0, 15, 0)scene.add(dirLight5)const dirLight6 = new THREE.DirectionalLight()dirLight6.position.set(0, -15, 0)scene.add(dirLight6)const dirLight7 = new THREE.DirectionalLight()dirLight7.position.set(5, 15, 5)scene.add(dirLight7)const dirLight8 = new THREE.DirectionalLight()dirLight8.position.set(-5, -15, -5)scene.add(dirLight8)// 聚光灯const sportLight = new THREE.SpotLight(0xffffff, 0.8)sportLight.angle = Math.PI / 8; //散射角度,跟水平线的夹角sportLight.penumbra = 0.1;  // 聚光锥的半影衰减百分比sportLight.decay = 2; // 纵向:沿着光照距离的衰减量。sportLight.distance = 10;sportLight.shadow.radius = 10;// 阴影映射宽度,阴影映射高度 sportLight.shadow.mapSize.set(512, 512);sportLight.position.set(0, 15, 0);// 光照射的方向sportLight.target.position.set(0, 0, 0);sportLight.castShadow = true;scene.add(sportLight);
    }
    
  • 创建加载GLTF模型函数,使用Threejs提供的new GLTFLoader()方法加载gltf模型文件,具体代码如下:
    // 加载GLTF模型
    const initModel = () => {loader = new GLTFLoader()loader.load('/model/fm.gltf', gltf => {model = gltf.scene model.position.set(0, -0.08, 0)// 添加模型到场景scene.add(model)})
    }
    
  • 创建初始化渲染器函数,使用Threejs提供的new THREE.WebGLRenderer()方法创建渲染器,并设置相关参数,具体diam如下:
    // 创建渲染器const initRenderer = () => {renderer = new THREE.WebGLRenderer({ antialias: true })renderer.setSize(window.innerWidth, window.innerHeight)document.getElementById('scene').appendChild(renderer.domElement)}
    
  • 创建初始化控制器函数,添加控制器,使用Threejs提供的new OrbitControls()方法创建一个控制器,并设置相关参数,具体代码如下:
    // 添加控制器
    const initControl = () => {controls = new OrbitControls(camera, renderer.domElement)controls.enableDamping = truecontrols.dampingFactor = 0.25controls.enableZoom = true
    }
    
  • 创建循环渲染动画,通过调用requestAnimationFrame(animate) 循环调用该动画,并使用renderer.render(scene, camera)实时渲染场景,具体代码如下:
    // 渲染循环
    const playAnimate = () => {const animate = function () {requestAnimationFrame(animate) renderer.render(scene, camera)}animate()
    }
    
  • 将上面创建的各个初始化函数添加到init函数中,使其在初始化时分别调用各个函数,init函数具体代码如下:
    const init = () => { initScene()initCamera()initLight()initModel()initRenderer()initControl()playAnimate()
    }
    
  • 在vue生命周期onMounted中调用init函数,完成页面渲染,代码如下:
    onMounted(() => {init()
    })
    
  • 添加分解、组合控制动画,在template模板中,我们已经给两个按钮绑定了鼠标点击事件split(),它接收一个boolean类型的参数,为true时,我们对模型进行分解操作,为false时,我们对模型进行组合操作。我这里实现模型分解与组合的方法是获取模型中的Mesh数组,通过forEach循环遍历获取需要移动位置的Mesh,修改其相关的Position来移动Mesh的位置,这里使用了gsap动画来实现动画效果,具体代码如下:
    const split = (val) => {const myModel = model.children[0].childrenif(val) { myModel.forEach(item => {if(item.name === "Obj3d66-5355088-2-382") {// item.position.y = 0.03gsap.to(item.position,{duration:1,//动画持续时间y:0.03,//目标位置ease:'power2.inOut'})} else if (item.name === "Obj3d66-5355088-2-382_1"){// item.position.y = -0.05gsap.to(item.position,{duration:1,//动画持续时间y:-0.05,//目标位置ease:'power2.inOut'})}});} else {myModel.forEach(item => {if(item.name === "Obj3d66-5355088-2-382") {// item.position.y = 0.03gsap.to(item.position,{duration:1,//动画持续时间y:0,//目标位置ease:'line'})} else if (item.name === "Obj3d66-5355088-2-382_1"){// item.position.y = -0.05gsap.to(item.position,{duration:1,//动画持续时间y:0,//目标位置ease:'line'})}});}
    }
    

至此,我们就完成了产品三维爆炸图的分解与组合效果,运行程序,刷新浏览器,通过鼠标点击分解按钮和组合按钮查看动画效果,效果如下:
在这里插入图片描述
ok,threejs项目实战的第二个项目就实现了,小伙伴们有疑问的评论区留言,喜欢的小伙伴点赞关注+收藏哦!

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Android:java.lang.RuntimeException: Unable to start activity ComponentInfo

java.lang.RuntimeException: Unable to start activity ComponentInfo 报错描述&#xff1a; 在导入别人项目运行时出现了这个报错&#xff1a; java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.news/com.example.activity.DetailNews}: ja…

react之项目打包,本地预览,路由懒加载,打包体积分析以及如何配置CDN

react之项目打包,本地预览,路由懒加载,打包体积分析以及如何配置CDN 一、项目打包二、项目本地预览三、路由懒加载四、打包体积分析五、配置CDN 一、项目打包 执行命令 npm run build根目录下生成的build文件夹 及时打包后的文件 二、项目本地预览 1.全局安装本地服务包 npm…

python zblog API实现类似XMLRPC/发布文章

我发现python对Zblog的XML发布并不友好&#xff0c;虽然也有对应的模块&#xff0c;但是远远没有XPCRPC更直接方便&#xff0c;但是使用xmlRpc是直接给发布文章带来了不小的便利&#xff0c;但是对系统也并不友好&#xff0c;但是zblog也开放了Api&#xff0c;但是干部子弟不乐…

基于Spring+Spring boot的SpringBoot在线电子商城管理系统

SSM毕设分享 基于SpringSpring boot的SpringBoot在线电子商城管理系统 1 项目简介 Hi&#xff0c;各位同学好&#xff0c;这里是郑师兄&#xff01; 今天向大家分享一个毕业设计项目作品【基于SpringSpring boot的SpringBoot在线电子商城管理系统】 师兄根据实现的难度和等级…

【金融数据分析】计算沪深300指数行业权重分布并用饼图展示

前言 前面的文章我们已经介绍了如何获取沪深300成分股所述行业以及权重的数据&#xff0c;想要了解这部分内容的小伙伴可以阅读上一篇文章 springbootjdbcTemplatesqlite编程示例——以沪深300成分股数据处理为例-CSDN博客 那么有了上文获取的数据&#xff0c;我们实际上可以…

Python轴承故障诊断 (五)基于EMD-LSTM的故障分类

目录 前言 1 经验模态分解EMD的Python示例 2 轴承故障数据的预处理 2.1 导入数据 2.2 制作数据集和对应标签 2.3 故障数据的EMD分解可视化 2.4 故障数据的EMD分解预处理 3 基于EMD-LSTM的轴承故障诊断分类 3.1 训练数据、测试数据分组&#xff0c;数据分batch 3.2 定…

成都工业学院Web技术基础(WEB)实验四:CSS3布局应用

写在前面 1、基于2022级计算机大类实验指导书 2、代码仅提供参考&#xff0c;前端变化比较大&#xff0c;按照要求&#xff0c;只能做到像&#xff0c;不能做到一模一样 3、图片和文字仅为示例&#xff0c;需要自行替换 4、如果代码不满足你的要求&#xff0c;请寻求其他的…

APP备案(Android) - 获取签名证书公钥、MD5

因为近期刚针对各应用平台对APP备案时间节点要求进行了统一整理&#xff0c;然后隔天就被要求提供一下app相关的的公钥和MD5&#xff0c;虽然很快就解决了这个事情&#xff0c;但忍不住又稍微衍生了一下&#xff0c;但行小步&#xff0c;莫问远方吧 关联Blog APP备案(Android)…

为 Compose MultiPlatform 添加 C/C++ 支持(1):在 kotlin 中使用 cinterop 实现与 C/C++ 互操作

前言 在安卓中我们可以使用 jvm 提供的 jni 方便的编写 C/C 代码并与 java/kotlin 互操作。 但是当使用 kotlin MultiPlatform 时想要调用 C/C 代码将变得麻烦甚至是不可用&#xff0c;因为对于 Android 和 Desktop 来说依旧使用的是 jvm &#xff0c;所以只要稍微适配一下也…

二分查找|滑动窗口|前缀和|LeetCode209: 长度最小的子数组

长度最短的子数组 作者推荐 【动态规划】【广度优先】LeetCode2258:逃离火灾 本文涉及的基础知识点 二分查找算法合集 C算法&#xff1a;前缀和、前缀乘积、前缀异或的原理、源码及测试用例 包括课程视频 滑动窗口 题目 给定一个含有 n 个正整数的数组和一个正整数 target…

大数据HCIE成神之路之数据预处理(1)——缺失值处理

缺失值处理 1.1 删除1.1.1 实验任务1.1.1.1 实验背景1.1.1.2 实验目标1.1.1.3 实验数据解析 1.1.2 实验思路1.1.3 实验操作步骤1.1.4 结果验证 1.2 填充1.2.1 实验任务1.2.1.1 实验背景1.2.1.2 实验目标1.2.1.3 实验数据解析 1.2.2 实验思路1.2.3 实验操作步骤1.2.4 结果验证 1…

浅谈SPI机制之ServiceLoader的原理

大家好&#xff0c;我是G探险者。 今天我们聊聊SPI机制&#xff0c;先从JDK的ServiceLoader 类谈起。 1. ServiceLoader 介绍 ServiceLoader 类是 Java Development Kit (JDK) 的一部分&#xff0c;用于加载服务提供者。这个类是 Java 的服务提供者加载机制&#xff08;SPI&…