首发于第九艺术
用UE4做大世界3-雨天,闪电,水体的效果实现

用UE4做大世界3-雨天,闪电,水体的效果实现

继续,

用UE4做大世界1-一种原生UE的程序化植被生成方案
用UE4做大世界2-无缝过渡的动态天气和UE4的体积云
用UE4做大世界3-雨天,闪电,水体的效果实现

这部分介绍一下游戏里雨天,闪电,水体效果的实现

先看一下效果

雨天效果https://www.zhihu.com/video/1457782145783259136

游戏里雨天开始的时候,地面会慢慢潮湿,然后逐渐产生积水和涟漪,之后随着雨变大,积水增多,开始流动,然后斜面的流水略微增强,雨天结束过程会整个过程反向播放

  • 雨天开始过程
    • 地面慢慢潮湿
    • 逐渐产生积水和涟漪
    • 积水增多,开始流动
    • 斜面流水略微增强
  • 雨天结束过程
    • 斜面流水和积水扰动逐渐停止
    • 涟漪和雨丝一起停止
    • 积水慢慢消失,地面依旧潮湿
    • 最后地面逐渐变干

而具体实现,也参考了一些博客,包括国内逆水寒还有天刀的分享,比如

Water drop 2b – Dynamic rain and its effects

网易游戏雷火事业群:游戏中雨天效果开发:如何打造“最美下雨天”

《天涯明月刀》中的雨景渲染-腾讯游戏学堂

我们把它们实现在了UE里面,具体做法是这样的,我们在 BasePass 之后添加了一个后处理Pass,用于修改 GBuffer

潮湿

首先是潮湿,修改了 GBuffer 的 roughness 和 basecolor

积水

积水是修改Normal,去交错混合一张法线贴图

涟漪

涟漪是随时间播放一张 flipbook

流水

流水是将这张流水贴图在世界空间内做 XZ 和 YZ 平面的双平面映射

雨丝

雨丝刚开始我们的做法也是通过后期来做,把一个纺锤体套在相机前面,这种做法的优点是性能比较稳定,但缺点是雨很大或者很小的时候,容易穿帮

因此之后还是换成了粒子的做法,在camera周围生成,缺点是性能会随着粒子数量起伏,但是效果更好,因此权衡之后,还是采用了粒子的做法

遮挡

遮挡的话,因为原生的 SceneCapture 只用来拍摄深度,实在有些浪费,因为它相当于BasePass 什么的都走了,结果只读个深度出来用,因此我们

新增了一个DepthOnlyRender 专门用于拍摄深度

新增了一个 TopView 的 OcclusionPass,用于自定义的可以产生遮挡的 Primitve 的收集

游戏过程中,会有一个跟着玩家的相机,每帧拍摄更新一张 256*256 的 depth map,像这样

然后在 Camera Space 计算遮挡,为了遮挡边缘的柔滑,对边缘做了 PCF,为了防止自遮挡,深度判定加了一个 Bias

最后能看到,在这种房屋的破碎拐角的地方,也能形成比较自然的效果

Flag 标记

还有值得提一下的就是,在实现过程中还有一个需求是,由于是后处理来做雨效,大部分的像素信息我们是丢掉的,比如一个像素它属于一个正在移动的对象,我们不希望修改它的normal,但Movable 的属性是存在各个Primitive 上的,后处理的pass 已经丢掉了这些信息,因此需要在 BasePass 阶段把这些信息也存下来,写到GBuffer里面,为了存储这些bit,我们当时评估了这些地方

GBufferA.a = GBuffer.PerObjectGBufferData;
GBufferB.a
GBufferD = GBuffer.CustomData;
GBufferE = GBuffer.PrecomputedShadowFactors;
Stencil

但是考虑到版本升级,因为官方也可能接着使用和扩展这些bit,因此觉得这些都不是合适的位置,所以最后我们把这个bit放到了 specular 里面,原来的 specular 占了 8bit,我们做了重新的 pack 和 unpack,只留了 两个bit 给原来的specular,其他 6个bit都可以用来缓存我们需要的状态,比如这里我们把第三个bit用来标记是否是Movable

闪电

闪电表现上分为云中闪电,只在云层里,一种是云地闪电,会从云层连接到地面

云中闪电
云地闪电

具体的实现是,首先梯级先导,就是这个分形的过程,是使用 Niagara 粒子做的

回击过程是在分形结束后,让主干以更高亮度闪烁的同时照亮场景,并从闪电位置投射方向阴影,这里能看到,闪电发生时,瞬间的投影方向是根据闪电位置来计算的

同时整个过程会伴随雷声,包括轰鸣声和雷击声。

具体效果,可以看看在项目EA的宣传片里看到。

接下来介绍一下项目中水体的实现。

项目当时升级到了 4.26 后面临一个水体实现方案的抉择,需要考虑项目已有的水体是否需要换成 Water Plugin,因为它当时确实效果要比项目原来的水效果要好,但因为它目前还是一个实验性的插件,看它的实现,发现功能上还是有些限制的

第一个是River不影响 Landscape 的话,它只有纯平,而且不能调整 Scale,因为当时我们已经到了项目后期,地形已经固定,不好再变更, 而它的高度依赖于地形的 height map,因此不影响地形的话,就只有纯平,另外它的实现是根据不同类型的 WaterBody 的bounds去填充 WaterMesh QuadTree的一个个Tile ,River 是不规则的,因此Scale 出错

Lake::
    WaterQuadTree.AddLake(SplinePoints, LakeBounds, WaterBodyRenderDataIndex);

River::
    遍历所有 Components 的 BodySetup 找到所有box
    然后
    WaterQuadTree.AddWaterTilesInsideBounds;
    
Ocean::
	WaterQuadTree.AddOcean(SplinePoints, FVector2D(OceanBounds.Min.Z, OceanBounds.Max.Z + WaterBody->GetMaxWaveHeight()), WaterBodyRenderDataIndex);

第二是默认只有 3*3 平方公里的大小,不满足大世界的需求

第三是它整体的架构的设计,以及运行时合并 WaterMeshActor 的效率问题,不是很适合做流式加载

因此考虑到这些限制,我们最后的方案是 使用 SingleLayerWater 的 ShadingModel和材质,也就是只用它的表现,但功能还是在原来的,另外,表现上我们移植了 Gerstner 波,但考虑 lod 接缝问题和性能问题,最后并未启用

还有就是同样使用了 DepthOnlyRender 去拍摄了更精确的波纹形状,效果是这样的

游泳https://www.zhihu.com/video/1457782395038937088

跟大世界有关的就这些,那...大概这样~

编辑于 2022-01-05 13:45