主頁 > 企業開發 > 記錄--vue+three.js 構建 簡易全景圖

記錄--vue+three.js 構建 簡易全景圖

2022-08-30 08:42:05 企業開發

這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助

最近幾天在學習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); //監聽雙擊事件
   },
 onm ouseDblclick(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 >
		<el-card >
			<div slot="header">
				<div >
					<span>簡易版全景圖</span>

					<div >
						<span ref='property'></span>
					</div>

				</div>
			</div>

			<div >
				<div ref='threeDom' ></div>
				<div >
					<span >控制臺</span>
					<div >
						<span >是否自動旋轉</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

如果對您有所幫助,歡迎您點個關注,我會定時更新技術檔案,大家一起討論學習,一起進步,

 

轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/503135.html

標籤:其他

上一篇:uniapp實作IM即時通訊仿微信聊天功能

下一篇:大家都能看得懂的原始碼 - 那些關于DOM的常見Hook封裝(一)

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • IEEE1588PTP在數字化變電站時鐘同步方面的應用

    IEEE1588ptp在數字化變電站時鐘同步方面的應用 京準電子科技官微——ahjzsz 一、電力系統時間同步基本概況 隨著對IEC 61850標準研究的不斷深入,國內外學者提出基于IEC61850通信標準體系建設數字化變電站的發展思路。數字化變電站與常規變電站的顯著區別在于程序層傳統的電流/電壓互 ......

    uj5u.com 2020-09-10 03:51:52 more
  • HTTP request smuggling CL.TE

    CL.TE 簡介 前端通過Content-Length處理請求,通過反向代理或者負載均衡將請求轉發到后端,后端Transfer-Encoding優先級較高,以TE處理請求造成安全問題。 檢測 發送如下資料包 POST / HTTP/1.1 Host: ac391f7e1e9af821806e890 ......

    uj5u.com 2020-09-10 03:52:11 more
  • 網路滲透資料大全單——漏洞庫篇

    網路滲透資料大全單——漏洞庫篇漏洞庫 NVD ——美國國家漏洞庫 →http://nvd.nist.gov/。 CERT ——美國國家應急回應中心 →https://www.us-cert.gov/ OSVDB ——開源漏洞庫 →http://osvdb.org Bugtraq ——賽門鐵克 →ht ......

    uj5u.com 2020-09-10 03:52:15 more
  • 京準講述NTP時鐘服務器應用及原理

    京準講述NTP時鐘服務器應用及原理京準講述NTP時鐘服務器應用及原理 安徽京準電子科技官微——ahjzsz 北斗授時原理 授時是指接識訓通過某種方式獲得本地時間與北斗標準時間的鐘差,然后調整本地時鐘使時差控制在一定的精度范圍內。 衛星導航系統通常由三部分組成:導航授時衛星、地面檢測校正維護系統和用戶 ......

    uj5u.com 2020-09-10 03:52:25 more
  • 利用北斗衛星系統設計NTP網路時間服務器

    利用北斗衛星系統設計NTP網路時間服務器 利用北斗衛星系統設計NTP網路時間服務器 安徽京準電子科技官微——ahjzsz 概述 NTP網路時間服務器是一款支持NTP和SNTP網路時間同步協議,高精度、大容量、高品質的高科技時鐘產品。 NTP網路時間服務器設備采用冗余架構設計,高精度時鐘直接來源于北斗 ......

    uj5u.com 2020-09-10 03:52:35 more
  • 詳細解讀電力系統各種對時方式

    詳細解讀電力系統各種對時方式 詳細解讀電力系統各種對時方式 安徽京準電子科技官微——ahjzsz,更多資料請添加VX 衛星同步時鐘是我京準公司開發研制的應用衛星授時時技術的標準時間顯示和發送的裝置,該裝置以M國全球定位系統(GLOBAL POSITIONING SYSTEM,縮寫為GPS)或者我國北 ......

    uj5u.com 2020-09-10 03:52:45 more
  • 如何保證外包團隊接入企業內網安全

    不管企業規模的大小,只要企業想省錢,那么企業的某些服務就一定會采用外包的形式,然而看似美好又經濟的策略,其實也有不好的一面。下面我通過安全的角度來聊聊使用外包團的安全隱患問題。 先看看什么服務會使用外包的,最常見的就是話務/客服這種需要大量重復性、無技術性的服務,或者是一些銷售外包、特殊的職能外包等 ......

    uj5u.com 2020-09-10 03:52:57 more
  • PHP漏洞之【整型數字型SQL注入】

    0x01 什么是SQL注入 SQL是一種注入攻擊,通過前端帶入后端資料庫進行惡意的SQL陳述句查詢。 0x02 SQL整型注入原理 SQL注入一般發生在動態網站URL地址里,當然也會發生在其它地發,如登錄框等等也會存在注入,只要是和資料庫打交道的地方都有可能存在。 如這里http://192.168. ......

    uj5u.com 2020-09-10 03:55:40 more
  • [GXYCTF2019]禁止套娃

    git泄露獲取原始碼 使用GET傳參,引數為exp 經過三層過濾執行 第一層過濾偽協議,第二層過濾帶引數的函式,第三層過濾一些函式 preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'] (?R)參考當前正則運算式,相當于匹配函式里的引數 因此傳遞 ......

    uj5u.com 2020-09-10 03:56:07 more
  • 等保2.0實施流程

    流程 結論 ......

    uj5u.com 2020-09-10 03:56:16 more
最新发布
  • 使用Django Rest framework搭建Blog

    在前面的Blog例子中我們使用的是GraphQL, 雖然GraphQL的使用處于上升趨勢,但是Rest API還是使用的更廣泛一些. 所以還是決定回到傳統的rest api framework上來, Django rest framework的官網上給了一個很好用的QuickStart, 我參考Qu ......

    uj5u.com 2023-04-20 08:17:54 more
  • 記錄-new Date() 我忍你很久了!

    這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 大家平時在開發的時候有沒被new Date()折磨過?就是它的諸多怪異的設定讓你每每用的時候,都可能不小心踩坑。造成程式意外出錯,卻一下子找不到問題出處,那叫一個煩透了…… 下面,我就列舉它的“四宗罪”及應用思考 可惡的四宗罪 1. Sa ......

    uj5u.com 2023-04-20 08:17:47 more
  • 使用Vue.js實作文字跑馬燈效果

    實作文字跑馬燈效果,首先用到 substring()截取 和 setInterval計時器 clearInterval()清除計時器 效果如下: 實作代碼如下: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta ......

    uj5u.com 2023-04-20 08:12:31 more
  • JavaScript 運算子

    JavaScript 運算子/運算子 在 JavaScript 中,有一些運算子可以使代碼更簡潔、易讀和高效。以下是一些常見的運算子: 1、可選鏈運算子(optional chaining operator) ?.是可選鏈運算子(optional chaining operator)。?. 可選鏈操 ......

    uj5u.com 2023-04-20 08:02:25 more
  • CSS—相對單位rem

    一、概述 rem是一個相對長度單位,它的單位長度取決于根標簽html的字體尺寸。rem即root em的意思,中文翻譯為根em。瀏覽器的文本尺寸一般默認為16px,即默認情況下: 1rem = 16px rem布局原理:根據CSS媒體查詢功能,更改根標簽的字體尺寸,實作rem單位隨螢屏尺寸的變化,如 ......

    uj5u.com 2023-04-20 08:02:21 more
  • 我的第一個NPM包:panghu-planebattle-esm(胖虎飛機大戰)使用說明

    好家伙,我的包終于開發完啦 歡迎使用胖虎的飛機大戰包!! 為你的主頁添加色彩 這是一個有趣的網頁小游戲包,使用canvas和js開發 使用ES6模塊化開發 效果圖如下: (覺得圖片太sb的可以自己改) 代碼已開源!! Git: https://gitee.com/tang-and-han-dynas ......

    uj5u.com 2023-04-20 08:01:50 more
  • 如何在 vue3 中使用 jsx/tsx?

    我們都知道,通常情況下我們使用 vue 大多都是用的 SFC(Signle File Component)單檔案組件模式,即一個組件就是一個檔案,但其實 Vue 也是支持使用 JSX 來撰寫組件的。這里不討論 SFC 和 JSX 的好壞,這個仁者見仁智者見智。本篇文章旨在帶領大家快速了解和使用 Vu ......

    uj5u.com 2023-04-20 08:01:37 more
  • 【Vue2.x原始碼系列06】計算屬性computed原理

    本章目標:計算屬性是如何實作的?計算屬性快取原理以及洋蔥模型的應用?在初始化Vue實體時,我們會給每個計算屬性都創建一個對應watcher,我們稱之為計算屬性watcher ......

    uj5u.com 2023-04-20 08:01:31 more
  • http1.1與http2.0

    一、http是什么 通俗來講,http就是計算機通過網路進行通信的規則,是一個基于請求與回應,無狀態的,應用層協議。常用于TCP/IP協議傳輸資料。目前任何終端之間任何一種通信方式都必須按Http協議進行,否則無法連接。tcp(三次握手,四次揮手)。 請求與回應:客戶端請求、服務端回應資料。 無狀態 ......

    uj5u.com 2023-04-20 08:01:10 more
  • http1.1與http2.0

    一、http是什么 通俗來講,http就是計算機通過網路進行通信的規則,是一個基于請求與回應,無狀態的,應用層協議。常用于TCP/IP協議傳輸資料。目前任何終端之間任何一種通信方式都必須按Http協議進行,否則無法連接。tcp(三次握手,四次揮手)。 請求與回應:客戶端請求、服務端回應資料。 無狀態 ......

    uj5u.com 2023-04-20 08:00:32 more