vue中使用threejs
vue中使用threejs
参考网站 : http://www.webgl3d.cn/Three.js/
自建网站 three官网 (搭建参考:搭建本地tree官网)
官网源码-用于自建官网:https://gitcode.net/mirrors/mrdoob/three.js
开发参考思路,组件化开发 https://blog.csdn.net/weixin_43931376/article/details/125180625
模型添加文本-demo --待整理
1.安装
可能需要的插件
"three": "^0.146.0", "@tweenjs/tween.js": "^18.6.4", "dat.gui": "^0.7.9",
npm install three --save
2. 引入
需要引入模块儿不知道路径可以在node_modules去找到three -> examples -> jsm中去寻找。js目录下放的是非模块化的js文件,直接用script src去引入的。jsm目录下放的是模块化的文件。按需去引入即可
import * as THREE from 'three'
// 下面更具需要 按需导入
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
import { CSS2DRenderer, CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer.js'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js'
import { KTX2Loader } from 'three/examples/jsm/loaders/KTX2Loader.js'
import { MeshoptDecoder } from 'three/examples/jsm/libs/meshopt_decoder.module.js'
import Stats from 'three/examples/jsm/libs/stats.module.js'
import TWEEN from '@tweenjs/tween.js'
import { GUI } from 'dat.gui'
属性说明
1.
9. 相机对象
正投影相机OrthographicCamera和透视投影相机PerspectiveCamera

9.1 正投影相机对象OrthographicCamera
/**
* 正投影相机设置
*/
var width = window.innerWidth; //窗口宽度
var height = window.innerHeight; //窗口高度
var k = width / height; //窗口宽高比
var s = 150; //三维场景显示范围控制系数,系数越大,显示的范围越大
//创建相机对象
var camera = new THREE.OrthographicCamera(-s * k, s * k, s, -s, 1, 1000);
camera.position.set(200, 300, 200); //设置相机位置
camera.lookAt(scene.position); //设置相机方向(指向的场景对象)
// 构造函数格式
OrthographicCamera( left, right, top, bottom, near, far )
| 参数(属性) | 含义 |
|---|---|
| left | 渲染空间的左边界 |
| right | 渲染空间的右边界 |
| top | 渲染空间的上边界 |
| bottom | 渲染空间的下边界 |
| near | near属性表示的是从距离相机多远的位置开始渲染,一般情况会设置一个很小的值。 默认值0.1 |
| far | far属性表示的是距离相机多远的位置截止渲染,如果设置的值偏小小,会有部分场景看不到。 默认值1000 |

9.2 透视投影相机PerspectiveCamera
/**
* 透视投影相机设置
*/
var width = window.innerWidth; //窗口宽度
var height = window.innerHeight; //窗口高度
/**透视投影相机对象*/
var camera = new THREE.PerspectiveCamera(60, width / height, 1, 1000);
camera.position.set(200, 300, 200); //设置相机位置
camera.lookAt(scene.position); //设置相机方向(指向的场景对象)
构造函数PerspectiveCamera格式
PerspectiveCamera( fov, aspect, near, far )
| 参数 | 含义 | 默认值 |
|---|---|---|
| fov | fov表示视场,所谓视场就是能够看到的角度范围,人的眼睛大约能够看到180度的视场,视角大小设置要根据具体应用,一般游戏会设置60~90度 | 45 |
| aspect | aspect表示渲染窗口的长宽比,如果一个网页上只有一个全屏的canvas画布且画布上只有一个窗口,那么aspect的值就是网页窗口客户区的宽高比 | window.innerWidth/window.innerHeight |
| near | near属性表示的是从距离相机多远的位置开始渲染,一般情况会设置一个很小的值。 | 0.1 |
| far | far属性表示的是距离相机多远的位置截止渲染,如果设置的值偏小,会有部分场景看不到 | 1000 |

9.3 相机位置.posiiotn和.lookAt(相机拍摄目标位置)

相机Camera的基类是Object3D,相机对象Camera具有位置属性.posiiotn,通过位置属性.posiiotn可以设置相机的位置。
Three.js相关的开源库。
| 库 | 功能 |
|---|---|
| Physijs | Physijs是一款物理引擎,可以协助基于原生WebGL或使用three.js创建模拟物理现象,比如重力下落、物体碰撞等物理现 |
| stats.js | JavaScript性能监控器,同样也可以测试webgl的渲染性能 |
| dat.gui | 轻量级的icon形用户界面框架,可以用来控制Javascript的变量,比如WebGL中一个物体的尺寸、颜色 |
| TWEEN.js | 借助TWEEN.js快速创建补间动画,可以非常方便的控制机械、游戏角色运动 |
| ThreeBSP | 可以作为three.js的插件,完成几何模型的布尔,各类三维建模软件基本都有布尔的概念 |
1. tween.js
https://github.com/tweenjs/tween.js/
https://blog.csdn.net/weixin_43081805/article/details/89399031
tween.js允许以平滑的方式修改元素的属性值。只需要告诉tween想修改什么值,以及动画结束时它的最终值是什么,动画花费多少时间等信息,tween引擎就可以计算从开始动画点到结束动画点之间值,来产生平滑的动画效果
其他记录
const pointLabel = new CSS2DObject(label)
scene.remove(pointLabel) ;// 会将当前的dom元素删除
案例
工大大屏
webgl_loader_pdb

CSS2DObject标签绑定点击事件
const el = document.createElement('div')
el.textContent = '123'
el.style.pointerEvents = 'auto'
el.addEventListener('click', function (event) { //如果 click 不行 用 pointerdown
console.log(event)
})
new CSS2DObject(el)
// 给 el 加上 el.style.pointerEvents = 'auto'
// 版本 0.125.2
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { CSS2DRenderer, CSS2DObject } from 'three/addons/renderers/CSS2DRenderer.js';
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
let gui;
let camera, scene, renderer, labelRenderer;
const layers = {
'Toggle Name': function () {
camera.layers.toggle( 0 );
},
'Toggle Mass': function () {
camera.layers.toggle( 1 );
},
'Enable All': function () {
camera.layers.enableAll();
},
'Disable All': function () {
camera.layers.disableAll();
}
};
const clock = new THREE.Clock();
const textureLoader = new THREE.TextureLoader();
let moon;
init();
animate();
function init() {
const EARTH_RADIUS = 1;
const MOON_RADIUS = 0.27;
camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.1, 200 );
camera.position.set( 10, 5, 20 );
camera.layers.enableAll();
camera.layers.toggle( 1 );
scene = new THREE.Scene();
const dirLight = new THREE.DirectionalLight( 0xffffff );
dirLight.position.set( 0, 0, 1 );
dirLight.layers.enableAll();
scene.add( dirLight );
const axesHelper = new THREE.AxesHelper( 5 );
axesHelper.layers.enableAll();
scene.add( axesHelper );
//
const earthGeometry = new THREE.SphereGeometry( EARTH_RADIUS, 16, 16 );
const earthMaterial = new THREE.MeshPhongMaterial( {
specular: 0x333333,
shininess: 5,
map: textureLoader.load( 'textures/planets/earth_atmos_2048.jpg' ),
specularMap: textureLoader.load( 'textures/planets/earth_specular_2048.jpg' ),
normalMap: textureLoader.load( 'textures/planets/earth_normal_2048.jpg' ),
normalScale: new THREE.Vector2( 0.85, 0.85 )
} );
const earth = new THREE.Mesh( earthGeometry, earthMaterial );
scene.add( earth );
const moonGeometry = new THREE.SphereGeometry( MOON_RADIUS, 16, 16 );
const moonMaterial = new THREE.MeshPhongMaterial( {
shininess: 5,
map: textureLoader.load( 'textures/planets/moon_1024.jpg' )
} );
moon = new THREE.Mesh( moonGeometry, moonMaterial );
scene.add( moon );
//
earth.layers.enableAll();
moon.layers.enableAll();
const earthDiv = document.createElement( 'div' );
earthDiv.className = 'label';
earthDiv.textContent = 'Earth';
earthDiv.style.marginTop = '-1em';
earthDiv.style.pointerEvents = 'auto'
earthDiv.addEventListener('pointerdown', function (event) {
console.log(event)
})
const earthLabel = new CSS2DObject( earthDiv );
earthLabel.position.set( 0, EARTH_RADIUS, 0 );
earth.add( earthLabel );
earthLabel.layers.set( 0 );
earthLabel.element.addEventListener('click', ()=>{
alert(12121)
})
const earthMassDiv = document.createElement( 'div' );
earthMassDiv.className = 'label';
earthMassDiv.textContent = '5.97237e24 kg';
earthMassDiv.style.marginTop = '-1em';
const earthMassLabel = new CSS2DObject( earthMassDiv );
earthMassLabel.position.set( 0, - 2 * EARTH_RADIUS, 0 );
earth.add( earthMassLabel );
earthMassLabel.layers.set( 1 );
const moonDiv = document.createElement( 'div' );
moonDiv.className = 'label';
moonDiv.textContent = 'Moon';
moonDiv.style.marginTop = '-1em';
const moonLabel = new CSS2DObject( moonDiv );
moonLabel.position.set( 0, MOON_RADIUS, 0 );
moon.add( moonLabel );
moonLabel.layers.set( 0 );
const moonMassDiv = document.createElement( 'div' );
moonMassDiv.className = 'label';
moonMassDiv.textContent = '7.342e22 kg';
moonMassDiv.style.marginTop = '-1em';
const moonMassLabel = new CSS2DObject( moonMassDiv );
moonMassLabel.position.set( 0, - 2 * MOON_RADIUS, 0 );
moon.add( moonMassLabel );
moonMassLabel.layers.set( 1 );
//
renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
labelRenderer = new CSS2DRenderer();
labelRenderer.setSize( window.innerWidth, window.innerHeight );
labelRenderer.domElement.style.position = 'absolute';
labelRenderer.domElement.style.top = '0px';
document.body.appendChild( labelRenderer.domElement );
const controls = new OrbitControls( camera, labelRenderer.domElement );
controls.minDistance = 5;
controls.maxDistance = 100;
//
window.addEventListener( 'resize', onWindowResize );
initGui();
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
labelRenderer.setSize( window.innerWidth, window.innerHeight );
}
function animate() {
requestAnimationFrame( animate );
const elapsed = clock.getElapsedTime();
moon.position.set( Math.sin( elapsed ) * 5, 0, Math.cos( elapsed ) * 5 );
renderer.render( scene, camera );
labelRenderer.render( scene, camera );
}
//
function initGui() {
gui = new GUI();
gui.add( layers, 'Toggle Name' );
gui.add( layers, 'Toggle Mass' );
gui.add( layers, 'Enable All' );
gui.add( layers, 'Disable All' );
}
</script>
threejs点击获取三维坐标(Three.js获取鼠标点击的三维坐标)
addClick();
function addClick () {
var raycaster = new THREE.Raycaster();//光线投射,用于确定鼠标点击位置
var mouse = new THREE.Vector2();//创建二维平面
window.addEventListener("mousedown",mousedown);//页面绑定鼠标点击事件
//点击方法
function mousedown(e){
//将html坐标系转化为webgl坐标系,并确定鼠标点击位置
mouse.x = e.clientX / renderer.domElement.clientWidth*2-1;
mouse.y = -(e.clientY / renderer.domElement.clientHeight*2)+1;
//以camera为z坐标,确定所点击物体的3D空间位置
raycaster.setFromCamera(mouse,camera);
//确定所点击位置上的物体数量
var intersects = raycaster.intersectObjects(scene.children);
//选中后进行的操作
if(intersects.length){
var selected = intersects[0];//取第一个物体
console.log("x坐标:"+selected.point.x);
console.log("y坐标:"+selected.point.y);
console.log("z坐标:"+selected.point.z);
}
}
}
GLTFLoader 模型下加载 CSS2DObject(作为文字标记点)
场景说明:GLTF 模型需要添加标记点,标记点利用 CSS2DObject 创建
并且需要 能点击标记点

