Vue中ThreeJS加载渲染人物组合模型(以glb为例)

3,573 阅读2分钟

ThreeJS的渲染基础知识这里就不再说了,不太熟悉的可以去www.webgl3d.cn/学习

这里渲染人物组合模型主要涉及:身体渲染、头部模型渲染、头发模型渲染、场景渲染等 接下来我们一步一步拆解教学

初始化canvas等基础数据

所有的渲染都是在canvas上进行的,我们需要设置要canvas的大小,动态设置一下canvas的大小,保证canvas大小和浏览器窗口大小一样

/** 初始化canvas */
initCanvas () {
  window.onresize = this.updateCanvas; // 监听窗口大小变化事件,动态设置canvas大小
  this.updateCanvas();
},
/** 更新canvas的宽高 */
updateCanvas () {
  const threejsCreater = this.$refs.threejsCreater;
  const width = threejsCreater.clientWidth;
  const height = threejsCreater.clientHeight;
  this.canvasStyle = {
    width: width + 'px',
    height: height + 'px'
  };
},

初始化好场景、相机、光源、控制器等具体代码会放到最后

创建身体

组合模型其实就是在同一三维空间坐标中,通过调整模型的坐标位置让其看上去组合在一起,接下来我们以身体为根基进行拼装组合

createBody () {
  const loader = new GLTFLoader(); // 创建加载器,不同格式的3D模型使用不同的加载器,例fbx使用FBXLoader
  loader.load(`${process.env.BASE_URL}resources/base/body.glb`, (gltf) => {
    const model = gltf.scene; // GLTFLoader加载出来之后需要取.scene使用,不同的加载器加载出来的数据结构有一点点差异
    this.modelGroup.add(model); // 添加到组中
    model.position.y = this.position_y; // 通过变量position_y去统一模型位置
    model.traverse((o) => {
      // 设置皮肤颜色
      if (o.isMesh && o.name === 'Wolf3D_Body') {
        o.material.color = new THREE.Color(new THREE.Color('#996144'));
      }
      // 开启阴影投射
      if (o.isMesh) {
        o.castShadow = true;
        // o.receiveShadow = true; // 是否接收阴影
      }
    });
  });
},

image.png

创建头部

头部通过调整位置与身体重合就能组成一个人物模型

createHead () {
  const loader = new GLTFLoader();
  // 创建脸的material
  const texture_hair_base = new THREE.TextureLoader().load(
    `${process.env.BASE_URL}resources/base/hair_base.jfif`
  );
  texture_hair_base.flipY = false; // 纹理将沿垂直轴翻转,默认值是 true
  const texture_face_ao = new THREE.TextureLoader().load(
    `${process.env.BASE_URL}resources/base/face_ao.jpg`
  );
  const texture_face_roughness = new THREE.TextureLoader().load(
    `${process.env.BASE_URL}resources/base/roughness.jpg`
  );
  const material_face = new THREE.MeshStandardMaterial({
    map: texture_hair_base,
    aoMap: texture_face_ao,
    roughness: 0.5,
    roughnessMap: texture_face_roughness
  });

  // 创建眼睛的material
  const texture_eye = new THREE.TextureLoader().load(`${process.env.BASE_URL}resources/base/eye-01-mask.jpg`);
  const material_eye = new THREE.MeshPhysicalMaterial({
    map: texture_eye
  });

  loader.load(`${process.env.BASE_URL}resources/base/head.glb`, (gltf) => {
    const model = gltf.scene;
    this.modelGroup.add(model);
    model.position.y = this.position_y + 1.55;
    model.position.z = 0.04;
    model.traverse((o) => {
      if (o.isMesh && o.name === 'Wolf3D_Head_1') {
        // 启用投射和接收阴影的能力
        o.castShadow = true;
        o.material = material_face;
      } else if (o.isMesh && o.name === 'Wolf3D_Head_2') {
        // 启用投射和接收阴影的能力
        o.castShadow = true;
        o.material = material_eye;
      }
    });
  });
},

image.png

创建头发

头发和头的原理是一样的,不过是将头发模型盖在头上

createHair () {
  const loader = new GLTFLoader();
  loader.load(`${process.env.BASE_URL}resources/hair/hair1/hair.glb`, (gltf) => {
    const model = gltf.scene;
    this.hair_model = model;
    this.modelGroup.add(model);
    model.position.y = this.position_y + 1.55;
    model.position.z = 0.04;
    model.traverse((o) => {
      if (o.isMesh) {
        o.material.color = new THREE.Color('#050505'); // 设置头发颜色
      }
    });
  });
},

