3D数据可视化大屏开发详解,超炫酷 【二】
1. 前言
在前第一部分的文章中,分享了大屏地球的实现。
本次将会分享剩余的实现部分,文内大量干货,内容包括:
- 平面地图的实现
- 柱体的实现
- 性能优化
- 地图相关问题
2. 平面地图
平面地图的必要性在于
地球无法显示完整数据。就像太阳照射地球有了昼夜。

大屏
可以看到,平面地图这种全局的数据是地球无法完整表现的。
平面地图由地图数据、地图块和交互三部分组成。
其中交互使用THREE.Raycaster实现,较为简单不在赘述。
2.1 地图数据
与地球的实现方法不同,平面地图依赖geojson进行绘制。有什么样的geojson,绘制什么样的地图块。
不了解
geojson的开发者请先学习相关知识:GEOJSON规范[1]。
在做地图相关工作时,很多情况是没有现成的geojson供开发者们使用的。而从哪获取地图的geojson数据是很多人都会面临的问题。
此处只提供相关资源,有兴趣可以自行深入了解:
- datav.aliyun.com[2] 阿里平台提供的中国geojson
- gadm.org[3] 可以下载到世界范围不同级别的geojson
- naturalearthdata.com[4] 可以下载到世界范围不同精度的geojson
- mapshaper.org[5] 用来查看geojson,提供对geojson进行数据合并、简化和修正的功能
- geojson.io[6]手动修改geojson数据节点的在线网站。注:在处理MultiPolygon类型数据时有bug
*注1:gadm与naturalearthdata两个国外的平台下载到的中国领土数据都是错误的,错误的数据节点可在geojson.io自行调整。
*注2:本文中分享的资源下载到的geojson基本都是完整数据,数据体积在最小几M、最大几G 之间,可使用mapshaper.org的简化功能进行缩小(会使数据失真),失真后可使用geojson.io。
*注3:要注意在拼接不同来源的geojson和简化geojson后,可能会出现数据点不对齐的现象,需要人工花大量时间进行对齐。
2.2 坐标映射
在准备好geojson之后,绘制时要将经纬度与xy坐标进行映射。
这里我们直接使用了经纬度 <对应> xy坐标的关系来进行绘制。这一简化的对应关系会出现格陵兰岛跟澳大利亚大小相似的问题。
如果对视觉表现比较严苛,各位开发者可以使用d3-geo的投影模块来避免该问题。如Natural Earth projection(自然地球投影)[7]。
对应关系为投影作为经纬度与xy坐标中间的纽带:经纬度 <=> 自然地球投影 <=> xy坐标。
*注:错误的投影可能会导致格陵兰岛与非洲大小相似。
2.3 地图块
地图块的实现方式很简单,使用THREE.ExtrudeGeometr(挤压几何体)[8]配合THREE.Shape来将准备好的地图数据进行绘制即可。这里不在贴代码,开发者们可查阅文档自行开发。
挤压几何体创建Mesh时,可以传入有两个材质组成的数组。第一个材质将用于其表面;第二个材质则将用于其挤压出的侧面。
MultiPolygon
在geojson中,type为MultiPolygon的数据,对应的coordinates也会有多个(Polygon数据的coordinates只有1个子数据),常见的多为存在岛屿或飞地的国家。
这个时候如果直接使用Shape进行连结会出现模型间拉丝连线的现象。
如果将多个子数据分别绘制为几何体可以避免前一个问题,但是在做交互时多个几何体也会以个体的形式分别进行交互。会出现选中中国,海南省不跟着亮的问题。
尽管你也可以在交互时根据数据获取相关的其他几何体。
在这里我使用Geometry.merge[9]。
- 将多个
ExtrudeGeometry的顶点数据merge到同一个Geometry中。 - 将合并好的
Geometry作为几何体加入到Mesh
以上两个步骤即可。
注意:在销毁时需要将被merge的ExtrudeGeometry一同销毁。
3. 立体圆柱
立体圆柱用来表示某一区域的数据比例

