目录

一、Three.js是什么?

二、VUE简单使用Three.js步骤

1.npm安装

2.template模板

3.引入库

4.定义全局变量

5.初始化场景

6.初始化相机

7.初始化灯光

8.初始化渲染器

9.创建模型(这里我搭建的模型是一个简单双面货架模型)

10.根据浏览器窗口自适应

11.初始化函数,页面加载完成时调用(mounted()中调用)

12.Style样式

三、VUE进阶使用Three.js步骤(完成各种事件和效果)

在简单使用Three.js的基础上,添加以下控件和代码

1.引入库及需要使用的组件

2.template模板

3.定义全局变量

4.使用OrbitControls控制给模型添加缩放,旋转,平移和拖拽等效果

5.点击模型后的模型样式效果

6.相机跟随点击事件移动动画效果

7.返回主视角按钮事件

8.添加鼠标点击模型事件,并调用相机移动方法(第6点)和点击后样式方法(第5点)

9.运行动画

10.初始化函数,页面加载完成时调用(mounted()中调用)

11.Style样式

四、源码地址


一、Three.js是什么?

Three.js是一款基于原生WebGL封装通用Web 3D引擎,在小游戏、产品展示、物联网、数字孪生、智慧城市园区、机械、建筑、全景看房、GIS等各个领域基本上都有three.js的身影

二、VUE简单使用Three.js步骤

1.npm安装

npm install three
npm install @tweenjs/tween.js

2.template模板

<template><div class="container"><div id="model"></div></div>
</template>

3.引入库

import * as THREE from "three";

4.定义全局变量

data() {return {isDisabled: true,scene: null,camera: null,renderer: null,light: null,light2: null,//定义模型架子的长度long: 100,//定义模型架子的宽度tall: 24,//定义模型架子横纵板的宽度thickness: 0.4,};
}

5.初始化场景

//初始化场景
initScene() {this.scene = new THREE.Scene();
},

6.初始化相机

//初始化相机
initCamera() {const { long, tall } = this;this.camera = new THREE.PerspectiveCamera(45,window.innerWidth / window.innerHeight,0.1,50000);this.camera.position.set(0, -(5 * tall) / 12, (6 * long) / 5);
}

7.初始化灯光

//初始化灯光
initLight() {let ambientLight = new THREE.AmbientLight(0x404040);this.scene.add(ambientLight);//定义灯,并设置位置this.light = new THREE.DirectionalLight(0x333333);this.light.position.set(60, 30, 40);this.light2 = new THREE.DirectionalLight(0xdddddd);this.light2.position.set(-20, 20, -20);this.scene.add(this.light);this.scene.add(this.light2);
},

8.初始化渲染器

//初始化渲染器
initRender() {//dom元素渲染器this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });this.renderer.setSize(window.innerWidth, window.innerHeight); // 设置渲染区域尺寸this.renderer.setClearColor(0x000000, 0); // 设置背景颜色//window.devicePixelRatio 当前设备的物理分辨率与css分辨率之比this.renderer.setPixelRatio(window.devicePixelRatio);//按层级先后渲染this.renderer.sortObjects = true;document.getElementById("model").appendChild(this.renderer.domElement);
},

9.创建模型(这里我搭建的模型是一个简单双面货架模型)

//创建载入模型
initModel() {const { long, tall, thickness } = this;//坐标系// let axes = new THREE.AxesHelper(4000);// this.scene.add(axes);//所有横板for (let index = 1; index <= tall / 4 - 1; index++) {let geometry = new THREE.BoxGeometry(long, thickness, 12);let material = new THREE.MeshLambertMaterial({color: 0x808080,opacity: 0.7,transparent: true,}); //材质对象Materiallet mesh = new THREE.Mesh(geometry, material); //网格模型对象Meshmesh.position.set(0, index * 4, 0); //设置mesh3模型对象的xyz坐标为120,0,0this.scene.add(mesh); //网格模型添加到场景中}//所有纵板for (let index = 1; index <= long / 4 + 1; index++) {let geometry = new THREE.BoxGeometry(thickness, tall, 12);let material = new THREE.MeshLambertMaterial({color: 0x808080,opacity: 0.7,transparent: true,}); //材质对象Materiallet mesh = new THREE.Mesh(geometry, material); //网格模型对象Meshmesh.position.set(-long / 2 + (index - 1) * 4, tall / 2, 0); //设置mesh3模型对象的xyz坐标为120,0,0this.scene.add(mesh); //网格模型添加到场景中}//正面所有箱子let list1 = new Array(tall / 4);for (let i = 0; i < list1.length; i++) {list1[i] = new Array(long / 4);//不一定写for循环赋值,还可以直接赋值,在数量有限的情况下for (let j = 0; j < long / 4; j++) {// a[i][j] = i + j;let geometry = new THREE.BoxGeometry(3, 3, 5);let material = new THREE.MeshLambertMaterial({color: 0x00b1f7,opacity: 0.4,transparent: true,});let mesh = new THREE.Mesh(geometry, material);//定义每个箱子的名称(或者编号)mesh.name = i + 1 + "-" + (j + 1);//设置每个箱子的位置mesh.position.set(-long / 2 + j * 4 + 2, i * 4 + 2, 3);this.scene.add(mesh);}}//背面所有箱子let list2 = new Array(tall / 4);for (let i = 0; i < list2.length; i++) {list2[i] = new Array(long / 4);//不一定写for循环赋值,还可以直接赋值,在数量有限的情况下for (let j = 0; j < long / 4; j++) {// a[i][j] = i + j;let geometry = new THREE.BoxGeometry(3, 3, 5);let material = new THREE.MeshLambertMaterial({color: 0x00b1f7,opacity: 0.4,transparent: true,});let mesh = new THREE.Mesh(geometry, material);//定义每个箱子的名称(或者编号)mesh.name = "-" + (i + 1) + "-" + (j + 1);//设置每个箱子的位置mesh.position.set(-long / 2 + j * 4 + 2, i * 4 + 2, -3);this.scene.add(mesh);}}
}

10.根据浏览器窗口自适应

//根据浏览器窗口自适应
onWindowResize() {this.renderer.setSize(window.innerWidth, window.innerHeight);this.camera.aspect = window.innerWidth / window.innerHeight;this.camera.updateProjectionMatrix();
}

11.初始化函数,页面加载完成时调用(mounted()中调用)

//初始化函数,页面加载完成时调用
init() {this.initScene();this.initCamera();this.initLight();this.initRender();this.initModel();this.renderer.render(this.scene, this.camera);window.onresize = this.onWindowResize;
}

12.Style样式

<style scoped>
.container {width: 100%;height: 100%;position: relative;
}
/*模型样式*/
#model {width: 100%;height: 100%;position: relative;overflow: hidden;
}
</style>