// css2d渲染器-用作 文本渲染器
_createLabelRenderer () {
// 新建CSS2DRenderer
this.labelRenderer = new CSS2DRenderer()
this.labelRenderer.setSize(window.innerWidth, window.innerHeight)
this.labelRenderer.domElement.style.position = 'absolute'
this.labelRenderer.domElement.style.top = '0'
this.labelRenderer.domElement.style.pointerEvents = 'none'; // 这步很重要,设置.pointerEvents=none,以免模型标签HTML元素遮挡鼠标选择场景模型-然后添加的 CSS2DObject 元素 设置 label.style.pointerEvents = 'auto'
this.container.appendChild(this.labelRenderer.domElement); // 将渲染器创建在 容器中,会导致 该图层和画布冲突,导致无法操作
// document.body.appendChild(this.labelRenderer.domElement)
}
// 创建文本标记点,并定义点击事件
createLableObj (item, vector) {
const itemObj = JSON.stringify(item)
// 创建一个 div 标签
const labelTemp = document.createElement('div')
labelTemp.className = '3d-tag'
const id = `tag${item.id}`
labelTemp.id = id
document.getElementById('bimbox').appendChild(labelTemp)
const label = document.getElementById(id)
const img = document.createElement('img')
img.className = 'tag_img'
// type:工程是否告警 1-告警 0 不告警
img.src = item.type === 1 ? this.tagIconMap.default : this.tagIconMap.alarm
label.appendChild(img)
label.style.pointerEvents = 'auto' // 这步很重要,否则会 无法点击标记点
// 解决传参问题
label.addEventListener('click', (e) => this.labelClick(e, itemObj))
const pointLabel = new CSS2DObject(label)
pointLabel.position.set(vector.x, vector.y, vector.z)
this.group.add(pointLabel)
}
// 点击标记点-弹窗,转向
labelClick (e, item) {
console.log(e.target)
console.log(item)
}
释放内存
https://blog.csdn.net/u014361280/article/details/124309410
// 删除场景对象中Scene一个子对象Group,并释放组对象Group中所有网格模型几何体的顶点缓冲区占用内存
deleteObject(group) {
// 递归遍历组对象group释放所有后代网格模型绑定几何体占用内存
group.traverse(function(obj) {
if (obj.type === 'Mesh') {
obj.geometry.dispose();
obj.material.dispose();
}
})
// 删除场景对象scene的子对象group
scene.remove(group);
}
// 删除组
clearGroup(group) {
// 释放 几何体 和 材质
const clearCache = (item) => {
item.geometry.dispose();
item.material.dispose();
};
// 递归释放物体下的 几何体 和 材质
const removeObj = (obj) => {
let arr = obj.children.filter((x) => x);
arr.forEach((item) => {
if (item.children.length) {
removeObj(item);
} else {
clearCache(item);
item.clear();
}
});
obj.clear();
arr = null;
};
// 移除 group
removeObj(group);
}
// 1)如果是场景中
scene.remove(group); // 删除组
// 2)如果是 组中
groups.remove(group);// 删除模型
模型没有啥纹理材质

/**
*场景环境
* 解决 部分模型 没有 纹理材质,导致模型变成一坨,不好看
* @private
*/
_createEnvironment() {
this.renderer.physicallyCorrectLights = true;
this.renderer.outputEncoding = THREE.sRGBEncoding;
this.renderer.setClearColor(0xcccccc);
this.renderer.setPixelRatio(window.devicePixelRatio);
var pmremGenerator = new THREE.PMREMGenerator(this.renderer);
pmremGenerator.compileEquirectangularShader();
var neutralEnvironment = pmremGenerator.fromScene(new RoomEnvironment()).texture;
this.scene.environment = neutralEnvironment;
}
地图开发
https://zhuanlan.zhihu.com/p/109555689

