のりまき日記

Unityなどの活用リファレンスブログ。「こうしたい時どうする」をまとめたい

unity)陽炎・蜃気楼・重力・クリスタルみたいな表現をしたい

通常のユニティちゃんと空間を歪ませるユニティちゃん

はじめに

空間を歪ませてみます。火花の散る演出やワープする演出の時に後ろの空間を歪めるとお手軽にクオリティが上がって見える気がします。

球を配置

準備としてまずは球を配置します。

配置できた!

球に背景画像を写す

背景を透過して映すのではなく背景と同じ画像を映すことで透明感を出します。シェーダーは標準のUnlitShaderから改造していきます。

通常のUnlitShaderを作る様子

Shader "Unlit/Dist"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Transparent" "Queue" = "Transparent"}
        LOD 100

        GrabPass
        {
            "_BackgroundTexture"
        }

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

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

            struct v2f
            {
                float2 uv : TEXCOORD0;
                // Grabテクスチャ用のuv
                float4 guv : TEXCOORD1;
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            sampler2D _BackgroundTexture;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                // Grabテクスチャ用のuvを計算
                o.guv = ComputeGrabScreenPos(o.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2Dproj(_BackgroundTexture, i.guv);
                return col;
            }
            ENDCG
        }
    }
}

見えないけど球は存在している

note

背景の画像を取得するのにGrabという機能を使用しています。

docs.unity3d.com

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

Grabはそれまでに描画した結果を取得するため、GeometryとSkyboxを描画しおわった後で取得する必要があります。不透明オブジェクトなのでQueueは2500以下にしたいですがTransparentにしています。

GrabPass
{
      "_BackgroundTexture"
}

GrabPassは「GrabPass {}」のようにしても取得できるのですがこの場合、GrabPassを使用するオブジェクト分DrawCallが増えるので名前をつけて取得します。最初にレンダリングされるオブジェクト時点での描画結果を取得できます。

// Grabテクスチャ用のuvを計算
o.guv = ComputeGrabScreenPos(o.vertex);

...

fixed4 col = tex2Dproj(_BackgroundTexture, i.guv);

この辺はGrabTextureをオブジェクトに貼り付けるための処理になります。

背景画像を歪める

背景画像を取得するuvを移動させることで背景を歪めます。歪みを作るためにノイズとなるテクスチャが必要ですが、こちらの記事のFlowMapを使用させていただきました。uvを均等に歪ませるためにはリピートしてい均等に0〜1が分布しているテクスチャが必要なのですが作るのが大変なので借用しています。

catlikecoding.com

Shader "Unlit/Dist"
{
    Properties
    {
        // 歪みを追加
        _DistMap ("歪み画像", 2D) = "black" {}
        _DistPower ("歪みパワー", Range(0, 1)) = .01
    }
    SubShader
    {
        Tags { "RenderType"="Transparent" "Queue" = "Transparent"}
        LOD 100

        GrabPass
        {
            "_BackgroundTexture"
        }

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

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

            struct v2f
            {
                float2 uv : TEXCOORD0;
                // Grabテクスチャ用のuv
                float4 guv : TEXCOORD1;
                float4 vertex : SV_POSITION;
            };

            // 歪みを追加
            sampler2D _DistMap;
            float4 _DistMap_ST;
            float _DistPower;

            sampler2D _BackgroundTexture;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                // Grabテクスチャ用のuvを計算
                o.guv = ComputeGrabScreenPos(o.vertex);
                // 歪みテクスチャ用のuv
                o.uv = v.uv * _DistMap_ST.xy + _DistMap_ST.zw * _Time.x;
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                float2 dist = tex2D(_DistMap, i.uv) * 2 - 1;
                i.guv.xy += dist * _DistPower;
                fixed4 col = tex2Dproj(_BackgroundTexture, i.guv);
                return col;
            }
            ENDCG
        }
    }
}

note

// 歪みテクスチャ用のuv
o.uv = v.uv * _DistMap_ST.xy + _DistMap_ST.zw * _Time.x;

歪み画像をuvスクロールさせています。DistMapのOffset値をスクロール速度として利用しています。uvをズラす計算は頂点シェーダーでしてしまいます。頂点シェーダーは頂点分しか計算しませんが、フラグメントシェーダーはピクセル分計算が走るので、できるだけ頂点シェーダーで計算します。

ここの値

float2 dist = tex2D(_DistMap, i.uv) * 2 - 1;
i.guv.xy += dist * _DistPower;

歪み画像を取得しています。値は0〜1になるので-1〜1にしています。Grab画像を取得するuvに足すことで歪ませています。

スクロール値で歪み具合が変わっています

なじませる

今の状態だと急に歪むのでなじませてみます。またユニティちゃんも歪んでしまっているので対策します。

Shader "Unlit/Dist"
{
    Properties
    {
        // 歪みを追加
        _DistMap ("歪み画像", 2D) = "black" {}
        _DistPower ("歪みパワー", Range(0, 1)) = .01
        // リム用
        _RimPower ("なじませパワー", Range(0, 10)) = .5
    }
    SubShader
    {
        Tags { "RenderType"="Transparent" "Queue" = "Transparent"}
        LOD 100
        // 背面だけ描画する
        Cull Front

        GrabPass
        {
            "_BackgroundTexture"
        }

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                // リム用
                float3 normal : NORMAL;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                // Grabテクスチャ用のuv
                float4 guv : TEXCOORD1;
                float4 vertex : SV_POSITION;
                // リム用
                float3 normal : TEXCOORD2;
                float3 viewDir : TEXCOORD3;
            };

            // 歪みを追加
            sampler2D _DistMap;
            float4 _DistMap_ST;
            float _DistPower;
            // リム用
            float _RimPower;

            sampler2D _BackgroundTexture;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                // Grabテクスチャ用のuvを計算
                o.guv = ComputeGrabScreenPos(o.vertex);
                // 歪みテクスチャ用のuv
                o.uv = v.uv * _DistMap_ST.xy + _DistMap_ST.zw * _Time.x;
                // リム用
                o.normal = -UnityObjectToWorldNormal(v.normal);
                o.viewDir = normalize(WorldSpaceViewDir(v.vertex));
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                float rim = pow(1.0 - saturate(dot(i.viewDir, i.normal)),  _RimPower);

                float2 dist = tex2D(_DistMap, i.uv) * 2 - 1;
                i.guv.xy += dist * _DistPower * (1 - rim);
                fixed4 col = tex2Dproj(_BackgroundTexture, i.guv);
                return col;
            }
            ENDCG
        }
    }
}

なじんだ!

note

リムの処理を追加してリムの強度によって歪みを適用するようにしました。

// 背面だけ描画する
Cull Front

球の中にユニティちゃんがいるのでユニティちゃんも歪んでいます。なので球の背面だけ描画するようにしました。

o.normal = -UnityObjectToWorldNormal(v.normal);

球の背面だけ描画するのでリムがマイナス値になってしまうので反転しています。

おわり

最終的にはリムの値に応じてグレースケール化するなどの処理をして冒頭の表現にしました。グレースケール化する計算はこちらの記事を参考にしました。

xr-hub.com