呈现效果,如下图。

但是这样仅仅只是将自制的模型创建并渲染了出来,无法满足我们大多数情况的使用场景,例如旋转,点击,拖拽平移,相机视角跟随移动等,接下来就实现这些效果。

三、VUE进阶使用Three.js步骤(完成各种事件和效果)

在简单使用Three.js的基础上,添加以下控件和代码

1.引入库及需要使用的组件

import TWEEN from "@tweenjs/tween.js";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import { EffectComposer } from "three/examples/jsm/postprocessing/EffectComposer.js";
import { RenderPass } from "three/examples/jsm/postprocessing/RenderPass.js";
import { OutlinePass } from "three/examples/jsm/postprocessing/OutlinePass.js";

2.template模板

<template><div class="container"><div id="model"></div><buttonid="btns":style="{ cursor: isDisabled ? '' : 'pointer' }":disabled="isDisabled"type="isDisabled:"@click="toHomeView(1)">主视角</button></div>
</template>

3.定义全局变量

这里就是所有的全局变量了,直接复制替换就行了

data() {return {isDisabled: true,scene: null,camera: null,renderer: null,controls: null,light: null,light2: null,group: new THREE.Group(),composer: null, // 控制发光outlinePass: null,renderPass: null,// 选中的模型selectedObjects: [],mouse: new THREE.Vector2(),raycaster: new THREE.Raycaster(),tween: null,//定义模型架子的长度long: 100,//定义模型架子的宽度tall: 24,//定义模型架子横纵板的宽度thickness: 0.4,positionObj: null,};
}

4.使用OrbitControls控制给模型添加缩放,旋转,平移和拖拽等效果

//使用OrbitControls控制三维场景缩放和旋转等功能
initControls() {this.controls = new OrbitControls(this.camera, this.renderer.domElement);//动态阻尼系数 即鼠标拖拽旋转的灵敏度this.controls.dampingFactor = 0.25;// this.controls.target.set(0, 900, 0)// //上下旋转范围this.controls.minPolarAngle = 0;this.controls.maxPolarAngle = 1.5;this.controls.autoRotate = true;//惯性滑动,滑动大小默认0.25this.controls.dampingFactor = 0.25;//滚轮是否可控制zoom,zoom速度默认1//缩放倍数this.controls.zoomSpeed = 1.0;//最大最小相机移动距离(景深相机)this.controls.minDistance = 1;this.controls.maxDistance = Infinity;//水平方向视角限制this.minAzimuthAngle = -Math.PI * 2;this.maxAzimuthAngle = Math.PI * 2;this.controls.enabledPan = true;this.keyPanSpeed = 7.0;
}

5.点击模型后的模型样式效果

//高亮显示模型(呼吸灯)
outlineObj(selectedObjects) {// 创建一个EffectComposer(效果组合器)对象,然后在该对象上添加后期处理通道。this.composer = new EffectComposer(this.renderer);// 新建一个场景通道  为了覆盖到原理来的场景上this.renderPass = new RenderPass(this.scene, this.camera);this.composer.addPass(this.renderPass);// 物体边缘发光通道this.outlinePass = new OutlinePass(new THREE.Vector2(window.innerWidth, window.innerHeight),this.scene,this.camera,selectedObjects);this.outlinePass.edgeStrength = 8.0; // 高光边缘边框的亮度this.outlinePass.edgeGlow = 1; // 光晕[0,1]  边缘微光强度this.outlinePass.usePatternTexture = false; // 是否使用父级的材质,纹理覆盖this.outlinePass.edgeThickness = 3; // 边框宽度,高光厚度this.outlinePass.downSampleRatio = 1; // 边框弯曲度this.outlinePass.pulsePeriod = 2; // 呼吸闪烁的速度,数值越大,律动越慢this.outlinePass.visibleEdgeColor.set(parseInt(0x00f6ff)); // 呼吸显示的颜色this.outlinePass.hiddenEdgeColor = new THREE.Color(0, 0, 0); // 呼吸消失的颜色// this.outlinePass.clear = truethis.composer.addPass(this.outlinePass); // 加入高光特效this.outlinePass.selectedObjects = selectedObjects; // 需要高光的模型
}

6.相机跟随点击事件移动动画效果

// 相机移动动画
initTween(targetX, targetY, targetZ) {// 获取当前相机位置let initPosition = {x: this.camera.position.x,y: this.camera.position.y,z: this.camera.position.z,};//定义相机移动方法let tween = new TWEEN.Tween(initPosition).to({ x: targetX, y: targetY, z: targetZ }, 2000).easing(TWEEN.Easing.Sinusoidal.InOut);//格子位置参数let onUpdate = (pos) => {let x = pos.x;let y = pos.y;let z = pos.z;//z<0为背面格子,z>0为正面格子,并设置相机的位置if (pos.z < 0) {this.camera.position.set(x, y, z - 12);} else {this.camera.position.set(x, y, z + 12);}};//调用相机方法并传入格子位置参数tween.onUpdate(onUpdate);tween.start();//设置相机target位置(相机看向格子的位置)if (this.positionObj.z < 0) {this.controls.target.set(this.positionObj.x,this.positionObj.y - 0.4,-12);} else {this.controls.target.set(this.positionObj.x,this.positionObj.y - 0.4,12);}
}

7.返回主视角按钮事件

//相机返回主视角动画
toHomeView(id) {const { long, tall } = this;if (id === 1) {// 获取当前相机位置let initPosition = {x: this.camera.position.x,y: this.camera.position.y,z: this.camera.position.z,};//定义相机移动方法let tween = new TWEEN.Tween(initPosition).to({ x: 0, y: -(5 * tall) / 12, z: (6 * long) / 5 }, 2000).easing(TWEEN.Easing.Sinusoidal.InOut);//主视角相机位置(点击前原位置)let onUpdate = (pos) => {let x = pos.x;let y = pos.y;let z = pos.z;this.camera.position.set(x, y, z);};tween.onUpdate(onUpdate);tween.start();//设置相机target位置(看向坐标轴零点的位置)this.controls.target.set(0, 0, 0);//相机返回原点后,返回主视角禁用this.isDisabled = true;//相机返回原点后,开启模型自动旋转this.controls.autoRotate = true;}
}

8.添加鼠标点击模型事件,并调用相机移动方法(第6点)和点击后样式方法(第5点)

// 鼠标点击模型
onMouseClick(event) {//通过鼠标点击的位置计算出raycaster所需要的点的位置,以屏幕中心为原点,值的范围为-1到1this.mouse.x = (event.clientX / window.innerWidth) * 2 - 1;this.mouse.y = -(event.clientY / (window.innerHeight-50)) * 2 + 1;// 通过鼠标点的位置和当前相机的矩阵计算出raycasterthis.raycaster.setFromCamera(this.mouse, this.camera);// 获取raycaster直线和所有模型相交的数组集合let intersects = this.raycaster.intersectObjects(this.scene.children);if (!intersects[0]) {return;} else {//这样会获取所有模型,因为我们这里只需要小格子//所以只需要获取我们之前设置name!=""属性的object即可if (!intersects[0].object.name == "") {this.selectedObjects = [];this.selectedObjects.push(intersects[0].object);//调用高亮显示模型(呼吸灯)的方法给点击的格子添加点击后的样式this.outlineObj(this.selectedObjects);//拿到格子的position坐标this.positionObj = {x: intersects[0].object.position.x,y: intersects[0].object.position.y,z: intersects[0].object.position.z,};//调用机移动动画,将相机移动至被点击的格子处this.initTween(this.positionObj.x,this.positionObj.y,this.positionObj.z);//点击格子后,开放返回主视角的点击权限this.isDisabled = false;//点击格子后,禁止模型自动旋转this.controls.autoRotate = false;}}
}

9.运行动画

//运行动画
animate() {//运行相机旋转动画TWEEN.update();//渲染场景和相机this.renderer.render(this.scene, this.camera);this.controls.update();if (this.composer) {this.composer.render();}//使用requestAnimationFrame周期性渲染requestAnimationFrame(this.animate);
}

10.初始化函数,页面加载完成时调用(mounted()中调用)

//初始化函数,页面加载完成时调用
init() {this.initScene();this.initCamera();this.initLight();this.initRender();this.initModel();this.renderer.render(this.scene, this.camera);this.initControls();this.animate();window.onresize = this.onWindowResize;window.onclick = this.onMouseClick;
}

11.Style样式

<style scoped>
.container {width: 100%;height: 100%;position: relative;
}
#btns {position: absolute;background-color: #031b34;bottom: 0;left: 50%;width: 80px;height: 30px;line-height: 30px;transform: translate(-50%, -50%);text-align: center;z-index: 9999;color: #00eeff;font-weight: bold;box-shadow: 0px 0px 2px 1px #00f6ff;border-radius: 6px;border: 1px solid #00f6ff;padding: 0;
}
/*模型样式*/
#model {width: 100%;height: 100%;position: relative;overflow: hidden;
}
</style>

