雨夜效果

特症

  1. 长长的高光反射。 不限于夜晚,潮湿的表面也会出现。
  2. 高光反射根据表面粗糙度,呈现出多样性。(越粗糙越暗)。
  3. 高光大小依赖于视角。各向异性反射似乎遵循Blinn-Phong行为(同时Blinn-Phong模型不允许拉伸太多):,然而,当表面是光滑的,我们有一个完美的反射(左),无论距离,遵循Phong行为
  4. 反射模糊度取决于视角、光线角度、折射率(即镜面)、表面粗糙度和距离光源的距离(但仅在粗糙表面的情况下,对于光滑表面的模糊度不变化):
  5. :不仅明亮的光源会被反射,所有的东西都会被反射,即使在夜晚反射也是有颜色的(通常黑暗的地方避免看到彩色的反射,因为没有足够的反射光):
  6. 水会在堆积在深处。当有足够的水积累,我们可以看到一层薄的水或一个深的水坑表现出双层行为。我们可以看到水层和下面的表层:
  7. 水层的反射遵循菲涅耳定律。这意味着你在掠射角度(0°)有全反射,而在垂直于表面(90°)时有低反射(事实上每个表面都有菲涅尔反射,但这在水的情况下更明显):
  8. 潮湿表面整体变暗,镜面更明亮

实现潮湿的地表

主要分为: * 干燥的区域 * 潮湿,但是没有积水的区域,建议反射系数为5~10, 漫反射Spencular 0.1~0.3 * 有积水,但是还没有形成水坑,或者在水坑边缘的区域。 * 水坑区域

潮湿但没有积水的地面, 地面变暗, 镜面反射更明亮,高光凝聚更高。设定 WetLevel: 0 为干燥, 1 为湿润, 通过潮湿成都 WetLevel 来控制 gloss 高光参数, 压暗 diffuse 颜色。

    void DoWetShading(inout float3 albedo, inout float gloss, float wetLevel)
    {
        // 越潮湿,地面越暗
        albedo *= lerp(1.0, 0.3, wetLevel);
        gloss = min(gloss * lerp(1.0, 2.5, wetLevel), 1.0)

    }

再加上一个Mask贴图, 表示水坑的深度和大小。 再加上 环境反射和菲尼尔反射。

菲尼尔反射

当光到达介质表面,一部分光反射,一部分光折射,当视线垂直于表面,反射会变得非常弱,当视线接近平行于介质表面,反射会变得非常强。 比如在湖边,你远远看去,湖面波光粼粼,低头直视湖面,就清澈见底。 我们常用的是菲涅尔的近似公式:

\[ F_{Schlick}(n, v, F_0) = F_0 + (1 - F_0)(1 - (n*v))^5 \]

其中F(0)表示当光纤垂直, 0度角摄入介质的基础反射率、常见的菲尼尔 F0 反射率:

设置一个积水系数 **_AccumulateWater**. 设BrickF0 为地面的F0值, 0.02 为谁的F0 值。

    float f0 = lerp(_BrickF0, 0.02, _AccumulateWater);
    float frensel = f0 + (1- f0)*pow(( 1- dotNV), 5);
    float3 specular = frensel * ((specularPower +2.0)/ 8.0) * pow(dotNH, specularPower)* dotNL;

    float3 reflection = reflect(viewDir, normal);
    float3 reflCol = texCUBE(_CubeMap, reflection) * glossParam;
    float3 ambient = saturate(_ReflectFactor +frensel) * reflCol;

地表睡眠涟漪

大部分游戏使用一个动态的ripple 法线贴图来实现这个效果。每帧生成一个新的ripple 法线贴图。 ​生成波纹比较简单的方法是使用动画纹理,缺点就是显示形式比较固定。本文选择程序化纹理方式来实现。从而避免显示重复和比物理模拟成本更低。

我们从纹理中生成不一样大小的动画圆形波纹。文章里的算法会生成不同大小的圆圈,用了一张很特殊的纹理来生成不一样大小的圆形涟漪。

float3 ComputeRipple(float2 uv, float currentTime, float weight)
{
    float4 ripple = tex2D(_MainTex,uv);
    //gb 通道从[0,1]变成[-1,1]
    ripple.yz = ripple.yz * 2.0 - 1.0;
    //rippleTex的w通道(Alpha通道)圆圈外面都是0
    //加上时间偏移。dropFrac 限制在[0,1)
    float dropFrac = frac(ripple.w +currentTime);
    //timeFrac限制在[-1,1],dropFrac - 1.0,可以确保圆圈的外面(ripple.x=0),timeFrac<=0.
    //从而波纹外面的法线是垂直的。也就是水平的。
    float timeFrac = dropFrac - 1.0 + ripple.x;
    // 波纹随着时间扩大(dropFrac 变大),dropFator越小,波纹慢慢变平
    float dropFator = saturate(0.2 + weight * 0.8 - dropFrac);
    //从波纹出现开始,时间越大(dropFrac 变大),dropFator 越小,final 越小,波纹越平。
    //sin(clamp(timeFrac * 9.0,0.0,3.0)PI):0-3PI之间,sin就是一个波峰-波谷-波峰的函数图像,
    //在0-3*PI之外sin函数皆为0.9.0是一个把timeFrac参数放大的因子。
    //波纹圈内的值,比如波纹中心值,经过sin函数的计算,变成0.也就是说波纹中心点也是平的。这个平的区域,
    // 随着时间变大,慢慢扩大,(时间越大,只有红色通道部分越小的地方(红色通道ripple.x渐变到0),
    //sin函数才不为0.所有波纹就会有慢慢变大的效果
    float final = dropFator * ripple.x * sin(clamp(timeFrac * 9.0,0.0,3.0)*PI);
    return float3(ripple.yz * final * 0.35,1.0);
}

输出发现特图

return float4(normalize(N) * 0.5 + 0.5, 1.0); 

涟漪法线贴图和地面混合

我们可以利用生成的涟漪法线和地表原本的法线做一次线性插值。

// ripple tex
            float3 rippleNormal = tex2D(_RippleTex,i.uv * _RippleDensity).xyz * 2 - 1;
            float3 wRippleNormal = normalize(mul(matToW,rippleNormal));
            float3 normal = lerp(wNormal,wRippleNormal,_AccumulateWater);

凹凸间隙积水

​ 为了模拟出在地表凹凸间隙的水积累效果,文章用高度图表现砖缝的高度变化,用顶点颜色表现水坑深度(这里我们用mask图来控制水坑的深度)。


文章作者: Yonggang Long
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Yonggang Long !
 上一篇
2022-08-10 Yonggang Long
下一篇 
2022-08-10 Yonggang Long
  目录