前言

数字孪生近几年被提到的越来越多,基于此来看看学习记录一下vue+three.js学习中的一些问题

一、什么是Three.js?

在这里插入图片描述

一款运行在浏览器中的3D引擎。three.js是一个webgl为基础的库,对webGL的3D渲染工具方法与渲染循环封装的js库,省去与繁琐底层接口的交互,通过threeJS就可以快速生成三维模型。

二、vue引入使用

1.安装引入

npm install --s three

2.组件内引用

import * as THREE from “three”;

3.示例代码

<template>
  <div>
    <div id="container"></div>
  </div>
</template>

<script>
import * as THREE from "three";
import {OrbitControls} from 'three/examples/jsm/controls/OrbitControls.js';
export default {
  data() {
    return {
      camera: null,
      scene: null,
      renderer: null,
      mesh: null,
      controls:null
    };
  },
  mounted() {
    this.init();
    this.animate();
  },
  methods: {
    //初始化
    init: function() {
      //  创建场景对象Scene
      this.scene = new THREE.Scene();

      //网格模型添加到场景中
      let geometry = new THREE.BoxGeometry(0.2, 0.2, 0.2);
      let material = new THREE.MeshNormalMaterial({
        color: "white"
      });
      this.mesh = new THREE.Mesh(geometry, material);
      this.scene.add(this.mesh);

      /**
       * 相机设置
       */
      let container = document.getElementById("container");
      this.camera = new THREE.PerspectiveCamera(
        70,
        container.clientWidth / container.clientHeight,
        0.01,
        10
      );
      this.camera.position.z = 1;

      /**
       * 创建渲染器对象
       */
      this.renderer = new THREE.WebGLRenderer({ antialias: true });
      this.renderer.setSize(container.clientWidth, container.clientHeight);
      container.appendChild(this.renderer.domElement);

       //创建控件对象
      this.controls = new OrbitControls(this.camera, this.renderer.domElement);
    },

    // 动画
    animate: function() {
      requestAnimationFrame(this.animate);
      this.mesh.rotation.x += 0.01;
      this.mesh.rotation.y += 0.02;
      this.renderer.render(this.scene, this.camera);
    }
  }
};
</script>

<style>
#container {
  position: absolute;
  width: 100%;
  height: 100%;
}
</style>

引用自简书,点击链接跳转

三、官网学习

1.创建模型

需要场景(scene)、相机(camera)和渲染器(renderer),他们是图形渲染得重要部分

(1)场景(scene)

承载所有模板的容器,允许渲染模型和位置

new THREE.Scene()

(2)相机(camera)

场景中人眼的角色,决定场景中模型的远近、高度角度等参数。
提供正投影相机、透视相机、立体相机等多种相机模式,常用的为前两种

正投影相机

new THREE.OrthographicCamera( left, right, top, bottom, near, far )

分别设置相机的左边界,右边界,上边界,下边界,远面,近面

透视相机

new THREE.PerspectiveCamera( fov, aspect, near, far )

分别设置相机的视场角度,长宽比,近面,远面

(3)渲染器(renderer)

this.renderer = new THREE.WebGLRenderer({ antialias: true });

this.renderer.setSize(container.clientWidth, 
container.clientHeight);

this.renderer.render(scene, camera)

渲染器决定了渲染的结果应该画在页面的什么元素上面,并且以怎样的方式来绘制

2.处理

(1)背景透明

//创建渲染器,alpha设置为true
renderer = new THREE.WebGLRenderer({antialias: true, logarithmicDepthBuffer: true, preserveDrawingBuffer: true, alpha: true});
//设置场景显示区背景色,参数2是透明度
renderer.setClearColor(0xffffff, 0);

四、碰到的一些问题及解决方案

1.加载顺序

项目中为了代码可读性,最好保证一定的场景创建顺序

      this.initRender(); //初始化场景渲染器
      this.initScene(); //加载场景容器
      this.initCamera(); //加载摄像机
      this.initLight(); //加载灯光
      this.initControls(); //鼠标操作镜头画面
      this.animate();
      
	  //加载gltf模型

2.选中某个模型聚焦到该模型时,需要更改camera.position以及controls.target坐标

