WebGIS 利用 WebGL 在 MapboxGL 上渲染 DEM 三维空间数据
毕业两年,一直在地图相关的公司工作,虽然不是 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&gl=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张数据片渲染)得到的效果就很不错了,拖动和缩放基本比较流畅,体验和正常地图差别不大。
纹理渲染效果:
不得不说好像还是颜色渲染的视觉效果更(yao)好(yan)一(jian)些(huo)~
对于 WebGL 方向上的探索一些大公司也有一些成果:
高德 Loca:https://lbs.amap.com/api/java...
百度 Echarts: http://echarts.baidu.com/exam...
UBER: https://deck.gl/
等等,所以对于 WebGL 的前景个人觉得在数据可视化、高精地图(无人驾驶)等方面还是有很多价值的~
第一次写文章,很多地方可能没有解释清楚,欢迎拍砖~
WebGIS 利用 WebGL 在 MapboxGL 上渲染 DEM 三维空间数据的更多相关文章
- 分享:计算机图形学期末作业!!利用WebGL的第三方库three.js写一个简单的网页版“我的世界小游戏”
这几天一直在忙着期末考试,所以一直没有更新我的博客,今天刚把我的期末作业完成了,心情澎湃,所以晚上不管怎么样,我也要写一篇博客纪念一下我上课都没有听,还是通过强大的度娘完成了我的作业的经历.(当然作业 ...
- Java 利用SWFUpload多文件上传 session 为空失效,不能验证的问题 swfUpload多文件上传
Java 利用SWFUpload多文件上传 session 为空失效,不能验证的问题(转) 我们都知道普通的文件上传是通过表单进行文件上传的,还不能达到异步上传的目的.通过使用某些技术手段,比如jqu ...
- selenium+java利用AutoIT实现文件上传
转自https://www.cnblogs.com/yunman/p/7112882.html?utm_source=itdadao&utm_medium=referral 1.AutoIT介 ...
- 利用SQLite在android上实现增删改查
利用SQLite在android上实现增删改查 方法: 一.直接利用database.execSQL()方法输入完整sql语句进行操作 这种方法适用于复杂的sql语句,比如多表查询等等 这里适合于增删 ...
- 利用SQLite在android上创建数据库
利用SQLite在android上创建数据库 方法: 1.创建我们的数据库类继承SQLiteOpenHelper类 完成相关函数的重写和数据库对象的初始化 public MySQLiteOpenHel ...
- 利用Tengine在树莓派上跑深度学习网络
树莓派是国内比较流行的一款卡片式计算机,但是受限于其硬件配置,用树莓派玩深度学习似乎有些艰难.最近OPENAI为嵌入式设备推出了一款AI框架Tengine,其对于配置的要求相比传统框架降低了很多,我尝 ...
- 利用TortoiseGit向Github上传文件
利用TortoiseGit向Github上传文件 第一步:建一个新文件夹,作为本地仓库 第二步:右键选择设置为版本库 若弹出,确认即可 重新打开改文件,会发现多了一个绿色的小勾 在文件夹中会自动生成一 ...
- CVPR2020:利用图像投票增强点云中的三维目标检测(ImVoteNet)
CVPR2020:利用图像投票增强点云中的三维目标检测(ImVoteNet) ImVoteNet: Boosting 3D Object Detection in Point Clouds With ...
- [WebGL入门]四,渲染准备
注意:文章翻译http://wgld.org/,原作者杉本雅広(doxas),文章中假设有我的额外说明,我会加上[lufy:].另外.鄙人webgl研究还不够深入,一些专业词语,假设翻译有误,欢迎大家 ...
随机推荐
- [LC] 271. Encode and Decode Strings
Design an algorithm to encode a list of strings to a string. The encoded string is then sent over th ...
- Linux安装svn服务
安装svn yum -y install subversion 创建保存仓库/版本库的目录 mkdir -p /opt/data/svndir 创建仓库/版本库, 这里同时创建两个仓库(project ...
- android geendao简单使用
引入依赖 implementation 'org.greenrobot:greendao:3.2.2'implementation 'com.github.yuweiguocn:GreenDaoUpg ...
- pip install torch出现错误
首先使用Python的pip安装命令: pip install torch 出现错误 解决办法:这时需要先下载pytorch包,根据自己的python版本选择.pytorch包链接: https:// ...
- yaml文件的格式
后缀为.yml 格式一般就是key:空格 value 这里的value可以是 普通数据(数字,字符串) ---------------------------------------- ...
- MOOC(15)- 接口异常处理
首先通过fiddler拿到正确的传参 把value复制下来,存到json数据中,传参时通过键去json的值即可 但是运行请求的时候报错了 再运行,还是出错 查看fiddler结果 修改,从fiddle ...
- 对Java8新的日期时间类的学习(一)
引用自Java译站http://it.deepinmind.com/java/2015/03/17/20-examples-of-date-and-time-api-from-Java8.html 除 ...
- s检验|k-S检验|适应性检验|独立性检验|Cintinuity correction |Fisher‘s Exact Test|Likelihood Ratio|Person Chi-Square|φ系数|Cramer’s V|列联系数
应用统计学: s检验是检验否符合正态,而k-S检验是检验否符合一种分布. 已知分布便知道参数,知道参数不知道分布. 适应性检验 多项式分布的情况如下例: 二项分布是多项式分布一种情况,所以就是上式中只 ...
- Critical-Value|Critical-Value Approach to Hypothesis Testing
9.2 Critical-Value Approach to Hypothesis Testing example: 对于mean 值 275 的假设: 有一个关于sample mean的distri ...
- 密码子演化假说|凝固事件假说|立体化学假说|共进化假说|代谢途径相关性假说|四重兼并|假四重兼并|最小损伤原则|AU-rich|GC-rich|逐步进化假说|分子机制进化假说
生命组学 将密码子表重排后发现,嘌呤嘧啶含量不同,密码子的氨基酸种类由第一二位决定,同时第三位变化大却没有蛋白质层面上实质性的改变,这说明第三位氨基酸是用于维持氨基酸组成不发生变化同时保证蛋白质稳定性 ...