Unity uGUI用、逆魚眼レンズシェーダー。糸巻歪曲収差の実装。
(Pincushion Distortion Shader for Unity uGUI)
関連ページ
参考URL
最近レトロフリークで遊ぶために、少年時代買えなかったSFCのソフトを大量に買い集めてる。
その中で天地創造というゲームのマップ移動がなかなか美しくて、これを自分の今の技術で真似てみようと考えた。
天地創造のマップ表現は複数の技術を使っていて完コピとまでいかなかったが、ある程度コピーできたので共有。
逆魚眼レンズシェーダー概要
逆魚眼レンズの視覚効果は、正式には糸巻歪曲収差と言う。
画面の中心から離れて端に近づくほど、ぐいっと手前に画像が近寄ってくる感じ。
下はこのシェーダーを作る動機となった、天地創造の動画になる。
天地創造の場合、画面の下4/3は単純なy軸のみの糸巻歪曲収差シェーダーを使っているように見える。
対して残りの上1/4は、3~4つぐらいの技術を併用して描画してるように思う。
たぶん糸巻歪曲収差+y軸反転+色相を青色に寄せる+境目部分の黒いボヤけた表現など。
そもそも何十年も前の作品なので、今でいうシェーダーという技術は使ってなかったかもしれない。
とりあえず自分がやりたかったのは画面の周囲が歪む糸巻歪曲収差の部分だったので、それを作った。
下の動画はそのサンプル。
天地創造ではy軸のみの歪みを使っていたが、このシェーダーではx軸+y軸、y軸のみ、x軸のみの3パターンを選べるようにした。
逆魚眼レンズシェーダー、コード全文
糸巻歪曲収差処理はネットで検索してすぐ見つかったものの、そのコードが何をやってるかよく分からなかった。
なのでEaseを使って自前で実装した。
見る人が見るとやたら長くて汚いコードに見えるかもしれない。
以下がそのコード全文。uGUI Imageへの反映方法は
こちらの記事
参照。
今回の場合GrabPassを使っているので、
こちら
の知識も必要になる。
Shader
"UI/Pincushion"
{
Properties
{
[HideInInspector]_MainTex("-",2D)="white"{}
[KeywordEnum(All,
Vertical,
Horizontal)]
_Direction("Direction",
Int)
=
0
_DistortionRange
("DistortionRange",
Range(0.0,
10.0))
=
5
_EasePow
("EasePow",
Range(0,
10))
=
10
}
SubShader
{
Tags
{
"Queue"
=
"Transparent"
"RenderType"="Transparent"
}
Cull
Off
ZWrite
Off
Blend
SrcAlpha
OneMinusSrcAlpha
GrabPass{}
Pass
{
CGPROGRAM
#pragma
vertex
vert
#pragma
fragment
frag
#include
"UnityCG.cginc"
sampler2D
_GrabTexture;
#pragma
multi_compile
_DIRECTION_ALL
_DIRECTION_VERTICAL
_DIRECTION_HORIZONTAL
fixed
_DistortionRange;
fixed
_EasePow;
struct
appdata
{
fixed4
vertex
:
POSITION;
fixed4
uv
:
TEXCOORD0;
};
struct
v2f
{
fixed4
vertex
:
SV_POSITION;
fixed2
uv
:
TEXCOORD0;
};
v2f
vert(appdata
i)
{
v2f
o;
o.vertex
=
UnityObjectToClipPos(i.vertex);
o.uv
=
ComputeGrabScreenPos(o.vertex);
return
o;
}
fixed2
getIsUnderHalf(v2f
i)
{
//x軸が0.5より小さければ1を代入
fixed
isUnderHalfX
=
step(i.uv.x,
0.5);
//y軸が0.5より小さければ1を代入
fixed
isUnderHalfY
=
step(i.uv.y,
0.5);
return
fixed2(isUnderHalfX,
isUnderHalfY);
}
fixed2
getFarFromSafe(v2f
i)
{
fixed
fixRange
=
_DistortionRange
*
0.1;
fixed
safeRange
=
1
-
fixRange;
fixed
farFromSafeX
=
abs(0.5
-
i.uv.x)
-
(safeRange
/
2);
fixed
farFromSafeY
=
abs(0.5
-
i.uv.y)
-
(safeRange
/
2);
return
fixed2(farFromSafeX,
farFromSafeY);
}
fixed2
getFarFromCenter(v2f
i)
{
fixed
farFromCenterX
=
abs(0.5
-
i.uv.x);
fixed
farFromCenterY
=
abs(0.5
-
i.uv.y);
return
fixed2(farFromCenterX,
farFromCenterY);
}
fixed2
getMainDist(fixed2
farFromSafe)
{
//InQuadのEaseでfarFromSafeが大きい程指数関数的に座標がずれるようにする
fixed
quadX
=
farFromSafe.x
*
(farFromSafe.x
-
2);
fixed
quadY
=
farFromSafe.y
*
(farFromSafe.y
-
2);
//指数関数の伸びが強すぎるのでfixEaseで抑える
//_EasePowのrangeが0~10なのでfixEaseの振れ幅は12.7~2.7
fixed
fixEase
=
12.7
-
_EasePow;
fixed
mainDistX
=
quadX
*
quadX
/
fixEase;
fixed
mainDistY
=
quadY
*
quadY
/
fixEase;
return
fixed2(mainDistX,
mainDistY);
}
fixed2
getSubDist(fixed2
farFromCenter,
fixed2
mainDist)
{
fixed
subDistX
=
farFromCenter.x
*
mainDist.y;
fixed
subDistY
=
farFromCenter.y
*
mainDist.x;
return
fixed2(subDistX,
subDistY);
}
fixed2
getMainReduce(fixed2
isUnderHalf,
fixed2
mainDist)
{
fixed
mainReduceX
=
(isUnderHalf.x
*
mainDist.x)
+
((1
-
isUnderHalf.x)
*
-1
*
(mainDist.x));
fixed
mainReduceY
=
(isUnderHalf.y
*
mainDist.y)
+
((1
-
isUnderHalf.y)
*
-1
*
(mainDist.y));
return
fixed2(mainReduceX,
mainReduceY);
}
fixed2
getSubReduce(fixed2
isUnderHalf,
fixed2
subDist)
{
fixed
subReduceX
=
(isUnderHalf.x
*
subDist.x)
+
((1
-
isUnderHalf.x)
*
-1
*
(subDist.x));
fixed
subReduceY
=
(isUnderHalf.y
*
subDist.y)
+
((1
-
isUnderHalf.y)
*
-1
*
(subDist.y));
return
fixed2(subReduceX,
subReduceY);
}
fixed2
getUseDist(fixed2
farFromSafe)
{
//x軸が歪み対象エリア内かどうか
fixed
useDistX
=
step(0,
farFromSafe.x);
//y軸が歪み対象エリア内かどうか
fixed
useDistY
=
step(0,
farFromSafe.y);
return
fixed2(useDistX,
useDistY);
}
fixed4
frag(v2f
i)
:
SV_Target
{
//対象のy軸とx軸座標が中心点を超えてるかどうかを習得
fixed2
isUnderHalf
=
getIsUnderHalf(i);
//非歪みエリアから外側にx軸、y軸がどれだけ離れてるかを取得
fixed2
farFromSafe
=
getFarFromSafe(i);
//中心からx軸、y軸がどれだけ離れてるかを取得
fixed2
farFromCenter
=
getFarFromCenter(i);
//メインの歪みの基本値を取得
fixed2
mainDist
=
getMainDist(farFromSafe);
//サブの歪みの基本値を取得
fixed2
subDist
=
getSubDist(farFromCenter,
mainDist);
//メインの座標の減退値を取得
fixed2
mainReduce
=
getMainReduce(isUnderHalf,
mainDist);
//サブの座標の減退値を取得
fixed2
subReduce
=
getSubReduce(isUnderHalf,
subDist);
//対象のx軸、y軸が歪み対象エリア内かどうか
fixed2
useDist
=
getUseDist(farFromSafe);
#ifdef
_DIRECTION_ALL
i.uv.x
=
i.uv.x
+
useDist.x
*
mainReduce.x
+
useDist.y
*
subReduce.x;
i.uv.y
=
i.uv.y
+
useDist.y
*
mainReduce.y
+
useDist.x
*
subReduce.y;
#elif
_DIRECTION_VERTICAL
i.uv.x
=
i.uv.x
+
useDist.y
*
subReduce.x
*
2;
i.uv.y
=
i.uv.y
+
useDist.y
*
mainReduce.y;
#else
i.uv.x
=
i.uv.x
+
useDist.x
*
mainReduce.x;
i.uv.y
=
i.uv.y
+
useDist.x
*
subReduce.y
*
2;
#endif
fixed4
col
=
tex2D(_GrabTexture,
i.uv);
return
col;
}
ENDCG
}
}
}
プロパティを解説
用意してあるプロパティは3つ。
EasePowは初期値で最大にしてあるが、歪みが強すぎると思う人は小さくするとその分歪みが柔らかくなる。
1
1