首页 前端知识 three.js给模型添加CSS3DSprite精灵标签

three.js给模型添加CSS3DSprite精灵标签

2024-03-31 09:03:48 前端知识 前端哥 493 73 我要收藏

先看下效果,如果符合你的需求可以接着往下看,避免浪费大家的时间

这个需求主要有以下功能

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博客

这世界很喧嚣,做你自己就好

转载请注明出处或者链接地址:https://www.qianduange.cn//article/4303.html
标签
评论
发布的文章
大家推荐的文章
会员中心 联系我 留言建议 回顶部
复制成功!