建筑物阴影效果的实现

光照分析是一个很常见的需求,那么如何在地图中实现类似的效果呢?

理想效果

框架选择

Cesium

Cesium 本身是支持光照阴影效果的,只需要将 Viewer 中的 shadows 属性置为 true 即可。不过,默认的视觉效果不太好,锯齿状严重,如下图所示。我们可以通过修改 ShadowMap 类的参数来优化效果。

const viewer = new Cesium.Viewer("cesiumContainer", {
  shadows: true
});

viewer.shadowMap.size = 10240;
viewer.shadowMap.softShadows = true;

从类的属性上看,Cesium 的 ShadowMap 类和 threejs 的 LightShadow 类很相似,本质上都是通过创建阴影纹理贴图来实现的。更高的阴影效果也就意味着更多的性能消耗。

Mapbox

对于 Mapbox 而言,虽然它在三维方面的可扩展性很强,但实际上,目前没怎么看到成熟的基于 Mapbox 的三维可视化插件,更多的是成熟的框架利用 WebGL 实现与 Mapbox 的兼容,例如 deck.gl。几年前由 Peter Liu 开发的 threebox 框架已不再维护,一位微软的工程师 fork 了一份后在继续维护,工作的主要内容是让 threebox 支持 v2 版本的 Mapbox。

当前的 threebox 其实不太适合作为一个成熟的产品去使用,代码中有很多的硬编码部分,需要对 Mapbox 很熟悉,对 threebox 源码以及 three.js 框架很熟悉,才能实际的使用 threebox 这个框架。

而 threebox 中的建筑物阴影效果没有兼容 v2 版的 Mapbox。所以本篇文章接下来将会介绍如何修复好这个功能。

BuildingShadows 图层逻辑

threebox 中的 BuildingShadows 图层使用了另外一种方式来实现建筑物的阴影效果。

看看它的顶点着色器和片段着色器:

// vertexShader

uniform mat4 u_matrix;
uniform float u_height_factor;
uniform float u_altitude;
uniform float u_azimuth;
attribute vec2 a_pos;
attribute vec4 a_normal_ed;
attribute lowp vec2 a_base;
attribute lowp vec2 a_height;
void main() {
    float base = max(0.0, a_base.x);
    float height = max(0.0, a_height.x);
    float t = mod(a_normal_ed.x, 2.0);
    vec4 pos = vec4(a_pos, t > 0.0 ? height : base, 1);
    float len = pos.z * u_height_factor / tan(u_altitude);
    pos.x += cos(u_azimuth) * len;
    pos.y += sin(u_azimuth) * len;
    pos.z = 0.0;
    gl_Position = u_matrix * pos;
}


// fragmentShader

void main() {
    gl_FragColor = vec4(0.0, 0.0, 0.0, 0.7);
}

颜色的计算很简单,关键在于顶点的计算。首先构造出来的顶点:

vec4(a_pos, t > 0.0 ? height : base, 1)

是原始的拉伸后的建筑顶点。接着计算阴影的长度:

pos.z * u_height_factor / tan(u_altitude)

并结合阴影的方位,计算出投射后的阴影顶点位置:

pos.x += cos(u_azimuth) * len;
pos.y += sin(u_azimuth) * len;

而 u_azimuth 的值,则是根据指定的当地时间,以及建筑的地理位置计算得到的光照角度。

Mapbox 中的建筑物阴影

兼容 v2 版的 mapbox

那为什么这段代码与 Mapbox 不再兼容了呢?问题出在读取的建筑图层数据上。Mapbox 在升级到 v2 的过程中,为了减小 fill-extrusion 图层的内存占用,传给着色器的数据做了一些调整,将顶点以及法向量合并成了一个变量,具体的改动可以参考这个提交

所以,将顶点着色器的改动同步到 threebox 中的 BuildingShadows 中即可。

最终效果

最后,将建筑物阴影,Mapbox 的地图光源,以及天空盒效果结合起来,可以得到以下的效果。

基于 Mapbox 实现的建筑物阴影,与 Mapbox 本身高度耦合,实际视觉体验不及 Cesium,不过胜在实现简单,性能负担小。

发布者

Zhang

hope you enjoy my articles.

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注