知識0からのUnityShader勉強

知識0からのUnityShader勉強

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

【UnityShader】円でトランジション【1】 #30

前回の成果

ブロックノイズでトランジションができるようになりました。

soramamenatan.hatenablog.com


今回やること

円でトランジションをしていこうと思います

karanokan.info


事前準備

SceneにCanvasを配置し、Imageを配置します。
Imageに今回制作するmaterialをアタッチしてください。

f:id:soramamenatan:20191207124903p:plain


ソースコード

今回はコードの量が多いので、小分けにして理解していこうと思います。

Shader "Unlit/transitionCircle
" {
    Properties {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader {
        Tags { "RenderType" = "Transparent" }
        Blend SrcAlpha OneMinusSrcAlpha

        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;

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

            float circle(float2 p) {
                return dot(p, p);
            }

            fixed4 frag (v2f i) : SV_Target {
                float2 f_st = frac(i.uv) * 2.0 - 1.0;
                float ci = circle(f_st);
                fixed4 col = 0.0;
                col.a = step(0.1, ci);
                return col;
            }
            ENDCG
        }
    }
}

vertex shaderは特に何もしておらず、fragment shaderで内積を出しています。


frac

これは小数値の小数部分を返す関数になります。

// 0.5が返ってくる
frac(1.5);

// 0.8が返ってくる
frac(0.8);


step

これはy<=xなら1を、y>xなら0を返す関数です。

step(x, y);

// 1が返ってくる
step(1.0, 1.2);

// 0が返ってくる
step(1.0, 0.8);


結果

下記の図のような円になれば成功です。

f:id:soramamenatan:20191204095515p:plain


円になる理由

円を描画しているのは、fragment shader内で行なっているのですが、

float2 f_st = frac(i.uv) * 2.0 - 1.0;
// circle関数を展開
float ci =dot(f_st, f_st);

この2行で計算をしています。


float2 f_st = frac(i.uv) * 2.0 - 1.0

この計算で、円の中心の位置を決めています。
円の大きさは乗算で求めています。
今回の場合ですと、*2で1/2にしています。

円の位置は減算で求めています。
中央に表示するためには-0.5をするのですが、今回乗算で円の大きさを1/2にしているので2倍の1.0をかけています。


float ci =dot(f_st, f_st)

この計算で、step関数を用いて円を描画しています。
円の形になる理由は実際に代入してみると簡単です。
dotの中は下記の式になっているので

// 2次元ベクトルの場合
dot(x, y) = x.x * y.x + x.y * y.y

中央の周りはstep関数により0、端の部分は1となります。


次に画面の解像度によって円の形が変わってしまうので、そこを修正していきます。


画面の解像度

以前、ワイプエフェクトで解像度を取得したのですが、今回はもっと容易に取得していこうと思います。

soramamenatan.hatenablog.com


追記するソースコード

frag関数を変更してください。

fixed4 frag (v2f i) : SV_Target {
    float2 f_st = frac(i.uv) * 2.0 - 1.0;
    // 追加
    f_st.y *= _ScreenParams.y / _ScreenParams.x;
    float ci = circle(f_st);
    fixed4 col = 0.0;
    col.a = step(0.1, ci);
    return col;
}


_ScreenParams

float4型で、中身は下記のようになっています。

xyzw 用途
x 現在のレンダリングターゲットのピクセルの幅
y 現在のレンダリングターゲットのピクセルの高さ
z 1.0 + 1.0 / 幅
w 1.0 + 1.0 / 高さ

となっています。

qiita.com

これで画面の解像度が取れるので、f_stに乗算してあげれば解像度で円の形が変化することがなくなります。


前回の解像度との違い

以前の

float4 projectionSpaceUpperRight = float4(1, 1, UNITY_NEAR_CLIP_VALUE, _ProjectionParams.y);
float4 viewSpaceUpperRight = mul(unity_CameraInvProjection, projectionSpaceUpperRight);
i.uv.x *= viewSpaceUpperRight.x / viewSpaceUpperRight.y;

との違いはもちろんあります。
今回の_ScreenParamsはあくまで、RenderTargetのピクセルの幅を取得しているものです。
ですので、オフスクリーンレンダリングのカメラが存在し、そのRenderTextureの解像度とカメラのアスペクト比が異なる場合等では、_ScreenParamsでは取得できなくなってしまいます。

qiita.com

余談ですが、オフスクリーンレンダリングとはバックグラウンドでメモリ空間上にレンダリングを行うことです。
詳しくはこちらのサイト様に紹介されています。

wgld.org


結果

円がアスペクト比に対応すれば成功です

f:id:soramamenatan:20191207124438p:plain


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

Shader "Unlit/transitionCircle" {
    Properties {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader {
        Tags { "RenderType" = "Transparent" }
        Blend SrcAlpha OneMinusSrcAlpha

        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;

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

            // 内積を出す
            float circle(float2 p) {
                return dot(p, p);
            }

            fixed4 frag (v2f i) : SV_Target {
                // 円の大きさと位置の調整
                float2 f_st = frac(i.uv) * 2.0 - 1.0;
                // 画面解像度に影響されないようにする
                f_st.y *= _ScreenParams.y / _ScreenParams.x;
                float ci = circle(f_st);
                fixed4 col = 0.0;
                // ciが0.1未満なら0を、そうでないなら1を返す
                col.a = step(0.1, ci);
                return col;
            }
            ENDCG
        }
    }
}

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