这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助

最近几天在学习three.js ,因为我相信只有实践才能出真理,捣鼓捣鼓做了一个简易的全景图,这里主要是分享做这个vue版全景图中遇到的问题,有些代码可能与其他做过全景图的大佬有些相似毕竟原因都差不多

本文属于技术总结类的文章

将介绍在 vue中如何安装并使用 three.js 以及一些配套插件 , 使用three.js 实现全景图的原理 , vue打包后图片显示的问题 ,及在32位谷歌49版本的浏览器无法使用three.js等问题,至于如何安装 Node服务这里就不再赘述了

在 vue 中安装 three.js 以及配套插件

npm 安装 three.js npm install three 然后在对应页面上将three的功能模块全部导入进来

three.js - npm地址

<script>
import * as THREE from "three";
...
</script>

npm 安装 OrbitControls.js 操作三维场景插件 npm install three-orbit-controls 在引入插件时必须保证three被成功引入否则页面会报错,如果你不想通过npm下载 , 其实在 npm three 的时候已经下载对应的插件, 在 node_modules 文件夹下找到 three/examples/jsm/controls/OrbitControls 这个路径里面也能找到对应的插件, 通过下面注释里面的形式也能导入,但是不推荐这样导入因为在谷歌32位49版本的浏览器中这样导入控制器是无法使用的

<script>
import * as THREE from "three";
const OrbitControls = require('three-orbit-controls')(THREE);
//import {OrbitControls} from 'three/examples/jsm/controls/OrbitControls'
...
</script>

npm 安装.obj 和.mtl 文件的插件 npm i --save three-obj-mtl-loader 加载 .obj 模型文件 , .mtl 材质信息文件这里我就不过多赘述了想要试试的小伙伴可以看看 郭隆邦老师的电子书指南-第14小节 , 还有fbx模型文件 ,也就是除了包含几何、材质信息,还可以存储骨骼动画等数据的模型文件 ,可以通过 npm i three-fbx-loader进行安装

说到这些材质文件的导入我要忍不住吐槽两句 .stl格式 , obj文件 大多数按照官方的导入方法来做是没有问题的 , 但.fbx格式就很特殊 , 之前我在网上下载的比较多的fbx格式的模型和对应的材质但大多数都用不了 ,我真的是裂开了。

后面到处查原因,总结了一下就是插件的兼容性不好 网上下载的fbx动画大多数都是用不了的,有版本问题、也有文件本身的问题 后面找到一篇大佬的开荒文章 THREE.js中加载不同格式的模型及动画(fbx、json和obj) 上面写的很详细 ,有问题的同学可以去看看

<script>
import * as THREE from "three";
import {OBJLoader,MTLLoader} from 'three-obj-mtl-loader';
const OrbitControls = require('three-orbit-controls')(THREE);
...
</script>

npm 安装性能检测插件 , npm i three-stats 主要作用就是 主要用于检测动画运行时的帧数

<script>
import * as THREE from "three";
import {OBJLoader,MTLLoader} from 'three-obj-mtl-loader';
const OrbitControls = require('three-orbit-controls')(THREE);
import * as ThreeStats from 'three-stats'
...
</script>

文章到这里当前项目的配置文件就已经介绍完毕了,后面我就会开始介绍一些three.js的最基本的原理以及全景图的实现方式 ,完整的代码我会贴到文章的最下方

three.js 的基本原理 (渲染器-renderer, 场景-scene,相机-camera)

这里只对原理进行简单的讲述,想要详细了解的同学请进 郭隆邦老师的电子书指南

举个栗子 ,假如我是一名导演, 我已经准备了最好了演员 ,还请了岛国一流的拍摄团队 , 最后物色一块风水宝地 ,准备拍一部让人热血沸腾的青春偶像动作片 ,一战成名 , 然后走向人生巅峰

  1. 渲染器-renderer 就好比刚刚物色的那块风水宝地 ,我什么都准备好了总要找个合适的地方进行拍摄嘛 ,这里就是通过渲染器来创建一个自定义大小的拍摄地点 渲染器中文文档
