基于vue3+threejs實現(xiàn)可視化大屏效果
前言
Three.js是一款基于原生WebGL封裝通用Web 3D引擎,在小游戲、產(chǎn)品展示、物聯(lián)網(wǎng)、數(shù)字孿生、智慧城市園區(qū)、機(jī)械、建筑、全景看房、GIS等各個領(lǐng)域基本上都有three.js的身影。
本文需要對 threejs 的一些基本概念和 api 有一定了解。
本文主要主要講述對 threejs 的一些 api 進(jìn)行基本的封裝,在 vue3 項目中來實現(xiàn)一個可視化的3d項目。包含了一些常用的功能,場景、燈光、攝像機(jī)初始化,模型、天空盒的加載,以及鼠標(biāo)點擊和懸浮的事件交互。
項目截圖:

Github 地址:GitHub - fh332393900/threejs-demo
項目預(yù)覽地址:Three Demo (stevenfeng.cn)
基礎(chǔ)功能
1.場景 Viewer 類
首先我們第一步需要初始化場景、攝像機(jī)、渲染器、燈光等。這些功能只需要加載一次,我們都放到 Viewer 類中可以分離關(guān)注點,在業(yè)務(wù)代碼中就不需要關(guān)注這一部分邏輯。業(yè)務(wù)代碼中我們只需要關(guān)注數(shù)據(jù)與交互即可。
1.1 初始化場景和攝像機(jī)
private initScene() {
this.scene = new Scene();
}
private initCamera() {
// 渲染相機(jī)
this.camera = new PerspectiveCamera(25, window.innerWidth / window.innerHeight, 1, 2000);
//設(shè)置相機(jī)位置
this.camera.position.set(4, 2, -3);
//設(shè)置相機(jī)方向
this.camera.lookAt(0, 0, 0);
}1.2 初始化攝像機(jī)控制器
private initControl() {
this.controls = new OrbitControls(
this.camera as Camera,
this.renderer?.domElement
);
this.controls.enableDamping = false;
this.controls.screenSpacePanning = false; // 定義平移時如何平移相機(jī)的位置 控制不上下移動
this.controls.minDistance = 2;
this.controls.maxDistance = 1000;
this.controls.addEventListener('change', ()=>{
this.renderer.render(this.scene, this.camera);
});
}1.3 初始化燈光
這里放了一個環(huán)境燈光和平行燈光,這里是寫在 Viewer 類里面的,如果想靈活一點,也可以抽出去。
private initLight() {
const ambient = new AmbientLight(0xffffff, 0.6);
this.scene.add(ambient);
const light = new THREE.DirectionalLight( 0xffffff );
light.position.set( 0, 200, 100 );
light.castShadow = true;
light.shadow.camera.top = 180;
light.shadow.camera.bottom = -100;
light.shadow.camera.left = -120;
light.shadow.camera.right = 400;
light.shadow.camera.near = 0.1;
light.shadow.camera.far = 400;
// 設(shè)置mapSize屬性可以使陰影更清晰,不那么模糊
light.shadow.mapSize.set(1024, 1024);
this.scene.add(light);
}1.4 初始化渲染器
private initRenderer() {
// 獲取畫布dom
this.viewerDom = document.getElementById(this.id) as HTMLElement;
// 初始化渲染器
this.renderer = new WebGLRenderer({
logarithmicDepthBuffer: true,
antialias: true, // true/false表示是否開啟反鋸齒
alpha: true, // true/false 表示是否可以設(shè)置背景色透明
precision: 'mediump', // highp/mediump/lowp 表示著色精度選擇
premultipliedAlpha: true, // true/false 表示是否可以設(shè)置像素深度(用來度量圖像的分辨率)
// preserveDrawingBuffer: false, // true/false 表示是否保存繪圖緩沖
// physicallyCorrectLights: true, // true/false 表示是否開啟物理光照
});
this.renderer.clearDepth();
this.renderer.shadowMap.enabled = true;
this.renderer.outputColorSpace = SRGBColorSpace; // 可以看到更亮的材質(zhì),同時這也影響到環(huán)境貼圖。
this.viewerDom.appendChild(this.renderer.domElement);
}Viewer 里面還加了一些 addAxis 添加坐標(biāo)軸、addStats 性能監(jiān)控等輔助的公用方法。具體可以看倉庫完整代碼。
1.5 鼠標(biāo)事件
里面主要使用了 mitt 這個庫,來發(fā)布訂閱事件。
threejs里面的鼠標(biāo)事件主要通過把屏幕坐標(biāo)轉(zhuǎn)換成 3D 坐標(biāo)。通過raycaster.intersectObjects方法轉(zhuǎn)換。
/**注冊鼠標(biāo)事件監(jiān)聽 */
public initRaycaster() {
this.raycaster = new Raycaster();
const initRaycasterEvent: Function = (eventName: keyof HTMLElementEventMap): void => {
const funWrap = throttle(
(event: any) => {
this.mouseEvent = event;
this.mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
this.mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;
// @ts-expect-error
this.emitter.emit(Events[eventName].raycaster, this.getRaycasterIntersectObjects());
},
50
);
this.viewerDom.addEventListener(eventName, funWrap, false);
};
// 初始化常用的幾種鼠標(biāo)事件
initRaycasterEvent('click');
initRaycasterEvent('dblclick');
initRaycasterEvent('mousemove');
}
/**自定義鼠標(biāo)事件觸發(fā)的范圍,給定一個模型組,對給定的模型組鼠標(biāo)事件才生效 */
public setRaycasterObjects (objList: THREE.Object3D[]): void {
this.raycasterObjects = objList;
}
private getRaycasterIntersectObjects(): THREE.Intersection[] {
if (!this.raycasterObjects.length) return [];
this.raycaster.setFromCamera(this.mouse, this.camera);
return this.raycaster.intersectObjects(this.raycasterObjects, true);
}通過 setRaycasterObjects 方法,傳遞一個觸發(fā)鼠標(biāo)事件的模型范圍,可以避免在整個場景中都去觸發(fā)鼠標(biāo)事件。這里也可以用一個 Map 去存不同模型的事件,在取消訂閱時再移除。
使用方式:
let viewer: Viewer;
viewer = new Viewer('three');
viewer.initRaycaster();
viewer.emitter.on(Event.dblclick.raycaster, (list: THREE.Intersection[]) => {
onMouseClick(list);
});
viewer.emitter.on(Event.mousemove.raycaster, (list: THREE.Intersection[]) => {
onMouseMove(list);
});2.模型加載器 ModelLoder 類
模型的加載我們需要用的threejs里面的,GLTFLoader、DRACOLoader 這兩個類。
模型加載器 ModelLoder 初始化的時候需要把 Viewer 的實例傳進(jìn)去。
需要注意的是,需要把 draco 從 node_modules 拷貝到項目的 public 目錄中去。

