资讯专栏INFORMATION COLUMN

WebGIS 利用 WebGL 在 MapboxGL 上渲染 DEM 三维空间数据

tracy / 3222人阅读

摘要:毕业两年,一直在地图相关的公司工作,虽然不是出身,但是也对地图有些耳濡目染最近在看的东西,就拿做了一个关于的三维数据渲染的练手。

毕业两年,一直在地图相关的公司工作,虽然不是 GIS 出身,但是也对地图有些耳濡目染;最近在看 WebGl 的东西,就拿 MapboxGL 做了一个关于 WebGL 的三维数据渲染的 DEMO 练手。

首先大致看了一下 MapboxGL 的 GLGS 到图层的一个结构:


大体就是先做 WebGl 的 Shader 代码放进 Painter(WebGL 的 Context 就在这个对象里面) 里面,然后通过 Source 层去加载处理需要的数据(包括矢量和栅格数据),把数据通过 Tile 对象传进 Render 里面,去做一些 WebGL 的数据处理和渲染,然后扔进 Tile 里面传入到 Layer 层,最后就是一些样式和事件的管理。

MapboxGL 大体就说这么多,下面就是 WebGL 的三维数据处理和渲染以及添加卫星影像纹理的过程(代码实在太多,只写出部分关键步骤代码):
第一步:拿到需要渲染的数据片(瓦片形式)

</>复制代码

  1. // 序列化瓦片地址,将数据瓦片的 xyz 坐标计算出来
  2. let url = normalizeURL(
  3. tile.coord.url(this.tiles, null, this.scheme),
  4. this.url,
  5. this.tileSize
  6. );
  7. ...
  8. // 用 MapboxGl 封装的获取二进制数据格式的 Ajax 请求拿到二进制数据
  9. tile.request = ajax.getArrayBuffer(url, done.bind(this));
  10. ...
  11. // 将数据进行转码处理成 JS 对象,并传递给 tile
  12. tile.pixelObj = pixelObj; // 处理好的数据
  13. ...
第二步:在 Render 里面拿到数据和 Painter,去做数据片的渲染:

</>复制代码

  1. const divisions = 257;
  2. let vertexPositionData = new Float32Array(divisions * divisions * 3);
  3. const pixels = pixelObj.pixels[0];
  4. if (coord.vertexPositionData) {
  5. // 做了缓存优化
  6. console.log("缓存", "coord");
  7. vertexPositionData = coord.vertexPositionData;
  8. } else {
  9. console.time("vertex");
  10. // 全数据量
  11. for (let i = 0; i < divisions; ++i) {
  12. for (let j = 0; j < divisions; ++j) {
  13. const bufferLength = (i * divisions + j) * 3;
  14. let dem = parseInt(pixels[bufferLength / 3]);
  15. if (!dem || dem === -3) {
  16. // 对于无效数据给一个默认值(PS: DEM 高程数据质量不高 )
  17. dem = -1000;
  18. }
  19. vertexPositionData[bufferLength] = j * SCALE;
  20. vertexPositionData[bufferLength + 1] = i * SCALE * 1;
  21. vertexPositionData[bufferLength + 2] = dem;
  22. }
  23. }
  24. // 计算数据处理的耗时,优化的时候要用
  25. console.timeEnd("vertex");
  26. coord.vertexPositionData = vertexPositionData;
  27. }
  28. const indexData = getIndex(divisions);
  29. const FSIZE = vertexPositionData.BYTES_PER_ELEMENT;
  30. const positionBuffer = gl.createBuffer();
  31. gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
  32. gl.bufferData(gl.ARRAY_BUFFER, vertexPositionData, gl.STATIC_DRAW);
  33. const aPosiLoc = gl.getAttribLocation(gl.program, "a_Position");
  34. gl.vertexAttribPointer(aPosiLoc, 3, gl.FLOAT, false, FSIZE * 3, 0);
  35. gl.enableVertexAttribArray(aPosiLoc);
  36. // 设置索引
  37. const indexBuffer = gl.createBuffer();
  38. gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
  39. gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indexData, gl.STATIC_DRAW);
  40. // https://stackoverflow.com/questions/28324162/webgl-element-array-buffers-not-working
  41. gl.getExtension("OES_element_index_uint");
  42. gl.drawElements(gl.TRIANGLES, indexData.length, gl.UNSIGNED_INT, 0);
  43. ...
  44. // 生成索引,WebGL 的渲染有两种方式,一种是 drawElements,一种是 drawArray,我们这里采用第一种
  45. function getIndex(divisions) {
  46. if (drawLerc3D.indexData) {
  47. return drawLerc3D.indexData;
  48. }
  49. console.time("获取索引");
  50. const indexData = [];
  51. // 这个是全数据量渲染
  52. // for (let row = 0; row < divisions - 1; ++row) {
  53. // for (let i = 0; i < divisions; ++i) {
  54. // const base = row * divisions + i;
  55. // if (i < divisions - 1) {
  56. // indexData.push(base);
  57. // indexData.push(base + 1);
  58. // indexData.push(base + divisions);
  59. // indexData.push(base + 1);
  60. // indexData.push(base + divisions);
  61. // indexData.push(base + divisions + 1);
  62. // }
  63. // }
  64. // }
  65. // 这是一半数据(PS: 这是为了优化,牺牲一些精度)
  66. for (let row = 0; row < divisions - 2; row += 2) {
  67. for (let i = 0; i < divisions; i += 2) {
  68. const base = row * divisions + i;
  69. if (i < divisions - 2) {
  70. indexData.push(base);
  71. indexData.push(base + 2);
  72. indexData.push(base + divisions * 2);
  73. indexData.push(base + 2);
  74. indexData.push(base + divisions * 2);
  75. indexData.push(base + divisions * 2 + 2);
  76. }
  77. }
  78. }
  79. console.timeEnd("获取索引");
  80. drawLerc3D.indexData = new Uint32Array(indexData);
  81. return drawLerc3D.indexData;
  82. }
