three开发VR全景时的几种标签写法

three中的标签一般是根据物体进行定位,比如这样:three-label例子

但是在VR开发中,由于VR场景是通过贴图来模拟出伪VR效果,并没有实体在场景中,在实际开发中很不好定位,所以要想一些其他方案来在场景中定位标注,通过归纳,大致有以下三种方案:

方案一 : 使用three自带的sprite类

首先使用TextureLoader读取图片材质,注意尽量不要读取jpg或png这种标量图,最好使用svg这种矢量图,不会失真,另外svg可以使用xml来描述,后期文字样式这类信息也可以由前端来控制


three开发VR全景时的几种标签写法_第1张图片
放大10倍,使用png的sprite,有些失真
three开发VR全景时的几种标签写法_第2张图片
放大10倍,使用svg的sprite

可以看出在同样的缩放比例下二者清晰度有较大差异.

导入图片之后创建材质, 设置精灵纹理贴图,生成Sprite对象. three中的sprite对象,始终朝向相机,且不会随场景变化产生透视效果. 但是此时Sprite对象的中心点在其几何中心, 而非底部, 这样在场景拖放时,标注会与标注物体发生偏移. 那么更改标注几何中心的方法是将sprite.center设置成一个新的二维向量, 例如sprite.center.set(0.5, 0), 这是简易写法, 其实是一个THREE.Vector2({x:0.5,y:0})向量赋给了sprite.center属性. 之后再设置scale属性, 由于使用的是svg贴图, 因此对scale属性设置不会对清晰度造成影响.

伪代码:
ThreeVR.prototype.renderSVGLabels = function () {
  const label = {
    img: require("@vr/2.svg"),
    x: 0,
    y: 1,
    z: 50
  }
  const {
    img,
    x,
    y,
    z
  } = label
  // 读取材质,使用svg不会失真
  const texture = new THREE.TextureLoader().load(img);
  const spriteMaterial = new THREE.SpriteMaterial({
    map: texture, // 设置精灵纹理贴图
  });
  // three的sprite对象,始终朝向相机,且不会随场景变化产生透视效果
  const sprite = new THREE.Sprite(spriteMaterial);
  // 中心点设置成sprite底部,这其实是一个THREE.Vector2({x:0.5,y:0})向量
  sprite.center.set(0.5, 0)
  // x,y轴放大10倍,由于一直面向前前方,z轴不需要放大
  sprite.scale.set(10, 10, 1);
  sprite.position.set(x, y, z)
  this.scene.add(sprite);
}
方案二 : 使用CSS2DRenderer渲染成2D标签

这种标签显示效果与第一种完全一致, 优点是使用css直接渲染成dom, 文字和样式可以完全由前端控制, 劣势是在渲染时由于要实时计算标签dom位置, 切换时要加载与卸载dom元素,比较浪费性能,但是经过测算,一百个之内的dom加载不会超过60fps, 不会有肉眼可见的卡顿现象, 在可接受范围之内,相当多的竞品也是使用的该方案

首先要引入CSS2DRenderer库的 CSS2DRenderer, CSS2DObject两个构造函数

在场景中选定一个定位点作为标签位置, 然后新建一个dom, 在这个定位点用2D渲染器成标签,同时这个标签也可以引入onclick 事件, 这个事件可以被dom响应,也可以在vuex中捕获该事件,将点击事件注册到vuex中,最后将CSS2DRenderer挂载到dom中,再更新相机投影矩阵即可, js逻辑不复杂, 这种技术的难点在于css编写

css中,我首先新建了一个div,class为talent-labels,这是一个0*0的容器,在three中用于被定位, 接收三维坐标, 它的内部是一个class为talent-label-texts的div,这个div用于调整标签的中心点,由于这个标签默认中心点在左上方,我们使用 position: absolute; transform: translate(-50%, -100%);把中心点调整到底部中央. talent-label-texts里面有三个div分别是text,line,和ball用于展示,注意这些元素必须是 position: relative;display: block; 不能脱离父元素的css流.这样一个会随场景移动的标签就制作好了

.talent-labels接收接收三维坐标

 .talent-labels {
    //接收三维坐标
    color: #fff;
    font-size: 15px;
    cursor: pointer;
}
three开发VR全景时的几种标签写法_第3张图片
.talent-labels接收接收三维坐标

.talent-label-texts把中心点调整到底部中央

.talent-labels .talent-label-texts {
    //把中心点调整到底部中央
    position: absolute;
    transform: translate(-50%, -100%);
    height: auto;
}
three开发VR全景时的几种标签写法_第4张图片
talent-label-texts把中心点调整到底部中央

下面是标签的css样式,可根据实际去处理

.talent-labels .talent-label-texts .text {
    width: 20px;
    border-radius: 5px;
    padding: 5px;
    background-color: rgba(0, 0, 0, 0.5);
}

.talent-labels .talent-label-texts .line {
    display: block;
    position: relative;
    height: 30px;
    width: 6px;
    margin-left: 12px;
    background-color: rgba(0, 0, 0, 0.5);
}

.talent-labels .talent-label-texts .ball {
    display: block;
    background-color: rgba(255, 0, 0, 0.5);
    position: relative;
    border-radius: 50%;
    width: 10px;
    height: 10px;
    margin-left: 10px;
    background-color: rgba(0, 0, 0, 1);
}
js伪代码:
import {
  CSS2DRenderer,
  CSS2DObject
} from "@/utils/renderers/CSS2DRenderer.js";
  this.labelRenderer = new CSS2DRenderer();
