WebGL-3D地图大俯仰角的雾化处理
腾讯位置服务Javascript API GL版,是基于WebGL技术打造的地图API库,使得浏览器环境下也可实现APP端的应用体验,提供2D/3D模式,运行流畅。
当前版本提供地图展示、标记、信息窗口、折线、多边形、3D棱柱(多边形拔起)、自定义栅格图等等多种功能,可满足绝大多数应用需要,更有丰富能力研发中,敬请期待!以下内容转载自掘金文章《WebGL-3D地图大俯仰角的雾化处理》
作者:多多洛爱学习
链接:https://juejin.im/post/5db2a58a51882559ee62dfba来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
导语:除了画布宽高之外,3D地图的可视范围还受到俯仰角、旋转角度的影响。在大俯仰角情况下,为了降低瓦片加载和渲染的耗时,在地图远端不加载瓦片,瓦片边缘会变得非常突兀。所以需要对瓦片边缘进行雾化处理,实现渐隐效果,优化用户体验。
问题背景
地图的渲染原理请自行了解:Web地图呈现原理,其中重点了解地图数据以瓦片为单位进行加载和绘制这一点即可。在3D地图中,由于有俯仰角和水平旋转角的存在,墨卡托投影上的可视范围与可视窗口大小并不相同,会呈现一个梯形:
当俯仰角增大时,梯形上半部分会快速扩大,导致加载的瓦片过多而影响性能,所以需要对远端瓦片进行截断。而截断后边缘比较突兀,所以需要对边缘做雾化处理,优化体验,如下图。
解决方案
瓦片加载限制
注:下文涉及到3D世界中各坐标系统的相互转换,需提前了解OpenGL坐标系统,以及略懂线性代数,否则可能晕头转向不知所云(画图太麻烦了,见谅)。
在投影空间中地图的俯仰角变化所引起的可视范围的变化如下图左侧所示,45度时瓦片加载数量大约为0度时的1.5倍,并且随着俯仰角增大而快速上涨。为了减少瓦片加载数量,必须舍弃一部分瓦片。考虑到透视投影造成的近大远小,远端的瓦片被压缩后可分辨度很低,所以可以舍弃,即对可视空间的顶部进行裁切。
如何裁切,这里需要先介绍一下可视范围的计算方法。在不考虑裁切的情况下,可视区域的四个顶点均在地图平面(z_world=0
平面)上,即顶点在世界空间内的坐标可表示为(x_world, y_world, 0, 1)
,然后经过与视图投影矩阵相乘得到裁剪空间坐标,裁剪空间可简单映射到屏幕空间。所以可反推回去,通过屏幕空间的四个顶点,映射到裁剪空间坐标,与视图投影矩阵的逆矩阵相乘后可得到可视区域的四个顶点坐标。其中较难理解的是裁剪空间的z_clip
值无法确定,可以取-1和1两个值,逆变换后可得到两个世界坐标确定一条直线,该线与z_world=0
平面的交点即最终的顶点坐标。
所以如果要裁切,由上图所示,在世界空间内我们切掉的是顶部梯形阴影部分,所对应到屏幕空间切掉的是顶部矩形阴影部分,那么我们需要得到屏幕空间的一条水平裁切线,即图中的y值。假如以45度时的梯形上半高度为标准,通过简单的几何计算可得其为1.93185 * view.top
,可近似为2 * view.top
,那么可取世界坐标(0, 2 * view.top, 0, 1)
作为裁切点,将其变换到屏幕空间(x_screen, y_screen)
即可得到y值,记为fogEdge
。之后再根据上文所述的逆变换,使用(0,y), (1,y), (0,1), (1,1)
四个顶点反推可视区域的四个顶点。
至此我们得到的俯仰角70度时的瓦片加载效果如下图:
雾化处理
雾化并不难理解,其本质上是一种颜色混合,将本色与雾色(为了适应个性化的地图样式我们使用的雾色与大地颜色保持一致)进行混合。随着混合因子的变化实现渐变效果。可参考WebGL 雾,着色器代码如下所示:
gl_FragColor = originalColor * (1.0 - fogFactor) + fogColor * fogFactor;
地图是分图层绘制的,若在每个图层的着色器中实现雾化逻辑实在过于冗杂,所以后处理方式更为合理。所谓后处理,即在帧缓冲中绘制完毕后,将缓冲关联的纹理作为输入进行图像处理。为了让渐变效果更平滑,可以使用smoothstep
函数让雾化因子从0平滑过渡到1,如下图所示:
着色器代码为:
float fogFactor = 1. - smoothstep(fogEdge, fogEdge + fogRange, y_screen);
至此我们得到初步的雾化效果如下图:
可见边缘已被雾完全遮盖了,但是顶部仍显空洞,可以加上一点蓝天的背景效果,可以使用纹理也可以使用纯色,仍然也需要一个渐变过程,雾化因子变化如下:
得到效果图如下:
根据深度进行雾化
注:以下内容涉及到深度测试,需提前了解OpenGL深度测试,否则可能晕头转向不知所云。
以上是直接根据纹理纵向坐标计算雾化因子,虽然效果已符合预期,但仍有不合理之处。比如楼块,如果楼块过高,就算其距离视点较近,其顶部仍然会被雾化,不符合自然认知。所以雾化因子应根据深度值进行计算。
深度纹理
一般来说,帧缓冲区对象的深度关联对象为渲染缓冲区对象(renderbuffer object),而渲染缓冲区是无法传入着色器进行读取的,所以需要关联到一个纹理对象。要让纹理对象支持深度值的写入,需要使用一个扩展WEBGL_depth_text
,这个扩展使纹理支持gl.DEPTH_COMPONENT
格式,同时能支持gl.UNSIGNED_SHORT
和gl.UNSIGNED_INT
类型,相应代码如下:
// 开启深度纹理扩展
if (!gl.getExtension("WEBGL_depth_texture")) {
console.error("depth textures not supported");
} // 设置纹理大小
gl.texImage2D(gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT, width, height, 0, gl.DEPTH_COMPONENT, gl.UNSIGNED_INT, null);
在帧缓冲上绘制完毕后,将深度纹理传入着色器,将其可视化可得到下图(图中仅对需要雾化的部分读取了深度值进行展示):
深度计算
如上图所示,虽然我们能够获取到深度纹理,但这个深度纹理是不完善的,在没有楼块的地方是一片空白,说明深度值为1即没有写入深度。这是因为:(1) 为了避免深度冲突,除了建筑物之外其他图层绘制时都关闭了深度测试,所以并没有写入深度值;(2) 即使在其他图层上开启了深度测试,使用背景色的地面是没有绘制物的,仍然没有深度值。简单来说,即目前无法获取到大地平面的深度值。
如何获取到大地平面的深度值?其实深度值是可以计算的,参考OpenGL 投影矩阵。不过并不需要知道它的具体公式,只需要了解到透视投影下深度值与观察空间中的1/z_view
是成正比的,即depth = A * (1 / z_view) + B
。另一方面,我们通过简单的几何计算可以得到:
由此可得大地平面在裁剪空间中的坐标的y值y_clip
与深度值depth
是成正比关系的,即depth = E * y_clip + F
。那么就可以选择两个平面上的坐标点,比如点1(0, 0, 0, 1)
和点2(0, 2 * view.top, 0, 1)
,通过视图投影矩阵得到(0, 0, z_clip_1, 1)
和(x_clip_2, y_clip_2, z_clip_2, 1)
,再根据depth = (z_clip + 1) / 2
得到depth_1
和depth_2
,两个方程联立求解即可得到E
和F
。
float getDepthFromY(float y_clip) {
return min(mix(depth_1, depth_2, y_clip / y_clip_2), 1.);
}
至此可以得到纹理上空白处的深度值了,结合从深度纹理中读取的数值,可视化如下:
雾化因子应相应修改为随深度变化:
得到最终的雾化效果如下图:
可以再看看此时的高楼效果:
动态效果参见附件视频。
问题总结
- 无论是可视范围裁切还是深度计算都需要熟练掌握3D的各个空间之间的相互变换关系,需要正反双向思维能力
- 雾化本质上是一种渐变的颜色混合,渐变的维度可自由把握
- 透视投影下深度值与观察空间坐标的1/z成正比
- 深度缓冲和模板缓冲一般是关联到renderbuffer对象,renderbuffer是无法传入着色器进行读取的,在这里使用了texture对象替代了renderbuffer用于存储深度值,在其他应用场景下也可以直接通过着色器绘制深度或模板到颜色缓冲上,提供更高的精度,也能实现更加自定义的功能
WebGL-3D地图大俯仰角的雾化处理的更多相关文章
- Threejs 开发3D地图实践总结
前段时间连续上了一个月班,加班加点完成了一个3D攻坚项目.也算是由传统web转型到webgl图形学开发中,坑不少,做了一下总结分享. 1.法向量问题 法线是垂直于我们想要照亮的物体表面的向量.法线代表 ...
- Threejs 开发3D地图实践总结【转】
Threejs 开发3D地图实践总结 前段时间连续上了一个月班,加班加点完成了一个3D攻坚项目.也算是由传统web转型到webgl图形学开发中,坑不少,做了一下总结分享. 1.法向量问题 法线是垂 ...
- Three.js实现3D地图实例分享
本文主要给大家介绍了关于利用Three.js开发实现3D地图的实践过程,文中通过示例代码介绍的非常详细,对大家学习或者使用three.js具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习 ...
- 使用Three.js实现炫酷的赛博朋克风格3D数字地球大屏 🌐
声明:本文涉及图文和模型素材仅用于个人学习.研究和欣赏,请勿二次修改.非法传播.转载.出版.商用.及进行其他获利行为. 背景 近期工作有涉及到数字大屏的需求,于是利用业余时间,结合 Three.js ...
- three.js 绘制3d地图
通过地图数据配合three可以做出非常酷炫的地图,在大数据展示中十分常见. 这篇郭先生就来说说使用three.js几何体制作3D地图.在线案例点击原文地址. 地图的数据是各个地图块的点数组,通过THR ...
- threejs三维地图大屏项目分享
这是最近公司的一个项目.客户的需求是基于总公司和子公司的数据,开发一个数据展示大屏. 大屏两边都是一些图表展示数据,中间部分是一个三维中国地图,点击中国地图的某个省份,可以下钻到省份地图的展示. 地图 ...
- 基于 WebSocket 实现 WebGL 3D 拓扑图实时数据通讯同步(二)
我们上一篇<基于 WebSocket 实现 WebGL 3D 拓扑图实时数据通讯同步(一)>主要讲解了如何搭建一个实时数据通讯服务器,客户端与服务端是如何通讯的,相信通过上一篇的讲解,再配 ...
- 基于 WebSocket 实现 WebGL 3D 拓扑图实时数据通讯同步(一)
今天没有延续上一篇讲的内容,穿插一段小插曲,WebSocket 实时数据通讯同步的问题,今天我们并不是很纯粹地讲 WebSocket 相关知识,我们通过 WebGL 3D 拓扑图来呈现一个有趣的 De ...
- 通过 WebSocket 实现 WebGL 3D 拓扑图实时数据通讯同步(二)
我们上一篇<基于 WebSocket 实现 WebGL 3D 拓扑图实时数据通讯同步(一)>主要讲解了如何搭建一个实时数据通讯服务器,客户端与服务端是如何通讯的,相信通过上一篇的讲解,再配 ...
随机推荐
- 简单看看es6解构赋值
哎,我真的是太难了,今天就被这个解构赋值(也可以叫做析构,貌似析构是在c++中的,所以我这里叫做解构赋值吧)弄的我很烦,本来以为很容易的,结果还是弄了好久...就总结一下解构吧! 1.解构的基本使用 ...
- 如何在阿里云服务器上搭建wordpress个人网站
1.购买云服务器.域名.域名解析.配置linux系统上的web环境.FTP等参照下面的链接. https://www.cnblogs.com/smyhvae/p/4965163.html?tdsour ...
- js的动态表格的增删改查完整代码
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8" ...
- jQuery 源码解析(二十五) DOM操作模块 html和text方法的区别
html和text都可以获取和修改DOM节点里的内容,方法如下: html(value) ;获取匹配元素集合中的一个元素的innerHTML内容,或者设置每个元素的innerHTML内容, ...
- rsync高级同步工具
1.什么是rsync rsync 是一款开源的.快速的.多功能的.可实现全量及增量的本地或远程数据同步备份的优秀工具,rsync软件使用于 unix/linux/windows等多种操作系统平台. 2 ...
- vim 入门笔记
前言 本文的初衷 从知道 vim 开始我就有心学习并尝试过几次,每次都是暂时的心血来潮,最终全部不了了之,就连最基本的 vimtutor 我都是学个两三节就半途而废,所以这次干脆写篇文章,利用几次学习 ...
- 测试工程师如何使用 CODING 进行测试管理
CODING 为您的企业提供从概念到软件开发再到产品发布的全流程全周期软件研发管理,为您的研发团队提供全程助力,帮助研发团队捋清需求.不断迭代.快速反馈并能实时追踪项目进度直到完成.同时 CODING ...
- jQuery仿京东首页广告图片切换图片轮播
1.效果图如下: 2.源码如下: <!DOCTYPE html> <html lang="en"> <head> <meta charse ...
- 服务治理-Resilience4j(限流)
Bulkhead Bulkhead一般用于服务调用客户端,用于限定对特定的服务的并发请求数量,起到一下作用:1.防⽌下游依赖被并发请求冲击2.防⽌发⽣连环故障 1.配置规则“order” //允许最大 ...
- CentOS自动化安装LAMP脚本
#!/bin/bash #-- #blog:lizhenliang.blog.51cto.com ########## function ########## depend_pkg () { yum ...