LoginSignup
11
8

More than 3 years have passed since last update.

【Unity(Shader)】Shader初心者が送る水面表現①(屈折)

Last updated at Posted at 2019-11-29

屈折

今回作るのはタイトル通り水面表現です。
具体的には屈折の表現について頑張ります。

屈折デモ

Water.gif

この表現については検索したらほぼ答えのものが出てきたので助かりました。

【参考リンク】:【Unity】【シェーダ】GrabPassを使って歪みシェーダを作る

屈折Shader

LIGHT11より拝借
Shader "Wave"
{
    Properties
    {
        _DistortionTex("Distortion Texture(RG)", 2D) = "grey" {}
        _DistortionPower("Distortion Power", Range(0, 1)) = 0
    }

        SubShader
        {
            Tags {"Queue" = "Transparent" "RenderType" = "Transparent" }

            Cull Back
            ZWrite On
            ZTest LEqual
            ColorMask RGB

            GrabPass { "_GrabPassTexture" }

            Pass {

                CGPROGRAM
               #pragma vertex vert
               #pragma fragment frag

               #include "UnityCG.cginc"

                struct appdata {
                    half4 vertex  : POSITION;
                    half4 texcoord  : TEXCOORD0;
                };

                struct v2f {
                    half4 vertex  : SV_POSITION;
                    half2 uv  : TEXCOORD0;
                    half4 grabPos : TEXCOORD1;
                };

                sampler2D _DistortionTex;
                half4 _DistortionTex_ST;
                sampler2D _GrabPassTexture;
                half _DistortionPower;

                v2f vert(appdata v)
                {
                    v2f o = (v2f)0;

                    o.vertex = UnityObjectToClipPos(v.vertex);
                    o.uv = TRANSFORM_TEX(v.texcoord, _DistortionTex);
                    o.grabPos = ComputeGrabScreenPos(o.vertex);

                    return o;
                }

                fixed4 frag(v2f i) : SV_Target
                {
                    // w除算
                    half2 uv = half2(i.grabPos.x / i.grabPos.w, i.grabPos.y / i.grabPos.w);

                    // Distortionの値に応じてサンプリングするUVをずらす
                    half2 distortion = tex2D(_DistortionTex, i.uv + _Time.x*0.1f).rg - 0.5;
                    distortion *= _DistortionPower;

                    uv = uv + distortion;
                    return tex2D(_GrabPassTexture, uv);
                }
                ENDCG
            }
        }
}

_Time

時間の経過によってUVをスクロールさせたかったので
_Timeを追記しました。時間が経過すればするほど値が大きくなると理解しました。

_Timeはx,y,z,wを指定して任意の速度に変更可能です。
(x,y,x,w) = (t/20, t, t*2, t*3)

ただ、UVの値って0~1なのに加算し続けてスクロールするのはなぜだろう?と疑問に思いました。
その疑問に関しては下記リンクの動画を見ればスッキリです。
0~1の範囲を超えても大丈夫ってことですね。

【参考リンク】:UV座標について

GrabPass

GrabPassについて、記事にいろんな表現がありましたが、私が一番しっくり来たのは

このパスが実行される時点のレンダリング結果を取得できる特殊なパス

という説明です。

引用リンク:【Unity】【シェーダ】GrabPassの説明とシェーダの実装例

色を付ける

合ってるかは置いといて色は付きました。

WaterAddColor.gif

ただ、本来は水に色があるのではなく、
空の色が映りこんでいるはずなのでここらへんは改良の余地有りです。
しかも、背景に乗算して色が乗っているので青くなりませんでした。
どうやったらきれいになるかもう少し調べます。

色付きの屈折Shader

Shader "Wave"
{
    Properties
    {
        _DistortionTex("Distortion Texture(RG)", 2D) = "grey" {}
        _DistortionPower("Distortion Power", Range(0, 1)) = 0
        _Color("WaterColor", Color) = (0,0,0,0)
    }

        SubShader
        {
            Tags {"Queue" = "Transparent" "RenderType" = "Transparent" }

            Cull Back
            ZWrite On
            ZTest LEqual
            ColorMask RGB

            GrabPass { "_GrabPassTexture" }

            Pass {

                CGPROGRAM
               #pragma vertex vert
               #pragma fragment frag

               #include "UnityCG.cginc"

                struct appdata {
                    half4 vertex  : POSITION;
                    half4 texcoord  : TEXCOORD0;
                };

                struct v2f {
                    half4 vertex  : SV_POSITION;
                    half2 uv  : TEXCOORD0;
                    half4 grabPos : TEXCOORD1;
                };

                sampler2D _DistortionTex;
                half4 _DistortionTex_ST;
                sampler2D _GrabPassTexture;
                half _DistortionPower;
                half4 _Color;

                v2f vert(appdata v)
                {
                    v2f o = (v2f)0;

                    o.vertex = UnityObjectToClipPos(v.vertex);
                    o.uv = TRANSFORM_TEX(v.texcoord, _DistortionTex);
                    o.grabPos = ComputeGrabScreenPos(o.vertex);

                    return o;
                }

                fixed4 frag(v2f i) : SV_Target
                {
                    // w除算
                    half2 uv = half2(i.grabPos.x / i.grabPos.w, i.grabPos.y / i.grabPos.w);

                    // Distortionの値に応じてサンプリングするUVをずらす
                    half2 distortion = UnpackNormal(tex2D(_DistortionTex, i.uv + _Time.x * 0.1f)).rg;
                    distortion *= _DistortionPower;

                    uv += distortion;
                    return tex2D(_GrabPassTexture, uv)* _Color;
                }
                ENDCG
            }
        }
}

屈折の仕方

Textureに影響して屈折の仕方が変わります。

なぜ変わるのかは完全には理解できませんでした。
下記リンクからノーマルマップの凹凸情報だけ取り出していると勝手に理解しました。

【参考リンク】:Unity シェーダーチュートリアル屈折表現

例えば、こういうノーマルマップを用意したとします。
NRandom200.PNG

上空から見ると、凹凸情報が反映されているのがよくわかります。

NWave200.gif

ただ、コメントアウトされている方のコードでもそれっぽくなったのが腑に落ちていません。
凹凸情報の無いただのテクスチャーでも良い感じになっちゃうのは一体なんなんでしょうね。。。

//half2 distortion = tex2D(_DistortionTex, i.uv + _Time.x*0.1f);
  half2 distortion = UnpackNormal(tex2D(_DistortionTex, i.uv + _Time.x * 0.1f)).rg;

2019/12/02 追記

本当にほしい値はUnpackNormalで変換した-1~1だけど、
0~1でもたまたまそれっぽく見えてるだけのようです。
(教えてくださった方、スーパー感謝です。)

ノイズ画像

下記記事で作成した画像を利用して解説します。

【ImageJ Fiji, Python】砂嵐画像を任意のピクセル数で簡単に生成する方法

20×20

20.PNG

Wave20.gif

200×200

200.PNG

Wave200.gif

おわりに

Shader難しいですね~。早く完全に理解したい。。。

参考リンク

Unityシェーダプログラム入門 UnlitShaderの要素を全て解説

11
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
8