【UnityShader】uGUIで波紋を出す #73
前回の成果
uGUIのImageをズラした。
今回やること
uGUIに波紋を出します。
事前準備
まず、Scene上にImageを配置します。
Imageに
- 適当な画像
- 今回制作したScript
上記2点をアタッチして準備完了となります。
ソースコード
Script
using UnityEngine; using UnityEngine.UI; namespace onMouseMove { [RequireComponent(typeof(Image))] public class RipplesImage : MonoBehaviour { [SerializeField] private Shader _shader; [SerializeField] private float _speed; [SerializeField, Range(0, 1)] private float _strength; [SerializeField] private float _effectRadius; private Material _material; private Image _image; private RectTransform _rectTrans; private int _touchPosPropertyId; private int _speedPropertyId; private int _strengthPropertyId; private int _radiusPropertyId; private bool _isInitialized; void Start () { _image = GetComponent<Image>(); _rectTrans = GetComponent<RectTransform>(); _material = new Material(_shader); _image.material = _material; _touchPosPropertyId = Shader.PropertyToID("_TouchPos"); _speedPropertyId = Shader.PropertyToID("_Speed"); _strengthPropertyId = Shader.PropertyToID("_Strength"); _radiusPropertyId = Shader.PropertyToID("_EffectRadius"); _isInitialized = true; UpdateRipplesValue(); } void Update () { Vector2 localPos = _rectTrans.InverseTransformPoint(Input.mousePosition); _image.material.SetVector(_touchPosPropertyId, localPos); } void OnValidate() { UpdateRipplesValue(); } private void UpdateRipplesValue() { if (_isInitialized == false) { return; } _image.material.SetFloat(_speedPropertyId, _speed); _image.material.SetFloat(_strengthPropertyId, _strength); _image.material.SetFloat(_radiusPropertyId, _effectRadius); } } }
主に、マウスの座標をShaderに渡しています。
Shader
Shader "Unlit/RipplesImage" { Properties { [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {} _Color ("Tint", Color) = (1,1,1,1) _StencilComp ("Stencil Comparison", Float) = 8 _Stencil ("Stencil ID", Float) = 0 _StencilOp ("Stencil Operation", Float) = 0 _StencilWriteMask ("Stencil Write Mask", Float) = 255 _StencilReadMask ("Stencil Read Mask", Float) = 255 _ColorMask ("Color Mask", Float) = 15 [Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip ("Use Alpha Clip", Float) = 0 _TouchPos ("Touch Pos", Vector) = (0,0,0,0) _EffectRadius ("Radius", float) = 0 _Strength ("Strength", float) = 0 _Speed ("Speed", float) = 0 } SubShader { Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "PreviewType"="Plane" "CanUseSpriteAtlas"="True" } Stencil { Ref [_Stencil] Comp [_StencilComp] Pass [_StencilOp] ReadMask [_StencilReadMask] WriteMask [_StencilWriteMask] } Cull Off Lighting Off ZWrite Off ZTest [unity_GUIZTestMode] Blend SrcAlpha OneMinusSrcAlpha ColorMask [_ColorMask] Pass { Name "Default" CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma target 2.0 #include "UnityCG.cginc" #include "UnityUI.cginc" #pragma multi_compile_local _ UNITY_UI_CLIP_RECT #pragma multi_compile_local _ UNITY_UI_ALPHACLIP struct appdata_t { float4 vertex : POSITION; float4 color : COLOR; float2 texcoord : TEXCOORD0; UNITY_VERTEX_INPUT_INSTANCE_ID }; struct v2f { float4 vertex : SV_POSITION; fixed4 color : COLOR; float2 texcoord : TEXCOORD0; float4 worldPosition : TEXCOORD1; UNITY_VERTEX_OUTPUT_STEREO }; sampler2D _MainTex; fixed4 _Color; fixed4 _TextureSampleAdd; float4 _ClipRect; float4 _MainTex_ST; float2 _TouchPos; float _EffectRadius; float _Strength; float _Speed; v2f vert(appdata_t v) { v2f o; UNITY_SETUP_INSTANCE_ID(v); UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o); o.worldPosition = v.vertex; o.vertex = UnityObjectToClipPos(o.worldPosition); o.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex); o.color = v.color * _Color; return o; } fixed4 frag(v2f i) : SV_Target { float2 gap = i.worldPosition - _TouchPos; float moveX = 0; float len = length(gap); if (len <= _EffectRadius) { float p = sqrt(pow(_EffectRadius, 2) - pow(gap.x, 2) - pow(gap.y, 2)); moveX = p * _Strength * 0.0002 * (cos((len - _Time.w * _Speed) + 0.5)); } half4 color = (tex2D(_MainTex, i.texcoord + float2(-moveX, 0)) + _TextureSampleAdd) * i.color; #ifdef UNITY_UI_CLIP_RECT color.a *= UnityGet2DClipping(i.worldPosition.xy, _ClipRect); #endif #ifdef UNITY_UI_ALPHACLIP clip (color.a - 0.001); #endif return color; } ENDCG } } }
ShaderはUnityのビルドインシェーダーのUI-Default.shaderを元に作成しています。
ビルドインシェーダーは、以下のUnity公式サイトから取得することができます。
対象のUnityバージョンを選択して、以下画像のようにダウンロードしてください。
波紋の生成
先に波紋のイメージを起こして、そこからソースコードに落とし込んでいきます。
基礎となる波紋
今回使用する波紋の計算式は以下となります。
イメージはこのような感じです。
基礎となる波紋のイメージ
スケール用の式
次に、スケールさせるための式を用意します。
計算式は以下となります。
スケールのイメージ
乗算する
基礎となる波紋と、スケールを乗算していきます。
計算式は以下となります。
乗算したイメージ
移動させる
このままですと、動かないのでtimeを使用して波紋のように動かすようにします。
また、移動幅が大きすぎるため最後に0.1を乗算して調節しています。
計算式は以下となります。
静止時のイメージ
移動させたときのイメージ
これで波紋の計算式が完成したのでソースコードに落とし込みます。
Shader側の解説
Unityのビルドインシェーダーを使用したため、フラグメントシェーダーのみの解説となります。
fixed4 frag(v2f i) : SV_Target { float2 gap = i.worldPosition - _TouchPos; float moveX = 0; float len = length(gap); // 波紋の範囲内 if (len <= _EffectRadius) { // p = √100-x^2-y^2, スケール用 float p = sqrt(pow(_EffectRadius, 2) - pow(gap.x, 2) - pow(gap.y, 2)); // 波紋の式である√x^2+y^2を元に調整 moveX = p * _Strength * 0.0002 * (cos((len - _Time.w * _Speed) + 0.5)); } half4 color = (tex2D(_MainTex, i.texcoord + float2(-moveX, 0)) + _TextureSampleAdd) * i.color; // 以下はビルドインシェーダーのままのため省略
特に難しい部分はないため、コメントでの解説となります。
結果
マウスの位置を中心として波紋が出れば成功です。
inspectorの値
+α
+αで修正していこうと思います。
まずは、波紋の数が多かったので少し減らします。
// +0.5 -> *0.2に変更 moveX = p * _Strength * 0.0002 * (cos((len - _Time.w * _Speed) * 0.2));
結果
緩やかな波紋となりました。
最後に明るさを調整します。
// 上記省略 if (len <= _EffectRadius) { float p = sqrt(pow(_EffectRadius, 2) - pow(gap.x, 2) - pow(gap.y, 2)); moveX = p * _Strength * 0.0002 * (cos((len - _Time.w * _Speed) * 0.2)); // 追加 brightGap = _Strength * 20 * moveX * cos(45); } half4 color = (tex2D(_MainTex, i.texcoord + float2(-moveX, 0)) + _TextureSampleAdd) * i.color; // 追加 color.rgb += brightGap; #ifdef UNITY_UI_CLIP_RECT // 下記省略
結果
明暗ができれば成功です。
ソースコードにコメントを付与
Script
using UnityEngine; using UnityEngine.UI; namespace onMouseMove { [RequireComponent(typeof(Image))] public class RipplesImage : MonoBehaviour { [SerializeField] private Shader _shader; [SerializeField] private float _speed; [SerializeField, Range(0, 1)] private float _strength; [SerializeField] private float _effectRadius; private Material _material; private Image _image; private RectTransform _rectTrans; private int _touchPosPropertyId; private int _speedPropertyId; private int _strengthPropertyId; private int _radiusPropertyId; private bool _isInitialized; /// <summary> /// 初期化 /// </summary> void Start () { _image = GetComponent<Image>(); _rectTrans = GetComponent<RectTransform>(); _material = new Material(_shader); _image.material = _material; _touchPosPropertyId = Shader.PropertyToID("_TouchPos"); _speedPropertyId = Shader.PropertyToID("_Speed"); _strengthPropertyId = Shader.PropertyToID("_Strength"); _radiusPropertyId = Shader.PropertyToID("_EffectRadius"); _isInitialized = true; UpdateRipplesValue(); } /// <summary> /// 更新 /// </summary> void Update () { // ワールド空間からローカル空間へマウス座標を変換 Vector2 localPos = _rectTrans.InverseTransformPoint(Input.mousePosition); // shaderに設定 _image.material.SetVector(_touchPosPropertyId, localPos); } /// <summary> /// inspector操作時 /// </summary> void OnValidate() { UpdateRipplesValue(); } /// <summary> /// 波紋の値の更新 /// </summary> private void UpdateRipplesValue() { if (_isInitialized == false) { return; } _image.material.SetFloat(_speedPropertyId, _speed); _image.material.SetFloat(_strengthPropertyId, _strength); _image.material.SetFloat(_radiusPropertyId, _effectRadius); } } }
Shader
Shader "Unlit/RipplesImage" { Properties { [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {} _Color ("Tint", Color) = (1,1,1,1) _StencilComp ("Stencil Comparison", Float) = 8 _Stencil ("Stencil ID", Float) = 0 _StencilOp ("Stencil Operation", Float) = 0 _StencilWriteMask ("Stencil Write Mask", Float) = 255 _StencilReadMask ("Stencil Read Mask", Float) = 255 _ColorMask ("Color Mask", Float) = 15 [Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip ("Use Alpha Clip", Float) = 0 _TouchPos ("Touch Pos", Vector) = (0,0,0,0) _EffectRadius ("Radius", float) = 0 _Strength ("Strength", float) = 0 _Speed ("Speed", float) = 0 } SubShader { Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "PreviewType"="Plane" "CanUseSpriteAtlas"="True" } Stencil { Ref [_Stencil] Comp [_StencilComp] Pass [_StencilOp] ReadMask [_StencilReadMask] WriteMask [_StencilWriteMask] } Cull Off Lighting Off ZWrite Off ZTest [unity_GUIZTestMode] Blend SrcAlpha OneMinusSrcAlpha ColorMask [_ColorMask] Pass { Name "Default" CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma target 2.0 #include "UnityCG.cginc" #include "UnityUI.cginc" #pragma multi_compile_local _ UNITY_UI_CLIP_RECT #pragma multi_compile_local _ UNITY_UI_ALPHACLIP struct appdata_t { float4 vertex : POSITION; float4 color : COLOR; float2 texcoord : TEXCOORD0; UNITY_VERTEX_INPUT_INSTANCE_ID }; struct v2f { float4 vertex : SV_POSITION; fixed4 color : COLOR; float2 texcoord : TEXCOORD0; float4 worldPosition : TEXCOORD1; UNITY_VERTEX_OUTPUT_STEREO }; sampler2D _MainTex; fixed4 _Color; fixed4 _TextureSampleAdd; float4 _ClipRect; float4 _MainTex_ST; float2 _TouchPos; float _EffectRadius; float _Strength; float _Speed; v2f vert(appdata_t v) { v2f o; UNITY_SETUP_INSTANCE_ID(v); UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o); o.worldPosition = v.vertex; o.vertex = UnityObjectToClipPos(o.worldPosition); o.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex); o.color = v.color * _Color; return o; } fixed4 frag(v2f i) : SV_Target { float2 gap = i.worldPosition - _TouchPos; float moveX = 0; float len = length(gap); float brightGap = 0; // 波紋の範囲内 if (len <= _EffectRadius) { // p = √100-x^2-y^2 float p = sqrt(pow(_EffectRadius, 2) - pow(gap.x, 2) - pow(gap.y, 2)); // 波紋の式である√x^2+y^2を元に調整 moveX = p * _Strength * 0.0002 * (cos((len - _Time.w * _Speed) * 0.2)); // 外側への移動を明るく、内側への移動を暗くする brightGap = _Strength * 20 * moveX * cos(45); } half4 color = (tex2D(_MainTex, i.texcoord + float2(-moveX, 0)) + _TextureSampleAdd) * i.color; color.rgb += brightGap; #ifdef UNITY_UI_CLIP_RECT color.a *= UnityGet2DClipping(i.worldPosition.xy, _ClipRect); #endif #ifdef UNITY_UI_ALPHACLIP clip (color.a - 0.001); #endif return color; } ENDCG } } }
今回は以上となります。
ここまでご視聴ありがとうございました。