【UnityShader】円でトランジション【1】 #30
前回の成果
ブロックノイズでトランジションができるようになりました。
今回やること
円でトランジションをしていこうと思います
事前準備
SceneにCanvasを配置し、Imageを配置します。
Imageに今回制作するmaterialをアタッチしてください。
ソースコード
今回はコードの量が多いので、小分けにして理解していこうと思います。
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);
結果
下記の図のような円になれば成功です。
円になる理由
円を描画しているのは、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となります。
次に画面の解像度によって円の形が変わってしまうので、そこを修正していきます。
画面の解像度
以前、ワイプエフェクトで解像度を取得したのですが、今回はもっと容易に取得していこうと思います。
追記するソースコード
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 / 高さ |
となっています。
これで画面の解像度が取れるので、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では取得できなくなってしまいます。
余談ですが、オフスクリーンレンダリングとはバックグラウンドでメモリ空間上にレンダリングを行うことです。
詳しくはこちらのサイト様に紹介されています。
結果
円がアスペクト比に対応すれば成功です
ソースコードにコメントを付与
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 } } }
今回は以上となります。
ご視聴ありがとうございました!