GIS融合之路(四)如何用CesiumJS做出Cesium For Unreal的效果

发布时间 2023-06-28 11:20:23作者: 山海鲸可视化

同样在这篇文章开始前重申一下,山海鲸并没有使用ThreeJS引擎。但由于ThreeJS引擎使用广泛,下文中直接用ThreeJS同CesiumJS的整合方案代替山海鲸中3D引擎和CesiumJS整合。

系列传送门:

山海鲸可视化:GIS融合之路(一)技术选型CesiumJS/loaders.gl/iTowns?

山海鲸可视化:GIS融合之路(二)CesiumJS和ThreeJS深度缓冲区整合

山海鲸可视化:GIS融合之路(三)CesiumJS和ThreeJS相机同步

到了这一篇文章,直接融合的工作基本结束,剩下的是对光影效果的整合和提升。说到视觉效果,那不得不提大名鼎鼎的虚幻引擎,恰好Cesium团队也做了一个Cesium For Unreal的产品,将Cesium对地理信息的展示能力结合了Unreal的视觉效果,可谓如虎添翼。我们先放一张CesiumJS和CesiumForUnreal的对比图,我选取了同一个经纬度的地区,图中上半部分时Cesium的Sandcastle效果图,下半部分是CesiumForUnreal的Samples中的截图。(这个地区就是CesiumForUnreal默认的项目经纬度)

可以看出,Cesium For Unreal默认项目的表现力显著强于CesiumJS的效果。那我们就要问到底是什么让Cesium For Unreal的效果更加真实呢,这里就不得不提真实感渲染中大名鼎鼎的大气散射了。

同样的,我们放以下在山海鲸中整合CesiumJS的效果(实际上山海鲸中默认还会有体积云,为了同效果对比,我在图中把体积云关掉进行预览)

山海鲸CesiumJS

可以看出山海鲸中默认的效果已经非常逼近CesiumForUnreal的效果了,而且山海鲸默认自带了体积云效果及非常灵活的体积云设置,同样在Unreal中想要达到类似效果需要购买插件或者需要自己用蓝图对体积云进行建模。同时由于山海鲸中整合的是CesiumJS,因此大家可以用自己熟悉的JS语言和CesiumJS接口来对山海鲸中CesiumJS进行二开,之前的CesiumJS项目甚至都不需要改什么代码就可以一键迁移,开发成本和学习难度远远低于Unreal的C++或者蓝图。

好了,广告打完了,下面就要开始正题了。山海鲸到底是通过怎样的改造将CesiumJS的效果提升到Cesium For Unreal的效果呢?那我们需要先了解一下什么是大气散射以及大气散射的渲染原理。

一、大气散射原理

什么是大气散射呢?大家可以做一个思维实验,首先我们知道光在空气中是沿着直线传播的。我们也知道地球大气层以外就是一望无垠的宇宙空间,那么我们白天看向天空时,按说我们应该看到外太空才对(就像晴朗无云的晚上一样),为什么我们却看到的是蓝色呢?原因很简单,是因为太阳光被我们大气层中的大小粒子散射到了我们眼睛里,就是部分光线拐弯了,这个就是大气散射。同样的,当我们看向远处的山的时候,为什么山越远,看着就越接近天空的颜色(所谓秋水共长天一色)也是因为离视线越远,那么经过的空气粒子越多,那么太阳光经过空气粒子散射到我们眼睛里的颜色占比就越大,这个就是空气透视(AerialPerspective)。

大气散射不同时间不同高度观测效果

二、大气散射渲染方法

这类的文章可以说非常多了,我这里给大家简单归纳一下,毕竟这个系列文章主要着重在整合,而不是渲染层面。技术细节我就不说太多了。

简单实现:实际上大家用过ThreeJS就知道,ThreeJS的Examples里自己就有动态天空,我记得山海鲸第一版动态天空就是从这段代码里移植的,这个基本就是一个典型的Single Scattering的天空渲染模型。如果大家想了解技术细节,可以移步这篇文章:Simulating the Colors of the Sky。我记得山海鲸刚开始使用这版天空时,所有同事看着都摇头,表示这天空太灰了,能不能变蓝一些。我表示:你们都不懂物理!现在想来看来还是我不懂物理啊,因为这里缺了MultiScattering和Ozone。

