最近刚好学到Shader Graph水体流动,看下其他实现方式记录下
1 什么是flow map
1 什么是Flow map?
flowmap的实质:一张记录了2D向量信息的纹理Flow map上的颜色(通常为RG通道) 记录该处向量场的方向,让模型上某一点表现出定量流动的特征。通过在shader中偏移uv再对纹理进行采样,来模拟流动效果
2 纹理映射,左边是UV坐标值,右边是采样结果
UV使用flow map偏移出现不同效果
为什么要使用flowmap?
类似UV动画,而非顶点动画。换言之,无需对模型顶点进行操作,易实现运算开销小。
不仅仅是水面,任何和流动相关的效果都可以采用flowmap.flowmap的原理:通过所带有的向量场信息对uv进行了一个偏移,来干扰我们采样时候的这个过程。如图可以看到,经过flowmap发生偏移后,让原本正常的采样变成了一个扭曲的效果
我们需要更好的运动方向,正确的方法是从flowMap获取流动方向,但是,flow map不能直接使用,需要将flow map上的色值从[0,1]的范围映射到方向向量的范围[-1.1].
2 flow map Shader
借助Shader Graph理解Flow map
1.采样Flow map获取向量场信息
2.用向量场信息,使采样贴图时的UV随时间变化
3.对同一贴图以半个周期的相位差采集两次,并线性插值,使贴图流动连续
这里用到了一个函数模拟网站
https://www.desmos.com/calculator?lang=zh-CN
最简单的随时]间偏移?
time
为什么是相减?
先来看看 uv+time 的情况uv) + (time.0) :模型上某个点: 随着time增加,采样到的像素越远
视觉上可以形容为: 更远距离的像素偏移向该点,视觉效果和我们直观认识到的运算法则是相反的.
UV值作为向量 (u,v) ,自然也遵循向量的运算法则.但UV偏移时,改变的不是顶点的位置。
https://teckartist.com/?page_id=107 点击下载流形图绘制工具
随着时间进行,变形越来越夸张,需要把偏移控制在一定范围内实现无缝循环
依据Flowmap的原理,我们想要做出流动效果,就需要uv随时间进行周期性偏移
既然是周期性偏移,那么时间必须有个阀值 周期有阀值人们第一想到是frac()函数
但frac()函数有个问题,那就是周期的有断层人们希望0.99循环 到1的断层去掉 这时候有个聪明人想到了加权平均.用两条有周期有规律变换的线 蓝紫两线的加权不断变换使断层效果消失 x=0.5时蓝线权重1 紫线权重0 x=1时蓝线权重0紫线权重1
那么 x在0~1周期 y在0
1
0线性波动的怎么做呢? 有人给公式了
有人会想到 sin和cos +一些变化代替上面的公式 这并不好用因为输入是弧度π是无理数. 你无法准确控制sin和cos的周期 用/3.1415926 这种形式运算也废GPU指令 时间拉长就会有越来越大误差.
3 Shader实现
frac函数的作用
Shader "MyShader/Water"
{
Properties
{
// basecolor
_MainTex ("Texture", 2D) = "white" {}
//混色
_Color("Tint", Color) = (1,1,1,0.5)
//水体法线
_MainTex ("Texture", 2D) = "white" {}
_FlowMap("FlowMap", 2D) = "white" {}
//向量场强度
_FlowSpeed("intensity", float) = 0.1
//采样速度
_TimeSpeed("speed", float)= 1
//创建开关
[Toggle]_reverse_flow("reverse", Int) = 0
}
SubShader
{
Tags {
"RenderType"="Opaque"
"IgnoreProjector" = "True"
}
Cull Off
Lighting Off
ZWrite On
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma shader_feature _REVERSE_FLOW_ON
#include "UnityCG.cginc"
struct a2v
{
float4 pos : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 pos : SV_POSITION;
};
fixed4 _Color;
sampler2D _MainTex;
sampler2D _FlowMap;
float4 _MainTex_ST;
float _FlowSpeed;
float _TimeSpeed;
v2f vert (a2v v)
{
v2f o;
//顶点坐标
o.pos = UnityObjectToClipPos(v.pos);
//顶点UV
o.uv = v.uv;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
//0~1 *2-1 得到 -1~1 方向
fixed4 flowDir = tex2D(_FlowMap, i.uv) * 2.0 - 1.0;
//强度修正
flowDir *= _FlowSpeed;
//正负修正
#ifdef _REVERSE_FLOW_ON
flowDir *= -1;
#endif
//两个0~1循环 计时
float phase0 = frac(_Time * 0.1 * _TimeSpeed);
float phase1 = frac(_Time * 0.1 * _TimeSpeed + 0.5);
float2 tiling_uv = i.uv * _MainTex_ST.xy + _MainTex_ST.zw;
half3 tex0 = tex2D(_MainTex, tiling_uv + flowDir.xy * phase0);
half3 tex1 = tex2D(_MainTex, tiling_uv + flowDir.xy * phase1);
float flowLerp = abs((0.5 - phase0) / 0.5);
half3 finalColor = lerp(tex0, tex1, flowLerp);
return float4(finalColor, 1.0) * _Color;
}
ENDCG
}
}
}
想试试加上NormalMap视频里代码不全,用到了TBN向量计算世界空间法线,后续补。