呈现效果如下图,如下图。

四、源码地址

建议认真看每一部分的代码,实在不清楚可以看源码,毕竟最近才做这方面的项目,写的不好的地方,还请各位嘴下留情。
货架三维模型: 简单的货架三维模型实现点击交互,相机旋转等功能https://gitee.com/halsixsixsix/3d-model-of-shelf.git

VUE使用Three.js实现模型,点击交互,相机旋转视角跟随移动(Threejs中使用Tweenjs,含demo源码)相关推荐

  1. iOS自定义相机:带拍摄区域边框、半透明遮罩层、点击屏幕对焦、自动裁剪(含demo源码)

    文章目录 前言 I.案例1:加一个长方形的框框并裁剪身份证照片(无半透明遮罩层) 1.1 demo 源码 1.2 控制屏幕旋转方向 1.3 封装富文本API 1.4 设置相机预览层和证件框框的fram ...

  2. 【数学建模】基于matlab船舶三自由度MMG模型【含Matlab源码 1925期】

    ⛄一.获取代码方式 获取代码方式1: 完整代码已上传我的资源:[数学建模]基于matlab船舶三自由度MMG模型[含Matlab源码 1925期] 点击上面蓝色字体,直接付费下载,即可. 获取代码方式 ...

  3. 【数学建模】基于matlab三维海浪模型仿真【含Matlab源码 1159期】

    一.获取代码方式 获取代码方式1: 完整代码已上传我的资源:[数学建模]基于matlab三维海浪模型仿真[含Matlab源码 1159期] 点击上面蓝色字体,直接付费下载,即可. 获取代码方式2: 付 ...

  4. 【电力预测】基于matlab GUI灰色模型电力负荷预测【含Matlab源码 769期】

    一.获取代码方式 获取代码方式1: 完整代码已上传我的资源: [电力负荷预测]基于matlab GUI灰色模型电力负荷预测[含Matlab源码 769期] 获取代码方式2: 通过订阅紫极神光博客付费专 ...

  5. 原生js+css3实现点击图片正方体旋转展现图片

    --------------武汉加油!!中国加油!!陕西也给我加油!!2020-02-16------------ 点击图片,正方体中图片对应的面会旋转过来. 上图: 上代码 <!DOCTYPE ...

  6. 原生js实现经典扫雷游戏(含完整源码)

    项目结构 实现效果 思路流程 写出基本的布局 利用js生成扫雷的table表格 利用随机数来做地雷在表格中的索引 初始化table表格 根据地雷的坐标生成地雷周围的数字 点击事件分成鼠标左键点击和右键 ...

  7. springboot项目文档源码_基于SpringBoot和Vue的企业级中后台项目(附源码)

    简介 SpringBoot和Vue,前后端分离,我们开源一套漂亮的代码和一套整洁的代码规范,让大家在这浮躁的代码世界里感受到一股把代码写好的清流!同时又让开发者节省大量的时间,减少加班,快乐工作,热爱 ...

  8. Vue - 实现垂直菜单分类栏目,鼠标移入后右侧出现悬浮二级菜单容器效果(完整示例源码,详细代码注释,一键复制开箱即用)

    前言 网上的教程都太乱了,各种杂乱无注释代码.图片资源丢失.一堆样式代码,根本无法改造后应用到自己的项目中. 本文实现了 在 Vue / Nuxt 项目中,垂直分类菜单项,当用户鼠标移入菜单后,右侧自 ...

  9. pythonipo模型_【python量化】Fama-French三因子回归A股实证(附源码)

    01 三因子回归模型 Fama-French三因子回归是量化中最经典的模型之一,最早提出是在论文<Common risk factors in the returns on stocks and ...

