内容简介:毕业两年,一直在地图相关的公司工作,虽然不是 GIS 出身,但是也对地图有些耳濡目染;最近在看 WebGl 的东西,就拿 MapboxGL 做了一个关于 WebGL 的三维数据渲染的 DEMO 练手。大体就是先做 WebGl 的 Shader 代码放进 Painter(WebGL 的 Context 就在这个对象里面) 里面,然后通过 Source 层去加载处理需要的数据(包括矢量和栅格数据),把数据通过 Tile 对象传进 Render 里面,去做一些 WebGL 的数据处理和渲染,然后扔进 Tile
毕业两年,一直在地图相关的公司工作,虽然不是 GIS 出身,但是也对地图有些耳濡目染;最近在看 WebGl 的东西,就拿 MapboxGL 做了一个关于 WebGL 的三维数据渲染的 DEMO 练手。
首先大致看了一下 MapboxGL 的 GLGS 到图层的一个结构:
大体就是先做 WebGl 的 Shader 代码放进 Painter(WebGL 的 Context 就在这个对象里面) 里面,然后通过 Source 层去加载处理需要的数据(包括矢量和栅格数据),把数据通过 Tile 对象传进 Render 里面,去做一些 WebGL 的数据处理和渲染,然后扔进 Tile 里面传入到 Layer 层,最后就是一些样式和事件的管理。
MapboxGL 大体就说这么多,下面就是 WebGL 的三维数据处理和渲染以及添加卫星影像纹理的过程(代码实在太多,只写出部分关键步骤代码):
第一步:拿到需要渲染的数据片(瓦片形式)
// 序列化瓦片地址,将数据瓦片的 xyz 坐标计算出来 let url = normalizeURL( tile.coord.url(this.tiles, null, this.scheme), this.url, this.tileSize ); ... // 用 MapboxGl 封装的获取二进制数据格式的 Ajax 请求拿到二进制数据 tile.request = ajax.getArrayBuffer(url, done.bind(this)); ... // 将数据进行转码处理成 JS 对象,并传递给 tile tile.pixelObj = pixelObj; // 处理好的数据 ... 复制代码
第二步:在 Render 里面拿到数据和 Painter,去做数据片的渲染:
const divisions = 257; let vertexPositionData = new Float32Array(divisions * divisions * 3); const pixels = pixelObj.pixels[0]; if (coord.vertexPositionData) { // 做了缓存优化 console.log('缓存', 'coord'); vertexPositionData = coord.vertexPositionData; } else { console.time('vertex'); // 全数据量 for (let i = 0; i < divisions; ++i) { for (let j = 0; j < divisions; ++j) { const bufferLength = (i * divisions + j) * 3; let dem = parseInt(pixels[bufferLength / 3]); if (!dem || dem === -3) { // 对于无效数据给一个默认值(PS: DEM 高程数据质量不高 ) dem = -1000; } vertexPositionData[bufferLength] = j * SCALE; vertexPositionData[bufferLength + 1] = i * SCALE * 1; vertexPositionData[bufferLength + 2] = dem; } } // 计算数据处理的耗时,优化的时候要用 console.timeEnd('vertex'); coord.vertexPositionData = vertexPositionData; } const indexData = getIndex(divisions); const FSIZE = vertexPositionData.BYTES_PER_ELEMENT; const positionBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); gl.bufferData(gl.ARRAY_BUFFER, vertexPositionData, gl.STATIC_DRAW); const aPosiLoc = gl.getAttribLocation(gl.program, "a_Position"); gl.vertexAttribPointer(aPosiLoc, 3, gl.FLOAT, false, FSIZE * 3, 0); gl.enableVertexAttribArray(aPosiLoc); // 设置索引 const indexBuffer = gl.createBuffer(); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer); gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indexData, gl.STATIC_DRAW); // https://stackoverflow.com/questions/28324162/webgl-element-array-buffers-not-working gl.getExtension('OES_element_index_uint'); gl.drawElements(gl.TRIANGLES, indexData.length, gl.UNSIGNED_INT, 0); ... // 生成索引,WebGL 的渲染有两种方式,一种是 drawElements,一种是 drawArray,我们这里采用第一种 function getIndex(divisions) { if (drawLerc3D.indexData) { return drawLerc3D.indexData; } console.time('获取索引'); const indexData = []; // 这个是全数据量渲染 // for (let row = 0; row < divisions - 1; ++row) { // for (let i = 0; i < divisions; ++i) { // const base = row * divisions + i; // if (i < divisions - 1) { // indexData.push(base); // indexData.push(base + 1); // indexData.push(base + divisions); // indexData.push(base + 1); // indexData.push(base + divisions); // indexData.push(base + divisions + 1); // } // } // } // 这是一半数据(PS: 这是为了优化,牺牲一些精度) for (let row = 0; row < divisions - 2; row += 2) { for (let i = 0; i < divisions; i += 2) { const base = row * divisions + i; if (i < divisions - 2) { indexData.push(base); indexData.push(base + 2); indexData.push(base + divisions * 2); indexData.push(base + 2); indexData.push(base + divisions * 2); indexData.push(base + divisions * 2 + 2); } } } console.timeEnd('获取索引'); drawLerc3D.indexData = new Uint32Array(indexData); return drawLerc3D.indexData; } 复制代码
第三步:编写 GLSL,在 GPU 里面处理不同高度对应渲染的不同颜色值
vertex shader
// 视角矩阵 uniform mat4 u_matrix; // 顶点位置数据 attribute vec3 a_Position; // 纹理数据,贴图卫星影像 attribute vec2 a_texCoord; varying vec2 v_texCoord; // 高程数据 varying float dem; void main(){ dem = a_Position.z; gl_Position = u_matrix * vec4(a_Position.x, a_Position.y, dem * 32.0, 1.0); v_texCoord = a_texCoord; } 复制代码
fragment shader
// precision lowp float; // uniform float u_brightness_low; // uniform float u_brightness_high; // 颜色 // varying vec3 v_Color; varying float dem; // 纹理 uniform sampler2D u_image; varying vec2 v_texCoord; // 根据不同高程取不同颜色 vec4 getColor() { // 颜色数组 const int COLORS_SIZE = 11; vec3 colors[COLORS_SIZE]; // 对 dem 进行归一化 float n_dem = -2.0 * (dem / 6000.0 - 0.5); const float MINDEM = -1.0; const float MAXDEM = 1.0; const float STEP = (MAXDEM - MINDEM) / float(COLORS_SIZE - 1); int index = int(ceil((n_dem - MINDEM) / STEP)); colors[10] = vec3(0.3686274509803922,0.30980392156862746,0.6352941176470588); colors[9] = vec3(0.19607843137254902,0.5333333333333333,0.7411764705882353); colors[8] = vec3(0.4, 0.7607843137254902,0.6470588235294118); colors[7] = vec3(0.6705882352941176,0.8666666666666667,0.6431372549019608); colors[6] = vec3(0.9019607843137255,0.9607843137254902,0.596078431372549); colors[5] = vec3(1.0, 1.0, 0.7490196078431373); colors[4] = vec3(0.996078431372549,0.8784313725490196,0.5450980392156862); colors[3] = vec3(0.9921568627450981,0.6823529411764706,0.3803921568627451); colors[2] = vec3(0.9568627450980393,0.42745098039215684,0.2627450980392157); colors[1] = vec3(0.8352941176470589,0.24313725490196078,0.30980392156862746); colors[0] = vec3(0.6196078431372549,0.00392156862745098,0.25882352941176473); if(index > 10){ return vec4(0.3, 0.3, 0.9, 0.5); } if(index < 0){ index = 0; } for (int i = 0; i < COLORS_SIZE; i++) { if (i == index) return vec4(colors[i], 1.0); } } void main(){ // 用颜色渲染 DEM 数据,和纹理二选一 gl_FragColor = getColor(); // 用纹理(卫星影像)渲染效果 gl_FragColor = texture2D(u_image, v_texCoord / 256.0 / 32.0); } 复制代码
最后:在 MapboxGL 里面使用我们自己定义的 Source 和 Layer
map.addSource('DEMImgSource', { //高程数据 "type": "DEM3D", "tiles": [ 'http://xxx.xxx.xxx.xxx/{x}/{y}/{z}', ], "tileSize": 512, // 谷歌瓦片地址,用来渲染纹理贴图 "rasterUrl": 'http://www.google.cn/maps/vt?lyrs=s@189≷=cn&x={x}&y={y}&z={z}', // 高德的 // "rasterUrl": 'https://webst04.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}' }); map.addLayer({ // layer 'id': 'DEMlayer', 'type': 'DEM3D', 'source': 'DEMImgSource' }); 复制代码
因为数据量实在是太大(一般整张3D屏幕渲染需要40张瓦片,每张都有256*256个数据点),一开始没有做优化的时候非常卡,根本无法进行地图拖动和缩放,后来将数据进行缓存,顶点信息进行精简,瓦片大小进行放大(一屏幕只需要20张数据片渲染)得到的效果就很不错了,拖动和缩放基本比较流畅,体验和正常地图差别不大。
以上所述就是小编给大家介绍的《WebGIS 利用 WebGL 在 MapboxGL 上渲染 DEM 三维空间数据》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 利用webpack4搭建vue服务器端渲染SSR(一)
- Octane渲染入门-渲染设置图文版
- 通过分析 WPF 的渲染脏区优化渲染性能
- React 服务器端渲染和客户端渲染效果对比
- iOS渲染-将视频原始数据(RGB,YUV)渲染到屏幕上
- 通过共享内存优化 Flutter 外接纹理的渲染性能,实时渲染不是梦
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。