new THREE.WebGLRenderer(); //创建渲染器
<template>
<div ref='threeDom'></div>
</template>
<script>
rendererInit(){ //初始化渲染器
var width = 1000; //窗口宽度 window.innerWidth 浏览器窗口可视区宽度(不包括浏览器控制台、菜单栏、工具栏)包含滚条
var height = 800; //窗口高度 window.innerHeight
this.renderer = new THREE.WebGLRenderer(); //创建渲染器
this.renderer.setClearColor(0xffffff); //添加背景颜色
this.renderer.setSize(width, height); // 设定渲染器尺寸
this.$refs.threeDom.appendChild(this.renderer.domElement); //通过 this.$refs获取页面的dom将场景初始化上去
},
</script>
  1. 场景-scene 就是你拍摄地点找好了,但里面什么都没有一片漆黑伸手不见五指 ,作为导演的我们是不是应该把光源装上去在把演员请进来呢 (这里光源就代表-环境光 , 演员-就代表创建好的模型) 不然我们这个导演就当的不合格,那还怎么走向人生巅峰啊
sceneInit(){ //初始化场景 并向场景添加光源和辅助坐标系
this.scene = new THREE.Scene(); //初始化场景
var ambient = new THREE.AmbientLight(0x444444, 3); //添加光源 颜色和光照强度
var axisHelper = new THREE.AxesHelper(600); //添加辅助坐标系 参数位辅助坐标系的长度
this.scene.add(ambient, axisHelper); //向场景中添加光源 和 辅助坐标系
}, modelling(){ //开始建立模型
this.mygroup = new THREE.Group(); //建立一个分组
var textureLoader = new THREE.TextureLoader(); //创建纹理贴图
var img = textureLoader.load(require('../../public/img/qjt.jpeg'));
var geometry = new THREE.SphereGeometry(130, 256, 256); // 球体网格模型
var material = new THREE.MeshLambertMaterial({
map: img, //设置颜色贴图属性值
side: THREE.DoubleSide, //双面渲染
});
var meshSphere = new THREE.Mesh(geometry, material); //网格模型对象Mesh
meshSphere.name = '球体容器';
this.mygroup.add(meshSphere);
this.scene.add(this.mygroup);
...
}
  1. 相机-camera 相机顾名思义就是拍摄用的道具 , 相机的视角也就是我们最终画面呈现的视角 ,这里我们使用透视相机因为透视相机的视角更贴近真实人眼看的视角,透视相机具体参数可以看 透视相机中文文档
cameraInit() { //初始化相机
var width = 800; //窗口宽度
var height = 800; //窗口高度
this.camera = new THREE.PerspectiveCamera(90, width / height, 1, 1000); //使用透视相机
this.camera.position.set(0, 0, 10); //设置相机位置
this.camera.lookAt(new THREE.Vector3(0, 0, 0)); // 相机看向
},
  • 开始实现简易的全景图

终于到这里了现在正式开搞 , 先通过上面介绍的基础原理把 渲染器-renderer, 场景-scene,相机-camera , 弄出来 , 然后全景图实现原理是,首先在坐标轴的中心创建一个,带图片纹理的小球 当前这里不一定要用球体 ,其他形状也是可以实现的 , 具体根据使用场景来定义

首先创建一个球体网格模型和对应的纹理贴图

建立球体模型以及使用 TextureLoader 生成纹理贴图 - 纹理贴图中文文档 纹理贴图默认渲染模式为 THREE.FrontSide 前面渲染 , 设置配置的时候需要注意一下

modelling(){ //开始建立模型
this.mygroup = new THREE.Group();
var textureLoader = new THREE.TextureLoader(); //创建纹理贴图
var img = textureLoader.load(require('../../public/img/home3.jpeg'));
var geometry = new THREE.SphereGeometry(130, 256, 256); // 球体网格模型
var material = new THREE.MeshLambertMaterial({
map: img, //设置颜色贴图属性值
side: THREE.DoubleSide, //双面渲染
});
var meshSphere = new THREE.Mesh(geometry, material); //网格模型对象Mesh
meshSphere.name = '球体容器';
this.mygroup.add(meshSphere);
this.scene.add(this.mygroup);
}, },

建立矩形平面自定义文字 three.js中自定义文字的方式大概分为以下几种

形成文字的方式 实现方案 优点 缺点
DOM + CSS 一般的实现方式使用绝对定位和足够大的z-index让组件或者文字在3D图形的上方 实现简单效果强大 3d效果和物体联动性差
THREE.CanvasTexture 在canvas中绘制文字,然后使用CanvasTexture作为纹理进行贴图 文字效果较为丰富 一旦生成,分辨率固定,放大会产生失真
THREE.TextGeometry 使用原生的TextGeometry进行渲染生成 效果好,可与场景进行同步 字体的颜色和动画制作较为复杂,特别耗费资源
3d字体模型 使用3d制作的字体模型,使用threejs进行加载控制 效果好,可定制效果 加载模型耗费资源,字体内容无法自定义
位图字体 通过BmpFont生成文字模板,然后进行加载显示 可自定义字体和效果 加载模型耗费资源,字体内容无法自定义
Three.Sprite精灵材质 Sprite加载图像纹理 永远面向相机的平面,适合作为标签显示 一旦生成,分辨率固定,放大会产生失真