image.png

创建场景

为了让模型看上去更真实,我们可以创建一个场景,来接收阴影,烘托渲染效果

createScene () {
  const loader = new GLTFLoader();
  loader.load(`${process.env.BASE_URL}resources/scene/scene1/scene.glb`, (gltf) => {
    const model = gltf.scene;
    this.scene.add(model);

    model.position.y = -16.36;
    model.position.z = 1.5;
    model.rotateY(-1.5707963267948966);

    model.traverse((o) => {
      if (o.isMesh) {
        o.receiveShadow = true; // 接收阴影
      }
    });
  });
}

image.png

模型资源来自readyplayerme,仅供大家学习。

完整代码放在下方

<template>
  <div ref="threejsCreater" class="threejs-creater">
    <canvas
      id="canvas"
      ref="canvas"
      class="canvas"
      :style="canvasStyle"
    ></canvas>
  </div>
</template>
<script>
import * as THREE from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
export default {
  data () {
    return {
      scene: null,
      camera: null,
      renderer: null,
      modelGroup: null,
      canvasStyle: null,
      position_y: -1, // 模型y轴位置
      hairColor: '#050505' // 头发颜色
    };
  },
  mounted () {
    this.initCanvas();
    this.init();
  },
  methods: {
    /** 初始化canvas */
    initCanvas () {
      window.onresize = this.updateCanvas;
      this.updateCanvas();
    },
    /** 更新canvas的宽高 */
    updateCanvas () {
      const threejsCreater = this.$refs.threejsCreater;
      const width = threejsCreater.clientWidth;
      const height = threejsCreater.clientHeight;
      this.canvasStyle = {
        width: width + 'px',
        height: height + 'px'
      };
    },

    /** 创建场景 */
    initScene () {
      const scene = new THREE.Scene();
      this.scene = scene;

      // 将modelGroup作为容器存放组合的模型
      this.modelGroup = new THREE.Object3D();
      this.scene.add(this.modelGroup);
    },

    /** 创建相机 */
    initCamera () {
      const camera = new THREE.PerspectiveCamera(
        50,
        window.innerWidth / window.innerHeight,
        0.1,
        1000
      );
      // 设置相机位置
      camera.position.z = 3;
      camera.position.x = 0;
      camera.position.y = 0;

      this.camera = camera;
    },

    /** 创建光源 */
    initLight () {
      const hemiLight = new THREE.HemisphereLight(0xffffff, 0xffffff, 0.5);
      hemiLight.position.set(0, 50, 0);
      this.scene.add(hemiLight);

      const dirLight1 = new THREE.DirectionalLight(0xffffff, 1);
      dirLight1.position.set(5, 5, 5);
      dirLight1.castShadow = true;
      this.scene.add(dirLight1);
    },

    /** 创建渲染器 */
    initRenderer () {
      const canvas = document.querySelector('#canvas');
      const renderer = new THREE.WebGLRenderer({
        canvas,
        antialias: true // 抗齿距
      });
      renderer.outputEncoding = THREE.sRGBEncoding;
      renderer.shadowMap.enabled = true; // 投射阴影
      renderer.shadowMap.type = THREE.PCFShadowMap;
      renderer.setPixelRatio(window.devicePixelRatio);
      this.$refs.threejsCreater.appendChild(renderer.domElement);
      this.renderer = renderer;

      // Threejs控制器,可以帮助我们实现以目标为焦点的旋转缩放等
      const controls = new OrbitControls(this.camera, renderer.domElement);
      controls.maxPolarAngle = Math.PI / 2;
      controls.minPolarAngle = Math.PI / 3;
      controls.enableDamping = true;
      // controls.enablePan = false;
      controls.dampingFactor = 0.1;
      controls.autoRotate = false; // Toggle this if you'd like the chair to automatically rotate
      controls.autoRotateSpeed = 0.2; // 30
      controls.minDistance = 1;
      controls.maxDistance = 20;
    },

    /** 初始化 */
    init () {
      this.initScene();
      this.initCamera();
      this.initLight();
      this.initRenderer();

      this.createBody();
      this.createHead();
      this.createHair();
      this.createScene();
      this.clock = new THREE.Clock();
      this.update();
    },

    update () {
      if (this.resizeRendererToDisplaySize(this.renderer)) {
        const canvas = this.renderer.domElement;
        this.camera.aspect = canvas.clientWidth / canvas.clientHeight;
        this.camera.updateProjectionMatrix();
      }

      this.renderer.render(this.scene, this.camera);
      requestAnimationFrame(this.update);
    },

    resizeRendererToDisplaySize (renderer) {
      const canvas = renderer.domElement;
      const width = window.innerWidth;
      const height = window.innerHeight;
      const canvasPixelWidth = canvas.width / window.devicePixelRatio;
      const canvasPixelHeight = canvas.height / window.devicePixelRatio;

      const needResize =
        canvasPixelWidth !== width || canvasPixelHeight !== height;
      if (needResize) {
        renderer.setSize(width, height, false);
      }
      return needResize;
    },

    createBody () {
      const loader = new GLTFLoader();
      loader.load(`${process.env.BASE_URL}resources/base/body.glb`, (gltf) => {
        const model = gltf.scene;
        this.modelGroup.add(model); // 添加到组中
        model.position.y = this.position_y; // 通过变量position_y去统一模型位置
        model.traverse((o) => {
          // 设置皮肤颜色
          if (o.isMesh && o.name === 'Wolf3D_Body') {
            o.material.color = new THREE.Color(new THREE.Color('#996144'));
          }
          // 开启阴影投射
          if (o.isMesh) {
            o.castShadow = true;
            // o.receiveShadow = true; // 是否接收阴影
          }
        });
      });
    },

    createHead () {
      const loader = new GLTFLoader();
      // 创建脸的material
      const texture_hair_base = new THREE.TextureLoader().load(
        `${process.env.BASE_URL}resources/base/hair_base.jfif`
      );
      texture_hair_base.flipY = false; // 纹理将沿垂直轴翻转,默认值是 true
      const texture_face_ao = new THREE.TextureLoader().load(
        `${process.env.BASE_URL}resources/base/face_ao.jpg`
      );
      const texture_face_roughness = new THREE.TextureLoader().load(
        `${process.env.BASE_URL}resources/base/roughness.jpg`
      );
      const material_face = new THREE.MeshStandardMaterial({
        map: texture_hair_base,
        aoMap: texture_face_ao,
        roughness: 0.5,
        roughnessMap: texture_face_roughness
      });

      // 创建眼睛的material
      const texture_eye = new THREE.TextureLoader().load(`${process.env.BASE_URL}resources/base/eye-01-mask.jpg`);
      const material_eye = new THREE.MeshPhysicalMaterial({
        map: texture_eye
      });

      loader.load(`${process.env.BASE_URL}resources/base/head.glb`, (gltf) => {
        const model = gltf.scene;
        this.modelGroup.add(model);
        model.position.y = this.position_y + 1.55;
        model.position.z = 0.04;
        model.traverse((o) => {
          if (o.isMesh && o.name === 'Wolf3D_Head_1') {
            // 启用投射和接收阴影的能力
            o.castShadow = true;
            o.material = material_face;
          } else if (o.isMesh && o.name === 'Wolf3D_Head_2') {
            // 启用投射和接收阴影的能力
            o.castShadow = true;
            o.material = material_eye;
          }
        });
      });
    },

    createHair () {
      const loader = new GLTFLoader();
      loader.load(`${process.env.BASE_URL}resources/hair/hair1/hair.glb`, (gltf) => {
        const model = gltf.scene;
        this.hair_model = model;
        this.modelGroup.add(model);
        model.position.y = this.position_y + 1.55;
        model.position.z = 0.04;
        model.traverse((o) => {
          if (o.isMesh) {
            o.material.color = new THREE.Color('#050505'); // 设置头发颜色
          }
        });
      });
    },

    createScene () {
      const loader = new GLTFLoader();
      loader.load(`${process.env.BASE_URL}resources/scene/scene1/scene.glb`, (gltf) => {
        const model = gltf.scene;
        this.scene.add(model);

        model.position.y = -16.36;
        model.position.z = 1.5;
        model.rotateY(-1.5707963267948966);

        model.traverse((o) => {
          if (o.isMesh) {
            o.receiveShadow = true; // 接收阴影
          }
        });
      });
    }

  }
};
</script>
<style lang="scss" scoped>
.threejs-creater {
  position: relative;
  width: 100%;
  height: 100%;
  .canvas {
    display: block;
    touch-action: none;
  }
}
</style>