// 渲染固定位标签
ThreeVR.prototype.renderCSS2DLabels = async function (activeChooseRoom, labels) {
  const that = this;
  // 新建一个3D容器,用于包含标签
  const tagObject = new THREE.Object3D();
  this.scene.add(tagObject);
  let scenelabels = [];
  scenelabels = labels[activeChooseRoom];
  scenelabels = [{
      label: '这是一个标签',
      x: 0,
      y: 0,
      z: 100
    },
    {
      label: '这是另一个标签',
      x: -100,
      y: -5,
      z: 10
    }
  ]
  if (scenelabels && scenelabels.length > 0) {
    scenelabels.forEach((scenelabel) => {
      const {
        label,
        x,
        y,
        z
      } = scenelabel;
      /**
       * 新建一个dom,用2D渲染器成标签
       *  */
      const earthDiv = document.createElement("div");
      earthDiv.className = "talent-labels";
      earthDiv.innerHTML = `
${label}
`; earthDiv.onclick = function (e) { console.log("点击了", e.target.innerHTML); // 在vuex中捕获事件,这是一个观察者模式,将vuex的引用导入到three中,就可以将点击事件注册到vuex中 that.Vuex.dispatch("tag/setClickedTag", e.target.innerHTML); }; const earthLabel = new CSS2DObject(earthDiv); earthLabel.position.set(x, y, z); // 将标签放入3D容器,便于管理 tagObject.add(earthLabel); }); document .getElementById(this.canvasId) .appendChild(this.labelRenderer.domElement); } // 更新相机投影矩阵,使渲染器生效 this.camera.updateProjectionMatrix() };

当然创造了标签之后,在切换场景时还要删除标签,删除他们就是一个递归,遍历dom就好了,从尾部开始查找,反向递归比较节省性能

伪代码: 
ThreeVR.prototype.clearLabels = function (className = "talent-labels") {
  // 根据class名获取到dom
  let labels = document.getElementsByClassName(className);
  // 转化成数组
  labels = Array.from(labels)
  while (labels.length > 0) {
    const firstLabel = labels.shift()
    // 父元素
    const labelsDiv = firstLabel.parentNode;
    let child = null
    if (labelsDiv)
      child = labelsDiv.lastElementChild;
    while (child) {
      // 若有子元素,则递归
      if (child.lastElementChild) {
        this.clearLabels(child.lastElementChild.className)
      }
      // 若没有子元素,循环删掉最后一个
      labelsDiv.removeChild(child);
      child = labelsDiv.lastElementChild;
    }
  }
};
方案三 : 使用CSS3DRenderer渲染成3D标签

2D标签与3D标签最大的不同就是 : 3D标签在场景中会跟随相机变化而产生透视效果

three开发VR全景时的几种标签写法_第5张图片
3D标签
three开发VR全景时的几种标签写法_第6张图片
2D标签

可以看出在这个特殊的仰视角度下,3D标签跟随相机发生了透视,而2D标签依旧方方正正

3D代码渲染代码与2D雷同, 唯一要关注的是,3D标签不是默认朝向相机的,要多加一个rotateY属性, 这个函数接受的是角度值

伪代码:
ThreeVR.prototype.renderCSS3DLabels = async function (activeChooseRoom, labels) {
  const that = this;
  // 新建一个3D容器,用于包含标签
  const tagObject = new THREE.Object3D();
  this.scene.add(tagObject);
  let scenelabels = [];
  scenelabels = labels[activeChooseRoom];
  scenelabels = [{
      label: '这是一个标签',
      x: 0,
      y: 0,
      z: 100,
      r: Math.PI
    },
    {
      label: '这是另一个标签',
      x: -100,
      y: -5,
      z: 10,
      r: Math.PI / 2
    }
  ]
  if (scenelabels && scenelabels.length > 0) {
    scenelabels.forEach((scenelabel) => {
      const {
        label,
        x,
        y,
        z,
        r
      } = scenelabel;
      /**
       * 新建一个dom,用3D渲染器成标签
       *  */
      const earthDiv = document.createElement("div");
      earthDiv.className = "talent-labels";
      earthDiv.innerHTML = `
${label}
`; earthDiv.onclick = function (e) { console.log("点击了", e.target.innerHTML); // 在vuex中捕获事件,这是一个观察者模式,将vuex的引用导入到three中,就可以将点击事件注册到vuex中 that.Vuex.dispatch("tag/setClickedTag", e.target.innerHTML); }; const earthLabel = new CSS3DObject(earthDiv); // css3d渲染的标签不是正对着相机的,要进行角度调整 if (r) earthLabel.rotateY(r) earthLabel.position.set(x, y, z); // 适配标签大小 earthLabel.scale.set(0.2, .2, .2) tagObject.add(earthLabel); }); document .getElementById(this.canvasId) .appendChild(this.labelRenderer3.domElement); } // 更新相机投影矩阵,使渲染器生效 this.camera.updateProjectionMatrix() };

最后,若使用camera.lookAt(scene.position)看向场景, 记得不要把camera放在(0,0,0),这样会使controls失效,要让camera有一个小的偏移

伪代码:
ThreeVR.prototype.initCamera = function () {
    this.camera.lookAt(this.scene.position);
    this.camera.position.x = -0.16;
    this.camera.position.y = 0.1;
    this.camera.position.z = 0.1;
};

你可能感兴趣的:(three开发VR全景时的几种标签写法)