知識0からのUnityShader勉強

知識0からのUnityShader勉強

UnityのShaderをメインとして、0から学んでいくブログです。

【UnityShader】フローマップ #43

前回の成果

視差オクルージョンマップを完成させた。

soramamenatan.hatenablog.com


今回やること

フローマップを制作します。

light11.hatenadiary.com


事前準備

Scene上にPlaneを配置します。

f:id:soramamenatan:20200306151605p:plain

また、こちらの画像はMainTextureとFlowMapに使用します。

MainTexture

f:id:soramamenatan:20200306151712j:plain

水面のテクスチャー|無料の写真素材はフリー素材のぱくたそ:より引用

FlowMap

f:id:soramamenatan:20200306151727p:plain

【Unity】【シェーダ】フローマップで水流を作る - LIGHT11:より引用


ソースコード

Shader "Unlit/Flowmap" {
    Properties {
        _MainTex ("Texture", 2D) = "white" {}
        _FlowMap ("Flow Map", 2D) = "white" {}
        _FlowSpeed ("Flow Speed", float) = 1.0
        _FlowPower ("Flow Power", float) = 1.0
    }
    SubShader {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            sampler2D _FlowMap;
            float _FlowSpeed;
            float _FlowPower;

            v2f vert (appdata v) {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target {
                float2 flowDir = tex2D(_FlowMap, i.uv) - 0.5;
                flowDir *= _FlowPower;
                float progress = frac(_Time.x * _FlowSpeed);
                float2 uv = i.uv + flowDir * progress;
                return tex2D(_MainTex, uv);
            }
            ENDCG
        }
    }
}

まずは簡易的にフローマップを実装します。


フローマップ

フローマップとは、水などの流れの方向を決めるテクスチャです。

詳しく説明すると、水など流体の流れを2次元で表現するために使用するものです。
Y-upでX方向への動きを赤チャンネル(R)に、Z方向への動きを緑チャンネル(G)に渡します。
X(R)=128,Z(G)=128で静止状態となります。

f:id:soramamenatan:20200306153452p:plain

サルにもわかる Houdini: Game Dev Flowmap:より引用


uv値をズラす

float2 flowDir = tex2D(_FlowMap, i.uv) - 0.5;
flowDir *= _FlowPower;

ここでは、フローマップのuv値を0~1.0から-0.5~0.5にします。
そして、これをメインテクスチャのuv値に加算することにより、フローマップで定義した方向にuvをズラします。


メインテクスチャに適応させる

float progress = frac(_Time.x * _FlowSpeed);
float2 uv = i.uv + flowDir * progress;

備忘録程度に下記に関数の詳細を記載します。

_Time
xyzw
x time / 20
y time
z time * 2
w time * 3
frac
// xの小数値を返す
frac(x)

frac関数により、0~1の間でループします。
それを上記で出したflowDirに乗算することにより、0~flowDirの値をループさせuv値を変化させていきます。


結果

流れているっぽくなりました。
※ Flow Speed = 1.0, Flow Power = 1.0

f:id:soramamenatan:20200306171022g:plain

ただ、今は0~flowDirをループしています。
なので、下記のgifのようにループの切れ目がわかってしまいます。

f:id:soramamenatan:20200306171622g:plain


ループの切れ目を修正

ループの切れ目があまりにも不自然なため、修正します。


ソースコード

fragment shaderを以下に置き換えてください。

fixed4 frag (v2f i) : SV_Target {
    float2 flowDir = tex2D(_FlowMap, i.uv) - 0.5;
    flowDir *= _FlowPower;
    float progress1 = frac(_Time.x * _FlowSpeed);
    float progress2 = frac(_Time.x * _FlowSpeed + 0.5);
    float2 uv1 = i.uv + flowDir * progress1;
    float2 uv2 = i.uv + flowDir * progress2;

    float lerpRate = abs((0.5 - progress1) / 0.5);
    fixed4 col1 = tex2D(_MainTex, uv1);
    fixed4 col2 = tex2D(_MainTex, uv2);
    return lerp(col1, col2, lerpRate);
}


uvを2つ作成する

float2 flowDir = tex2D(_FlowMap, i.uv) - 0.5;
flowDir *= _FlowPower;
float progress1 = frac(_Time.x * _FlowSpeed);
float progress2 = frac(_Time.x * _FlowSpeed + 0.5);
float2 uv1 = i.uv + flowDir * progress1;
float2 uv2 = i.uv + flowDir * progress2;

progress1は前のものと同じです。
progress2で、uvを0.5ズラしています。


補間する

float lerpRate = abs((0.5 - progress1) / 0.5);
fixed4 col1 = tex2D(_MainTex, uv1);
fixed4 col2 = tex2D(_MainTex, uv2);
return lerp(col1, col2, lerpRate);

ここで2つのuv値を補間することにより切れ目を自然にしています。

progress1が0か1に近づくほど、lerpRateは1に近づきます。
progress1が0か1の状態は、ループの切れ目の部分です。
その状態の時にuvをズラしたprogress2を使用することにより、自然にしています。

progress1とlerpRate
progress1 lerpRate
0 1.0
0.1 0.8
0.2 0.6
0.3 0.4
0.4 0.2
0.5 0.0
0.6 0.2
0.7 0.4
0.8 0.6
0.9 0.8
1.0 1.0


結果

ループの切れ目が自然になりました。
※ Flow Speed = 10.0, Flow Power = 1.0

f:id:soramamenatan:20200306173842g:plain


ソースコードにコメントを付与

Shader "Unlit/Flowmap" {
    Properties {
        _MainTex ("Texture", 2D) = "white" {}
        _FlowMap ("Flow Map", 2D) = "white" {}
        _FlowSpeed ("Flow Speed", float) = 1.0
        _FlowPower ("Flow Power", float) = 1.0
    }
    SubShader {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            sampler2D _FlowMap;
            float _FlowSpeed;
            float _FlowPower;

            v2f vert (appdata v) {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target {
                // フローマップのuv値を-0.5~0.5に
                float2 flowDir = tex2D(_FlowMap, i.uv) - 0.5;
                flowDir *= _FlowPower;
                // timeを0~1間でループ
                float progress1 = frac(_Time.x * _FlowSpeed);
                // 補間用に0.5ズラす
                float progress2 = frac(_Time.x * _FlowSpeed + 0.5);
                // uv + 0~flowDir
                float2 uv1 = i.uv + flowDir * progress1;
                float2 uv2 = i.uv + flowDir * progress2;

                // progress1が0か1に近づく時(ループの切れ目)にlerpRateが1に近づくように
                float lerpRate = abs((0.5 - progress1) / 0.5);
                fixed4 col1 = tex2D(_MainTex, uv1);
                fixed4 col2 = tex2D(_MainTex, uv2);
                // ループの切れ目を補間する
                return lerp(col1, col2, lerpRate);
            }
            ENDCG
        }
    }
}

今回は以上となります。
ここまでご視聴ありがとうございました!