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

实现潮湿的地表
主要分为: * 干燥的区域 * 潮湿,但是没有积水的区域,建议反射系数为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图来控制水坑的深度)。