这里我选择的是canvas绘制文字 , 至于为什么,就是因为不用导入图片,并且自定义文字比较方便 

modelling(){ //开始建立模型
this.mygroup = new THREE.Group();
var canvasText = this.getcanvers('进门'); //生成一个canvers 文字图案对象
var texture = new THREE.CanvasTexture(canvasText);
var geometryText = new THREE.PlaneGeometry(16, 10, 60, 60); //生成一个平面模型
var materialText = new THREE.MeshPhongMaterial({
map: texture, // 设置纹理贴图
side: THREE.DoubleSide, //双面渲染
});
var meshText = new THREE.Mesh(geometryText, materialText);
meshText.name = '进门';
meshText.position.set(40, 20, -90)
this.mygroup.add(meshText);
this.scene.add(this.mygroup);
}, getcanvers(text) { //生成一个canvers图案
var canvasText = document.createElement("canvas");
var c = canvasText.getContext('2d');
// 矩形区域填充背景
c.fillStyle = "#FFFFFF"; //canver背景
c.fillRect(0, 0, 300, 200); //生成一个矩形
c.translate(160, 80);
c.fillStyle = "#000000"; //文本填充颜色
c.font = "bold 100px 宋体"; //字体样式设置
c.textBaseline = "middle"; //文本与
c.textAlign = "center"; //文本居中
c.fillText(text, 0, 0);
var texture = new THREE.CanvasTexture(canvasText); //Canvas纹理
var geometryText = new THREE.PlaneGeometry(16, 10, 60, 60); //生成一个矩形平面
var materialText = new THREE.MeshPhongMaterial({
map: texture, // 设置纹理贴图
side: THREE.DoubleSide, //双面渲染
});
var meshText = new THREE.Mesh(geometryText, materialText);
meshText.name = text;
meshText.position.set(40, 20, -90);
return canvasText;
},
},

通过点击矩形平面切换场景

在一般的 HTML 中触发点击事件只需要给对应的dom绑定事件即可 , 但是在three.js 里面就行不通 , 因为three生成的图形页面其实就是一张canvas画布无法直接取到对应的dom , 更不用说了给dom绑定事件了 ,不过好在three.js 提供了一个 new THREE.Raycaster() 光线投射 (用于拾取鼠标的位置以及在三维空间中计算出鼠标移过了什么物体)

射线会记录与之相交几何体,并以数组的形式从近到远返回对应模型的mesh ,只需要向射线中传入鼠标的位置和当前相机即可,这样我们就可以根据模型的名称获取当前点击的那个模型并触发对应的事件

init(){
this.$refs.threeDom.addEventListener('dblclick', this.onMouseDblclick); //监听双击事件
},
onMouseDblclick(event){ //触发双击事件
// 获取 raycaster 和所有模型相交的数组,其中的元素按照距离排序,越近的越靠前
var intersects = this.getIntersects(event);
...
},
getIntersects(event) { // 获取与射线相交的对象数组
event.preventDefault();
// 声明 raycaster 和 mouse 变量
var raycaster = new THREE.Raycaster(); //生成射线
var mouse = new THREE.Vector2();
var container = this.$refs.threeDom;
let getBoundingClientRect = container.getBoundingClientRect();
// 通过鼠标点击位置,计算出 raycaster 所需点的位置 分量,以屏幕为中心点,范围 -1 到 1
mouse.x = ((event.clientX - getBoundingClientRect.left) / container.offsetWidth) * 2 - 1;
mouse.y = -((event.clientY - getBoundingClientRect.top) / container.offsetHeight) * 2 + 1;
//通过鼠标点击的位置(二维坐标)和当前相机的矩阵计算出射线位置
raycaster.setFromCamera(mouse, this.camera);
// 获取与射线相交的对象数组,其中的元素按照距离排序,越近的越靠前
var intersects = raycaster.intersectObjects(this.scene.children[2].children);
//返回选中的对象
return intersects;
},

定义相机的位置