立体圆柱
它的特点是会把不同颜色的数据渲染在立体圆柱上。
它由纹理和THREE.CylinderGeometry实现。
纹理
与一般物体不同,圆柱的纹理需要根据数据动态计算。
利用THREE.ClampToEdgeWrapping的特性,绘制了1 x 100大小的canvas来当做纹理使用。
代码中使用了d3-scale模块中的scalePow[10]做数据比例尺。
纹理计算
function getTexture(data) {
const canvas = document.createElement('canvas');
canvas.height = 100;
canvas.width = 1;
const y = d3
.scalePow()
.rangeRound([0, 100])
.domain([0, d3.sum(data.map(({ value }) => value))]);
const ctx = canvas.getContext('2d');
let left = 0;
data.forEach((item) => {
const current = y(item.value);
ctx.moveTo(0.5, 100 - left);
ctx.lineTo(0.5, 100 - (left + current));
left += current;
ctx.lineWidth = 1;
ctx.strokeStyle = item.color;
ctx.stroke();
ctx.beginPath();
});
return canvas;
}
const canvas = getTexture([
{ name: '数据1', value: 10, color: 'red' },
{ name: '数据2', value: 5, color: '#123456' }
]);
const texture = new THREE.CanvasTexture(canvas);
const material = new THREE.MeshBasicMaterial({ map: texture });
// ...
CylinderGeometry
在计算好material之后还不能直接将材质交给Mesh使用。与挤压几何体传入两个材质类似,CylinderGeometry将会接收三个材质。
分别对应圆柱侧面、圆柱底面和圆柱顶面。
如果直接将material传入,地面和顶面也将会使用侧面的材质。
所以需要这么创建:
// ...
const { size, height } = options;
const material = new THREE.MeshBasicMaterial({ map: texture });
const materialBottom = new THREE.MeshBasicMaterial({
color: 'red',
});
const materialTop = new THREE.MeshBasicMaterial({
color: '#123456',
});
const geometry = new THREE.CylinderBufferGeometry(
size, size, height, 64, 20, false,
);
const cylinder = new THREE.Mesh(geometry, [
material,
materialBottom,
materialTop,
]);
4. 性能优化
前面的内容已经将具体的实现方法讲解的七七八八了。
在做的过程中也遇到性能和内存的问题。
这里简单说一下遇到的问题和处理的方法。
4.1 Geometry.merge 导致大量的内存无法被释放
因项目使用Vue Router前端路由,在离开大屏页面并重新进入时会触发
离开大屏前 => 销毁大屏 => 离开大屏 => … => 回到大屏 => 重绘大屏
这一流程。
每次的绘制都会使页面增加几十M的内存占用无法被GC回收。
经过排查发现这一部分内存都是在Geometry.merge操作时增加的。
这是因为没有注意Geometry.merge,只销毁了要合并到的Geometry对象,被合并的Geometry对象没有被销毁,导致大量的顶点信息遗留在内存中无法被GC清理。
4.2 场景背景导致的卡顿
在开发过程中,发现随着窗口分辨率的越来越大,动画也会卡顿的越来越严重。
这是随着分辨率像素点的增多造成的硬性性能门槛。
在分析了交互行为以后,我采用了以下方案规避这一问题:
- 将
背景与场景内容分离成两个renderer - 只在
摄像机变化时渲染背景,摄像机静止时只绘制场景内容 渲染背景时进行节流,原本绘制2帧 或 绘制3帧的时间长度只绘制1帧,给渲染留出更多的时间
达到的效果:
- 整个画面在摄像机不变化时帧率稳定
- 在鼠标拖拽移动摄像机时不会因为
mousemove的频繁触发导致渲染任务阻塞在很短的时间内
缺点:
背景随着节流限制的大小会有不同程度的延迟与卡顿。
但对大屏来讲,摄像机通常都是静止不动的,只有部分业务场景需要人机交互。
5. 总结
写在文章的最后。
纸上得来终觉浅,绝知此事要躬行。
开发者们还是要多去实践,在实践中验证理论知识是最有效的提升能力的方式。
参考文献
GEOJSON规范– https://geojson.org/datav.aliyun.com– http://datav.aliyun.com/tools/atlasgadm.org– https://gadm.org/www.naturalearthdata.com– https://www.naturalearthdata.com/downloads/mapshaper.org– https://mapshaper.org/geojson.io– http://geojson.io/Natural Earth projection(自然地球投影)– http://www.shadedrelief.com/NE_proj/THREE.ExtrudeGeometr(挤压几何体)– https://threejs.org/docs/index.html#api/zh/geometries/ExtrudeGeometryGeometry.merge– https://threejs.org/docs/index.html#api/zh/core/Geometry.mergescalePow– https://github.com/d3/d3-scale/blob/v3.2.2/README.md#scalePow

来源:https://blog.csdn.net/luckywinty/article/details/109882121
WEBGL学习网 » 3D数据可视化大屏开发详解,超炫酷 【二】