美文网首页
体积云染色方案

体积云染色方案

作者: 离原春草 | 来源:发表于2021-04-16 21:27 被阅读0次

    最近接了一个实现体积云风格化染色的需求,策划大致的意思是想实现如下面几张图片示意的效果:

    Ref 1 Ref 2 Ref 3 Ref 4

    目前实现的体积云颜色效果大多是通过以白色作为基色,经过阴影叠加后呈现出来的,大致如下图所示:

    在叠加了太阳光之后,可以做到如下的效果:

    可以看到,其效果距离预期还有不小的差距,因此需要对方案做修正。

    先对参考图中的效果做一下简单的分析,Ref 2中表现出来的效果通过一个简单的叠色就能够输出,而不同区域不同的颜色效果就需要结合叠色贴图来给出(或者按照某些规则,比如与大气散射相结合来给出不同位置的不同叠色);其余三张贴图,假如不考虑阳光打在云层底部散射造成的明亮着色,其实也就是一个简单的叠色就能输出,因此总的来说,参考图中的效果主要包含两项:

    1. 叠色,如果想要实现局部叠色,就需要给出一种规则,在不同的区域输出不同的叠色效果。
    2. inscattering散射,通过相函数对太阳光散射效果进行模拟

    下面我们一起来看一下要想实现给定的着色效果,需要做怎么样的处理。

    为了快速确定方案,这里使用shadertoy上现成的案例来进行验证,这里是测试方案地址,如不做说明,下面不同方案尝试中的效果截图都来自于此方案。

    1. 方案预研

    1.1 原始着色

    首先来看一下,不做任何处理下的云层效果表现,如下图所示:

    基本颜色为白色,通过一些灰色来表示云层对光线的遮挡效果。

    1.2 全局叠色

    所谓的全局叠色指的是在原始着色的基础上乘上一个给定的颜色值来实现云层着色的方案,这个方案用如下公式来表示:

    return float4(originCol.rgb * tintCol, originCol.a);
    

    效果图:

    tintCol = float3(1.0, 0.3, 0.0) tintCol = float3(1.0, 0.9, 0.0) tintCol = float3(0.0, 0.9, 0.8) tintCol = float3(0.0, 0.8, 1.8)

    从结果可以看出,虽然相对于原始着色方案而言,确实可以给出非常丰富的着色效果,但是相距预期方案中丰富的层次变化还有一定距离。

    1.3 明暗过渡着色

    所谓的明暗过渡着色,指的是根据原始方案中的着色结果作为权重值对两个给定的颜色进行混合的着色方案,用公式来表示:

    float3 finalCol = saturate(lerp(darkCol, brightCol, originCol.rgb));
    return float4(finalCol, originCol.a);
    

    效果图:

    darkCol = vec3(0.0, 0.3, 0.1), brightCol = vec3(1.0, 0.3, 0.0) darkCol = vec3(0.0, 0.0, 0.0), brightCol = vec3(1.0, 0.0, 0.0) darkCol = vec3(-0.5, 0.0, 0.0), brightCol = vec3(1.0, 0.0, 0.0)

    从效果上来看,明暗过渡颜色在明暗颜色一脉相承(比如都是红色,只是亮度不相同)时,可以得到比较自然的表现,且对于颜色过渡的力度控制更为细腻,但是在明暗颜色相去较远时,就会使得效果比较魔幻。

    1.4 渐变着色

    明暗过渡是通过两个颜色的混合来实现颜色过渡的,但是从前面的参考效果来看,云层颜色效果可能会涉及到不少于两种颜色的参与,比如可以从颜色A过渡到B,再过渡到C的情况,对于这种情况,可以考虑使用类似photoshop的渐变方案来进行着色,其实现可以用如下公式来表示:

        float points[] = float[](0.25, 0.55, 0.9, 0.93, 0.98);
        vec3 cols[] = vec3[](
            vec3(1.5, 0.0, 0.0),
            vec3(1.0, 0.4, 0.6),
            vec3(1.0, 0.6, 0.7),
            vec3(1.0, 0.8, 0.2),
            vec3(1.0, 1.0, 0.0)
            );
        int colNum = points.length();
        float originalCol = OriginalColor.r;
        float scale = 1.0;
        originalCol = originalCol * scale;
        if(originalCol < points[0])
            return cols[0];
        for(int i = 1; i < colNum; i++)
        {
            if(originalCol < points[i])
                return mix(cols[i - 1], cols[i], (OriginalColor.r - points[i - 1])/(points[i] - points[i - 1]));
        }
        if(originalCol > points[colNum - 1])
            return cols[colNum - 1];
    

    效果图:

        float points[] = float[](0.0, 0.25, 0.55, 0.9, 0.93);
        vec3 cols[] = vec3[](
            vec3(0.0, 0.0, 0.0),
            vec3(0.6, 0.0, 0.0),
            vec3(1.0, 0.4, 0.6),
            vec3(1.0, 0.65, 0.6),
            vec3(1.0, 0.8, 0.6)
            );
    
        float points[] = float[](0.0, 0.25, 0.55, 0.9, 0.93, 1.0);
        vec3 cols[] = vec3[](
            vec3(0.0, 0.0, 0.0),
            vec3(1.0, 0.4, 0.6),
            vec3(0.5, 0.5, 0.6),
            vec3(1.0, 0.6, 0.7),
            vec3(1.0, 0.7, 0.7),
            vec3(1.0, 1.0, 0.7)
            );
    
        float points[] = float[](0.0, 0.25, 0.55, 0.9, 0.93);
        vec3 cols[] = vec3[](
            vec3(0.0, 0.0, 0.1),
            vec3(0.6, 0.0, 0.6),
            vec3(1.0, 0.4, 0.6),
            vec3(1.0, 0.65, 0.6),
            vec3(1.0, 0.7, 0.6)
            );
    

    这种方案可以给出多层渐变的着色效果,但是存在如下的一些问题:

    1. 参与渐变的颜色需要保持一致,颜色跳变会使得效果非常难看
    2. 依然无法模拟参考图中太阳光打在云层底部经由相函数散射出来的明亮效果(如Ref4的明黄色)。

    1.5 添加inscattering相函数散射

    在原始方案中添加相函数控制的inscattering,已经能基本模拟底部受太阳光照下的金边效果:

    在此基础上再添加叠色方案,可以得到如下结果:

    这个方案虽然可以生成边缘处的金边,但是在背光处的黑色太深了,这是因为只考虑了单次散射的效果,为了消除这种死黑的表现,还需要添加天光与大地反射导致的散射。

    添加了大地散射后,可以做到如下的效果:

    在此基础上再添加天光散射,可以得到如下效果:

    这是未做任何叠色的情况下输出的结果,其中黄色表示的是太阳光散射的效果,紫色表示的是大地散射的效果,蓝色表示的是天光散射的效果。

    可以看到,在这种算法作用下,不需要做叠色方案,就已经能够得到比前面的叠色方案还要好的结果。

    2. 方案验证

    shadertoy的实现较为简单,不过已经基本验证了体积云着色的相关效果,为了能应用到项目中,最终还是需要在引擎中进行相关着色处理,下面给出在UE4.26中应用各种inscattering之后的效果表现。

    2.1 现有染色方案

    2.1.1 太阳光染色

    UE4.26提供了一种双光源体积云着色方案,其中第一盏光源是默认的太阳光,通过对太阳光颜色与亮度进行调整,可以影响到云层的染色效果:

    这种方案的一个问题是,这种染色不但会影响到云层染色效果,还会影响到大气(天空)散射效果,没有办法做到解耦。而要想避开对大气散射的影响,则需要调整光源的另一项属性:

    Atmosphere Sun Light开关用于控制此光源是否用于Cloud/Atmosphere着色,当关闭时,云层绘制全黑。当开启后,就可以通过调整Cloud Scattered Luminance来调整散射颜色。

    这种状态下的云层着色就不会影响到大气散射了。

    2.1.2 双太阳光染色

    再尝试打开第二盏光源,依然是平行光,得到的效果并不能令人满意,除了效果调整十分费劲之外,且没有办法如前面shadertoy demo一样,能够实现多层染色。

    2.1.2 Ground染色

    VolumetricCloudComponent上有一个变量叫GroundAlbedo,从描述上来看跟我们前面在shadertoy demo中的ground inscattering相似,先来看下这个参数调整是否能得到不一样的表现。

    这个开关的打开需要在材质的Volumetric Advanced节点中打开GroundContribution开关,但实际上这个节点并没有这个选项:

    这是怎么回事呢?实际上这个是在此节点的选项面板中进行打开的:

    此外,在源码追踪的过程中,我们发现Shader在处理Ground相关的着色时,还存在一个由宏MATERIAL_VOLUMETRIC_ADVANCED_GROUND_CONTRIBUTION进行控制的实现逻辑,这个宏
    同样也是由上述开关进行控制。

    打开之后,调节GroundAlbedo,效果对比如下所示:


    GroundAlbedo = (255, 0, 0) GroundAlbedo = (0, 255, 0)

    可以看到,通过对颜色的调节,可以实现底部染色的修正,不过这个修正效果不是很明显,也就是说,GroundAlbedo对云层的影响力度比较小,是否可以增强这个影响呢?通过对源码的分析,发现当前并无此scale因子,而要想实现就需要添加额外的代码进行处理。

    下面给出Cloud Scattered Luminance为绿色,GroundAlbedo为紫色时的表现:

    在此基础上对阳光颜色及亮度进行修正,可以得到如下效果:

    从上面给出的效果可以看到,GroundAlbedo跟CloudScattered Luminance两个参数都是能够很好的用于调整云层着色效果的,只是GroundAlbedo影响的强度是固定的(CloudScattered Luminance由于数据使用的是浮点数,可以直接调整亮度),使用起来不太灵活,这里尝试增加GroundAlbedoScale参数,用于对这个参数进行增强。

    2.1.3 Modified CloudScattering/GroundAlbedo

    在shader与cpp代码中添加了GroundAlbedoScale参数后,可以实现更为明显的GroundAlbedo调整效果,下面给出的是仅仅调整GroundAlbedoScale的效果对比:

    Scale = 20 Scale = 1

    测试的过程中发现,Shader中存在multiscattering相关的逻辑,这个逻辑是通过Volumetric Advanced Output节点的Multi Scattering Approximation Octave Count参数进行控制的,此参数对应的是在RayMarching的过程中,射线上的每个Sample在沿着太阳光方向的二次采样的采样点数目,默认为1.0(实际上shader使用的采样点数目是在这个基础上加了1),下面我们改成0换个角度看下效果:

    Scale = 1 Scale = 20 Scale = 5

    其中绿色是GroundAlbedo的颜色,黄色是太阳光颜色,紫色是Cloud Scattered Luminance的颜色,看起来效果相对于1.0还更柔和一点。

    此外,在Volumetric Advanced Output节点的属性面板上还有一个Per Sample Phase Evaluation开关,这个从名字上来判断,就是用于为射线上的每个sample做单独的phase计算,这个开关打开会加重计算消耗,因此在使用的时候需要确认是否有必要。

    通过对各种参数的调整,我们是可以实现如下的一些效果的:

    Cloud Scattered Luminance = (0, 200, 5), CloudAlbedo = (123, 0, 170), CloudAlbedoScale = 100

    相对于ShaderToy的Demo而言,UE4.26渲染的体积云,在底部缺少了云层遮蔽导致的黑色效果,从而显得立体感不足,我们尝试来确定这是UE实现机制的问题,还是因为参数调整不当导致。

    下面给出了不同Albedo Color下的云层表现,可以看到,Albedo Color是可以用于调整云层底部的亮度的。

    Albedo Color = 0.1 Albedo Color = 0.3 Albedo Color = 0.9

    虽然如此,云层底部颜色依然无法出现demo中接近黑色的表现,理论上来说,太阳光从上往下照射时,如果不考虑天光等间接光,那么云层底部肯定是黑色的,而当前这种表现只能说明是间接光对于云层渲染具有较大的影响,如果希望削弱这个影响,就需要降低间接光对云层着色效果的比重。

    反应到shader代码中,我们可以将DistantLightLuminance对SunSkyLuminance的累加作用置为0,就可以得到如下这种比较接近的效果(远景处存在一些问题,可能因为相对于近景处,采样数目存在差异导致)

    作为对比,打开这个逻辑时的效果表现如下图所示:

    不考虑染色效果作用的话,后者相对而言更接近现实情况中的表现。

    此外,在调试效果的过程中,发现GroundAlbedo并不是像此前Demo中一样通过沿着大地散射方向二次采样得到,而是按照如下的公式计算得到ScatteredLightLuminance之后叠加到最终输出的Color上面。

    float3 ScatteredLightLuminance = AtmosphereTransmittanceToGround * LightIlluminance * GroundToCloudTransfertIsoScatter;
    

    其中AtmosphereTransmittanceToGround从名字上推断应该是从Ground到当前被采样点位置的Atmosphere Transmittance,即未考虑云层遮挡情况下的Transmittance,但实际我们知道,对于云层表面的点,这种计算是合理的,而如果采样点位于云层内部,这种做法就存在较大误差了;LightIlluminance对应的是AtmosphereLightColor,从C++侧赋值逻辑来看,这个值就是太阳光颜色;最后GroundToCloudTransfertIsoScatter对应的是GroundAlbedo经过一系列加工后的结果,反映的是光照打在大地上的反射输出关系(相当于经过了BRDF处理)。

    从这个公式看出,ScatteredLightLuminance实际上指的是太阳光颜色经过大地反射后加上大气散射衰减后的结果。不过正如前面所说,这里的结果没有考虑云层内部遮挡作用,因此效果上会有些差异。这里尝试对散射衰减项进行修正,添加上云层内部穿透代价,看看效果上是否能有所改善。

    2.1.4 Secondary Raymarching Scattering

    UE实现中是增加了沿着太阳光方向的二次散射计算以实现云层背离太阳时的阴影效果的,而这个功能是通过Volumetric Advanced Output节点属性面板的RayMarchVolumeShadow开关进行打开的,关闭这个开关时,就会使用shadow map来计算云层阴影(包括light shaft),打开时就会启用沿着光源方向的二次RayMarching进行计算,关闭跟打开开关时的效果对比如下所示:

    On Off

    看起来对比不是特别明显,不过在关闭情况下,底部相对更暗一点。

    这里增加沿着向下方向的Ground Inscattering处理逻辑,查看是否对效果有所增益:

    Ground Inscattering On Ground Inscattering Off

    从效果上来看,是有变化的,但是这个变化方向是有还是劣暂时还不清楚,调整下其他效果看下:

    On Off On Off

    看起来关闭情况下饱和度要高一点,不过也不清楚美术同学倾向于哪种风格,这里将这套逻辑通过Volumetric Advanced Output节点的选项面板上的RayMarchVolumeGroundScatter开关进行控制,默认关闭。

    相关文章

      网友评论

          本文标题:体积云染色方案

          本文链接:https://www.haomeiwen.com/subject/ijcdlltx.html