先看下效果,如果符合你的需求可以接着往下看,避免浪费大家的时间
这个需求主要有以下功能
1)给模型添加热点,可以通过点击热点弹出模型标签
2)给模型添加Sprite精灵图
3)给模型添加CSS3DSprite模型标签
在写代码之前先了解下 CSS3DSprite 是什么
CSS3精灵模型 CSS3DSprite 对应的HTML标签,可以跟着场景缩放,位置可以跟着场景旋转,但是自身的姿态角度始终平行于canvas画布,不受旋转影响,就像精灵模型一样。CSS3精灵模型 CSS3DSprite尺寸、位置、缩放等渲染规律和CSS3对象模型CSS3DObject基本一致。
一、给模型添加热点
创建 hotspot.js 文件 专门用于创建热点模型
import * as THREE from "three";
import * as TWEEN from "@tweenjs/tween.js";
import { ResourceTracker } from "@/views/modelShow/trackResource.js";
// 在外层定义resMgr和track
let resMgr = new ResourceTracker();
const track = resMgr.track.bind(resMgr);
function createHotPot(scene, hotPosition, hotName) {
const map = new THREE.TextureLoader().load("/textues/point2.png");
const material = new THREE.SpriteMaterial({
map: map,
// color: 0xccffcc, //设置精灵矩形区域颜色
});
const sprite = new THREE.Sprite(material);
sprite.scale.set(2.5, 2.5, 2.5); //0.3,
sprite.position.set(hotPosition.x, hotPosition.y, hotPosition.z);
sprite.name = hotName; //精灵图名称
scene.add(track(sprite));
const pos = {
scale: 2.5, //0.3
};
const spriteTween = new TWEEN.Tween(pos)
.to(
{
scale: 1, //0.2
},
1500
)
.easing(TWEEN.Easing.Quadratic.Out) // 缓动函数
.onUpdate(function () {
sprite.scale.set(pos.scale, pos.scale, pos.scale);
});
spriteTween.yoyo(false); // 是否循环反转,默认值为false,表示不反转
spriteTween.repeat(Infinity); // 重复次数,默认值为0,Infinity表示无限循环
spriteTween.start();
return sprite;
}
export { createHotPot };
二、创建 Sprite 模型精灵
import * as THREE from "three";
import * as TWEEN from "@tweenjs/tween.js";
import {
ResourceTracker
} from "@/views/modelShow/trackResource.js";
// 在外层定义resMgr和track
let resMgr = new ResourceTracker();
const track = resMgr.track.bind(resMgr);
function createSprite(obj, imgPath, spritePosition, spriteName) {
//obj: 添加精灵图的对象, imgPath:精灵图地址 spritePosition:精灵图位置
const texLoader = new THREE.TextureLoader();
let texture = null;
texture = texLoader.load(imgPath);
// 创建精灵材质对象
const spriteMaterial = new THREE.SpriteMaterial({
// color: 0xccffcc, //设置精灵矩形区域颜色
// rotation: Math.PI/4, //旋转精灵对象45度,弧度值
map: texture, //设置精灵纹理贴图
transparent: true,
});
// 精灵图淡入动画
const pos = {
opacity: 0.0, //完全透明
};
new TWEEN.Tween(pos)
.to({ opacity: 1.0 }, 500)
.onUpdate(function (obj) {
spriteMaterial.opacity = obj.opacity;
})
.onComplete(function () {
// 动画结束:关闭允许透明,恢复到模型原来状态
// spriteMaterial.transparent = false;
})
.easing(TWEEN.Easing.Quadratic.InOut)
.start();
// 创建精灵模型对象
const sprite = new THREE.Sprite(spriteMaterial);
// 控制精灵大小
sprite.scale.set(2, 2, 2); //只需要设置x、y两个分量就可以 1
sprite.position.set(spritePosition.x, spritePosition.y, spritePosition.z); //精灵图位置设置
sprite.name = spriteName; //精灵图名称
obj.add(track(sprite)); //精灵图会标注在空对象obj对应的位置
return sprite;
}
export { createSprite };
三、创建CSS3DRenderer渲染器
import { CSS3DRenderer } from "three/examples/jsm/renderers/CSS3DRenderer.js";
// 创建一个css3d渲染器
var labelRenderer3D = new CSS3DRenderer();
labelRenderer3D.setSize(window.innerWidth, window.innerHeight);
labelRenderer3D.domElement.style.position = "absolute";
// 相对标签原位置位置偏移大小
labelRenderer3D.domElement.style.top = "0px";
labelRenderer3D.domElement.style.left = "0px";
// //设置.pointerEvents=none,以免模型标签HTML元素遮挡鼠标选择场景模型
labelRenderer3D.domElement.style.pointerEvents = "none";
document.body.appendChild(labelRenderer3D.domElement);
export { labelRenderer3D };
四、使用
在需要的vue文件引入
添加CSS3DSprite精灵模型
addMarker(sName, x, y, z) {
let tipsData = [{
text: '项目',
tipsVal: '数值',
unit: '单位'
}];
if (sName === '模型1标签') {
tipsData = tipsData.concat(this.model1Data);
} else if (sName === '模型2标签') {
tipsData = tipsData.concat(this.model2Data);
} else if (sName === '模型3标签') {
tipsData = tipsData.concat(this.model3Data);
} else if (sName === '模型4标签') {
tipsData = tipsData.concat(this.model4Data);
} else {
tipsData = tipsData.concat(this.model1Data);
}
let markerDom = document.createElement('div');
markerDom.style.width = '300px';
markerDom.style.padding = "15px 30px";
markerDom.style.color = "#fff";
markerDom.style.fontSize = "22px";
markerDom.style.background = `rgba(25,25,25,0.7) url(${labelBg})no-repeat center center`;
markerDom.style.borderRadius = "10px";
markerDom.style.backgroundSize = "100% 100%";
markerDom.innerHTML = `
<div>
${tipsData.map(item=>{
return `<div style="display: flex;">
<div style='width:50%;line-height:40px;'>${item.text}</div>
<div style='width:25%;line-height:40px;'>${item.tipsVal}</div>
<div style='width:25%;line-height:40px;'>${item.unit}</div>
</div>`
}).join('')}
</div>`;
markerDom.style.color = '#ffffff';
const marker = new CSS3DSprite(markerDom);
marker.name = sName;
marker.scale.set(0.08, 0.08, 0.08); //根据相机渲染范围控制HTML 3D标签尺寸
marker.rotateY(Math.PI / 2); //控制HTML标签CSS3对象姿态角度
marker.position.set(x, y, z);
this.scene.add(track(marker));
new TWEEN.Tween({
opacity: 0
}).to({
opacity: 1.0
}, 500).onUpdate(function (obj) {
//动态更新div元素透明度
markerDom.style.opacity = obj.opacity;
}).start();
return markerDom;
}
五、注意事项
在实际项目开发中,不可避免会从一个模型页面切换到其他页面,这样来回反复切换,会非常消耗系统性能,甚至导致模型不能正常加载渲染。我也遇到了这个问题,于是查阅了些资料,总算是解决了~
beforeDestroy() {
// 清除所有点击事件
document.body.removeEventListener("click", this.handleClickPage);
try {
this.$refs.modelRef.innerHTML = ''; // 3d容器,下面挂载着 canvas 容器,这里直接清空3d容器子元素
this.scene.clear();
resMgr && resMgr.dispose()
this.renderer.dispose();
this.renderer.forceContextLoss();
this.renderer.content = null;
labelRenderer3D.content = null; // 清除3d渲染内容
cancelAnimationFrame(this.aniFlag) // 去除animationFrame
let gl = this.renderer.domElement.getContext("webgl");
gl && gl.getExtension("WEBGL_lose_context").loseContext();
// console.log(this.renderer.info) // 模型内存占用清空,查看memery字段即可
// console.log(this.scene) // 查看scene ,children为空数组,表示 模型等销毁ok
} catch (e) {
console.log(e)
}
},
这种方法不能彻底清除掉scene场景内的一些geometry、texture等,而且页面离开也不会自动释放内存。重复切换页面,可以看到CPU占用越来越高。
import * as THREE from 'three/build/three.module'
export class ResourceTracker {
constructor() {
this.resources = new Set();
}
track(resource) {
if (!resource) {
return resource;
}
// handle children and when material is an array of materials or
// uniform is array of textures
if (Array.isArray(resource)) {
resource.forEach(resource => this.track(resource));
return resource;
}
if (resource.dispose || resource instanceof THREE.Object3D) {
this.resources.add(resource);
}
if (resource instanceof THREE.Object3D) {
this.track(resource.geometry);
this.track(resource.material);
this.track(resource.children);
} else if (resource instanceof THREE.Material) {
// We have to check if there are any textures on the material
for (const value of Object.values(resource)) {
if (value instanceof THREE.Texture) {
this.track(value);
}
}
// We also have to check if any uniforms reference textures or arrays of textures
if (resource.uniforms) {
for (const value of Object.values(resource.uniforms)) {
if (value) {
const uniformValue = value.value;
if (uniformValue instanceof THREE.Texture ||
Array.isArray(uniformValue)) {
this.track(uniformValue);
}
}
}
}
}
return resource;
}
untrack(resource) {
this.resources.delete(resource);
}
dispose() {
for (const resource of this.resources) {
if (resource instanceof THREE.Object3D) {
if (resource.parent) {
resource.parent.remove(resource);
}
}
if (resource.dispose) {
resource.dispose();
}
}
this.resources.clear();
}
}
参考文章:
https://www.cnblogs.com/Hijacku/p/15927784.html
Three.js 内存释放问题_forcecontextloss-CSDN博客
这世界很喧嚣,做你自己就好