最近刚好学到Shader Graph水体流动,看下其他实现方式记录下

1 什么是flow map

1 什么是Flow map?
flowmap的实质:一张记录了2D向量信息的纹理Flow map上的颜色(通常为RG通道) 记录该处向量场的方向,让模型上某一点表现出定量流动的特征。通过在shader中偏移uv再对纹理进行采样,来模拟流动效果

unity管道内流动 unity 流体效果_图形渲染

2 纹理映射,左边是UV坐标值,右边是采样结果

unity管道内流动 unity 流体效果_unity_02

UV使用flow map偏移出现不同效果

unity管道内流动 unity 流体效果_图形渲染_03

为什么要使用flowmap?
类似UV动画,而非顶点动画。换言之,无需对模型顶点进行操作,易实现运算开销小。
不仅仅是水面,任何和流动相关的效果都可以采用flowmap.

flowmap的原理:通过所带有的向量场信息对uv进行了一个偏移,来干扰我们采样时候的这个过程。如图可以看到,经过flowmap发生偏移后,让原本正常的采样变成了一个扭曲的效果

我们需要更好的运动方向,正确的方法是从flowMap获取流动方向,但是,flow map不能直接使用,需要将flow map上的色值从[0,1]的范围映射到方向向量的范围[-1.1].

unity管道内流动 unity 流体效果_unity_04

unity管道内流动 unity 流体效果_unity管道内流动_05

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偏移时,改变的不是顶点的位置。

unity管道内流动 unity 流体效果_游戏引擎_06


unity管道内流动 unity 流体效果_图形渲染_07

unity管道内流动 unity 流体效果_unity管道内流动_08

https://teckartist.com/?page_id=107 点击下载流形图绘制工具

unity管道内流动 unity 流体效果_学习_09

unity管道内流动 unity 流体效果_unity管道内流动_10


随着时间进行,变形越来越夸张,需要把偏移控制在一定范围内实现无缝循环

unity管道内流动 unity 流体效果_游戏引擎_11

依据Flowmap的原理,我们想要做出流动效果,就需要uv随时间进行周期性偏移
既然是周期性偏移,那么时间必须有个阀值 周期有阀值人们第一想到是frac()函数
但frac()函数有个问题,那就是周期的有断层

unity管道内流动 unity 流体效果_unity管道内流动_12

人们希望0.99循环 到1的断层去掉 这时候有个聪明人想到了加权平均.用两条有周期有规律变换的线 蓝紫两线的加权不断变换使断层效果消失 x=0.5时蓝线权重1 紫线权重0 x=1时蓝线权重0紫线权重1


unity管道内流动 unity 流体效果_unity_13


那么 x在0~1周期 y在0

1

0线性波动的怎么做呢? 有人给公式了


有人会想到 sin和cos +一些变化代替上面的公式 这并不好用因为输入是弧度π是无理数. 你无法准确控制sin和cos的周期 用/3.1415926 这种形式运算也废GPU指令 时间拉长就会有越来越大误差.


unity管道内流动 unity 流体效果_学习_14

unity管道内流动 unity 流体效果_unity_15


unity管道内流动 unity 流体效果_学习_16


unity管道内流动 unity 流体效果_游戏引擎_17

3 Shader实现

frac函数的作用

unity管道内流动 unity 流体效果_学习_18

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向量计算世界空间法线,后续补。