PISCOnoob

导航

FlowMap的简单应用

写在前面:

本文章为个人学习笔记,方便以后自己复习,也希望能帮助到他人。

由于本人水平有限难免出现错误,还请评论区指出,多多指教。

部分图元和素材来源于网络,如有侵权请联系本人删除。

参考资料与链接会在文章末尾贴出。

=======================================================================

1.让纹理动起来

我们应用一张水的纹理在面片上,假设这是水流,但水流应该是会动的,我们可以手动修改matetial的offset来让纹理“流动”。

要想在playmode中让纹理自动“流动”,我们可以加入时间变量来扰动UV:

float4 frag (v2f i) : SV_Target
{
      //获取unity内置时间变量
      float offset=_Time.x;
      //控制x y两个方向的移动
      float4 finalCol=tex2D(_MainTex,i.uv+float2(offset,offset)*_FlowSpeed);

      return finalCol;
  }

这样“河流”就自己“流动”起来了。

但是这样横平竖直地流动有点单调了,这时候我们引用flowmap来丰富效果。

flowmap如下所示:

乍眼一看我们无法理解flowmap到底是什么,有什么作用。实际上flowmap是用纹理的RG两个通道的记录该处向量场的方向,让纹理上某一点出现定向流动的效果。简单理解,flowmap给上面水纹理指出:你接下来要“流动”的方向。我们将flowmap采样的结果放到原uv上进行扰动便有了向不同方向流动的结果。

我们还缺少点东西。如果我们在DCC工具上做了一张flowmap是0~255,到了shader里面计算会normalized成0~1,实际上我们向量表示范围应该是-1到1之间,因此我们需要把flowmap采样结果从0~1映射到-1~1来自由表示上下左右方向:

 float4 frag (v2f i) : SV_Target
 {
 
     //获取unity内置时间变量
      float offset = _Time.x;
      float2 flowDir = tex2D(_FlowpMap,i.uv).xy;
      flowDir=flowDir * 2 - 1 ;
      //控制x y两个方向的移动
      float4 finalCol = tex2D(_MainTex,i.uv + flowDir * _FlowSpeed * offset);
 
      return finalCol;
}

如此一来就有了还不错的效果。

2.advanced in flowmap

上面提到,我们使用了时间变量来让其流动,时间是不断增长的,可以简单看是是f(x)=x这样一个函数:

我们会发现flowmap一开始的几秒效果不错,然而随着时间推移效果逐渐变得离谱,我们希望只要开头几秒的效果。

ps : 我们通过desmos这个网站帮我们可视化一下,但是desmos[1]中数学公式与unity shaderlab中写法有所不同,不过表达的意思是一样的。

如此一来我们就可以只要开头比较漂亮的动画。

新的问题是,每当周期结束就会跳回开头,比较突兀,我们希望平滑处理让玩家看不出来。

Valve在2010年的GDC上给出了相关技术的分享,[2]当时用在了求生之路2和传送门2上。

简单来说,我们构造两个一模一样的周期函数A和B,但是相差半个相位,用线性插值的方法,当A快要结束时B补上,B快要结束时A补上,如此循环往复。

          // 首先从flowmap中获取流向,并将结果映射
                float4 flowDir=tex2D(_FlowMap,i.uv) * 2.0 - 1.0;
                flowDir *= _Intensity;

                // 平铺贴图用的uv
                float2 tillingUV = i.uv * _MainTex_ST.xy + _MainTex_ST.zw;
                // 构造周期相同,但是差半个相位的函数。0-1就是一个周期
                float phase0 = frac(_Time.x * _FlowSpeed ) ;
                float phase1 = frac(_Time.x * _FlowSpeed + 0.5); 
                // 采样两次
                float4 layer0 = tex2D(_MainTex, tillingUV + flowDir.xy * phase0 );
                float4 layer1 = tex2D(_MainTex, tillingUV + flowDir.xy * phase1 );

接下来的问题是我们应该如何混合两层,让变换看起来比较自然。我们会用到线性插值,

公式:

lerp(A,B,W) = (1- W A + W *B

A B分别为两次采样的结果,W为权重。

那么该如何去设计权重曲线呢?答案是不唯一的,重要的是谁的曲线效果更好看。我个人感觉这是整个shader里面最费脑子的一步,我自己在草稿纸和demos上画了很多次最后才得出一条比较满意的曲线。

Valve官方的曲线:

我们先只看红色曲线(周期y1),如果让我们凭空想出一条权重曲线比较难,我们不妨简化一下,y1现在只有

0,0.5, 1.0三个值,我们来思考一下对应位置的权重。因为越开始的位置越好看,不妨设置0的位置权重为1,如此一来即权重随着时间下降。再设置1.0位置权重为1,这时我们发现行不通:

权重的周期跟y1周期并不一致,导致第二个周期的时候权重就不对了。

这也给了我们一个思路,权重函数周期要跟y1一样,于是有:

          float weight = abs(2.0 * phase0 - 1);

                float4 finalCol = lerp(layer0,layer1,weight);

                return finalCol;

 

 

 

上面只是flowmap的一个简单应用,它还可以用来做很多东西,比如火焰和沙漠高温扭曲空气的效果,很多3A大作比如The last of us, God of war里面也会用到flowmap做天空顶的效果[4]。所以我们要先把简单的学会充实自己的武器库才能做出更好看的效果。

另外Valve使用houdini做的flowmap,我们可以用一个简单的工具生成flowmap练习[5]。作者TECH LEE TAN目前是Unity的Tech Artist和Field Engineer。

 

参考资料:

1.Desmos | Let's learn together.

2.

3.【技术美术百人计划】图形 2.8 flowmap的实现——流动效果实现_哔哩哔哩_bilibili

4.Moving the Heavens: An Artistic and Technical Look at the Skies of The Last of Us

5.FlowMap Painter - teckArtist

6.

 

posted on 2022-11-01 14:25  PISCOnoob  阅读(169)  评论(0编辑  收藏  举报