写在前面
前几天一个以前的同事突然说想叫我帮忙搞个着色器,想要做一个UI的过场效果。大概就像类似动画片里面结束的时候的黑场效果,需要用一个透明UI图在一个全黑的背景中间做放大缩小的操作。当透明UI最大时,显示场景的其他UI,当透明UI最小时,整个场景黑场,效果如下。
其实这就是同事想要的效果了,实现的原理很简单,没花什么功夫就写出来了。后来想想写都写了,看看能不能优化一下这个显示效果,让这个过场更加好看点,于是便有了这篇文章。
正文
实现这个效果原理也十分简单,首先获取到这张需要放大小缩小的图片uv,用于修改它的tilling跟offset,因为uv都是从0到1的,如果直接改变的话uv的话,只会以左上角为原点进行缩放,所以这里要将图片的offset偏移到(0.5,0.5)的位置再进行缩放;然后就是获取需要放大缩小的这张图片的alpha通道的信息,与底图进行一个比对,让两张图重合在一起并剔除掉缩放图的alpha为1的部分,也就是常说的掩膜效果。代码如下所示
Shader "custom/masknormal"
{
Properties
{
//底图
_MainTex("_MainTex",2D) = "white"{}
//需要缩放的图
_MaskTex("_MaskTex", 2D) = "white" {}
//缩放比例
_Progress("Progress", Range(-0.5,0.5)) = 0
}
SubShader
{
Tags{ "RenderType" = "Transparent" "Queue" = "Transparent" }
Blend SrcAlpha OneMinusSrcAlpha
Cull Off ZWrite Off ZTest Always
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma target 3.0
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float2 uv2:TEXCOORD1;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
sampler2D _MaskTex;
float _Progress;
float4 _MaskTex_ST;
v2f vert(appdata v)
{
v2f o;
o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
o.uv = v.uv;
return o;
}
fixed4 frag(v2f i) : SV_Target
{
//_Progress = pow(_Progress,2);
_Progress = log(0.5 - _Progress);
_MaskTex_ST.xy = _Progress;
_MaskTex_ST.zw = (_Progress - 1)*-0.5;
//缩放图 同时做为掩膜图
fixed4 maskcol = tex2D(_MaskTex, i.uv*_MaskTex_ST.xy + _MaskTex_ST.zw);
//底图取样
fixed4 outcol = tex2D(_MainTex,i.uv);
//去除缩放图的alpha值
outcol.w = outcol.a*(1 - maskcol.a);
return outcol;
}
ENDCG
}
}
}
用了上面的shader,再在c#代码里面对_Progress进行一个插值控制,那么实现的效果就会跟这图一样了。
虽然说这效果给那前同事看了还挺满意的,但是这效果还是十分的简陋,本着写都写了还是弄个更好看的效果吧!
替换底色
黑色的背景换成了可调色,这个实现比较简单,直接将原本的输出颜色跟输入的颜色相乘即可。或者因为是纯色图,可以直接拿输入颜色的rgb与缩放的掩膜图的alpha通道作为颜色输出
模糊效果
透明的缩放图与底图之间的过度效果有点突兀,加上了一个模糊效果,实现的原理也相当简单,直接取当前顶点的左上,左下,右上,右下和本身五个点做平均再输出,得到的平均颜色作为片段的输出即可。
附上个效果图
代码如下
Shader "custom/mask"
{
Properties
{
//底图
_MainTex("_MainTex",2D) = "white"{}
//需要缩放的图
_MaskTex("_MaskTex", 2D) = "white" {}
//缩放比例
_Progress("Progress", Range(-0.5,0.5)) = 0
//底图颜色
_Color("Color",color) = (0,0,0,0)
//模糊系数
_blurOffset("Blur",Range(0,0.01)) = 0.0075
}
SubShader
{
Tags{ "RenderType" = "Transparent" "Queue" = "Transparent" }
Blend SrcAlpha OneMinusSrcAlpha
Cull Off ZWrite Off ZTest Always
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma target 3.0
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float2 uv2:TEXCOORD1;
float4 vertex : SV_POSITION;
};
sampler2D _MaskTex;
sampler2D _MainTex;
float _Progress;
float4 _MaskTex_ST;
fixed4 _Color;
fixed _blurOffset;
v2f vert(appdata v)
{
v2f o;
o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
o.uv = v.uv;
o.uv2 = cos(o.uv*_Time.x);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
//_Progress = pow(_Progress,2);
_Progress = log2(0.5 - _Progress);
_MaskTex_ST.xy = _Progress;
_MaskTex_ST.zw = (_Progress - 1)*-0.5;
fixed4 maskcol = tex2D(_MaskTex, i.uv*_MaskTex_ST.xy + _MaskTex_ST.zw);
//高斯模糊
//leftup
fixed4 maskcol1 = tex2D(_MaskTex, i.uv*_MaskTex_ST.xy + _MaskTex_ST.zw + fixed2(-_blurOffset, _blurOffset));
//leftdown
fixed4 maskcol2 = tex2D(_MaskTex, i.uv*_MaskTex_ST.xy + _MaskTex_ST.zw + fixed2(-_blurOffset, -_blurOffset));
//rightup
fixed4 maskcol3 = tex2D(_MaskTex, i.uv*_MaskTex_ST.xy + _MaskTex_ST.zw + fixed2(_blurOffset, _blurOffset));
//rightdown
fixed4 maskcol4 = tex2D(_MaskTex, i.uv*_MaskTex_ST.xy + _MaskTex_ST.zw + fixed2(_blurOffset, -_blurOffset));
fixed4 mix = (maskcol + maskcol1 + maskcol2 + maskcol3 + maskcol4)*0.2;
mix = lerp(maskcol, mix, 0.5);
fixed4 outcol = tex2D(_MainTex,i.uv);
outcol.w = outcol.a*(1 - mix.a);
outcol = fixed4(_Color.x,_Color.y,_Color.z, outcol.w);
return outcol;
}
ENDCG
}
}
}
这么一来看起来就顺眼多了。。
但是,不知道是脑抽还是怎么了,我还是想加个缩放图旋转的效果。就是因为这抽一抽上网研究了几天才研究出来,只怪自己以前数学菜又没好好听课,先贴上个uv旋转公式
至于为什么是这个公式,可以看下我的笔记。
附上个效果图
最后附上代码
Shader "custom/mask1"
{
Properties
{
_MainTex("_MainTex",2D) = "white"{}
_MaskTex("_MaskTex", 2D) = "white" {}
_Progress("Progress", Range(0,1)) = 0
_Color("Color",color) = (0,0,0,0)
_blurOffset("Blur",Range(0,0.03)) = 0.0075
_RotateSpeed("RotateSpeed",Range(0,10))=5
}
SubShader
{
Tags{ "RenderType" = "Transparent" "Queue" = "Transparent" }
Blend SrcAlpha OneMinusSrcAlpha
Cull Off ZWrite Off ZTest Always
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma target 3.0
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MaskTex;
sampler2D _MainTex;
float _Progress; //声明缩放量
float4 _MaskTex_ST;
fixed4 _Color;
fixed _blurOffset;
fixed _RotateSpeed;
v2f vert(appdata v)
{
v2f o;
o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
o.uv = v.uv;
return o;
}
fixed4 frag(v2f i) : SV_Target
{
fixed4 uv;
//手动控制缩放
//_Progress =abs(log(_Progress)));
_Progress =abs(log(sin(_Time.y)));
//offset偏移
uv.xy = i.uv.xy- fixed2(0.5, 0.5);
uv.zw *=(1-_Progress) ;
//旋转公式 乘上缩放
uv.xy = fixed2(uv.x * cos(_RotateSpeed * _Time.y) - uv.y * sin(_RotateSpeed * _Time.y),
uv.x * sin(_RotateSpeed * _Time.y) + uv.y * cos(_RotateSpeed * _Time.y))*_Progress;
//复原offset
uv.xy += fixed2(0.5, 0.5);
fixed4 maskcol = tex2D(_MaskTex, uv);
//高斯模糊
//leftup
fixed4 maskcol1 = tex2D(_MaskTex, uv+fixed2(-_blurOffset, _blurOffset));
//leftdown
fixed4 maskcol2 = tex2D(_MaskTex, uv + fixed2(-_blurOffset, -_blurOffset));
//rightup
fixed4 maskcol3 = tex2D(_MaskTex, uv + fixed2(_blurOffset, _blurOffset));
//rightdown
fixed4 maskcol4 = tex2D(_MaskTex, uv + fixed2(-_blurOffset, -_blurOffset));
fixed4 mix = (maskcol + maskcol1 + maskcol2 + maskcol3 + maskcol4)*0.2;
mix = lerp(maskcol, mix, 0.5);
fixed4 outcol = tex2D(_MainTex,uv);
outcol.w = outcol.a*(1 - mix.a);
outcol = fixed4(_Color.x,_Color.y,_Color.z, outcol.w);
return outcol;
}
ENDCG
}
}
}
总结
因为懒得再写C#代码来控制shader,所以直接用sin函数来控制了缩放所以看起来周期会有点长,同时缩放比例的控制还是有点不太理想,只想到用一个log方法来控制,所以缩放会一下子消失,之后再想想优化的方法吧就写到这里。uv的旋转以前都是直接网上抄别人的公式,这次是自己来推导了一次,还好没忘光。uv的旋转和缩放这两个分开来写比较简单,但运用在一起了就会乱,改了几天代码缩放是没问题的,但旋转却是绕着某一个顶点进行旋转了,都几乎快放弃了不知为何又脑子一抽想出来了,果然做事只要坚持下去还是会有收获的!