在上一篇《React + ThreeJS 实践(一)》中摸索了场景构建和数据存储方式,这一篇来讲述一下 MQTT 实时数据的展示与更新。
在本文中我们要考虑如何在物体上实时显示数据、如何在物体上新增 popup 效果。
在 Threejs 中有很多种文字展示方式:CSS2DRenderer、CSS3DRenderer、Texture 等。
具体说明:https://threejs.org/docs/index.html?q=text#manual/en/introduction/Creating-text
在实际开发中,为了节省时间,我们尽可能选择最方便的一种方式,在此之前我们简单了解其中几个方式。
CSS2DRenderer 是 CSS3DRenderer 的简化版,它们可以直接将 DOM 元素以 2D / 3D 的形式显示在页面中。
这两个方式无疑是最容易实现好看效果的,因为可以直接用 CSS 写样式。
在这种标签渲染器中,需要创建一个 Render,它与 Three 模型层是分开的,这意味着他不存在物体的前后遮挡关系。同时,我们在对模型层进行更新的时候也需要更新标签渲染器,比如大小更新、动画更新。
回想开头我们想要实现的 popup 效果,是不是可以根据 CSS2DRenderer 来实现?
参考文档:
https://threejs.org/docs/index.html?q=css#examples/en/renderers/CSS2DRenderer
https://threejs.org/docs/index.html?q=css#examples/en/renderers/CSS3DRenderer
由于以上的方式,没能实现我们需要在物体上实时更新数据,因为它缺少物体的前后遮挡关系。
物体显示的层级关系,还有物体中的旋转角度、位置向量都是我们需要考虑的问题,于是我想到了“纹理贴图”。
纹理的实现方式是使用 CanvasTexture,在 Canvas 中创建文本贴图,再将 Canvas 转为物体材质映射到物体上即可。使用贴图的优势是不需要考虑层级关系、也不需要考虑物体的位置或旋转,只需要将贴图往物体上一贴,便可以轻松实现我们需要的效果。
在 React 代码中,我将 MQTT 封装为一个 Context,提供:SetTopic、AddCallback、RemoveCallback、RemoveAllCallback 的方法。Callback 数据作为一个 Map 数据类型,每次 MQTT 接收到订阅提醒就会执行 Callback 中所有的方法。
// 创建 mqtt 链接
window.$wujie.props.methods.mqttConnect((data) => {
state.mqttCallbackMap.forEach(value => {
value(data)
})
})
在前文中,我们已经可以实现将物体数据存放到 Context 中,所以在 afterInit
的生命周期后,我们就可以对物体进行订阅。afterInit
是我们自己实现的,在模型场景等数据加载完成后执行。
以下代码我们获取标记的屏幕对象,并逐一添加 MQTT Callback:
// 根据屏幕对象订阅数据
state.sceneData.forEach(item => {
if (item.data.type !== '表计') return false;
const currentId = (item.data as TSceneData<ICabinetData>).idx;
const cabinetData = item as ISceneData<ICabinetData>;
dispatch({
type: 'ADDMQTTCALLBACK',
name: 'ScreenDisplay_'+ item.id + '_' + currentId,
callback(data) {
// 物体贴图创建
}
})
})
获取到对象后我们需要对数据贴图进行创建,通过 DOM 操作创建 Canvas,并创建文本,最后通过 THREE.CanvasTexture
将 Canvas 转为 Three 可用的材质贴图(如果无背景颜色,可以创建为透明贴图):
;((screenObject as THREE.Mesh).material as THREE.MeshBasicMaterial) = new THREE.MeshBasicMaterial({
map: instance.textTextureMaker.createTextureFromArray(screenData),
});
其中移入物体显示的标签为 CSS2DRenderer,物体上的数据显示为 CanvasTexture。