three.js加载3D模型(glb/gltf/fbx)

在这里插入图片描述

理解three:

1.一个可以在某个3D建模软件打开的东西,通过某种方案在浏览器中打开;
2.不要试图手动去创建3D图形,当然比较闲的话可以这样操作,
3.把three当作一个3D模型播放器,在播放器里可以对模型做一些操作:调色调光,调整坐标,切换视角,播放模型中的动画…;
4.某个建模软件(3dMax…)把已经做好的模型导出了模型文件(ojb,glb,gltf,fbx等格式),通过某个可支持此格式的Loader,最终渲染到场景里 ,就像webpack打包css需要css-loader一样的道理;
5.three 把WebGL的图形引擎封装成了3d api 理解WebGL:www.cnblogs.com/wanbo/p/675…
使用three:

基本思路:

1.构建场景Scene;
2.通过
Renderer渲染场景到画布(此时看到应是黑屏);
3.需要借助***Camera帮忙观察物体(此时仍然啥也木有,因为没放物体进去)
4.通过loader(GLTFLoader,FBXLoader…)加载模型;
5.通过requestAnimationFrame 把最新场景数据渲染出来

在这里插入图片描述
你通过屏幕看到永远只是平面,你看到3d物体也是相机观察后的投影,也就是下图编号区域,下方的图片也是另一台相机投影后的结果

全景项目这里推荐photo-sphere-viewer 它的依赖也是three

全景项目:https://source.nullno.com/quanjingnew

接下来直接上源码,有用欢迎start:
https://github.com/nullno/three-cli

演示效果:source.nullno.com/three-demo/

import * as THREE from './lib.js';

import BG from './model/environment/bg.jpeg';

//模型
import xsr_fbx from './model/xsr/xsr.fbx';

//纹理贴图
import xsr_fbx_texture from './model/xsr/Stormland Robo 03H.png';

import xsr_fbx_logo_texture from './model/xsr/stormland_logo.png';

const models = [
  { name: '机器头', path: require('./model/DamagedHelmet.glb').default, position: [0, 0, 5], type: 'glb' },
  {
    name: '像素人',
    path: xsr_fbx,
    position: [0, 0, 50],
    type: 'fbx',
    texture: [
      { name: 'gardener,hologram_2,hologram', path: xsr_fbx_texture },
      { name: 'Plane', path: xsr_fbx_logo_texture },
    ],
  },
];