第三步:编写 GLSL,在 GPU 里面处理不同高度对应渲染的不同颜色值

</>复制代码

  1. vertex shader

</>复制代码

  1. // 视角矩阵
  2. uniform mat4 u_matrix;
  3. // 顶点位置数据
  4. attribute vec3 a_Position;
  5. // 纹理数据,贴图卫星影像
  6. attribute vec2 a_texCoord;
  7. varying vec2 v_texCoord;
  8. // 高程数据
  9. varying float dem;
  10. void main(){
  11. dem = a_Position.z;
  12. gl_Position = u_matrix * vec4(a_Position.x, a_Position.y, dem * 32.0, 1.0);
  13. v_texCoord = a_texCoord;
  14. }

</>复制代码

  1. fragment shader

</>复制代码

  1. // precision lowp float;
  2. // uniform float u_brightness_low;
  3. // uniform float u_brightness_high;
  4. // 颜色
  5. // varying vec3 v_Color;
  6. varying float dem;
  7. // 纹理
  8. uniform sampler2D u_image;
  9. varying vec2 v_texCoord;
  10. // 根据不同高程取不同颜色
  11. vec4 getColor() {
  12. // 颜色数组
  13. const int COLORS_SIZE = 11;
  14. vec3 colors[COLORS_SIZE];
  15. // 对 dem 进行归一化
  16. float n_dem = -2.0 * (dem / 6000.0 - 0.5);
  17. const float MINDEM = -1.0;
  18. const float MAXDEM = 1.0;
  19. const float STEP = (MAXDEM - MINDEM) / float(COLORS_SIZE - 1);
  20. int index = int(ceil((n_dem - MINDEM) / STEP));
  21. colors[10] = vec3(0.3686274509803922,0.30980392156862746,0.6352941176470588);
  22. colors[9] = vec3(0.19607843137254902,0.5333333333333333,0.7411764705882353);
  23. colors[8] = vec3(0.4, 0.7607843137254902,0.6470588235294118);
  24. colors[7] = vec3(0.6705882352941176,0.8666666666666667,0.6431372549019608);
  25. colors[6] = vec3(0.9019607843137255,0.9607843137254902,0.596078431372549);
  26. colors[5] = vec3(1.0, 1.0, 0.7490196078431373);
  27. colors[4] = vec3(0.996078431372549,0.8784313725490196,0.5450980392156862);
  28. colors[3] = vec3(0.9921568627450981,0.6823529411764706,0.3803921568627451);
  29. colors[2] = vec3(0.9568627450980393,0.42745098039215684,0.2627450980392157);
  30. colors[1] = vec3(0.8352941176470589,0.24313725490196078,0.30980392156862746);
  31. colors[0] = vec3(0.6196078431372549,0.00392156862745098,0.25882352941176473);
  32. if(index > 10){
  33. return vec4(0.3, 0.3, 0.9, 0.5);
  34. }
  35. if(index < 0){
  36. index = 0;
  37. }
  38. for (int i = 0; i < COLORS_SIZE; i++) {
  39. if (i == index) return vec4(colors[i], 1.0);
  40. }
  41. }
  42. void main(){
  43. // 用颜色渲染 DEM 数据,和纹理二选一
  44. gl_FragColor = getColor();
  45. // 用纹理(卫星影像)渲染效果
  46. gl_FragColor = texture2D(u_image, v_texCoord / 256.0 / 32.0);
  47. }