我们需要将透视投影相机放在球体的中心模拟人在在房间里面的位置 ,调整相机位置和相机看向即可

  cameraInit() { //初始化相机
var width = 800; //窗口宽度
var height = 800; //窗口高度
this.camera = new THREE.PerspectiveCamera(90, width / height, 1, 1000); //使用透视相机
this.camera.position.set(0, 0, 10); //设置相机位置
this.camera.lookAt(new THREE.Vector3(0, 0, 0)); // 相机看向
},

初始化控制器

控制器也就是我们最开始引入的 OrbitControls.js 操作三维场景插件 , OrbitControls 的刷新机制是当控制器监听到页面改变时不停的高频率执行重新渲染的操作动态改变页面

    controlInit(){ //初始化控制器
this.controls = new OrbitControls(this.camera, this.$refs.threeDom); // 初始化控制器
this.controls.target.set(0, 0, 0); // 设置控制器的焦点,使控制器围绕这个焦点进行旋转
this.controls.minDistance = 10; // 设置移动的最短距离(默认为零)
this.controls.maxPolarAngle = Math.PI; //绕垂直轨道的距离(范围是0-Math.PI,默认为Math.PI)
this.controls.maxDistance = 30; // 设置移动的最长距离(默认为无穷)
this.controls.enablePan = false; //禁用右键功能
this.controls.addEventListener('change', this.refresh); //监听鼠标、键盘事件 让整个控件可以拖动
},
refresh(){ //刷新页面
this.renderer.render(this.scene, this.camera); //执行渲染操作
this.stats.update(); //更新性能监控的值
},

定义可控制的自动旋转动画

上面几个步骤做完后,全景图功能差不多都实现了 , 但是页面不会自动旋转总感觉少了点意思 ,现在就给这个项目加上自动旋转的功能同时能根据按钮来停止和开启自动旋转 , 实现方案时通过three.js 准备好的 new THREE.KeyframeTrack() 定义关键帧 , new THREE.AnimationClip() 剪辑keyframe对象 , new THREE.AnimationMixer() 动画混合实例

想要详细了解一下动画基本原理的小伙伴可以看下大佬写的这篇文章 Three.js - KeyframeTrack 帧动画

addAnimation(){ //添加并开启动画
this.clock = new THREE.Clock(); // three.js 时钟对象
var times = [0, 3600]; // 创建帧动画序列
var position_x = [0, 360]; //离散属性值
var keyframe = new THREE.KeyframeTrack('meshSphere.rotation[y]', times, position_x);
var duration = 100; //持续时间
var cilp = new THREE.AnimationClip('sphereRotate', duration, [keyframe]); //剪辑 keyframe对象
this.mixer = new THREE.AnimationMixer(this.mygroup); //动画混合实例
this.action = this.mixer.clipAction(cilp);
this.action.timeScale = 1; //播放速度
this.action.setLoop(THREE.LoopPingPong).play(); //开始播放 像乒乓球一样在起始点与结束点之间来回循环
this.animate(); //开启动画
}, animate() { //循环渲染
this.rotateAnimate = requestAnimationFrame(this.animate);
this.renderer.render(this.scene, this.camera);
this.update();
},

全景图完整代码

