在上一篇我们讲解了组合模型的渲染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;
}
});
});
},
// ... 省略其他代码
}
}
换发
换发实现原理其实和换装是一样的,都是替换掉发型模型,这里我们尝试下换发色
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');
变化效果如下
换妆
上面的换装换发都没有什么技术难度,如果是直接替换贴图实现换装也不难,这里讲一下脸部贴图与妆容贴图的自由组合的这种方式实现的换装
实现原理就是拿一个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的变化,重新创建贴图之后再进行头部的渲染就能实现换妆容效果了