最后:在 MapboxGL 里面使用我们自己定义的 Source 和 Layer

</>复制代码

  1. map.addSource("DEMImgSource", { //高程数据
  2. "type": "DEM3D",
  3. "tiles": [
  4. "http://xxx.xxx.xxx.xxx/{x}/{y}/{z}",
  5. ],
  6. "tileSize": 512,
  7. // 谷歌瓦片地址,用来渲染纹理贴图
  8. "rasterUrl": "http://www.google.cn/maps/vt?lyrs=s@189&gl=cn&x={x}&y={y}&z={z}",
  9. // 高德的
  10. // "rasterUrl": "https://webst04.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}"
  11. });
  12. map.addLayer({ // layer
  13. "id": "DEMlayer",
  14. "type": "DEM3D",
  15. "source": "DEMImgSource"
  16. });
最终的渲染效果(颜色渲染):


因为数据量实在是太大(一般整张3D屏幕渲染需要40张瓦片,每张都有256*256个数据点),一开始没有做优化的时候非常卡,根本无法进行地图拖动和缩放,后来将数据进行缓存,顶点信息进行精简,瓦片大小进行放大(一屏幕只需要20张数据片渲染)得到的效果就很不错了,拖动和缩放基本比较流畅,体验和正常地图差别不大。

纹理渲染效果:

不得不说好像还是颜色渲染的视觉效果更(yao)好(yan)一(jian)些(huo)~

对于 WebGL 方向上的探索一些大公司也有一些成果:
高德 Loca:https://lbs.amap.com/api/java...

百度 Echarts: http://echarts.baidu.com/exam...

UBER: https://deck.gl/

等等,所以对于 WebGL 的前景个人觉得在数据可视化、高精地图(无人驾驶)等方面还是有很多价值的~

第一次写文章,很多地方可能没有解释清楚,欢迎拍砖~

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/113783.html

相关文章

  • WebGIS 利用 WebGL MapboxGL 渲染 DEM 三维空间数据

    摘要:毕业两年,一直在地图相关的公司工作,虽然不是出身,但是也对地图有些耳濡目染最近在看的东西,就拿做了一个关于的三维数据渲染的练手。 毕业两年,一直在地图相关的公司工作,虽然不是 GIS 出身,但是也对地图有些耳濡目染;最近在看 WebGl 的东西,就拿 MapboxGL 做了一个关于 WebGL 的三维数据渲染的 DEMO 练手。 首先大致看了一下 MapboxGL 的 GLGS 到图层...

    RobinTang 评论0 收藏0
  • WebGIS 利用 WebGL MapboxGL 渲染 DEM 三维空间数据

    摘要:毕业两年,一直在地图相关的公司工作,虽然不是出身,但是也对地图有些耳濡目染最近在看的东西,就拿做了一个关于的三维数据渲染的练手。 毕业两年,一直在地图相关的公司工作,虽然不是 GIS 出身,但是也对地图有些耳濡目染;最近在看 WebGl 的东西,就拿 MapboxGL 做了一个关于 WebGL 的三维数据渲染的 DEMO 练手。 首先大致看了一下 MapboxGL 的 GLGS 到图层...

    sixgo 评论0 收藏0
  • 数字高程模型(Digital Elevation Model) DEM 切片以及数据发布与展示学习笔

    摘要:是一套库,用来渲染地球,区域地图,和多种要素,不需要安装任何插件就能在支持最新标准的浏览器上运行,支持硬件加速,非常适合动态数据在图层上的展示,是一个跨平台,开源,非常有前途的表现层库。 Cesiumjs 是一套javascript库,用来渲染3D地球,2D区域地图,和多种GIS要素,不需要安装任何插件就能在支持最新HTML5标准的浏览器上运行,支持WebGL硬件加速,非常适合动态数据...

    FleyX 评论0 收藏0

发表评论

0条评论

tracy

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<