首次接手three项目个人小结

1,107 阅读4分钟

简介

    最近新入职一家主要做智慧城市的公司,也是我第一次实战使用 three.js ,目前要开发智慧社区类型的项目,学习使用相关 three 的效果,网上资料还不是很全面,该文章简单介绍了我的开发历程,后续会逐步更新实战效果,供大家参考。

开发环境

vue3.2 + vite4.0 + node16.16.0 + three0.145.0

最终效果图

dd518353899ed1ff53c6cfc1e4a24d8.png c1852d01dd0feed1957719ec45a548d.png

three学习资料

    目前最火的 three 教学博主有两位,一位是郭隆邦,一位是老陈打码,两位的课程目前在B站上还是不错的,讲一些基础的视频和一些练习适合入门。两位博主视频网址如下:

加载天空图

设置天空图还可以加载视频的格式作为背景,也可以准备6张图片按顺序摆好,创建盒子作为6个面贴上,还可以创建一个圆球帖上图片,下面是最简单的一种方法。

    const loader = new THREE.TextureLoader();
    const texture = loader.load('./skyImg/skys.jpg', () => {
      scene.background = texture;
    });
    

设置灯光

灯光可以重复设置多组,也可叠加,创建灯光的参数分别是(参数1:灯光颜色,参数2:颜色深度)

   // 环境光
    let AmbientLight = new THREE.AmbientLight(0xffffff, 2);
    AmbientLight.position.set(0, 10, 0);
    scene.add(AmbientLight);
    
    // 平行光
    let dirLight = new THREE.DirectionalLight(0xffffff, 2);
    dirLight.position.set(-5, 10, 20);
    scene.add(dirLight)

设置相机

相机的位置可以移动,也可以转换镜头,跟随物体的视角移动,PerspectiveCamera(参数1:摄像机看向物体的倾斜度,参数2:可视区域横纵比,参数3:看到的最近距离,参数4:看到的最远距离)

    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000);
    camera.position.set(0, 10, 10)
    camera.lookAt(new THREE.Vector3(0, 0, 0));

加载精灵图

    const Sprite = new THREE.Sprite(
        new THREE.SpriteMaterial({
          map: new THREE.TextureLoader().load(img),
        })
      );
     // 控制精灵图的缩放比例
     Sprite.scale.set(0.3, 0.3, 0.3);
     
     // 控制精灵图的位置( x,y,z), 把某个物体的坐标轴赋值给精灵图,就可以加载到物体上,一起控制
     sprite.position.set(10, 10, 0);
     scene.add(sprite);

加载视频

        const geom = new THREE.BoxGeometry(22, 16, 0.6)
        const video = document.createElement("video");
        video.src = new URL(
          `xxx.mp4`,
          import.meta.url
        ).href;
        video.muted = true;
        video.autoplay = "autoplay";
        video.loop = true;
        video.play();
        const videoTexture = new THREE.VideoTexture(video);
        const videoMaterial = new THREE.MeshBasicMaterial({
          map: videoTexture,
        });
        videoMaterial.position.set(10, 10, 10);
        scene.add(new THREE.Mesh(geom, videoMaterial)); 

在3D场景中添加标签

        // 引入
        import { CSS2DRenderer, CSS2DObject, } from "three/examples/jsm/renderers/CSS2DRenderer.js";
        
        
        // 创建标签
        const earthDiv = document.createElement('div'); 
        earthDiv.className = 'label'; 
        earthDiv.innerHTML = '地球'
        const earthLabel = new CSS2DObject(earthDiv);
        earthLabel.position.set(0,1,0); 
        // 将2d对象添加到地球上 
        物体模型.add(earthLabel)


        // 设置渲染器,这部分代码和renderer放到一起
        labelRenderer = new CSS2DRenderer() 
        labelRenderer.setSize(window.innerWidth,innerHeight); 
        document.body.appendChild(labelRenderer.domElement); 
        
        //设置2d渲染器布局 
        labelRenderer.domElement.style.position = 'fixed'; 
        labelRenderer.domElement.style.top = '0px'; 
        labelRenderer.domElement.style.left = '0px'; 
        labelRenderer.domElement.style.zIndex = '0';
        
        //这部分代码和animate放一块
        labelRenderer.render(scene,camera);

模型压缩、加载进度

