最近在开发过程中,遇到这么一个需求,就是需要在某个FBX模型load完毕后,需要toJSON给另一个接口。结果发现,toJSON的数据中,一直缺少image属性,导致那边接收到toJSON的模型后,没有贴图。后来发现,模型只有在场景中渲染完毕后再toJSON,才会有image。这就需要我们找到,模型渲染完毕的回调函数。
查看官方文档发现了这么个方法:onAfterRender
Object3D – three.js docs (threejs.org)
反正大致意思呢,就是那些场景中看得见的东西可以用这个方法,比如material、geometry、mesh、points、line等。而那些概念性的对象,就不能用这个方法,因为它们在视觉上没有什么可渲染的,比如Object3D、Group、Bone这种,就拿Group来说,组是一个组合的概念,将多个模型对象组合在一起,但组合本身并不是一种模型,也不需要渲染,所以Group没有geometry,也没有material。我们这里需要监听的模型就是fbx,fbx导入后,就是一个group,所以没法直接用这个方法。以上纯属个人理解,如有错误,欢迎指摘,随时更正。
继续查阅资料,发现了关于WebGLRenderer.compile和WebGLRenderer.initTexture的简介,没太看明白怎么用,直接写上也没生效,没有达到预期效果。如果有大神明白这个是干什么的以及怎么用,请赐教,小弟感激不尽(类似的简介就算了)。
后来想到用textureLoader,但toJSON的数据已经有了texture,即使用textureLoader再loader一下,也不会渲染出image,反而会多一次加载texture的操作。
最后实在没辙,决定自己写一个吧。不正经的需求,往往需要用不正经的手段来解决。
通过对模型对象的观察发现,只要group子类中material.map.image不为null的时候,就代表该材质贴图渲染完毕。于
是就有了以下不太正经的思路:
写一个方法,统计当前模型当前已渲染出来的material.map.image个数。
再写一个方法,递归自己,检测当前模型已渲染的image个数与上次个数是否一致,若不一致,则继续递归,若一致,则记录一致次数,我们假定一致次数为三次时,就表示image完全渲染,就执行回调函数。
这个一致次数,我测试用3就完全可以表示渲染完毕了,当然你可以根据自己实验来调整,因为不是什么正经方法,这里只是实验的结果,并不是什么科学的参数。
有了上述不太正经的思路,于是就有了下面不太正经的代码:
/**
* 获取模型当前已渲染的贴图数
* @param model 模型对象
* @returns {number} 贴图数
*/
function getImageCount (model) {
let iCount = 0
model.traverse(item => {
if (item instanceof THREE.Mesh) {
if (Array.isArray(item.material)) {
item.material.forEach(m => {
if (m.map && m.map.image) {
iCount++
}
})
} else {
if (item.material.map && item.material.map.image) {
iCount++
}
}
}
})
return iCount
}
/**
*
* @param model 模型对象
* @param ic 贴图计数器
* @param ec 重复次数
* @param callback 回调函数
*/
function onAfterRender (model, ic, ec, callback) {
const wait = 3
let iCount = ic
let equalCount = ec
setTimeout(() => {
const res = getImageCount(model)
if (iCount === res) {
if (equalCount === wait) {
callback()
} else {
equalCount++
onAfterRender(model, iCount, equalCount, callback)
}
} else {
iCount = res
equalCount = 0
onAfterRender(model, iCount, equalCount, callback)
}
}, 100)
}
// 测试调用
new FBXLoader().load('model/fbx/test.fbx', res => {
ts.scene.add(res)
onAfterRender(res, 0, 0, () => {
alert('渲染完毕')
console.log('渲染完毕')
})
})
测试结果
目前这么写还没发什么问题,若发现问题,后期会同步更新;若发现大坑,我会直接删除此篇文章。
如果有更好的方式,可以交流一下,感激不尽!