ThreeJS之3D人物模型换装、换发、换妆(换肤)

3,136 阅读3分钟

在上一篇我们讲解了组合模型的渲染juejin.cn/post/726980…,我们要在组合模型的基础上才能实现这些功能

这里先将一下思路

首先是换装,换装是最简单的功能,其实就是替换掉衣服模型

再就是换发,换发也和换装差不多,就是换掉模型,如果需要改变发色,就通过贴图调整或者设置material颜色进行调整

最后是换妆,换妆的这里以面部妆容为例,实现的思路就是修改贴图,简单一点的就是将所有脸型的妆容贴图都准备好,这里讲解复杂一点的,脸部贴图与妆容贴图的自由组合的实现方式

换装

这里使用的资源全部来自readyplayerme,是将整个身体模型替换掉实现换装效果

export default {
  data () {
    return {
      body_: null, // 身体模型资源地址
      body_model: null, // 身体服饰模型
    }
  },
  props: {
    // 身体服饰资源地址
    body: {
      type: String,
      default: `${process.env.BASE_URL}resources/base/body.glb`
    },
  },
  watch: {
    // 通过监听资源地址的变化,来触发换装功能,替换掉原来的模型资源
    body: {
      handler (newValue) {
        if (newValue) {
          this.body_ = this.body;
        } else {
          this.body_ = `${process.env.BASE_URL}resources/base/body.glb`;
        }
        this.createBody();
      },
      immediate: true
    }
  },
  methods: {
    // ... 省略其他代码
    
    // 换装
    createBody () {
      const loader = new GLTFLoader();
      loader.load(this.body_, (gltf) => {
        // 如果模型不为空,则先将原有的模型从组中删除掉
        if (this.body_model) {
          this.modelGroup.remove(this.body_model);
        }
        const model = gltf.scene;
        // 给模型重新赋值并添加到组中
        this.body_model = model;
        this.modelGroup.add(this.body_model);
        model.position.y = this.position_y;
        model.traverse((o) => {
          if (o.isMesh && o.name === 'Wolf3D_Body') {
            o.material.color = new THREE.Color(new THREE.Color(this.skinColor)); // 这里也可以将skinColor做成一个动态值实现换肤色的功能
          }
          if (o.isMesh) {
            o.castShadow = true;
          }
        });
      });
    },
    
    // ... 省略其他代码
  }
}

换装.gif

换发

换发实现原理其实和换装是一样的,都是替换掉发型模型,这里我们尝试下换发色

createHair () {
  if (!this.hair_) {
    return;
  }
  const loader = new GLTFLoader();
  loader.load(this.hair_, (gltf) => {
    if (this.hair_model) {
      this.modelGroup.remove(this.hair_model);
    }
    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');
      }
    });
  });
},

将原本050505的颜色换成白色

o.material.color = new THREE.Color('#ffffff');

变化效果如下

image.png

image.png

换妆

上面的换装换发都没有什么技术难度,如果是直接替换贴图实现换装也不难,这里讲一下脸部贴图与妆容贴图的自由组合的这种方式实现的换装

实现原理就是拿一个png格式的妆容贴图,覆盖到脸部贴图上面(妆容贴图和脸部贴图的制作规格需要一致),具体采用的是canva图片合成技术

/** 创建贴图 */
createTextur () {
  // 创建一个canvas用来绘制图片
  const canvas = document.createElement('canvas');
  canvas.width = 1024;
  canvas.height = 1024;

  const ctx = canvas.getContext('2d');

  // 采用promise只是确保整体代码能够同步执行,这里不用在意
  return new Promise((resolve, reject) => {
    const img = new Image();
    img.src = this.hair_base_; // 这里是基础贴图地址(脸部妆容贴图)
    img.onload = () => { // 加载完成之后绘制到canvas上去
      ctx.drawImage(img, 0, 0, 1024, 1024);
      // 如果妆容贴图this.makeup存在值,则进行贴图合成
      if (this.makeup) {
        const img1 = new Image();
        img1.src = this.makeup; // 妆容贴图地址
        img1.onload = function () { // 加载完成之后绘制到canvas上去
          ctx.drawImage(img1, 0, 0, 1024, 1024);

          // texture的创建是能够直接使用canvas创建的
          const texture = new THREE.Texture(
            canvas,
            THREE.UVMapping, // UV坐标将被用于纹理映射
            THREE.RepeatWrapping, // 这个值定义了纹理贴图在水平方向上将如何包裹,在UV映射中对应于U
            THREE.RepeatWrapping // 这个值定义了纹理贴图在垂直方向上将如何包裹,在UV映射中对应于V
          );
          texture.needsUpdate = true;

          texture.flipY = false;

          resolve(texture);
        };
      } else {
        // 不存在值就只使用基础贴图
        const texture = new THREE.Texture(
          canvas,
          THREE.UVMapping,
          THREE.RepeatWrapping,
          THREE.RepeatWrapping
        );
        texture.needsUpdate = true;

        texture.flipY = false; // 纹理是否沿垂直轴翻转 默认值为true

        resolve(texture);
      }
    };
  });
},

监听makeup的变化,重新创建贴图之后再进行头部的渲染就能实现换妆容效果了

换妆容.gif