UE4 模拟流体水滴下雨

作者:转载小公举
2019-11-14
8 3 0

作者:DanSon Tang

这篇文章将介绍 UE4 中模拟流体水滴下雨效果的核心思路。

原文来自知乎专栏 UE4 笔记,已取得作者 DanSon Tang 授权发布。

正文

首先来看看要实现的效果

通过上图,可以把这个效果三个部分:

  1. 雨滑落时候雨痕迹
  2. 雨下滑的水滴
  3. 停留在玻璃上的雨滴

当然,这里面最核心的效果是雨滴的滑落,雨滴下滑的时候,不同雨滴的速度不同,如果只是用一张 mask 贴图,然后做 UV 移动,效果无法达到。所以想到能不能有办法来控制雨滴下滑的速度。我们自然想到定义一个数组 SpeedScaleArray[] 来存储一组数据,来控制雨滴下滑的速度。不同的雨滴在下滑时形状和拖尾的长短也不一样,但是我们只想用一张贴图来描述雨滴 d 形状,但是雨滴又要无规则分布。所以接着联想到用不同的 UV 来定位不同雨滴在 UV 坐标下的位置,所以有用一组数组 PointCenterArray[] c 来存放一组 UV 坐标数据。以下是在 Custom 节点 HLSL 部分代码。

const int ArraySize = 13;
const float2 PointCenterArray[ArraySize] =
{
  float2(0.616431, 0.8110922),
  float2(0.9301831, 0.1473412),
  float2(0.02223931, 0.1184332),
  float2(0.8592911, 0.3553432),
  float2(0.4200671, 0.283112),
  float2(0.2996781, 0.3984312),
  float2(0.06235941, 0.5027822),
  float2(0.2488471, 0.7196162),
  float2(0.129141, 0.9047732),
  float2(0.4199991, 0.8350922),
  float2(0.8572431, 0.8587682),
  float2(0.7221561, 0.5682152),
  float2(0.596571, 0.1159342)
};

const float SpeedScaleArray[ArraySize] =
{
  1.367651,
  0.8587561,
  1.01,
  0.8587561,
  1.01,
  0.6405531,
  0.6405531,
  0.5894141,
  0.7692751,
  0.5894141,
  1.522451,
  1.367651,
  1.01
};

上面每个数组总共存储了 13 组数据,来分别描述雨滴下滑的速度以及位置。

接下来需要三张贴图如下。第一张贴图红色通道存储的是雨滴的形状,绿色通道雨滴的痕迹,法线贴图只需要红绿通道来对这些形状做叠加混合以此来使这些形状变得不规则。第三章贴图是雨滴的法线贴图。

//Sample base distortion normal map and unpack it.
float2 Distortion = (Texture2DSample(TexD, TexDSampler, LocalDistUV).rg * DistPower - 0.5) * 2.0;

//Sample second (smaller) distortion normal map and unpack it.
float2 SmallDistortion = (Texture2DSample(TexD, TexDSampler, LocalDistUV * 3.0).rg * DistPower * 0.25 - 0.5) * 2.0;

//Add smaller distortion value to base distortion.
Distortion.x += SmallDistortion.x;
Distortion.y += SmallDistortion.y * 0.5;

//Local UVs for sampling droplets texture with array varitations.
float2 LocalUV = UV + PointCenterArray[i] + Speed * SpeedScaleArray[i] * 4.0;

第一行和第二行代码使用 Texture2DSample 对第二张法线贴图进行了采样。然后提取 RG 两个通道,后面再通过 DistPower 变量对两张贴图强度进行控制,最终将两张贴图的 RG 两个贴图叠加。这里主要是为了满足美术效果是雨痕和雨滴的形状看起来不规整。最后一段代码,将数组不同的 UV 以及不同流动速度,做了相应的 UV 动画。