模型过大的话,可以进行压缩,设置loaidng效果,压缩插件推荐2种。

  • gltf-pipeline 插件
    • 插件网址:github.com/AnalyticalG…

    • 准备工作和 obj 转 gltf 插件一样,切换插件目录下下载依赖,下载 npm install -g gltf-pipeline,准备工作完成,执行命令: gltf-pipeline -i model.gltf -o modelDraco.gltf -d 路径和 obj 转 gltf 一样

    • gltf-pipeline 压缩完,可能会造成材质丢失模糊。

    • 结尾使用-d压缩完,需要 draco 解码器编译,改动如下

        import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';//引入
        const dracoLoader = new DRACOLoader();
        dracoLoader.setDecoderPath('./public/draco-master/draco/')//存放draco的目录
        loader.setDRACOLoader(dracoLoader)
        loader.load('./public/models/xxx.gltf', (gltf) => {
            scene.add(gltf.scene);
        })
      
    • 需要下载 draco, 下载完放在 public文件下,就可以了。

    • draco下载网址:gitee.com/fan_jialong…

  • gltfpack插件
    • 插件网址:github.com/zeux/meshop…
    • 下载完插件,切换到目录下执行 npm install -g gltfpack
    • 压缩命令 gltfpack -i male.glb -o male-processed.glb -cc
    • 压缩完会产生两个文件,xxx.bin和xxx.gltf,这俩要放到一起。
    • 这个不需要插件,改动如下
      import { MeshoptDecoder } from 'three/examples/jsm/libs/meshopt_decoder.module.js';
      loader.setMeshoptDecoder(MeshoptDecoder)
      // 正常使用.....
      
    • 个人使用情况:模型60mb,这俩压缩完大概都在30mb左右,gltfpack插件压缩后的材质要比gltf-pipeline插件好些
  • 模型进度
    • 所有模型进度条

      const manager = new THREE.LoadingManager() 
      manager.onProgress = function (item, loaded, total) {
         console.log('百分比' + loaded / total * 100);
      };
      const loader = new GLTFLoader(manager);
      loader.load('url',(gltf)=>{
      scene.add(gltf.scene)          
      })
      
    • 单个模型进度条

      manager.onProgress = function (item, loaded, total) {
        console.log('百分比' + loaded / total * 100);
      };
      const loader = new GLTFLoader(manager);
      loader.load('url',(gltf)=>{
         scene.add(gltf.scene)          
      })
      

three销毁

 beforeDestroy() {
    try {
      scene.clear();
      renderer.dispose();
      renderer.forceContextLoss();
    } catch (e) {
      console.log(e);
    }
  },

three 点击模型事件

  modelClick(event) {
      event.stopPropagation();
      event.preventDefault();
      let Sx = event.clientX;
      let Sy = event.clientY;
      let x = (Sx / window.innerWidth) * 2 - 1;
      let y = -(Sy / window.innerHeight) * 2 + 1;
      let raycaster = new THREE.Raycaster();
      raycaster.setFromCamera(new THREE.Vector2(x, y), this.camera);
      let intersects = raycaster.intersectObjects(this.scene.children, true);
      let clickMesh = intersects[0] ? intersects[0].object : "";//获取到元素对象,改变点击的模型材质、颜色、位置等
      // clickMesh.material.color.set(0x444444),颜色值
      // clickMesh.material.opacity = 0,透明度
      // 点击模型弹框思路 创建一个盒子,改变盒子的left,top值,Sx对应left,Sy对应top 
  },

窗口自适应

mounted(){
  window.addEventListener("resize", this.onWindowResize, false);
}
onWindowResize() {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix(); //更新投影矩阵
  renderer.setSize(window.innerWidth, window.innerHeight);
},

可能会踩的坑

  • 模型存放路径,可以放在 public 文件下,如果放在src下面,vue2使用require方法,vue3使用new URL(url, import.meta.url)
  • 尽量避开使用obj格式,贴图容易丢失,转动相机,模型忽隐忽现,有可能是需要设置双面材质,或者设置下blog.csdn.net/eevee_1/art…
  • 在 vue 里开发的时候,那些场景实例和相机实例等相关实例最好不要放在 data 里,放在 vue 实例外面声明使用,性能会好点,或者可以封装成一个 class 类调用。
  • 设置模型透明属性(opacity),需要先设置transparent为true。
  • 在项目中发现,使用visible属性隐藏模型后,看不见了,但是点击那片区域还能获取到,很奇怪。
  • 后续踩到坑,会持续更新....