最新文章

  1. HSIC简介:一个有意思的判断相关性的思路
  2. 二分检索函数lower_bound()和upper_bound()
  3. 班级事务管理系统php源码,F学校网络办公系统 v2.2.3
  4. python 循环语句结果存储_Hello,Python!小鲸教你学Python(八)条件语句和循环
  5. 云服务器 ECS > 标签与资源 > 标签 > 标签概述
  6. 创建表的时候要添加目标语句use
  7. html给td内容加删除线,【原创】tr上加删除线-且满足一个页面多表格
  8. css盒模型(附图解)
  9. 运筹系列10:线性规划开源软件GLPK和PyMathProg
  10. 看阮一峰es6摘抄的笔记
  11. DVP 接口时钟配置错误导致的高温出图异常
  12. 聊聊UI的扁平化设计趋势与拟物化设计
  13. 用计算机求和公式,全国计算机一级等考excel之sum求和函数
  14. .计算机在生物信息学中的应用,计算机科学在生物信息学中的应用
  15. 服务器地址显示169.254,IP地址是169.254开头的
  16. c/c++ 头文件(.h)、源文件(.cpp)书写及接口与实现分离实例
  17. 建行与爱赏合作推出建行爱赏卡 刷卡消费“抢红包”
  18. 远端WWW服务支持TRACE请求
  19. 流量复制导流工具研究
  20. 关闭游戏功能界面,导致游戏闪退

热门文章

  1. Apple推送通知服务教程
  2. “幻核”跑路,数字藏品路在何方?
  3. python常用命令行命令
  4. html中的父子通信代码,Vue.js 父子组件通信的十种方式
  5. ps右键不显示编辑选项_PS教程教你如何删除和修改图片文字却不留痕迹
  6. 四轴飞行玩具的姿态解算的原理2.欧拉角及其表示
  7. 为什么说 交叉验证能 防止过拟合 呢?
  8. 如何给电脑选内存条加内存条
  9. Cross-Origin Read Blocking (CORB) blocked cross-origin response 问题
  10. 天下3新手攻略——技能解读