UE4.26的渲染逻辑相对于UE4.25做了较大幅度的改动,不同于4.25所有的实现逻辑都放在了材质蓝图(custom node)中,4.26则将大量的细节内嵌到了引擎shader中,虽然从代码实现细节来看,在当前这个时间节点,其中还包含了大量的临时代码,显然这并不是UE最终定稿的Volumetric Cloud实现框架,不过从大体框架来说,虽然引入了一些新的东西,但其实跟4.25的差别不是特别大,相信后续的改动也会遵循这个实现逻辑进行,加上最近可能要用到,因此这里就不等最终的版本出来再做分析了,先抛出一个版本,后续有新的改动再添加进来。
1. 代码入口
4.26体积云渲染的入口文件为VolumetricCloud.usf,入口函数为用SHADER_RENDERVIEW_PS宏包裹的MainPS。
除了上面这个MainPS之外,还有另外一个用SHADER_SHADOW_PS宏包裹的MainPS,这是用于绘制输出Cloud Shadow Map以方便前面MainPS计算采样点所在位置被Cloud遮挡住的Light数据的,由于实现逻辑大同小异,且不是核心的关注点,这里就直接一带而过了。
下面将重点介绍前面一个MainPS的实现逻辑。
2. 体积云渲染实现逻辑
MainPS函数中主要做了四个部分的工作:
- 首先是一些变量的初始化,比如RayOrigin/RayDir等RayTracing的基本参数
- 在初始化完成后,会调用TraceClouds接口完成RayMarching相关逻辑,这是Cloud Render的核心部分
- 在Cloud Render完成之后,会叠加Aerial Perspective等大气散射效果
- 必要的时候,添加HeightFog相关的计算逻辑
这四部分中,我们这里重点介绍第二部分RayMarching部分工作,下面按照计算顺序进行展开介绍。
2.1 RayMarching范围计算
RayMarching的第一步是计算RayMarching的起始点与终止点,也就是RayMarching的范围,这个逻辑是通过RayIntersectSphereSolution使用二项式求根公式完成的。
在完成直接求根得到的基本范围之后,还会根据当前像素的SceneDepth对范围进行修正,避免不可见部分Marching的浪费以及因此导致的计算结果异常。
2.2 光照参数获取
光照处理这边,4.26提供了最多两盏光源对云层进行Shading,其中第二盏光源是可选的,通过宏开关CLOUD_SAMPLE_SECOND_LIGHT进行控制。
对于每盏光源,需要计算对应的如下三个参数:
- LightIlluminance,光照强度
- LightIlluminanceFinal,这个参数是LightIlluminance乘上一个LuminanceScale后的数值,也是最终计算中所使用的数值
- LightDirection
2.3 Marching前准备工作
接下来要完成的是RayMarching前的准备工作,主要包括RayMarching过程中会用到的一些参数:
- Step相关的一些参数的计算,比如StepNum,StepSize等
- 天光强度参数DistantSkyLightLuminance,这个参数会根据开关EnableDistantSkyLightSampling与SkyAtmospherePresentInScene来决定是直接从DistantSkyLightLutTexture采样得到,还是通过GetSkySHDiffuseSimple计算得到
- RayMarching中的一些ScaleFactor参数,包括MsScattFactor,MsExtinFactor以及MsPhaseFactor
- 相函数相关的一些参数,这里需要注意的是,理论上来说相函数参数应该是逐sample评估的而非逐pixel计算,这里UE提供了一个宏MATERIAL_VOLUMETRIC_ADVANCED_PHASE_PERSAMPLE,在这个宏打开的情况下,这个地方计算得到的逐pixel相函数参数就会被后面逐sample的参数覆盖
- Ground散射相关的参数GroundToCloudTransfertIsoScatter,这个参数后面会用于对每个RayMarching Sample上的Incattering数据进行计算
- 对每条射线的起始Marching Offset进行随机化,避免采样点不足时的严重锯齿感
2.4 RayMarching Loop
2.4.1 参数初始化
RayMarching是以参数的初始化开始的:
1.CloudMaterial参数的初始化,这部分参数包括CloudSampleAltitude,CloudSampleAltitudeInLayer,AbsoluteWorldPosition,CloudSampleNormAltitudeInLayer等位置参数,以及VolumeSampleConservativeDensity等密度参数,其中密度参数是与材质蓝图中的VolumetricAdvancedOutput的最后一个输入绑定的,即这个参数是材质蓝图自行完成的,表示的是当前点的Cloud密度
-
Shadign相关参数初始化,包括ExtinctionCoefficients,EmissiveLuminance以及Albedo,后面两者基本上可以理解为是从材质蓝图的输出属性中获取的,而前者追踪到最后,对应的却是GetMaterialSubsurfaceDataRaw接口,但是从材质蓝图中并没有找到这个参数的赋值入口,这一点还有待确认。
-
在宏开关打开的情况下,计算前面说过的逐sample相函数参数
-
计算两盏光源的LightTransmittance参数:AtmosphereTransmittanceToLight0/AtmosphereTransmittanceToLight1,这两个参数表示的是从点A到点B传播过程中的Transmittance数值,是通过GetAtmosphereTransmittance接口(SkyAtmosphereCommon.ush文件)计算得到的,通过对TransmittanceLutTexture进行采样来得到返回值。
-
使用AO对天光参数DistantLightLuminance进行修正
将Albedo与ExtinctionCoefficients相乘得到ScatteringCoefficients,当这个参数大于0时,表示的是这一点有处于云层中,开启后续计算Marching逻辑。
2.4.2 Marching细节
-
首先使用前面计算得到的光照Inscattering数据AtmosphereTransmittanceToLight0与AtmosphereTransmittanceToLight1,光照强度数据Light0Illuminance与Light1Illuminance再加上散射系数GroundToCloudTransfertIsoScatter计算得到当前点输入的Inscattering强度ScatteredLightLuminance
-
在当前采样点可见性较好的情况下(TransmittanceToView > 0.01)进行Ground方向(float3(0, 0, -1))的Shadow处理
3.根据Ground方向的Shadow处理结果,得到一个OpticalDepth,使用比尔定律对这个参数进行处理后与之前的Inscattering参数相乘,就得到了Ground方向的LightLuminance输入,将这部分数据累加到DistantLightLuminance天光部分。
-
准备好光照方向Shadow Marching相关的Inscattering参数,包括ShadowLengthTest,ShadowStepCount等
-
计算光照方向上的Shadow参数,这里有两种方式:
5.1 一种是直接从shadowmap进行采样,这里的shadowmap是cloud绘制出来的shadowmap(仅限于第一盏光源),主要有三个通道:
R - ShadowFrontDepthKm - km
G - MeanExtinction - 1/m
B - MaxOpticalDepth
这里有个问题,就是如何判断当前点的OpticalDepth,对于从光源视角出发的某个点,沿着光照方向上的所有点,在ShadowMap上的UV都是相同的,这些点对应的Shadow显然是不一样的,要如何区分?这里的做法是通过估算得到:
- SampleDepthKm = saturate(1.0f - CloudShadowSampleZ) * ShadowmapFarDepthKm;
- 计算当前点在光源视角下的深度
- OutOpticalDepth = MeanExtinction * (max(0.0f, SampleDepthKm - ShadowFrontDepthKm) * KILOMETER_TO_METERS)
- 根据shadowmap的frontdepth与平均Extinction推算出一个OpticalDepth
- OutOpticalDepth = min(MaxOpticalDepth, OutOpticalDepth)
- 确保不要超出最大OpticalDepth
5.2 第二种方法就是传统的RayMarching,实现流程与Ground Shadow类似,这里就不展开了。
这里如果在开启第二盏光源的情况下,对应的Shadow计算就只能走第二种RayMarching计算了。
2.4.4 Aerial Perspective Blend处理
前面说过,最终计算得到的Cloud,还需要考虑与Aerial Perspective的融合,因此这里还需要根据TransmittanceToView参数计算出于Aerial Perspective融合的加权系数
2.4.5 MultiScattering处理
RayMarching Loop处理的最后一项工作则是考虑来自多个方向的Inscattering输入(不仅限于两盏输入光源与Ground,最多支持MSCOUNT)作用下的Luminance输出。
- 首先使用每个输入的TransmittanceToLight与LightIlluminanceFinal以及相函数输出相乘得到SunSkyLuminance,这里如果需要考虑两盏光源,那么就有两套参数,最终的SunSkyLuminance也是两盏光源共同作用之和,这是整个Cloud Shading的核心部分
1.1 相函数计算中一个非常重要的参数是光源方向,两个光源方向对应的是ResolvedView.AtmosphereLightDirection数组,而这个数组是在FViewInfo::SetupUniformBufferParameters函数中进行赋值的,一般来说第一盏光源对应的是太阳光,不过这里赋值逻辑稍微复杂了一点,根据是否需要进行大气散射计算,这里赋值的来源可以是大气散射相关数据,也可以是其他光源数据。
1.2 此外,LightIlluminanceFinal数据对应的是光源颜色,而这个数据也是来自于上面相对应的光源(或大气散射)数据。
1.3 默认情况下只有单盏光源,即太阳光;而如果需要打开第二盏光源,则需要在shader中开启CLOUD_SAMPLE_SECOND_LIGHT宏,而要想打开这个宏,就需要在场景中添加一盏额外的平行光,并且进行如下设置:
- 将DistantLightLuminance累加到SunSkyLuminance
- 使用衰减系数以及自发光参数计算得到最终的ScatteredLuminance = SunSkyLuminance * ScatteringCoefficients + EmissiveLuminance;
- 利用exp(x)的指数积分就是exp(x),对当前采样step上的每一点的ScatteredLuminance 遵循比尔定律进行积分,得到最终的LuminanceIntegral
- 而在这个step上的Luminance则等于TransmittanceToView * LuminanceIntegral,最终输出的Luminance则是所有step上的Luminance之和。
3. Aerial Perspective融合
体积云渲染结果与AP的融合遵循的是如下的公式:
Luminance = AerialPerspective.rgb * MeanCoverage + AerialPerspective.a * Luminance;
时间关系,具体参数含义这里就不展开了,有兴趣的同学请自行翻阅代码实现吧。
网友评论