<template>
<div class="homePage">
<el-card class="card">
<div slot="header">
<div class="card-title">
<span>简易版全景图</span> <div class="card-property">
<span ref='property'></span>
</div> </div>
</div> <div class="card-content">
<div ref='threeDom' class="model"></div>
<div class="control">
<span class="control-title">控制台</span>
<div class="control-block">
<span class="control-block-title">是否自动旋转</span>
<el-radio-group v-model="isRotate" @change="isSpin">
<el-radio :label="1">开启</el-radio>
<el-radio :label="0">关闭</el-radio>
</el-radio-group>
</div>
</div>
</div>
</el-card>
</div>
</template> <script>
import axios from 'axios';
import * as THREE from "three";
import * as TrackballControls from 'three-trackballcontrols'
import * as ThreeStats from 'three-stats'
import { OBJLoader, MTLLoader } from 'three-obj-mtl-loader';
const OrbitControls = require('three-orbit-controls')(THREE); export default {
props: {
msg: String
},
data() {
return {
renderer: '', //渲染器
scene: '', //场景
light: '', //光源
camera: '', //相机
controls: '', //控制器
stats: '', //性能监控器
mygroup: '', //模型组 action: '', //控制动画的值
clock: '', //时钟
mixer: '', //混合实例
rotateAnimate: '', //旋转动画
isRotate: 1, //是否开启旋转 }
}, mounted() {
this.init(); //初始化
}, methods: {
init() {
this.$refs.threeDom.addEventListener('dblclick', this.onMouseDblclick); //监听双击事件
this.rendererInit(); //创建渲染器
this.sceneInit(); //创建场景 包含光源和辅助坐标系
this.cameraInit(); //创建相机
this.controlInit(); //初始化控制器
this.propertyInit(); //性能监控
this.modelling(); //建立模型
}, modelling(){ //开始建立模型
this.mygroup = new THREE.Group();
var textureLoader = new THREE.TextureLoader(); //创建纹理贴图
var img = textureLoader.load(require('../../public/img/home3.jpeg')); var geometry = new THREE.SphereGeometry(130, 256, 256); // 球体网格模型
var material = new THREE.MeshLambertMaterial({
map: img, //设置颜色贴图属性值
side: THREE.DoubleSide, //双面渲染
});
var meshSphere = new THREE.Mesh(geometry, material); //网格模型对象Mesh
meshSphere.name = '球体容器';
this.mygroup.add(meshSphere); var canvasText = this.getcanvers('进门'); //生成一个canvers 文字图案对象
var texture = new THREE.CanvasTexture(canvasText);
var geometryText = new THREE.PlaneGeometry(16, 10, 60, 60);
var materialText = new THREE.MeshPhongMaterial({
map: texture, // 设置纹理贴图
side: THREE.DoubleSide, //双面渲染
});
var meshText = new THREE.Mesh(geometryText, materialText);
meshText.name = '进门';
meshText.position.set(40, 20, -90)
this.mygroup.add(meshText); this.scene.add(this.mygroup);
this.addAnimation(); //添加并开启动画
this.refresh();
}, isSpin(val) { //开启和关闭旋转
if (val == 0) { //关闭控制台
this.action.paused = true;
} else {
this.action.paused = false;
}
}, addAnimation() { //添加并开启动画
this.clock = new THREE.Clock(); // three.js 时钟对象
var times = [0, 3600]; // 创建帧动画序列
var position_x = [0, 360]; //离散属性值
var keyframe = new THREE.KeyframeTrack('meshSphere.rotation[y]', times, position_x);
var duration = 100; //持续时间
var cilp = new THREE.AnimationClip('sphereRotate', duration, [keyframe]); //剪辑 keyframe对象
this.mixer = new THREE.AnimationMixer(this.mygroup); //动画混合实例
this.action = this.mixer.clipAction(cilp);
this.action.timeScale = 1; //播放速度
this.action.setLoop(THREE.LoopPingPong).play(); //开始播放 像乒乓球一样在起始点与结束点之间来回循环
this.animate(); //开启动画
}, animate() { //循环渲染
this.rotateAnimate = requestAnimationFrame(this.animate);
this.renderer.render(this.scene, this.camera);
this.update();
}, update() { //数据更新
this.stats.update();
this.mixer.update(this.clock.getDelta());
}, rendererInit() { //初始化渲染器
var width = 1000; //窗口宽度
var height = 800; //窗口高度
this.renderer = new THREE.WebGLRenderer(); //创建渲染器
this.renderer.setClearColor(0xffffff); //添加背景颜色
this.renderer.setSize(width, height); // 设定渲染器尺寸
this.$refs.threeDom.appendChild(this.renderer.domElement);
}, sceneInit() { //初始化场景 并向场景添加光源和辅助坐标系
this.scene = new THREE.Scene();
var ambient = new THREE.AmbientLight(0x444444, 3); //添加光源 颜色和光照强度
var axisHelper = new THREE.AxesHelper(600); //添加辅助坐标系
this.scene.add(ambient, axisHelper);
}, cameraInit() { //初始化相机
var width = 800; //窗口宽度
var height = 800; //窗口高度
this.camera = new THREE.PerspectiveCamera(90, width / height, 1, 1000); //使用透视相机
this.camera.position.set(0, 0, 10); //设置相机位置
this.camera.lookAt(new THREE.Vector3(0, 0, 0)); // 相机看向
}, controlInit() { //初始化控制器
this.controls = new OrbitControls(this.camera, this.$refs.threeDom); // 初始化控制器
this.controls.target.set(0, 0, 0); // 设置控制器的焦点,使控制器围绕这个焦点进行旋转
this.controls.minDistance = 10; // 设置移动的最短距离(默认为零)
this.controls.maxPolarAngle = Math.PI; //绕垂直轨道的距离(范围是0-Math.PI,默认为Math.PI)
this.controls.maxDistance = 30; // 设置移动的最长距离(默认为无穷)
this.controls.enablePan = false; //禁用右键功能
this.controls.addEventListener('change', this.refresh); //监听鼠标、键盘事件 让整个控件可以拖动
}, propertyInit() { //初始化性能监控
this.stats = new ThreeStats.Stats(); // 创建一个性能监视器
this.stats.dom.style.position = 'absolute';
this.stats.dom.style.top = '-4px';
this.$refs.property.appendChild(this.stats.dom);
this.stats.update();
}, getcanvers(text) { //生成一个canvers图案
var canvasText = document.createElement("canvas");
var c = canvasText.getContext('2d');
// 矩形区域填充背景
c.fillStyle = "#FFFFFF"; //canver背景
c.fillRect(0, 0, 300, 200); //生成一个矩形
c.translate(160, 80);
c.fillStyle = "#000000"; //文本填充颜色
c.font = "bold 100px 宋体"; //字体样式设置
c.textBaseline = "middle"; //文本与
c.textAlign = "center"; //文本居中
c.fillText(text, 0, 0); var texture = new THREE.CanvasTexture(canvasText); //Canvas纹理
var geometryText = new THREE.PlaneGeometry(16, 10, 60, 60); //生成一个矩形平面
var materialText = new THREE.MeshPhongMaterial({
map: texture, // 设置纹理贴图
side: THREE.DoubleSide, //双面渲染
});
var meshText = new THREE.Mesh(geometryText, materialText);
meshText.name = text;
meshText.position.set(40, 20, -90);
return canvasText;
}, refresh(){ //刷新页面
this.renderer.render(this.scene, this.camera); //执行渲染操作
this.stats.update(); //更新性能监控的值
}, onMouseDblclick(event) { //触发双击事件
// 获取 raycaster 和所有模型相交的数组,其中的元素按照距离排序,越近的越靠前
var intersects = this.getIntersects(event);
if (intersects.length != 0) {
for (var item of intersects) {
if (item.object.name != '') { //找到第一个不等于空的模型 就是自定义最近的模型
this.action.paused = true; //停止旋转
this.$confirm('是否切换场景?', '提示', {
confirmButtonText: '切换',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.action.paused = false; //开启旋转
if (item.object.name == '进门') {
this.changeScene('enter'); //改变页面场景
} else if (item.object.name == '返回') {
this.changeScene('backtrack'); //改变页面场景
}
}).catch(() => {
this.action.paused = false; //开启旋转
});
break;
}
}
} else { //这里是未选中状态
}
}, changeScene(type) {
var img = '';
var names = '';
var canvasText = '';
var textureLoader = new THREE.TextureLoader(); //创建纹理贴图
if (type == 'enter') {
img = textureLoader.load(require('../../public/img/home1.jpg')); //vue加载图表需要用 require形式
canvasText = this.getcanvers('返回'); //生成一个canvers 文字图案对象
names = '返回';
} else if (type == 'backtrack') { //返回房间
img = textureLoader.load(require('../../public/img/home3.jpeg')); //vue加载图表需要用 require形式
canvasText = this.getcanvers('进门'); //生成一个canvers 文字图案对象
names = '进门';
} for (var item of this.scene.children[2].children) {
if (item.name == '球体容器') { //切换贴图 进入下一张贴图
var material = new THREE.MeshLambertMaterial({
map: img, //设置颜色贴图属性值
side: THREE.DoubleSide, //双面渲染
});
item.material = material;
} else if (item.name == '进门' || item.name == '返回') {
var texture = new THREE.CanvasTexture(canvasText);
var materialText = new THREE.MeshPhongMaterial({
map: texture, // 设置纹理贴图
side: THREE.DoubleSide, //双面渲染
}); item.name = names; //改名模型的名字
item.material = materialText;
}
} setTimeout(() => { //延迟刷新
this.refresh();
}, 100) }, getIntersects(event) { // 获取与射线相交的对象数组
event.preventDefault();
// 声明 raycaster 和 mouse 变量
var raycaster = new THREE.Raycaster(); //生成射线
var mouse = new THREE.Vector2();
var container = this.$refs.threeDom;
let getBoundingClientRect = container.getBoundingClientRect();
// 通过鼠标点击位置,计算出 raycaster 所需点的位置 分量,以屏幕为中心点,范围 -1 到 1
mouse.x = ((event.clientX - getBoundingClientRect.left) / container.offsetWidth) * 2 - 1;
mouse.y = -((event.clientY - getBoundingClientRect.top) / container.offsetHeight) * 2 + 1;
//通过鼠标点击的位置(二维坐标)和当前相机的矩阵计算出射线位置
raycaster.setFromCamera(mouse, this.camera);
// 获取与射线相交的对象数组,其中的元素按照距离排序,越近的越靠前
var intersects = raycaster.intersectObjects(this.scene.children[2].children);
//返回选中的对象
return intersects;
},
}
}
</script> <style>
.homePage {
position: absolute;
height: 100%;
width: 100%;
font-size: 14px;
color: #303133;
display: flex;
align-items: center;
justify-content: center;
} .card {
width: 1300px;
height: 900px;
} .card-title {
display: flex;
align-items: center;
justify-content: space-between;
} .card-title span {
font-weight: 600;
font-size: 18px;
} .card-property {
position: relative;
width: 70px;
height: 40px;
} .card-content {
display: flex;
flex-direction: row;
} .model {
border: 1px solid #DCDFE6;
} .control {
display: flex;
flex-direction: column;
width: 300px;
height: 800px;
border: 1px solid #DCDFE6;
border-left: none;
} .control-title {
font-size: 18px;
font-weight: 600;
text-align: center;
color: #409EFF;
padding: 10px;
border-bottom: 1px solid #DCDFE6;
} .control-block {
padding: 10px;
border-bottom: 1px solid #DCDFE6;
} .control-block-title {
display: block;
margin-bottom: 5px;
} /* 自定义element样式 */
.el-card__header {
padding: 10px 20px;
}
</style>

构建这个全景图时遇到的问题

  1. vue中直接放入图片失败

    因为我们使用的是node.js启动的前端服务所以引入本地图片需要使用 require('../../public/img/home3.jpeg') 形式进行引入 ,直接使用地址图片是不会显示的

  2. vue 打包后图片不显示

使用 require('') 打包后node把文件协议改为 file:// 形式的协议用于访问本地打包后的图片 , 然而 textureLoader.load(); 只接收 http:// 形式的文件所以打包后图片无法显示 , 这里只需要把自己的图片放在tomcat服务器上 , 在取自己tomcat服务器上的图片就可以了

  1. vue构建的项目放在谷歌32位49版本的浏览器中无法打开的问题

    为什么我老是会提起谷歌低版本毕竟现在一般人都不会使用低版本了 , 但我这边的客户还是有一小部分群体在使用 XP系统 我们虽然不能给他做到兼容 IE , 但至少谷歌低版本要给别人弄好 !

具体是什么原因我也不是特别清楚 , 大概就是低版本浏览器获取值的方式和高版本不怎么一样,解决方案就是在 node_modules\three\build 找到 three.module.js

注释掉 this.setSession 这个获取方法

还没完还得找到 node_modules\three-trackballcontrols 中的 index.js 文件并注释掉 const getMouseOnScreen 以及 const getMouseOnCircle 这两个方法

至于为什么要注释这几个方法我也没有特地去研究 ,对于低版本我的理念就是能解决问题就OK了! 如果有知道具体解决方案的大佬可以在留言里面告诉我一下, 阿里嘎多

本文转载于:

https://juejin.cn/post/6927193628724953096

如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。

记录--vue+three.js 构建 简易全景图的更多相关文章

  1. 关于node.js和npm,cnpm的安装记录以及gulp自动构建工具的使用

    关于node.js和npm,cnpm的安装记录以及gulp自动构建工具的使用   工作环境:window下 在一切的最开始,安装node.js (中文站,更新比较慢http://nodejs.cn/) ...

  2. 使用webpack+vue.js构建前端工程化

    参考文章:https://blog.csdn.net/qq_40208605/article/details/80661572 使用webpack+vue.js构建前端工程化本篇主要介绍三块知识点: ...

  3. 使用 Vuex + Vue.js 构建单页应用

    鉴于该篇文章阅读量大,回复的同学也挺多的,特地抽空写了一篇 vue2.0 下的 vuex 使用方法,传送门:使用 Vuex + Vue.js 构建单页应用[新篇] ------------------ ...

  4. 使用 Vuex + Vue.js 构建单页应用【新篇】

    使用 Vuex + Vue.js 构建单页应用[新篇] 在去年的七月六号的时候,发布了一篇 使用 Vuex + Vue.js 构建单页应用 的文章,文章主要是介绍 vuex 的基本使用方法,发现对大部 ...

  5. [Vue 牛刀小试]:第十七章 - 优化 Vue CLI 3 构建的前端项目模板(1)- 基础项目模板介绍

    一.前言 在上一章中,我们开始通过 Vue CLI 去搭建属于自己的前端 Vue 项目模板,就像我们 .NET 程序员在使用 asp.net core 时一样,我们更多的会在框架基础上按照自己的开发习 ...

  6. vue cli3 vue.config.js 配置详情

    module.exports = {   // 基本路径   baseUrl: process.env.NODE_ENV === 'production'     ? '/'     : '/',   ...

  7. node.js 开发简易的小爬虫

    node.js  开发简易的小爬虫 最近公司开发一款医药类的软件,所以需要一些药品的基础数据,所以本人就用node.js写一个简易的小爬虫,并写记录这个Demo以供大家参考. 一.开发前的准备: 1, ...

  8. 记录vue中一些有意思的坑

    记录vue中一些有意思的坑 'message' handler took 401ms 在出现这个之前,我一直纠结于 是如何使用vue-router或者不使用它,通过类似的v-if来实现.结果却出现这个 ...

  9. springboot2.0+Neo4j+d3.js构建知识图谱

    Welcome to the Neo4j wiki! 初衷这是一个知识图谱构建工具,最开始是对产品和领导为了做ppt临时要求配合做图谱展示的不厌其烦,做着做着就抽出一个目前看着还算通用的小工具 技术栈 ...

  10. Python 和 Elasticsearch 构建简易搜索

    Python 和 Elasticsearch 构建简易搜索 作者:白宁超 2019年5月24日17:22:41 导读:件开发最大的麻烦事之一就是环境配置,操作系统设置,各种库和组件的安装.只有它们都正 ...

随机推荐

  1. P3078题解

    P3078题解 看到题解区,我有点震惊,什么贪心.线段树.各种优化都有,在此%%%.但其实这道题一个小小的差分就可解决. 前置芝士:前缀和/差分 by OI Wiki 题意简述 在一个 $card$ ...

  2. idea 报错: Unable to import maven project: See logs for details

    错误再现: idea 工具日志: 1) No implementation for org.apache.maven.model.path.PathTranslator was bound. whil ...

  3. Reactive 简介

    1. 概念 Reactive 非常适合低延迟.高吞吐量的工作负载. Reactive Processing 是一种范式(规范),它使开发人员能够构建非阻塞的.异步的应用程序,这些应用程序能够处理背压( ...

  4. DS12C887时钟模块, STC89和STC12的代码实现

    DS12C887是时钟芯片DS12C885集成了电池和晶振的版本. 如果拆掉DS12C887的外壳, 能看到里面就是DS12C885. 功能特性 能输出世纪.年.月.日.时.分.秒等时间信息 集成电池 ...

  5. 使用@ControllerAdvice统一处理自定义异常

    最近工作中涉及到捕捉AOP方法中抛出的异常. 想针对某一种异常做一个统一的处理器并封装好异常信息以JSON格式交给前端进行提示. 主要实现的话有以下几步: 1.编写自定义异常类 package com ...

  6. 在python中发送自定义消息

    .py import win32api, win32con, win32gui import win32gui_struct import ctypes from ctypes import * GU ...

  7. 2021 虎符杯hate num 注入题

    前言 今天遇到个有意思的SQL盲注,花了不少功夫,也学到了新姿势,遂记录下来以备后续碰到相同场景使用. 题目 这是2021 虎符杯的一道web题,有一个目标站点且附带了源码. 源码内容包括: 主要逻辑 ...

  8. springboot多线程TaskExecutor的使用,以及使用@Async实现异步调用

    目录 @Async实现异步调用 pom.xml 启动类 定义controller 定义接口 实现类 将isDone换程CountDownLatch来判断线程是否执行完实例化CountDownLatch ...

  9. 【Azure Logic App】添加 Storage Account 来提升 Logic App 的性能

    文章原文:https://techcommunity.microsoft.com/t5/azure-integration-services-blog/scaling-logic-app-standa ...

  10. 动态代码框架发布-CZGL.Roslyn

    CZGL.Roslyn 开源项目位置:https://github.com/whuanle/CZGL.CodeAnalysis 基于 Roslyn 技术的 C# 动态代码构建器以及编译器,开发者可以使 ...