threejs在对3d模型渲染的时候,可能会遇到需要换肤的情况,比如手机颜色更换,汽车颜色更换等。虽然已经看过了threejs视频教程 ,但是只是看过了还不行,还需要自己手动实践,加深印象,同时事件后保留代码实例,也会为后面可能会遇到类似的情况做准备,直接翻阅旧代码拿来复用。
只是看一遍视频不做任何操作当时觉得已经学会了,过后再遇到八成还是不知道如何处理,变成了一看就会、一做就废。
关键代码
给模型换肤的关键代码就是通过纹理加载器加载纹理图片,然后替换模型的材质map达到换肤的效果。
var texture = new THREE.TextureLoader().load('texture3.png');
obj.children[0].material.map = texture
gltf加载
3d模型一般都比较大,虽然我们这次的实力是选择的一个小模型练手,但是大模型才是主旋律,所以模型加载前,我们要先zip压缩一下,再进行解压渲染,虽然流程复杂了点,但是会为后面节省服务器带宽做贡献。
zip压缩
这是一个小模型,压缩比例大概达到了一半,在之前渲染的300多M的大模型,通过zip压缩可以压缩到20M,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>