头图

前言

一开始做3D视图的时候,遇到需要定位加标签的需求——我是直接加TextGeometry,因为需要汉字,加载了很大的字体包,对性能来说非常不友好。而且他不能跟着鼠标旋转移动而始终正向面对用户,效果不好看。后面学到了threejs是支持与HTML标签结合的,所以有了这篇文章。

效果

其中蓝色柱体是blender的gltf模型,在blender里的坐标和名称,threejs都可以获取到。
求点赞

实现

CSS2DRenderer是CSS3DRenderer(CSS 3D渲染器)的简化版本,唯一支持的变换是位移。

如果你希望将三维物体和基于HTML的标签相结合,则这一渲染器将十分有用。在这里,各个DOM元素也被包含到一个CSS2DObject实例中,并被添加到场景图中。

首先是DOM:你可以写DOM,后面获取一下,也可以创建dom。

<!--tagArray是标签数组,需要获取设置一下。也可以创建dom,不写在这-->
<div
      :class="['tag', tag]"
      v-for="tag in tagArray"
      :key="tag"
    >
      <div class="tag__content">
        <span class="tag__txt">{{ tag }}</span>
      </div>
    </div>

下面是JS部分,主要代码就是这些,其它必要的渲染器创建没写了。

<script>
import * as THREE from 'three'
import { CSS2DRenderer, CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer.js'


//创建CSS2DRenderer(这一部分紧接着renderer的创建之后)
this.labelRenderer = new CSS2DRenderer()
this.labelRenderer.setSize(window.width, window.height)
this.labelRenderer.domElement.style.position = 'absolute'
this.labelRenderer.domElement.style.top = '0px'
this.DOM.appendChild(this.labelRenderer.domElement)

//controls的第二个参数需要改成this.labelRenderer.domElement
 this.controls = new MapControls(this.camera, this.labelRenderer.domElement)


//创建标签(单独出来的一个方法,下面调用了)
newTag(name){
    const labelOne = document.getElementsByClassName(name)[0]
    const labelObjOne = new CSS2DObject(labelOne)
    return labelObjOne
}


//labelObjOne就是创建的对象,之后就加入scene并定位就行了,我这里是与Blender导出的gltf模型保持一样的位置,参考代码如下:

      loader.load(
        '/provinceMap.gltf',
        gltf => {
          console.log('控制台查看加载gltf文件返回的对象结构', gltf)
          console.log('gltf对象场景属性', gltf.scene)

          gltf.scene.position.set(0, 0, 0)
          this.scene.add(gltf.scene)

          //循环gltf里面的children,每个柱体都需要配上标签
          gltf.scene.children.forEach(mesh => {
            if (mesh.type === 'Mesh' && mesh.name.indexOf('柱体') != -1) {
              const label = this.newTag(mesh.name) //把mesh名称作为标签
              // 添加label坐标
              const pos = new THREE.Vector3()
              mesh.getWorldPosition(pos) //获取obj世界坐标
              label.position.copy(pos) //标签标注在obj世界坐标
              this.scene.add(label)
            }
          })
          //执行渲染操作
          this.renderer.render(this.scene, this.camera)
        },
        xhr => {
          console.log((xhr.loaded / xhr.total) * 100 + '% loaded')
        },
        error => {
          console.error(error)
        }
      )

//render方法里要加更新
render(){
    this.labelRenderer.render(this.scene, this.camera) //渲染HTML标签对象
    this.renderer.render(this.scene, this.camera) //执行渲染操作
},

//监听屏幕改变大小的事件中也需要加入
 window.onresize = function() {
    this.renderer.setSize(window.width, window.height)
    this.labelRenderer.setSize(window.width, window.height)
    this.camera.aspect = window.width / window.height
    this.camera.updateProjectionMatrix()
}


</script>

这里附上tag标签的样式

<style lang="less" scoped>
.tag,
.tag__dot {
  display: inline-flex;
}
.tag {
  position: fixed;
  cursor: pointer;
}
.tag__dot {
  align-items: center;
  transform: rotate(-45deg);
}
.tag__dot:before {
  width: 0.42vw;
  height: 0.42vw;
  border-radius: 50%;
  border: 0.1vw solid #000;
  content: '';
}
.tag__dot:after {
  width: 1.82vw;
  height: 1px;
  background: #000;
  content: '';
}
.tag__content {
  display: flex;
  align-items: center;
  position: relative;
  top: -0.94vw;
  height: 1.88vw;
  // margin-left: -0.42vw;
  background: rgba(0, 0, 0, 0.5);
  border-radius: 1.88vw;
  padding: 0 1.04vw;
}
.tag__txt {
  white-space: nowrap;
  font-size: 12px;
  color: #fff;
  &:hover {
    color: #fff;
  }
}
</style>

总结

功能完美实现啦,就是样式有点丑。接下来就可以在标签上绑定点击事件,点击后相机通过动画缓慢移动到目标点,实现具体查看。这里就需要用到tween.js。不过我发现....如果是地图上加几个3D物体和2D DOM,直接AntV L7他不香吗???

附录

使用TextGeometry在threejs里显示汉字物体:https://segmentfault.com/a/1190000042551058
官网的CSS2DRenderer例子:https://threejs.org/examples/#css2d_label
加载blender导出的gltf模型教程:http://www.webgl3d.cn/pages/006fcb/


洋仔
191 声望3 粉丝

目前目标:手写深拷贝、阅读axios源码并掌握。