{
  ...

  //Adding distortion to UVs.
  LocalUV += Distortion;

  //Sample normal map of droplets and unpack it.
  float3 NewNormal = (Texture2DSample(TexN, TexNSampler, LocalUV).rgb - 0.5) * 2.0;

  //Blend normal maps together.
  NormalBlend.b += 1.0;
  NewNormal.rg *= -1.0;
  NormalBlend = NormalBlend * dot(NormalBlend, NewNormal) - NewNormal * NormalBlend.b;

  //Colamp blended normal map.
  NormalBlend = clamp(NormalBlend, -1.0, 1.0);

  //Add and mix droplets texture masks.
  Texture.b = max(Texture.b, Texture2DSample(TexM, TexMSampler, LocalUV).g);
  Texture.a = max(Texture.a, Texture2DSample(TexM, TexMSampler, LocalUV).r);
}

//Combine resulting maps into one.
return float4(NormalBlend.rg, Texture.ba);

接下来的第一段代码采样了雨滴法线贴图,接下来是一个法线融合算法。法线融合算法参考下面网页:

Blending in Detail

去看看

代码及效果

最终的代码:

//Const variables from script.
const int ArraySize = 13;
const float2 PointCenterArray[ArraySize] =
{
  float2(0.616431, 0.8110922),
  float2(0.9301831, 0.1473412),
  float2(0.02223931, 0.1184332),
  float2(0.8592911, 0.3553432),
  float2(0.4200671, 0.283112),
  float2(0.2996781, 0.3984312),
  float2(0.06235941, 0.5027822),
  float2(0.2488471, 0.7196162),
  float2(0.129141, 0.9047732),
  float2(0.4199991, 0.8350922),
  float2(0.8572431, 0.8587682),
  float2(0.7221561, 0.5682152),
  float2(0.596571, 0.1159342)
};
const float SpeedScaleArray[ArraySize] =
{
  1.367651,
  0.8587561,
  1.01,
  0.8587561,
  1.01,
  0.6405531,
  0.6405531,
  0.5894141,
  0.7692751,
  0.5894141,
  1.522451,
  1.367651,
  1.01
};

//Clamp amount of droplets to array count.
Count = (int) (clamp(Count, 0.0, ArraySize));

//Resulting texture.
float4 Texture = float4(0.0, 0.0, 0.0, 0.0);

//Resulting normal map.
float3 NormalBlend = float3(0.0, 0.0, 1.0);

//Lopp for droplets custom tile and animation.
for (int i = 0; i < Count; i++)
{
  //Local distortion UVs for each tile.
  float2 LocalDistUV = DistUV;
  
  //Sample base distortion normal map and unpack it.
  float2 Distortion = (Texture2DSample(TexD, TexDSampler, LocalDistUV).rg * DistPower - 0.5) * 2.0;
  
  //Sample second (smaller) distortion normal map and unpack it.
  float2 SmallDistortion = (Texture2DSample(TexD, TexDSampler, LocalDistUV * 3.0).rg * DistPower * 0.25 - 0.5) * 2.0;
  
  //Add smaller distortion value to base distortion.
  Distortion.x += SmallDistortion.x;
  Distortion.y += SmallDistortion.y * 0.5;
  
  //Local UVs for sampling droplets texture with array varitations.
  float2 LocalUV = UV + PointCenterArray[i] + Speed * SpeedScaleArray[i] * 4.0;
  
  //Adding distortion to UVs.
  LocalUV += Distortion;
  
  //Sample normal map of droplets and unpack it.
  float3 NewNormal = (Texture2DSample(TexN, TexNSampler, LocalUV).rgb - 0.5) * 2.0;
  
  //Blend normal maps together.
  NormalBlend.b += 1.0;
  NewNormal.rg *= -1.0;
  NormalBlend = NormalBlend * dot(NormalBlend, NewNormal) - NewNormal * NormalBlend.b;
  
  //Colamp blended normal map.
  NormalBlend = clamp(NormalBlend, -1.0, 1.0);
  
  //Add and mix droplets texture masks.
  Texture.b = max(Texture.b, Texture2DSample(TexM, TexMSampler, LocalUV).g);
  Texture.a = max(Texture.a, Texture2DSample(TexM, TexMSampler, LocalUV).r);
}

//Combine resulting maps into one.
return float4(NormalBlend.rg, Texture.ba);

材质截图:

最终效果:(视频)