實現(xiàn)代碼:
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
import BaseModel from '../BaseModel';
import type Viewer from '../Viewer';
type LoadModelCallbackFn<T = any> = (arg: T) => any;
/**模型加載器 */
export default class ModelLoder {
protected viewer: Viewer;
private gltfLoader: GLTFLoader;
private dracoLoader: DRACOLoader;
constructor(viewer: Viewer, dracolPath: string = '/draco/') {
this.viewer = viewer;
this.gltfLoader = new GLTFLoader();
this.dracoLoader = new DRACOLoader();
// 提供一個DracLoader實例來解碼壓縮網(wǎng)格數(shù)據(jù)
// 沒有這個會報錯 dracolPath 默認(rèn)放在public文件夾當(dāng)中
this.dracoLoader.setDecoderPath(dracolPath);
this.gltfLoader.setDRACOLoader(this.dracoLoader);
}
/**模型加載到場景 */
public loadModelToScene(url: string, callback: LoadModelCallbackFn<BaseModel>) {
this.loadModel(url, model => {
this.viewer.scene.add(model.object);
callback && callback(model);
});
}
private loadModel(url: string, callback: LoadModelCallbackFn<BaseModel>) {
this.gltfLoader.load(url, gltf => {
const baseModel = new BaseModel(gltf, this.viewer);
callback && callback(baseModel);
});
}
}3.模型 BaseModel 類
這里對模型外面包了一層,做了一些額外的功能,如模型克隆、播放動畫、設(shè)置模型特性、顏色、材質(zhì)等方法。
/**
* 設(shè)置模型動畫
* @param i 選擇模型動畫進(jìn)行播放
*/
public startAnima(i = 0) {
this.animaIndex = i;
if (!this.mixer) this.mixer = new THREE.AnimationMixer(this.object);
if (this.gltf.animations.length < 1) return;
this.mixer.clipAction(this.gltf.animations[i]).play();
// 傳入?yún)?shù)需要將函數(shù)與函數(shù)參數(shù)分開,在運(yùn)行時填入
this.animaObject = {
fun: this.updateAnima,
content: this,
};
this.viewer.addAnimate(this.animaObject);
}
private updateAnima(e: any) {
e.mixer.update(e.clock.getDelta());
}還有一些其他方法的實現(xiàn),可以看倉庫代碼。
4.天空盒 SkyBoxs 類
import * as THREE from 'three';
import type Viewer from '../Viewer';
import { Sky } from '../type';
/** 場景天空盒*/
export default class SkyBoxs {
protected viewer: Viewer;
constructor (viewer: Viewer) {
this.viewer = viewer;
}
/**
* 添加霧效果
* @param color 顏色
*/
public addFog (color = 0xa0a0a0, near = 500, far = 2000) {
this.viewer.scene.fog = new THREE.Fog(new THREE.Color(color), near, far);
}
/**
* 移除霧效果
*/
public removeFog () {
this.viewer.scene.fog = null;
}
/**
* 添加默認(rèn)天空盒
* @param skyType
*/
public addSkybox (skyType: keyof typeof Sky = Sky.daytime) {
const path = `/skybox/${Sky[skyType]}/`; // 設(shè)置路徑
const format = '.jpg'; // 設(shè)定格式
this.setSkybox(path, format);
}
/**
* 自定義添加天空盒
* @param path 天空盒地址
* @param format 圖片后綴名
*/
private setSkybox (path: string, format = '.jpg') {
const loaderbox = new THREE.CubeTextureLoader();
const cubeTexture = loaderbox.load([
path + 'posx' + format,
path + 'negx' + format,
path + 'posy' + format,
path + 'negy' + format,
path + 'posz' + format,
path + 'negz' + format,
]);
// 需要把色彩空間編碼改一下
cubeTexture.encoding = THREE.sRGBEncoding;
this.viewer.scene.background = cubeTexture;
}
}5.模型輪廓輔助線
通過 BoxHelper 可以實現(xiàn)簡單的鼠標(biāo)選中的特效。
也可以通過 OutlinePass 實現(xiàn)發(fā)光的特效。
import {
BoxHelper,
Color,
Object3D
} from 'three';
import type Viewer from '../Viewer';
export default class BoxHelperWrap {
protected viewer: Viewer;
public boxHelper: BoxHelper;
constructor (viewer: Viewer, color?: number) {
this.viewer = viewer;
const boxColor = color === undefined ? 0x00ffff : color;
this.boxHelper = new BoxHelper(new Object3D(), new Color(boxColor));
// // @ts-expect-error
// this.boxHelper.material.depthTest = false;
this.initBoxHelperWrap();
}
private initBoxHelperWrap () {
this.viewer.scene.add(this.boxHelper);
}
public setVisible (visible: boolean): void {
this.boxHelper.visible = visible;
}
public attach (obj: Object3D): void {
this.boxHelper.setFromObject(obj);
this.setVisible(true);
}
public dispose (): void {
const parent = this.boxHelper.parent;
if (parent !== null) {
parent.remove(this.boxHelper);
}
Object.keys(this).forEach(key => {
// @ts-expect-error
this[key] = null;
});
}
}使用方式:
let modelLoader = new ModelLoader(viewer); boxHelperWrap = new BoxHelperWrap(viewer); boxHelperWrap.setVisible(false);
以上就是基于vue3+threejs實現(xiàn)可視化大屏效果的詳細(xì)內(nèi)容,更多關(guān)于vue3+threejs 可視化的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
使用Vue開發(fā)自己的Chrome擴(kuò)展程序過程詳解
這篇文章主要介紹了使用Vue開發(fā)自己的Chrome擴(kuò)展程序過程詳解,瀏覽器擴(kuò)展程序是可以修改和增強(qiáng) Web 瀏覽器功能的小程序。它們可用于各種任務(wù),例如阻止廣告,管理密碼,組織標(biāo)簽,改變網(wǎng)頁的外觀和行為等等。,需要的朋友可以參考下2019-06-06
Vue結(jié)合Video.js播放m3u8視頻流的方法示例
本篇文章主要介紹了Vue+Video.js播放m3u8視頻流的方法示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-05-05