快捷获取到坐标方式:绑定双击事件,在模型中调整好视角之后双击,获取到当前坐标

 this.container.addEventListener('dblclick', this.onDoubleClick, false); //双击

 //添加双击事件
 onDoubleClick() {
   console.log('newT =', JSON.stringify(controls.target).replace(/\"/g, ''));
   console.log('newP =', JSON.stringify(camera.position).replace(/\"/g, ''));
   cssScene.traverse(item => console.log(item.name))
 },

3.相机漫游效果实现

使用tween实现

animateCamera(newP, newT, callBack, time = 2000, flag = '0') {
      let campos = camera.position, target = controls.target
      const tween = new TWEEN.Tween({x1: campos.x, y1: campos.y, z1: campos.z, x2: target.x, y2: target.y, z2: target.z});
      tween.to({x1: newP.x, y1: newP.y, z1: newP.z, x2: newT.x, y2: newT.y, z2: newT.z}, isProduct ? time : 0);
      tween.onUpdate(object => {
        camera.position.x = object.x1;
        camera.position.y = object.y1;
        camera.position.z = object.z1;
        controls.target.x = object.x2;
        controls.target.y = object.y2;
        controls.target.z = object.z2;
        controls.update();
      });
      tween.onComplete(() => this.callBack(flag));
      tween.easing(TWEEN.Easing.Cubic.InOut);
      tween.start();
    },

4.点击模型,模型发光效果实现

高亮显示模型(呼吸灯)https://wow.techbrood.com/fiddle/56603

OutlineObj(selectedObjects, color = 0x00ffff) {
	  
	  //移除发光标记
      this.iShine = JSON.stringify(selectedObjects) !== '[]';

      // 创建一个EffectComposer(效果组合器)对象,然后在该对象上添加后期处理通道。
      this.composer = new EffectComposer(renderer);
      // 新建一个场景通道  为了覆盖到原理来的场景上
      this.renderPass = new RenderPass(scene, camera);
      this.composer.addPass(this.renderPass);
      // 物体边缘发光通道
      let object = new THREE.Vector2(this.out.offsetWidth, this.out.offsetHeight);
      this.outlinePass = new OutlinePass(object, scene, camera, selectedObjects);
      this.outlinePass.selectedObjects = selectedObjects;
      this.outlinePass.edgeStrength = 10; // 边框的亮度,最大10
      this.outlinePass.edgeGlow = 1; // 光晕[0,1]//,最大1
      this.outlinePass.usePatternTexture = false; // 是否使用父级的材质,纯色的可以使用
      this.outlinePass.edgeThickness = 1; // 边框宽度//最大4
      this.outlinePass.downSampleRatio = 1.5; // 边框弯曲度//之前为2,,1比较合适
      this.outlinePass.pulsePeriod = 1; // 呼吸闪烁的速度//5
      this.outlinePass.visibleEdgeColor.set(color); // 呼吸显示的颜色16进制,0x00ffff
      this.outlinePass.hiddenEdgeColor = new THREE.Color(0, 0, 0); // 呼吸消失的颜色(0,0,0)
      this.outlinePass.clear = true;
      this.composer.addPass(this.outlinePass);
      // 自定义的着色器通道 作为参数
      const effectFXAA = new ShaderPass(FXAAShader);
      effectFXAA.uniforms.resolution.value.set(1 / this.out.offsetWidth, 1 / this.out.offsetHeight);
      effectFXAA.renderToScreen = true;
      this.composer.addPass(effectFXAA);
      //用于更新轨道控制器
      clock = new THREE.Clock();
      //是否可以缩放
      controls.enableZoom = true;
      //是否自动旋转
      controls.autoRotate = false;
      //最大纵向旋转角度
      controls.maxPolarAngle = Math.PI / 2;
      //是否开启右键拖拽
      controls.enablePan = true;
    },

5.流动管道效果实现

在这里插入图片描述
蓝色管道是流动效果的,灰色管道是静止的

  • 管道流向与数据绑定(大于零、小于零以及为零时方向)
  • 流动速度可以调整
  • 流程:根据路径创建曲线 =》 生成管道 =》 设置管道属性 =》创建mesh并命名 =》 将mesh加入group =》创建定时任务调用流动效果函数(图片更改时需要移除之前的mesh)
//line12是一半蓝色一半透明图片,line11是一半灰色一半透明图片
pipelineCurve:textureLoader.load(`${path}/组态图/line12.png`),
pipelineCurve2:textureLoader.load(`${path}/组态图/line12.png`),
//曲线路径以及在路径上重复铺几次
pipelineCurvePoints:[
        [
          new THREE.Vector3(-933,-144,-203), //1
          new THREE.Vector3(-933,-143,-243),
          new THREE.Vector3(-933,-142,-283),
          new THREE.Vector3(-933,-141,-323),
          new THREE.Vector3(-933,-140,-363),
          new THREE.Vector3(-933,-140,-373),
          new THREE.Vector3(-933,-140,-383),
          new THREE.Vector3(-933,-140,-393),
          new THREE.Vector3(-933,-140,-400),
          new THREE.Vector3(-933,-140,-408), //2
          new THREE.Vector3(-893,-140,-408),
          new THREE.Vector3(-853,-140,-408),
          new THREE.Vector3(-813,-140,-408),
          new THREE.Vector3(-773,-140,-408),
          new THREE.Vector3(-733,-140,-408),
          new THREE.Vector3(-693,-140,-408),
          new THREE.Vector3(-683,-140,-408),
          new THREE.Vector3(-673,-140,-408),
          new THREE.Vector3(-663,-140,-408),
          new THREE.Vector3(-654,-140,-408),//3
        ],
        [
          new THREE.Vector3(-654,-144,-203), //1
          new THREE.Vector3(-654,-140,-408), //2
        ]
],
pipelineCurveRepeatX:[10, 5]
//调用
this.pipeline(this.pipelineCurve, this.pipelineCurvePoints[0], this.pipelineCurveRepeatX[0], "pipelineCurve");
this.pipeline(this.pipelineCurve2, this.pipelineCurvePoints[1], this.pipelineCurveRepeatX[1], "pipelineCurve2");
      
	/* region 组态图管道 */
    pipeline(pipelineCurve, points, repeatX = 10, meshName) {
      let pipelineCurveClone = new THREE.CatmullRomCurve3(
          points,
          false
      );
      //曲线,路径,即管道的形状|管道分成多少段|管道的半径|管道口分成多少段,即管道口是几边形|是否闭合管道,首尾相接
      let tubeGeometry = new THREE.TubeGeometry(
          pipelineCurveClone,
          80,
          8,
          40,
          false
      ); //6  0.1

      // 设置阵列模式为 RepeatWrapping
      pipelineCurve.wrapS = THREE.RepeatWrapping;
      pipelineCurve.wrapT = THREE.RepeatWrapping;
      // 设置x方向的偏移(沿着管道路径方向),y方向默认1
      //等价texture.repeat= new THREE.Vector2(20,1)
      pipelineCurve.repeat.x = repeatX; //此路径上重复铺几次
      pipelineCurve.repeat.y = 1; //此路径上重复铺几次
      //this.pipelineCurve.offset.y = 1.5;//贴图旋转
      let tubeMaterial = new THREE.MeshPhongMaterial({
        map: pipelineCurve,
        transparent: true,
        //opacity: 1,//透明的
        side: THREE.DoubleSide //两面可见
      });
      let mesh = new THREE.Mesh(tubeGeometry, tubeMaterial);

      mesh.name = meshName;

      this.diagramGroup.add(mesh);
    },
    /* endregion */
//绑定数据以及流动效果
let arr = [];
this.loadPipeState(this.pipelineCurve, 'pvOnBuilding1', 0, 'pipelineCurve', arr)
this.loadPipeState(this.pipelineCurve2, 'chargeInBuilding1', 1, 'pipelineCurve2', arr)
if (arr.length > 0){
   arr.forEach(one => this.diagramGroup.remove(one));
}

//pipelineCurveOffset是这一整块的封装
setTimeout(() => levels === 'Diagram' && this.pipelineCurveOffset(), 60);      
    //加载每条管道的状态
    loadPipeState(pipelineCurve, valueName, number, meshName, arr){
      if (parseFloat(this.diagramData[valueName]) < 0){
        pipelineCurve.offset.x -= 0.08;
        if (this.pipelineCurveLine[number] === 11){
          this.diagramGroup.traverse(child => child instanceof THREE.Mesh && child.name === meshName&&arr.push(child));
          pipelineCurve = textureLoader.load(`${path}/组态图/line12.png`);
          this.pipeline(pipelineCurve, this.pipelineCurvePoints[number], this.pipelineCurveRepeatX[number], meshName);
          this.pipelineCurveLine[number] = 12;
        }
      }else if (parseFloat(this.diagramData[valueName]) > 0) {
        pipelineCurve.offset.x += 0.08;
        if (this.pipelineCurveLine[number] === 11){
          this.diagramGroup.traverse(child => child instanceof THREE.Mesh && child.name === meshName&&arr.push(child));
          pipelineCurve = textureLoader.load(`${path}/组态图/line12.png`);
          this.pipeline(pipelineCurve, this.pipelineCurvePoints[number], this.pipelineCurveRepeatX[number], meshName);
          this.pipelineCurveLine[number] = 12;
        }
      }else {
        if (this.pipelineCurveLine[number] === 12){
          this.diagramGroup.traverse(child => child instanceof THREE.Mesh && child.name === meshName&&arr.push(child));
          pipelineCurve = textureLoader.load(`${path}/组态图/line11.png`);
          this.pipeline(pipelineCurve, this.pipelineCurvePoints[number], this.pipelineCurveRepeatX[number], meshName);
          this.pipelineCurveLine[number] = 11;
        }
      }

    },

6.解决加载gltf格式模型纹理贴图和原图不一样问题

纹理中包含的颜色信息(.map, .emissiveMap, 和 .specularMap)在glTF中总是使用sRGB颜色空间,而顶点颜色和材质属性(.color, .emissive, .specular) 则使用线性颜色空间。在典型的渲染工作流程中,纹理会被渲染器转换为线性颜色空间,进行光照计算,然后最终输出会被转换回 sRGB 颜色空间并显示在屏幕上。在render中加入以下代码

renderer.outputEncoding = THREE.sRGBEncoding;
//不加下面这句,背景也会变亮
scene.background.encoding = THREE.sRGBEncoding;

另一个方案:材质丢失,再赋值

      this.diagramGroup.traverse(child =>{
        //材质丢失,再赋值
        if(child instanceof THREE.Mesh){
          child.material.emissive =  child.material.color;
          child.material.emissiveMap = child.material.map
        }
      })

7.解决加载hdr

它根据场景的明暗对比, 把 HDR 高动态范围光照非线性的 ToneMapping 映射到显示器能显示的 LDR 低动态光照范围,尽可能的保存了明暗对比细节,使最终渲染效果更加逼真。

pmremGenerator = new THREE.PMREMGenerator( renderer );
pmremGenerator.compileEquirectangularShader();

this.getCubeMapTexture();
    
getCubeMapTexture () {
      new RGBELoader()
          .setDataType( UnsignedByteType )
          .load( `${path}/组态图/venice_sunset_1k.hdr`, ( texture ) => {

            console.log(texture)

            const envMap = pmremGenerator.fromEquirectangular(texture).texture;

            console.log(envMap)

            pmremGenerator.dispose();

            scene.environment = envMap;
      });
},

8.压缩gltf文件

(1)使用gltf pipeLine

功能包括:
① lTF 与 glb 的相互转换
② 将缓冲区/纹理保存为嵌入或单独的文件
③ 将 glTF 1.0 模型转换为 glTF 2.0(使用KHR_techniques_webgl和KHR_blend)
④ 使用 Draco 进行网格压缩

这里使用到的主要是④,造成的问题是模型画质的损失是肉眼可观的

  • 首先需要安装gltf-pipeline

npm install -g gltf-pipeline

  • 安装完成之后需要在命令窗口中进入gltf文件所在文件夹,执行以下代码(demo.gltf是需要压缩的文件名称)

gltf-pipeline -i demo.gltf -d -s

  • vue将生成出来的所有文件放在public文件夹下
  • 加载压缩过的gltf文件需要DRACOLoader加载器,需要引入
  • 将three/examples/js/libs/draco文件复制到public下
//引入
import {DRACOLoader} from 'three/examples/jsm/loaders/DRACOLoader'

//创建一个gltf加载器
const loader = new GLTFLoader();

//这里是因为我将加载gltf整体封装了一个函数,compressed是true/false,代表传进来的是不是压缩过的gltf文件
if(compressed){
   const dracoLoader = new DRACOLoader();
   dracoLoader.setDecoderPath(`${publicPath}draco/gltf/`);
   //设置解压库文件路径
   loader.setDRACOLoader(dracoLoader);
}

	//loader加载gltf,省略
	......

(2)使用gltfpack

①KHR_mesh_quantization
顶点属性通常使用FLOAT类型存储,将原始始浮点值转换为16位或8位存储以适应统一的3D或2D网格,也就是我们所说的quantization向量化,该插件主要就是将其向量化。 例如,静态 PBR-ready 网格通常需要每个顶点POSITION(12 字节)、TEXCOORD(8 字节)、NORMAL(12 字节)和TANGENT(16 字节),总共 48 字节。通过此扩展,可以用于SHORT存储位置和纹理坐标数据(分别为 8 和 4 字节)以及BYTE存储法线和切线数据(各 4 字节),每个顶点总共 20 字节。

这种方式没有画质损失和加载时间过长的问题

-安装

npm install -g gltfpack --registry=https://registry.npmmirror.com

  • 压缩命令

gltfpack -i male.glb -o male-processed.glb

②EXT_meshopt_compression
此插件假定缓冲区视图数据针对 GPU 效率进行了优化——使用量化并使用最佳数据顺序进行 GPU 渲染——并在 bufferView 数据之上提供一个压缩层。每个 bufferView 都是独立压缩的,这允许加载器最大程度地将数据直接解压缩到 GPU 存储中。 除了优化压缩率之外,压缩格式还具有两个特性——非常快速的解码(使用 WebAssembly SIMD,解码器在现代桌面硬件上以约 1 GB/秒的速度运行),以及与通用压缩兼容的字节存储。也就是说,不是尽可能地减少编码大小,而是以通用压缩器可以进一步压缩它的方式构建比特流。

首次加载时间比原模型快上不少,并且这种方式没有画质损失和加载时间过长的问题

  • 压缩方式与使用方式

gltfpack -i male.glb -o male-processed.glb -cc

import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
import { MeshoptDecoder } from 'three/examples/jsm/libs/meshopt_decoder.module.js'

const loader = new GLTFLoader()
loader.setMeshoptDecoder(MeshoptDecoder)
loader.load(MODEL_FILE_PATH, (gltf) => {
 // ....
})

9.释放内存

项目里使用时是将页面嵌入到标签页中展示,使用的框架会对打开过的页面进行缓存,在切换标签时,触发不了beforeDestroy。所以首先需要解决页面缓存问题,之后解决释放内存问题。
在这里插入图片描述
首先是在缓存时增加判断,给title为"数字孪生"的项设置notCache为true
在这里插入图片描述
这里增加上是否需要缓存的判断,之后页面标签切换时可以正常触发beforeDestroy
下面是释放掉内存的方法, 不过存在一点问题,释放之后打印renderer.info其中的memory,依旧不是0。在释放以后再次加载时如果环境光等出现问题,可以scene先置null后new试一下

  beforeDestroy() {
    //清除定时器
    const lastTimeoutId = setTimeout(null);
    for (let i = 0; i <= lastTimeoutId._id; i++) {
      clearTimeout(i);
    }
    scene.remove();
    if (renderer){
      renderer.dispose();
      renderer.forceContextLoss();
      renderer.content = null;
      let gl = renderer.domElement.getContext('webgl');
      gl && gl.getExtension('WEBGL_lose_context').loseContext();
    }
    THREE.Cache.clear();
    window.removeEventListener('resize', this.onWindowResize);
    this.container.removeEventListener('mousedown', this.onMouseDown, false) // 鼠标按下
    this.container.removeEventListener('click', this.onMouseClick, false); //单击
    this.container.removeEventListener('dblclick', this.onDoubleClick, false); //双击
    cancelAnimationFrame(ranimationID);
  },

加载之前:

在这里插入图片描述
加载之后:
在这里插入图片描述
释放以后:
在这里插入图片描述

DRACOLoader加载器的问题,这个加载器在加载时会创建四个Dedicated Worker,而且并不会自动释放,数量少的时候感知不明显,当不停的关闭打开数字孪生页面时,这些Dedicated Worker会累加,越来越多,必须进行手动释放
在这里插入图片描述

LoadGLTFOnly(modelArray, compressed = false) {
      const loader = new GLTFLoader();

      if(compressed){
        const dracoLoader = new DRACOLoader();
        dracoLoader.setDecoderPath(`${publicPath}draco/gltf/`);
        //设置解压库文件路径
        loader.setDRACOLoader(dracoLoader);

        let number = 0;

        //加载gltf文件
        modelArray.forEach(one => {
          loader.load(one.gltfUrl, gltf => {
            number = number + 1;

            const model = gltf.scene;
            model.traverse(child => {
              if (child instanceof THREE.Mesh) {
                child.name = one.name;
              }
            });
            //场景中添加模型文件
            one.level === 'Cabinet' && this.cabinetGroup.add(model)
            if (number === modelArray.length ){
              for ( let i = 0; i < loader.dracoLoader.workerPool.length; ++ i ) {
              	//释放
                loader.dracoLoader.workerPool[ i ].terminate();
              }
            }
          });
        });

        return;
      }

      //加载gltf文件
      modelArray.forEach(one => {
        loader.load(one.gltfUrl, gltf => {
          const model = gltf.scene;
          model.traverse(child => {
            if (child instanceof THREE.Mesh) {
              child.name = one.name;
            }
          });
          //场景中添加模型文件
          one.level === 'Cabinet' && this.cabinetGroup.add(model)
        });
      });
    },

10.优化

  • 方向1:默认情况下浏览器对于同一个域名的请求是有并发限制的,如果有多个同域名的资源,浏览器会等待前面的资源下载完毕,然后复用tcp连接发起后续的请求。https://segmentfault.com/q/1010000008676262
  • 方向2:初次加载部分,点击具体模型或者拉近视野时加载详细模型信息

11.透明度导致的穿模问题

项目中需要给部分模型标注标签,使用的是精灵模型方式,标签后方出现这种情况,经过测试发现是透明度导致的
在这里插入图片描述

const spriteMaterial = new THREE.SpriteMaterial({map: textureTree, opacity: 1});
修改为:
const spriteMaterial = new THREE.SpriteMaterial({map: textureTree, transparent :false, alphaTest:0.1});

12.辉光效果实现的几种方式

(1)使用UnrealBloomPass

使用这种方式需要注意的是辉光是加在全局的,需要修改为局部。官方的代码里有具体的实现,主要使用到的是:THREE.Layers()图层、THREE.EffectComposer(renderer)后期处理通道、glsl着色器语言
three.js官方辉光链接
对于官方辉光的详细解释
实现效果:在这里插入图片描述

(2)使用outlinePass

这种实现方式的思路是,使用three.js的边缘检测方法EdgesGeometry,对获取到的几何边缘创建line,最后使用outlinePass使其发光。这个地方有个坑是,需要注意渲染顺序,否则物体会遮挡住一部分的线条。

//这里只有核心部分代码
modelArray.forEach(one => {
        loader.load(one.gltfUrl, gltf => {
          const model = gltf.scene;
          model.traverse(child => {
            if (child instanceof THREE.Mesh) {
              child.name = one.name;
              if (one.name === 'Park_Main_Building'){
                //我这修改了一下物体材质,这个不重要
                child.material = new THREE.MeshBasicMaterial({
                  color: 0x5685cc,
                  transparent: true,
                  opacity: 0.6,
                })

				//渲染顺序
                child.renderOrder = 5;

                let edges = new THREE.EdgesGeometry(child.geometry)
                let line = new THREE.LineSegments(edges, material)
                this.parkGroup.add(line)
              }
            }
          });
        });
      });

      this.outlinePass = new OutlinePass(resolution, scene, camera, selectedObjects);
      this.outlinePass.selectedObjects = selectedObjects;
      this.outlinePass.edgeStrength = 10; // 边框的亮度,最大10
      this.outlinePass.edgeGlow = 1; // 光晕[0,1]//,最大1
      this.outlinePass.usePatternTexture = false; // 是否使用父级的材质,纯色的可以使用
      this.outlinePass.edgeThickness = 1; // 边框宽度//最大4
      this.outlinePass.downSampleRatio = 1.5; // 边框弯曲度//之前为2,,1比较合适
      this.outlinePass.pulsePeriod = pulse ? 1 : 0; // 呼吸闪烁的速度//5
      this.outlinePass.visibleEdgeColor.set(color); // 呼吸显示的颜色16进制,0x00ffff
      this.outlinePass.hiddenEdgeColor = new THREE.Color(0, 0, 0); // 呼吸消失的颜色(0,0,0)
      this.outlinePass.clear = true;
      this.composer.addPass(this.outlinePass);

实现效果:在这里插入图片描述

(3)使用LineBasicMaterial模拟发光的效果

这种方式的思路与第二种有很大的相同之处,都是先获取到的几何边缘创建line,区别在于,使用LineBasicMaterial来设置line的材质

//核心代码
const material = new THREE.LineBasicMaterial({
        color: 0x00FFFF,
        transparent: true,
        opacity: 0.5,
        polygonOffset: true,
        depthTest: true,
        polygonOffsetFactor: 1,
        polygonOffsetUnits: 1
      });
let line = new THREE.LineSegments(edges, material)

实现效果:在这里插入图片描述


五、查看学习的博客

vue集成three.js加载外部模型-一路从容

three.js模型压缩/gltf/glb,gltf-pipeline

Three.js中文网

create.js-主要使用到了tween.js

Web Workers

3D性能优化 | 说一说glTF文件压缩

总结

3D渲染图形是一个很好玩的东西,欢迎大家一起交流

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