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数据可视化大屏开发详解,超炫酷 【二】