一、Flowmap 是什么
半条命2中水的流动
求生之路2中的水的流动
这种方式原理简单,容易实现,运算量少,如今也还在使用
1.flowmap的实质
Flow map(流向图) ,一张记录了2D向量信息的纹理,Flow map上的颜色(通常为RG通道)记录该处向量场的方向,让模型上某一点表现出定量流动的特征。
通过在shader中偏移uv再对纹理进行采样,来模拟流动效果。
右边向量场的方向于中间Flowmap和它的方向是不一致的,这是为什么呢?
2.前置了解:UV映射
UV坐标用于查找纹理的颜色值:
UV贴图(用于理解):使用(R,G)颜色通道表示坐标:
黑色(0,0),绿色处(0,1),红色处(1,0),黄色处(1,1)
如果我们要对一个矩形进行纹理查找的话,我们首先会用到它的UV坐标
左边为unity中的UV坐标 与我们熟知的xy坐标轴相似
用该UV坐标去查找右边这张贴图的颜色值,我们将会得到和原贴图一摸一样的结果
如果让更改UV坐标,让每一列都拥有相同的UV值再去采样,则会得到右图这样的条纹图结果
若使整个采样的时候用的UV值是同一个的话,如图我们将该UV坐标的UV值全都变为黑色,那么结果就会对左下角进行采样
UV贴图上颜色相同的地方就意味着它在采样纹理的时候使用了同一个位置
Flowmap则是通过它上面所带有的向量场的信息对UV进行一个偏移之后,去干扰我们采样纹理时候的过程,如上图
注意在UE4中,UV坐标是不同的,与unity相比是将绿通道反转了。所以使用的Flowmap也会发生变化,要根据引擎进行调整
偏转后的UV坐标去采样右图后得到下面扭曲贴图
2.为什么要使用flowmap?
原因:
类似UV动画,而非顶点动画。换言之,无需对模型顶点进行操作,易实现,运算开销小。
不仅仅是水面,任何和流动相关的效果都可以采用flowmap。
flowmap不仅仅被用于制作一些侵蚀效果或表面的流动效果,还被用于制作流动的天空球中
二、Flowmap shader
1. 借助Shader Graph与desmos理解flowmap
- 采样Flow map获取向量场信息
- 用向量场信息,使采样贴图时的UV随时间变化
- 对同一贴图以半个周期的相位差采集两次,并线性插值,使贴图流动连续
2. 在shader中实现flowmap
- 目标:根据flowmap上的值,使纹理随时间偏移。
- 最简单的随时间偏移:UV - time
- 为什么是相减:
- 先来看看 uv+time 的情况
- (u,v) + (time,0) :模型上某个点: 随着time增加,采样到的像素越远
- 视觉上可以形容为:更远距离的像素偏移向该点,视觉效果和我们直观认识到的运算法则是相反的。
-
UV值作为向量(u,v),自然也遵循向量的运算法则。但UV偏移时,改变的不是顶点的位置。
-
单方向运动X
由flow map获取流动方向√
-
flow map不能直接使用,因为flowmap的范围说[0,1]
先将flow map上的色值从[0,1]的范围映射到方向向量的范围[-1,1]即乘2减1
从flowmap获取需要的流动方向,再乘Time就可以让某个点去根据flowmap进行流动
调整采样时的UV为: adjust_uv = uv - flowDir * time
在这里我们用FlowSpeed来控制向量场的强度
但随着时间的增加,扭曲程度会越来越大,所以需要一个Fraction函数把时间变成[0,1]的循环 把它变成一个三角波函数
如何实现呢?
我们需要构造两层采样相差半个周期的采样,再对他们进行插值混合
随着时间进行,变形越来越夸张,为了把偏移控制在一定范围内:
frac函数是一个常用的数学函数,主要用于提取浮点数的小数部分。
解决frac产生的跳变,把上面改成:
我们希望的流动:无缝循环
用相位差半个周期的两层采样进行加权混合,使纹理流动一个周期重新开始时的不自然情况被另一层采样覆盖
用flowmap修改法线贴图
三、Flowmap的制作
1. Flowmap Painter
Unity制作的绘制flowmap的工具
注意:
用该工具得到的flowmap为线性空间下的颜色,
不需要gamma矫正,Unity中请取消勾选“□SRGB”
flowmap的烘培和相关设置
flowmap贴图设置:
1.无压缩或高质量
2.确认色彩空间
导出时注意gamma校正选项、UV匹配
2. Houdini Labs
Houdini Labs是内置在houdini中的一组游戏开发相关的节点,可以在github中搜索sidefx Labs或着直接在houdini中安装得到。
在较早版本的houdini中无法在shelf内找到该工具,你只能通过github下载。在这些未被内置到houdini的版本中,这组工具的名称为gamedev。
1. flowmap相关节点功能
2. flowmap的绘制与烘焙
Houdini目前还没有接触,所以这部分先留着以后再研究
四、作业
效果如下:
FGO 奇点
完整代码
Shader "Shader/Flow Map"
{
Properties
{
_MainTex("Main Texture", 2D) = "while"{}
_Color("Tint", Color) = (1,1,1,1)
//Flowmap
_FlowMap("Flow Map", 2D) = "while"{}
_FlowSpeed("向量场强度", float) = 0.1
_TimeSpeed("全局流速", float) = 1
[Toggle]_reverse_flow("反转流向", Int) = 0
}
SubShader
{
Tags
{
//开启透明渲染"Queue"="Transparent"
//注意渲染顺序
"RenderType" = "Opaque"
"IgnoreProject" = "True"
"RenderType" = "Opaque"
}
Cull Off
Lighting Off
ZWrite On
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma Shader_feature_REVESE_FLOW_ON
#include "UnityCG.cginc"
struct appdata_t
{
float4 vertex : POSITION;
float2 texcoord : TEXCOORD0;
};
struct v2f
{
float4 vertex : SV_POSITION;
float2 uv : TEXCOORD0;
};
sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _Color;
sampler2D _FlowMap;
float _FlowSpeed;
float _TimeSpeed;
v2f vert (appdata_t v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord;
//o.color = i.color * _Color;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
//从flowmap获取流向
float3 flowDir = tex2D(_FlowMap, i.uv) * 2.0 - 1.0;
//FlowSpeed影响向量场强度,值越大,不同位置流速差越明显
flowDir *= -_FlowSpeed;
//如果勾选,则反向流转
#ifdef _REVERSE_FLOW_ON
flowDir *= -1;
#endif
//构造周期相同,相位相差半个周期的波形函数
float phase0 = frac(_Time * 0.1 * _TimeSpeed);
float phase1 = frac(_Time * 0.1 * _TimeSpeed + 0.5);
//平铺贴图用的uv
float2 tilling_uv = i.uv * _MainTex_ST.xy + _MainTex_ST.zw;
//用偏移后的uv对材质进行偏移采样
half3 tex0 = tex2D(_MainTex, tilling_uv - flowDir.xy * phase0);
half3 tex1 = tex2D(_MainTex, tilling_uv - flowDir.xy * phase1);
//构造函数计算波形函数变化的权值,使用MainTex采样值在接近最大偏移时有权值为0,并因此消隐构造较为平滑的循环
float flowLerp = abs((0.5 - phase0) / 0.5);
half3 finalColor = lerp(tex0, tex1, flowLerp);
fixed4 c = float4(finalColor, 1.0) * _Color;
return c;
}
ENDCG
}
}
}