vue3 threejs 对gltf模型进行换肤

threejs yekong

threejs在对3d模型渲染的时候,可能会遇到需要换肤的情况,比如手机颜色更换,汽车颜色更换等。虽然已经看过了threejs视频教程 ,但是只是看过了还不行,还需要自己手动实践,加深印象,同时事件后保留代码实例,也会为后面可能会遇到类似的情况做准备,直接翻阅旧代码拿来复用。
只是看一遍视频不做任何操作当时觉得已经学会了,过后再遇到八成还是不知道如何处理,变成了一看就会、一做就废。
vue3 threejs 对gltf模型进行换肤vue3 threejs 对gltf模型进行换肤2

关键代码

给模型换肤的关键代码就是通过纹理加载器加载纹理图片,然后替换模型的材质map达到换肤的效果。

var texture = new THREE.TextureLoader().load('texture3.png');
obj.children[0].material.map = texture

gltf加载

3d模型一般都比较大,虽然我们这次的实力是选择的一个小模型练手,但是大模型才是主旋律,所以模型加载前,我们要先zip压缩一下,再进行解压渲染,虽然流程复杂了点,但是会为后面节省服务器带宽做贡献。

zip压缩

这是一个小模型,压缩比例大概达到了一半,在之前渲染的300多M的大模型,通过zip压缩可以压缩到20M,zip的压缩能力还是很强的。
模型zip压缩

下载渲染

压缩后我们需要使用axios进行请求下载,
下载时,增加一个进度提示,便于查看进度.
使用jszip进行文件解压.
使用URL.createObjectURL创建url
使用load进行渲染。
加载皮肤渲染

// 引入Three.js
import * as THREE from 'three'
// 引入gltf模型加载库GLTFLoader.js
import {GLTFLoader} from 'three/examples/jsm/loaders/GLTFLoader.js'
import axios from 'axios'
import JSZip from 'jszip'
import {modelUrl} from "@/config/config.js";


function handleProgress(progressEvent) {
    console.log('handleProgress', progressEvent.loaded, progressEvent.total)
    var div = document.getElementById('LoadingInfo')
    div.style.display = 'flex'
    div.innerText = `加载模型中:${(progressEvent.loaded / progressEvent.total * 100).toFixed(0)}%`
    if ((progressEvent.loaded / progressEvent.total * 100).toFixed(0) >= 100) {
        div.style.display = 'none'
    }
}

var model = new THREE.Group()//声明一个组对象,用来添加加载成功的三维场景
var loader = new GLTFLoader() //创建一个GLTF加载器
axios.get(modelUrl + 'gltf/yuzhuo/model.zip', {
    responseType: 'arraybuffer',
    onDownloadProgress: (progressEvent) => {
        let percentCompleted = Math.round(progressEvent.loaded * 100 / progressEvent.total)
        console.log(progressEvent.lengthComputable)
        console.log(percentCompleted)
        var div = document.getElementById('LoadingInfo')
        div.style.display = 'flex'
        div.innerText = `下载模型中:${percentCompleted}%`
        // if (progressEvent.lengthComputable >= 100) {
        //   div.style.display = 'none'
        // }
    }
}).then(resp => {
    let files = new window.File([resp.data], 'zipFile', {type: 'zip'})
    var new_zip = new JSZip()
    // 解压缩文件对象
    new_zip.loadAsync(files)
        .then(function (result) {
            // 压缩包的模型文件列表
            let fileList = result.files
            for (let key in fileList) {
                // 读取模型文件内容
                new_zip.file(key).async('arraybuffer').then(content => {
                    // Blob构造文件地址,通过url加载模型
                    let blob = new Blob([content])
                    console.log(blob)
                    let modelUrls = URL.createObjectURL(blob)
                    loader.load(modelUrls, function (gltf) {
                        // 递归遍历gltf.scene
                        var texture = new THREE.TextureLoader().load(modelUrl + 'gltf/yuzhuo/texture1.png');
                        gltf.scene.traverse(function (object) {
                            if (object.type === 'Mesh') {
                                // 批量更改所有Mesh的材质
                                object.material = new THREE.MeshLambertMaterial({
                                    color: object.material.color, //读取原来材质的颜色
                                    map: texture
                                })
                            }
                        })
                        model.add(gltf.scene)
                    }, handleProgress)
                })
            }
        })
})

