最新公告
  • 欢迎您光临网站无忧模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • vue中使用Three.js心得记录

    正文概述 掘金(setUser)   2020-12-11   684

    记录

    最近公司接到一个新项目,有关包装设计的,里面需要涉及的技术难点就是3d模型这块,由于我们公司的前端都没有接触过3d,也不知为何,老大将重任交付给了我,咱也只好接下了,选用的技术是Nuxt+three.js,这里主要讲一下three.js 的使用

    初始化

    在vue的项目中先要安装必要的three.js相关插件

    npm install three three-orbitcontrols three-obj-mtl-loader stats-js

    • three:核心库;
    • stats-js: 性能检测
    • three-orbitcontrols:控制器鼠标旋转3D模型使用;
    • three-obj-mtl-loader:模型导入加载器;

    一个模型能显示在屏幕上的必然条件

    • 需要一个cavnas作为3D渲染的空间;
    • 场景: 用来放置各种模型,相机或者光源的;
    • 相机: 用来观看场景; 相机分为透视相机和正视相机;
    • 光源: 用来照亮场景,以及物体;
    • 模型: 模型可以自己通过three.js构建,也可以采用3D建模软件建立好的模型进行导入,本文采用的是.Obj格式的模型文件导入。

    1、创建场景

    createScene() {
      this.container = document.getElementById("view-box");
      // 创建场景(所有的模型,相机,灯光,都需要通过add方法添加到场景中)
      this.scene = new THREE.Scene();
      // 添加场景背景色
      this.scene.background = new THREE.Color(0xf3f3f3);
      // 创建渲染函数
      this.renderer = new THREE.WebGLRenderer({
        antialias: true, // 抗锯齿 模型质量更高 消耗性能越大
        alpha: true // 开启透明度
      });
      this.renderer.setPixelRatio(window.devicePixelRatio);
      this.renderer.setSize(this.windowHalfX, this.windowHalfY);
      // 将渲染的3d场景挂载到指定的DOM上
      this.container.appendChild(this.renderer.domElement);
    }
    

    2、创建相机

    createCamera() {
      this.camera = new THREE.PerspectiveCamera(
        45, // 摄像机视锥体垂直视野角度  
        this.windowHalfX / this.windowHalfY, // 摄像机视锥体长宽比 (要渲染区域的 长 / 宽 ,如果不对渲染会出现变形效果)
        1, // 摄像机视锥体远端面 (最近能看到的距离)
        2000 // 摄像机视锥体近端面 (最远能看到的距离)
      );
      // 设置相机距离屏幕的距离
      this.camera.position.z = 50; 
      // 将相机添加到场景中
      this.scene.add(this.camera);
    },
    

    3、创建灯光

    灯光不做详细介绍,在本次项目中没有使用到,只用到了基本的环境光

    createLight() {
      let color = 0xf9f9f9
      // 创建环境光,环境光没有投影,会均匀的照亮环境中所有的物体
      let ambientLight = new THREE.AmbientLight(color);
      // 将环境光添加到场景中
      this.scene.add(ambientLight);
      // 创建聚光灯光源,有投影
      let directionalLight = new THREE.DirectionalLight(0xb4b6fd); 
      directionalLight.position.set(40, 60, 20);
      directionalLight.castShadow = true;
      directionalLight.shadowCameraNear = 2;
      directionalLight.shadowCameraFar = 100;
      directionalLight.shadowCameraLeft = -50;
      directionalLight.shadowCameraRight = 50;
      directionalLight.shadowCameraTop = 50;
      directionalLight.shadowCameraBottom = -50;
      // 将聚光灯添加到场景中
      this.scene.add(directionalLight);
    },
    

    4、创建模型 导入的模型格式为(.obj)

    createModel() {
      let mtlLoader = new MTLLoader() 
       // 加载材质.mtl文件
        mtlLoader.load(`/obj/white-box.mtl`, (materials) => {
        materials.preload();
        let loader = new OBJLoader(this.manager);
        loader.load(
          `/obj/抽屉盒-简模贴图.obj`,
          (obj) => {
            this.object = obj;
            // 设置模型中心点居中
            this.object.children[0].geometry.center()
            this.object.children[0].geometry.computeBoundingBox();
          }
        );
      })
    },
    

    5、创建贴图

    涉及到需要改变每个面上贴图的会有多层,所以选择使用canvas贴图,在canvas上操作更方便

    /**
     * 创建一个贴图
     */
    createTexture() {
      this.currentModelTexture.top = new THREE.CanvasTexture(this.createCanvas());
      this.lodingModelAndTexture()
    },
    /**
     * 加载模型贴图
     */
    lodingModelAndTexture() {
      let loadModel = () => {
        this.object.traverse((child) => {
        // 设置模型的长宽高比例
        if (child.isMesh) child.scale.set(1, 1, 1);
            if (child instanceof THREE.Mesh) {
              if(child.material instanceof Array) {
              	child.material.forEach(item => {
                    // item.map = this.currentModelTexture[item.name]  本身项目中有很多面
                    item.map = this.currentModelTexture.top // 这是为了方便理解,只开放了一个面
                });
                child.castShadow = true;
                child.receiveShadow = true;
              }
            }
        });
        // 将模型添加到场景
        this.scene.add(this.object);
      };
    },
    /**
     * 返回一个canvas
     */
    createCanvas() {
    	const canvas = utils.createHiDPICanvas(width, height, 4)
        const ctx = canvas.getContext('2d');
        ctx.fillStyle = #fff;
        ctx.fillRect(0, 0, 画布宽, 画布高);
        ctx.textBaseline = "hanging";
        let img = new Image();
            img.src = '1.jpg';
        let alpha = 1, // 透明度
        	deg = 180; // 旋转
        img.onload = (e) => {
          ctx.save(); // 保存画布状态
          ctx.globalAlpha = alpha;
          ctx.translate(画布宽 / 2, 画布高 / 2); // 设置旋转按照图片的中心旋转
          ctx.rotate(deg * Math.PI / 180);
          ctx.drawImage(zindex.img, x, y, 图片原始宽度, 图片原始高度, -(w / 2), -(h / 2), w, h);
          ctx.restore(); // 恢复状态
        }
        return canvas
    },
    /**
     * 创建高清canvas画布
     * @param { 宽 } w 
     * @param { 高 } h 
     * @param { 像素 } ratio 
     */
    createHiDPICanvas(w, h, ratio) {
      const PIXEL_RATIO = (function() {
          const c = document.createElement("canvas"),
              ctx = c.getContext("2d"),
              dpr = window.devicePixelRatio || 1,
              bsr = ctx['webkitBackingStorePixelRatio'] ||
              ctx['mozBackingStorePixelRatio'] ||
              ctx['msBackingStorePixelRatio'] ||
              ctx['oBackingStorePixelRatio'] ||
              ctx['backingStorePixelRatio'] || 1;
          return dpr / bsr;
      })();
      if (!ratio) { ratio = PIXEL_RATIO; }
      const can = document.createElement("canvas");
      can.width = w * ratio;
      can.height = h * ratio;
      can.style.width = w + "px";
      can.style.height = h + "px";
      can.getContext("2d").setTransform(ratio, 0, 0, ratio, 0, 0);
      return can;
    }
    

    6、动画

    // 动画
    animate() {
      if(this.object) {
          this.object.children[0].rotation.y += 0.001
      }
      // 原生js动画函数
      requestAnimationFrame(this.animate);
      this.renderer && this.render();
    },
    // 渲染
    render() {
      this.camera.lookAt(this.scene.position);
      this.renderer.render(this.scene, this.camera);
    },
    init() {
      this.createScene()
      this.createCamera()
      this.createLight()
      this.createTexture()
      this.$nextTick(() => {
        this.createModel()
      });
      this.createOther()
    },
    // 开始加载
    loding() {
      this.windowHalfX = this.$refs.three.offsetWidth;
      this.windowHalfY = window.innerHeight - 80;
      this.init();
      this.animate();
    },
    

    7、其他控制器以及辅助设置

    createOther() {
      // 辅助对象
      var AxesHelper = new THREE.AxesHelper(150);
      this.scene.add(AxesHelper);
      // 控制器
      var controls = new OrbitControls(this.camera, this.renderer.domElement);
      this.renderer.domElement.removeAttribute('tabindex')
      controls.addEventListener("change", this.render);
      this.render();
    },
    

    8、性能优化,缓存清理

    // 清空渲染器缓存
      clearRenderer() {
          this.object && this.clearCache(this.object.children[0])
          this.renderer.forceContextLoss();
          this.currentModelTexture = null
          this.renderer.dispose();
          this.renderer.domElement = null;
          this.renderer = null;
      }
      // 在vue的销毁钩子中调用
      destroyed() {
      	this.clearRenderer()
      }
    

    9、其他用到的函数方法

    插入排序

    /**
     * @param { 排序数组 } arr
     * @param { 要对比的值 } val
     */
    const Insertion = (arr, val) => {
        let handle = []
        handle.push(arr[0])
        for (let i = 1; i < arr.length; i++) {
            let a = arr[i][val]
            for (let j = handle.length - 1; j >= 0; j--) {
                let b = handle[j][val]
                if (a > b) {
                    handle.splice(j + 1, 0, arr[i])
                    break
                }
                if (j === 0) {
                    handle.unshift(arr[i])
                }
            }
        }
        return handle
    }
    

    图片转base64

    /**
     * @param { 图片加载地址 } url 
     */
    const converbase64Img = (url) => {
        var canvas = document.createElement('CANVAS'),
            ctx = canvas.getContext('2d');
        return new Promise((resolve, reject) => {
            let img = new Image;
            img.crossOrigin = '';
            img.src = url;
            img.onload = function() {
                canvas.height = img.height;
                canvas.width = img.width;
                ctx.drawImage(img, 0, 0);
                var dataURL = canvas.toDataURL('image/png');
                resolve(dataURL)
                canvas = null;
            };
            img.onerror = (e) => {
                reject(e)
            }
        })
    }
    
    • three.js 中文文档 www.yanhuangxueyuan.com/threejs/doc…
    • vue+threejs使用canvas纹理 lindasoft.com/view/articl…

    下载网 » vue中使用Three.js心得记录

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元