工业界实现:现在工业界领先的3A游戏或者顶尖引擎,大多都实现的是MultiScattering。同时由于性能因素,直接进行RayMarching实际上是非常浪费的,因此大多都基于Eric Bruneton等人在2008年提出的Precomputed Atmospheric Scattering模型,将大气散射的参数都预计算好成lut,在后续渲染中在进行查询的方式实现。最终UE在2020年优化了这个算法,进一步降低了LUT的计算难度。技术细节参考这个链接中的文章:

三、山海鲸的实现

好了,原理我们都懂了,下面就看我们能不能过好这一生了。不对,就看我们能不能在webgl中实现并且和已经合并渲染的CesiumJS进行整合了。由于UE不仅仅把自己的渲染原理写成了一篇文章,同时UE的代码也算是开源的,因此实际上我们是能看到全部的实现细节的,那我们就看看将其移植到webgl中的难度以及解决方案:

1.部分算法不在论文中

实际算法中除了论文中几个LUT以外,还有一个DistantskyLut,这个LUT是取了地面海拔6km高度的空气散射积分结果。主要用在体积云和高度雾的融合部分,本文内容暂且不涉及。值得一提的是我们在移植过程中也做了一个小优化。在UE当中DistantSkyLut是每帧渲染的,我们也采用TransmittanceLut的方案将太阳角度作为x轴一次性把所有结果渲染在一张贴图中,这样只用在场景加载时渲染一次就可以了。

2.webgl中不支持ComputeShader

不管是大气散射还是体积云的渲染,在现代客户端引擎中都大量使用ComputeShader。然而遗憾的是,Webgl中不支持ComputeShader(WebGPU倒是有,但是现在是真不敢用),所以采用ScreenViewQuad的方案去代替,虽然略微损失性能,起码在写的时候还算优雅。具体方案我系列文章二中已经有提及,这里就不再赘述了。

3.webgl中不支持GeometryShader

webgl啊webgl,你可是真的不争气啊,你但凡是采用的opengles3.1也比现在好用太多了,可惜木已成舟,咱只能带着镣铐跳舞。为什么我们需要GeometryShader呢,因为在AerialPerpectiveLut是一张3d的LUT。我们没有ComputeShader,也没有GeometryShader,那我们就没办法一次性渲染到一张3d texture的所有slice当中去。在论文或者UE实现中,AerialPerpectiveLut是一张32×32×32的贴图,难道在Webgl上要32次drawcall吗?不忍心啊,这就像苹果好不容易把iPhone做薄1mm,我买回来就装一个1cm的壳一样。对不起人家费尽心思优化的算法啊。经过一番搜索发现,webgl可以通过multirendertarget一次性写入多个slice,结合常见浏览器一次最多写8个pass的限制,最终实现了4次drawcall绘制一个AerialPerpectiveLut。

4.webgl中不支持读取MSAA的multisample

UE算法中建议空气透视在不透明物体渲染完后执行一次后处理,然后对于透明物体在VertexShader中叠加空气透视,但是由于我们无法在Shader中读取到一个像素点上的Multi Sample的值,因此直接做后处理就会有一些瑕疵。遗憾的是,这个问题目前我们依然还在考虑解决方案。

解决这些问题之后,就只剩下和Cesium整合了,这个很简单,就是在前面说到的Cesium渲染结果再次渲染到我们引擎的QuadMesh上的时候,再逐像素叠加空气透视效果即可,因为我们已经有了Cesium的Depth Buffer了。

好了,到这里总算是把UE算法中完整的大气散射和空气透视在webgl上同CesiumJS整合到了一起,而且对CesiumJS代码没有任何侵入性修改。下一篇我们看一看对于大气中其他元素的如体积云和指数高度雾的整合。帮人帮到底,送佛送到西。咱一步到位,把UE中天空插件和高度雾的效果也一并整合好,甲方爸爸们应该就可以心满意足了。最后我们看一下大气散射在山海鲸中最终的效果视频:

【山海鲸城市大师】空气透视效果演示

【山海鲸城市大师】昼夜大气散射变化