export {model}

gltf换肤

渲染完成后就是进行换肤了。通过选择切换使用TextureLoader加载对应的图片替换模型中材质的map就达到了换肤了效果。

/**
* @Author: 858834013@qq.com
* @Name:
* @Date: 2023年02月08日
* @Desc: vue3 threejs gltf换肤
*/
<template>
  <div class="canvasGLTFBody">
    <div ref='canvasGLTF' class="canvasGLTF">
    </div>
    <div class="LoadingInfo" id="LoadingInfo"></div>
    <div class="list">
      <div class="listItem" @click="getActive(index)" :class="{active:active==index}" :style="{background:item.color}"
           v-for="(item,index) in list" :key="index">
        {{ item.title }}
      </div>
    </div>
  </div>
</template>

<script>
import {renderer} from './js/index.js'
import {model} from './js/model.js'
import * as THREE from "three";
import {modelUrl} from "@/config/config.js";

export default {
  name: 'lesson11',
  data() {
    return {
      active: 0,
      list: [{
        title: '绿',
        color: '#529664'
      }, {
        title: '黄',
        color: '#8d500c'
      }, {
        title: '白',
        color: '#aab184'
      }]
    }
  },
  props: {},
  created: function () {

  },
  mounted: function () {
    var that = this
    setTimeout(() => {
      that.$refs.canvasGLTF.appendChild(renderer.domElement)
    }, 1000)
  },
  methods: {
    getActive(index) {
      this.active = index
      this.huanfu()
    },
    huanfu() {
      var texture = new THREE.TextureLoader().load(modelUrl + 'gltf/yuzhuo/texture' + (Number(this.active) + 1) + '.png');
      model.traverse(function (object) {
        if (object.type === 'Mesh') {
          // 批量更改所有Mesh的材质
          object.material = new THREE.MeshLambertMaterial({
            color: object.material.color, //读取原来材质的颜色
            map: texture
          })
        }
      })
    }
  }
}
</script>

<style lang="scss" scoped>
.canvasGLTFBody {
  position: fixed;
  width: 100%;
  height: 100%;
  display: flex;
  justify-content: flex-start;
  align-items: flex-start;
  flex-wrap: nowrap;
  flex-direction: column;
  align-content: flex-start;
}

.canvasGLTF {
  position: relative;
  margin: 0 auto;
  width: 800px;
  height: 600px;
}

.info {
  display: flex;
  justify-content: flex-start;
  align-items: flex-start;
  flex-wrap: nowrap;
  flex-direction: column;
  align-content: flex-start;
  margin: 40px;
  line-height: 30px;

  p {
    color: #fff;
    font-size: 14px;
  }

  a {
    color: #fff;
    font-size: 14px;
  }
}

.LoadingInfo {
  width: 100%;
  height: 100%;
  position: absolute;
  background: rgba(0, 0, 0, 0.8);
  display: flex;
  justify-content: center;
  align-items: center;
  flex-wrap: nowrap;
  flex-direction: row;
  align-content: flex-start;
  color: #fff;
  z-index: 1000000;
  top: 0;
  left: 0;
  font-size: 20px;
  display: none;
}

.list {
  display: flex;
  justify-content: center;
  align-items: center;
  flex-wrap: nowrap;
  flex-direction: row;
  align-content: flex-start;
  position: fixed;
  bottom: 20px;
  width: 100%;

  .listItem {
    width: 100px;
    height: 100px;
    border-radius: 50%;
    display: flex;
    justify-content: center;
    align-items: center;
    flex-wrap: nowrap;
    flex-direction: row;
    align-content: flex-start;
    color: #fff;
    font-size: 16px;
    margin: 20px;
    cursor: pointer;
  }

  .listItem.active {
    border: red solid 1px;
  }
}
</style>

演示demo

vue3 threejs 对gltf模型进行换肤

喜欢