const modelScene = {
  State: {
    showGrid: false,
    showLightOrigin: false,
    wireframe: false,
  },
  Scene: null,
  Renderer: null,
  Camera: null,
  Model: null,
  Lights: null,
  AnimationMixer: null,
  Tclock: new THREE.Clock(),
  TestGui: null,
  TestStats: null,
  Controls: null,
  GridHelper: new THREE.GridHelper(300, 50, 0x00ff12, 0xffffff),

  init: {
    //添加场景
    Scene: function () {
      this.Scene = new THREE.Scene();
      this.Scene.background = new THREE.Color(0x282923);

      this.Scene.background = new THREE.TextureLoader().load(BG);

      // THREE.Cache.enabled = true;
    },
    //添加渲染器
    Renderer: function () {
      this.Renderer = new THREE.WebGLRenderer({
        antialias: true,
        alpha: true,
        premultipliedAlpha: true,
        precision: 'highp',
      });
      this.Renderer.setPixelRatio(window.devicePixelRatio);
      this.Renderer.setSize(window.innerWidth, window.innerHeight);
      this.Renderer.setClearColor(0xeeeeee);
      this.Renderer.shadowMap.enabled = true;
      this.Renderer.physicallyCorrectLights = true;
      this.Renderer.outputEncoding = THREE.sRGBEncoding;

      ThreeApp.appendChild(this.Renderer.domElement);
    },
    //添加相机
    Camera: function () {
      this.Camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 10000);
      this.Camera.position.set(0, 0, 50);
      this.Camera.lookAt(this.Scene.position);
    },

    TestGui: function () {
      let _this = this;
      this.TestGui = new THREE.Dat.GUI();
      this.TestGui.add(
        {
          changeBg: function () {
            _this.Scene.background = new THREE.Color(0x1a1a1a);
          },
        },
        'changeBg'
      );
    },
    //帧率状态
    Stats() {
      this.TestStats = new THREE.Stats();
      document.body.appendChild(this.TestStats.dom);
    },
  },

  //加载模型GLTF FBX
  modelLoader: function (MODEL) {
    const loadTip = this.addLoadTip();

    this.Controls.autoRotate = false;
    //添加环境hdr
    MODEL.hdr && this.HdrLoader(MODEL.hdr);

    let Loader = '',
      MTYPE = MODEL.type || 'glb';

    if ('glb,gltf'.indexOf(MTYPE) != -1) {
      Loader = new THREE.GLTFLoader();
    } else if ('fbx'.indexOf(MTYPE) != -1) {
      Loader = new THREE.FBXLoader();
    } else {
      loadTip.textContent = '请使用glb,gltf,fbx格式模型';
      return;
    }

    Loader.load(
      MODEL.path,
      geometry => {
        loadTip.textContent = '加载完成!';
        //移除模型
        this.Model && this.Scene.remove(this.Model);

        //设置相机位置
        this.Camera.position.set(...MODEL.position);

        this.Model = 'fbx'.indexOf(MTYPE) != -1 ? geometry : geometry.scene;

        //遍历模型字节点,获取相关参数设置
        this.Model.traverse(function (child) {
          if (MODEL.texture) {
            MODEL.texture.map(item => {
              if (item.name.indexOf(child.name) != -1) {
                child.material = new THREE.MeshPhongMaterial({
                  map: THREE.ImageUtils.loadTexture(item.path), //颜色贴图
                });
              }
            });
          }

          if (child.isMesh) {
            // child.material.emissiveMap = child.material.map;

            //child.material.side = THREE.DoubleSide;
            child.material.shininess = 1;

            child.castShadow = true;
            child.receiveShadow = true;

            child.material.transparent = true; //材质允许透明 如果有玻璃材质时开启
            child.material.opacity = 1; //材质默认透明度
          }
        });

        //模型自动居中
        THREE.ModelAutoCenter(this.Model);

        //查找模型动画,
        if (this.Model.animations.length > 0) {
          this.AnimationMixer = new THREE.AnimationMixer(this.Model);
          this.AnimationMixer.clipAction(this.Model.animations[0]).play();

          /* 其他模型动画方案:
                        const animationClip = this.Model.animations.find(animationClip => animationClip.name === "Walk");
                        this.AnimationMixer.clipAction(animationClip).play();
                        */
        }

        //把模型放入场景中
        this.Scene.add(this.Model);

        //加载完成后开始自动播放
        setTimeout(() => {
          loadTip.style.display = 'none';
          this.Controls.autoRotate = true;
        }, 1000);
      },
      xhr => {
        //加载进度
        loadTip.textContent = parseInt((xhr.loaded / xhr.total) * 100) + '%加载中...';
      },
      err => {
        loadTip.textContent = '模型加载失败!';
        console.log('模型加载失败!');
      }
    );
  },
  //加载光源
  addLight: function () {
    this.Lights = [
      { name: 'AmbientLight', obj: new THREE.AmbientLight(0xffffff, 1) },
      { name: 'DirectionalLight_top', obj: new THREE.DirectionalLight(0xffffff, 3), position: [0, 15, 0] },
      { name: 'DirectionalLight_bottom', obj: new THREE.DirectionalLight(0x1b1b1b, 3), position: [0, -200, 0] },
      { name: 'DirectionalLight_right1', obj: new THREE.DirectionalLight(0xffffff, 1.5), position: [0, -5, 20] },
      { name: 'DirectionalLight_right2', obj: new THREE.DirectionalLight(0xffffff, 1.5), position: [0, -5, -20] },
    ];

    this.Lights.map(item => {
      item.obj.name = item.name;
      item.position && item.obj.position.set(...item.position);
      item.Helper = new THREE.PointLightHelper(item.obj);
      this.Scene.add(item.obj);
    });
  },
  //加载HDR贴图环境光
  HdrLoader: function (HDR) {
    const pmremGenerator = new THREE.PMREMGenerator(this.Renderer); // 使用hdr作为背景色
    pmremGenerator.compileEquirectangularShader();
    const textureLoader = new THREE.RGBELoader();
    textureLoader.load(HDR, (texture, textureData) => {
      const envMap = pmremGenerator.fromEquirectangular(texture).texture;

      envMap.isPmremTexture = true;
      pmremGenerator.dispose();

      this.Scene.environment = envMap; // 给场景添加环境光效果
      this.Scene.background = envMap; // 给场景添加背景图
    });
  },
  //添加事件
  addControls: function () {
    this.Controls = new THREE.OrbitControls(this.Camera, this.Renderer.domElement);
    // 如果使用animate方法时,将此函数删除
    //controls.addEventListener( 'change', render );
    // 使动画循环使用时阻尼或自转 意思是否有惯性
    this.Controls.enableDamping = true;
    //是否可以缩放
    this.Controls.enableZoom = true;
    //设置相机距离原点的最远距离-可以控制缩放程度
    this.Controls.minDistance = 0;
    //设置相机距离原点的最远距离
    this.Controls.maxDistance = 3000; //800
    //是否开启右键拖拽
    this.Controls.enablePan = false;
    //动态阻尼系数 就是鼠标拖拽旋转灵敏度
    this.Controls.dampingFactor = 0.5;
    //是否自动旋转
    this.Controls.autoRotate = false;
    this.Controls.autoRotateSpeed = 1;
  },
  //模型切换
  switchModel() {
    const _scope = this;

    var switchModelStyle = document.createElement('style');
    switchModelStyle.type = 'text/css';
    switchModelStyle.innerText +=
      '.modelList{position:fixed;width:100%; display:flex;justify-content:space-around; bottom:0;left:0;color:#0EF4F4;background:rgba(14,14,44,0.9);cursor:pointer;}\
                .modelList li{width:50%;line-height:30px;padding:5px;text-align:center;font-size:14px;}.modelList li:last-child{border:0;}.modelList li:hover,.modelList li.on{background:#0E2440;}';

    const modelUL = document.createElement('ul');
    modelUL.className = 'modelList';

    models.map((item, index) => {
      modelUL.innerHTML += '<li class="' + (index == 0 ? 'on' : '') + '">' + item.name + '</li>';
    });

    document.head.insertBefore(switchModelStyle, document.head.lastChild);
    ThreeApp.insertBefore(modelUL, ThreeApp.firstChild);

    let LIS = modelUL.children;

    for (let i = 0; i < LIS.length; i++) {
      LIS[i].onclick = function () {
        for (let j = 0; j < LIS.length; j++) {
          LIS[j].className = '';
        }
        this.className = 'on';
        _scope.modelLoader(models[i]);
      };
    }
  },
  //添加加载进度
  addLoadTip: function () {
    document.querySelector('.loadTip') && ThreeApp.removeChild(document.querySelector('.loadTip'));
    let loadTip = document.createElement('div');
    loadTip.className = 'loadTip';
    loadTip.style.cssText +=
      'position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);border-radius:5px;background-color:rgba(0,0,0,0.5);padding:5px 10px;color:#fff;';
    ThreeApp.appendChild(loadTip);
    return loadTip;
  },

  //添加辅助面板
  addPanel: function () {
    const _scope = this;

    //帧率状态
    this.init.Stats.call(this);

    // 添加
    const Panels = [
      {
        name: '辅助网格',
        todo: function () {
          if (_scope.State.showGrid) {
            _scope.Scene.remove(_scope.GridHelper);
            _scope.State.showGrid = false;
          } else {
            _scope.Scene.add(_scope.GridHelper);
            _scope.State.showGrid = true;
          }
        },
      },
      {
        name: '显示光源',
        todo: function () {
          if (_scope.State.showLightOrigin) {
            _scope.Lights.map(item => {
              _scope.Scene.remove(item.Helper);
            });
            _scope.State.showLightOrigin = false;
          } else {
            _scope.Lights.map(item => {
              _scope.Scene.add(item.Helper);
            });

            _scope.State.showLightOrigin = true;
          }
        },
      },
      {
        name: '骨架模式',
        todo: function () {
          if (_scope.State.wireframe) {
            _scope.Model.traverse(child => {
              if (child.isMesh) {
                child.material.wireframe = false;
              }
            });
            _scope.State.wireframe = false;
          } else {
            _scope.Model.traverse(child => {
              if (child.isMesh) {
                child.material.wireframe = true;
              }
            });
            _scope.State.wireframe = true;
          }
        },
      },
    ];

    //辅助面板DomTree
    var helpPanelStyle = document.createElement('style');
    helpPanelStyle.type = 'text/css';
    helpPanelStyle.innerText +=
      '#helpPanel{position:fixed;width:80px;top:50px;left:0;color:#0EF4F4;background:#0E0E2C;cursor:pointer;}\
                 #helpPanel li{border-bottom:1px solid #fff;line-height:30px;text-align:center;font-size:14px;}#helpPanel li:last-child{border:0;}#helpPanel li.on{color:green;}';
    var helpPanel = document.createElement('ul');
    helpPanel.id = 'helpPanel';

    Panels.forEach(item => {
      let LI = document.createElement('li');

      LI.innerText = item.name;
      LI.onclick = function () {
        this.className = this.className == 'on' ? '' : 'on';
        item.todo(this);
      };
      helpPanel.appendChild(LI);
    });

    document.head.insertBefore(helpPanelStyle, document.head.lastChild);
    ThreeApp.insertBefore(helpPanel, ThreeApp.firstChild);
  },
  animation: function () {
    //更新控制器
    this.Renderer.render(this.Scene, this.Camera);
    this.TestStats.update();
    this.Controls.update();
    this.AnimationMixer && this.AnimationMixer.update(this.Tclock.getDelta());
    requestAnimationFrame(() => this.animation());
  },
  onWindowResize: function () {
    this.Camera.aspect = window.innerWidth / window.innerHeight;
    this.Camera.updateProjectionMatrix();
    this.Renderer.setSize(window.innerWidth, window.innerHeight);
    this.Renderer.render(this.Scene, this.Camera);
  },
  run: function () {
    this.init.Renderer.call(this);
    this.init.Scene.call(this);
    this.init.Camera.call(this);

    this.addControls();
    //添加环境光
    this.addLight();
    this.modelLoader(models[0]);

    //添加辅助面板
    this.addPanel();
    this.animation();
    this.switchModel();

    window.onresize = () => this.onWindowResize();
  },
};

modelScene.run();


  • 18
    点赞
  • 69
    收藏
    觉得还不错? 一键收藏
  • 12
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值