时间系统是游戏世界(尤其是开放类游戏)常见到的机制,除了为模拟现实世界的情况外,也经常作为衬托游戏剧情发展的因素。将它们封装起来并提供可配置的简单参数,有一定的必要性。本博文主要讨论以下几点:

  • 如何自定义日出和日落时间,在此期间可以看到日光,不在此期间时看到月亮;
  • 如何将天空有关的组件进行封装,可以方便地迁移到不同关卡或项目中;

天体相关的组件

打开一个基础关卡,在大纲视图中通常可以看到一些组成天体的组件:

  • Directional Light:方向光源,在这里指示着太阳的方向。这种光源在参与光源计算时只会使用其旋转信息,其位置不会影响到光源的计算。在虚幻引擎中,任意时刻都要保证至多一个方向光源可见。本例中在白天中仅 SunDirectionalLight 可见,黑色中仅 MoonDirectionalLight 可见。
  • Sky Atmosphere:模拟大气层的组件,会影响光源的散射,使画面更加逼真。
  • Sky Light:会将场景中较远部分的物体作为光源。在本例中,启用了 Real Time Capture 选项。
  • Exponential Height Fog:基于高度的云雾效果,可以设置密度值来增强或削弱光源的雾化效果。
  • Post Process:后处理体积,位于体积内的像素会进行额外的处理,类似于滤镜功能。在本例中,将其体积设置为了 Infinite,并将其 Min EV100Max EV100 都设置为了 0.5,设置为相同的值会使光源立即变化,而不会有自动曝光的处理过程。
  • Volumetric Cloud:体积云组件,将介质类材质覆盖在天体上。本例中使用该组件搭配自定义的材质实现云朵。
  • Static Mesh:静态网格组件。在本例中使用了引擎 /Content/EngineSky/SM_SkySphere 网格资产表示天体球,将其统一缩放值调整的很大(10000 倍),尽可能确保玩家自由视角下都不会离开该天体。

在本例中,以上组件都定义了在一个 Actor 中,这样就可以确保在不同的关卡中放置该 Actor 就可以立即生成可配置的天体系统。

昼夜变化的实现

在本例中日光和月光分别由控制 SunDirectionalLight 和 MoonDirectionalLight 的 Pitch 旋转信息来实现的。具体来说引入了三个浮点变量来抽象化它们的旋转参数:TimeOfDay 代表当前的时间,DawnTime 代表日出时间,DuskTime 代表日落时间。如果 TimeOfDay 的值落在了 [DawnTime, DuskTime] 区间时代表白天,应当仅 SunDirectionalLight 可见并且将其映射到 [-180, 0] 旋转度数区间上。不在该区间时就代表黑夜,我们需要将日落时间作为起点,然后到下一个日出时间作为终点,映射月光的旋转度数区间上,因此对于零点之前的时间可以直接使用 TimeOfDay 值,而对于零点之后的值需要进行偏移 DuskTime + (DuskTime 到 24 点的距离) + TimeOfDay = DuskTime + (24 - DuskTime) + TimeOfDay = 24 + TimeOfDay。对于这两个方向光源的旋转设置逻辑如下所示:

在前言中还提到了在任何时刻都应该保证只有一个方向光源,因此需要根据 TimeOfDay 是否落在表示白天的区间来设置两个 DirectionalLight 的可见性:

在材质方面,我们需要为天体球制作一个无灯光的着色模型,并在高级选项中勾选 Is Sky。首先我们至少要输出 SkyAtmosphereViewLuminanceSkyAtmosphereLightDiskLumiance[0] 相加后的结果,前者表示光源与 大气光 相互作用后天空的最终亮度,而后者表示应用了大气透射率运算后的太阳亮度,相加后就得到了在大气内可观察到的天空大气效果。对于白天来说并不需要专门的太阳纹理来展示,因为玩家也只会看到白色的发光球体。而在夜晚,我们需要一个月亮纹理来替换掉表示太阳的区域,幸运的是虚幻引擎提供了 SkyAtmosphereLightDirectionSkyAtmosphereImage 来让我们在太阳的位置处显示一张纹理,再额外添加一些如 TintScaleBrightness 等参数供设计师调整月亮纹理,最终有关于月亮纹理输出的逻辑如下:

除了月光以外,夜空中也可以加入群星纹理。可以使用 TimeWithSpeedVariablePanner 来平移纹理来实现星星的移动,我在这里还引入了噪波纹理来实现群星“若隐若现”的效果,最后根据顶点法向量 z 轴在世界空间中的值来限定星星出现的区域,越靠近地平线则越不会出现星星,最终群星的输出的逻辑如下:

将上述得到的输出节点相加在一起连接到材质输出节点的 Emissive Color 节点即可。在这里有一个参数称为 IsNightTime会在蓝图中可以创建一个动态材质实例在夜晚时设置为大于 0 的值,在白天设置为 0,这样就可以确保月亮和群星仅在夜晚可见。