更新日期:2021年8月23日。
Github源码:[点我获取源码]
索引
- BorderFlow 边框流动
- 思路分析
- 流光区域
- 流光区域的中心点
- 流光区域的宽度、高度
- 定义流光区域
- 流光效果
- 如何求一个二维点在一个矩形内
- 流光效果的平滑
- 流光动画
BorderFlow 边框流动
UI边框流动的效果非常常见,实现思路也五花八门,在此我就不贴网上的那些示例了,直接看我们最终的效果图:
思路分析
流光区域
我们首先将上下左右四个方向在流动的区域(青色)定义为流光区域
,其他的定义为非流光区域
,本质上也即是流光区域
叠加了一个流光颜色(比如这里的青色)
,非流光区域
就按图像的正常颜色输出即可。
那么,我们要如何确定哪些是流光区域
呢?
这个其实很简单,我们通过上方的效果图就可以看出来,流光区域
实际上就是一个矩形区域而已,那么如何确定一个矩形区域?
在一个二维平面中,只要确定了一个中心点
(矩形的中心点),然后再确定了宽度
和高度
,即可得到一个固定的矩形区域(我们不考虑旋转)。
众所周知,图像渲染就是一个不停输入与输出的过程,在这里我们不需要立即确定流光区域
的真正值,我们只需要制定一个规则,让流光区域
根据输入的值和我们制定的规则进行实时定义即可,规则如何制定?我们继续往后看。
流光区域的中心点
通过反复研究效果图,我们发现流光区域
的位置并不是固定的,他会不停的以顺时针方向流动,以上、右
的流光作为一组,下、左
的流光作为一组,规则如下:
(上、右)流光从上方的左侧
开始向右
流动,抵达右边界后,再次从右方的上侧
开始向下
移动,抵达下边界后,流动停止(由于这里使用了循环动画,所以流光再次从上方的左侧开始),另一组(下、左)流光雷同,方向依旧顺时针。
如果将整个流动的过程定义为x=1
的话,那么左上角
就是x=0
,右上角
就是x=0.5
,右下角
就是x=1
,如下图所示,整个流光的活动轨迹
:
那么,再次将流光区域看做一个矩形
,这个矩形的中心点(也即是流光区域的中心点)的x坐标便可以确定了,那便是我们上文的x
值(活动轨迹)。
至于中心点的y值,我们不难发现,上下左右四个方向的流光都是紧贴边缘的,而图像的四个边缘坐标都是已知的(uv坐标系),则y
值很明显可以直接求出来:
比如上方流光区域中心点y值 = 1 - 流光区域高度 * 0.5
由此得出流光区域的中心点为:
流光区域的宽度、高度
中心点的规则确定了,那么再来看宽度
和高度
。
通过再次研究效果图,我们可以很明显的发现,流光区域的宽度和高度没有规则可言,高度可以是任意值(当然不会大于图像的高度),宽度也是一样。
也就是说,宽度
和高度
的值我们可以直接定义为输入变量即可。
定义流光区域
思路理清楚了,接下来我们直接写代码定义流光区域,先设定好输入值:
Properties
{
//流光位置:上文中流光的活动轨迹(0-1),也即是流光区域中心点的x坐标
//由于流光区域中心点的y坐标可以通过厚度求得,所以这里不设直接输入
_FlowPos("流光位置", Range(0, 1)) = 0
_FlowWidth("流光区域宽度", Range(0, 1)) = 0.3
_FlowThickness("流光区域高度", Range(0, 1)) = 0.03
}
然后根据我们前文中的规则,算出流光区域的真正矩形区域
所在(这里以上边框
为例):
//根据规则,流光位置(_FlowPos)在0-0.5时,上边框的流光从左侧流动到右侧
//所以这里求出流光区域中心点的x坐标在整个上边框所占的比例 ratio
half ratio = smoothstep(0, 0.5, _FlowPos);
然后将流光区域中心点的x坐标比例 ratio 映射到图像上的真实位置:
//half realXPos = lerp(0, 1, ratio);
half realXPos = lerp(_FlowWidth * -0.5, 1 + _FlowWidth * 0.5, ratio);
half realYPos = 1 - _FlowThickness * 0.5;
这里有人可能会看不懂了,上边框从左至右的真实x值应该是0-1(注释那行),为什么这里却是另一个值呢?
很简单,看图你就明白了:
因为将上边框区间的左、右都拉伸半个流光区域宽度
,会给人一种流光从无到有
、从整到零
的过渡流动过程,不至于一开始就有半截的流光显示在图像上。
那么至此,我们已经求出了流光区域
的中心点,和已知的宽度
、高度
,已然可以确定一个矩形区域,那么只要uv坐标在该区域的像素,就会被叠加流光效果,反之则无视。
流光效果
叠加流光效果
的过程就简单了,首先,我们还是希望流光效果
的亮度和颜色都是可变的,所以我们再次新增输入参数:
Properties
{
//流光位置:上文中流光的活动轨迹(0-1),也即是流光区域中心点的x坐标
//由于流光区域中心点的y坐标可以通过厚度求得,所以这里不设直接输入
_FlowPos("流光位置", Range(0, 1)) = 0
_FlowWidth("流光区域宽度", Range(0, 1)) = 0.3
_FlowThickness("流光区域厚度", Range(0, 1)) = 0.03
_FlowColor("流光颜色", Color) = (1,1,1)
_FlowBrightness("流光亮度", Range(0, 1)) = 1
}
接下来,我们判断当前输入的像素点的uv坐标是否在流光区域矩形
内,在的话就叠加流光颜色
、流光亮度
,不在的话,就原封不动输出。
//IsInRect:求一个二维点是否在一个矩形内
//第一个参数为一个矩形(half4 格式,四个值分别为【矩形中心点x坐标,矩形中心点y坐标,矩形宽度,矩形高度】)
//第二个参数为指定的二维点
//返回值:当输入的二维点在矩形区域内时,返回1,否则返回0
//在这里我们将亮度 _FlowBrightness * IsInRect的返回值,使得在流光区域内的返回亮度值,不在的返回0
half brightness = IsInRect(half4(realXPos , realYPos , _FlowWidth, _FlowThickness), uv) * _FlowBrightness;
//将流光颜色叠加到主颜色,如果不在流光区域内,则上方求得的亮度为0,则叠加无效
color.rgb += color.a * brightness * _FlowColor;
那么,求出来一个上边框以后,其他三个边框的算法也雷同了,只是流光区域
所在的矩形定义
略有不同(比如计算右边框时,_FlowWidth 将成为高度,_FlowThickness 将成为宽度),这里就不再赘述了。
如何求一个二维点在一个矩形内
这里也即是IsInRect
的具体实现,其实很简单,看下算法就明白了:
//求一个点是否在指定方形区域内
fixed IsInRect(half4 rect, half2 point2)
{
half width = rect.z * 0.5;
half height = rect.w * 0.5;
//如果点的位置没有超过方形区域左边界
fixed left = step(rect.x - width, point2.x);
//如果点的位置没有超过方形区域右边界
fixed right = step(point2.x, rect.x + width);
//如果点的位置没有超过方形区域上边界
fixed up = step(rect.y - height, point2.y);
//如果点的位置没有超过方形区域下边界
fixed down = step(point2.y, rect.y + height);
//则点在方形区域内(任何一边不满足,都将返回0)
return left * right * up * down;
}
流光效果的平滑
我们再次回看一下效果图,会发现流光区域
是呈现一个从右至左变淡的平滑效果的(上边框),也就是给人一种流动中的速度感
:
这点要怎么实现?我们还是看代码,一行搞定:
//IsInRect:求一个二维点是否在一个矩形内
//第一个参数为一个矩形(half4 格式,四个值分别为【矩形中心点x坐标,矩形中心点y坐标,矩形宽度,矩形高度】)
//第二个参数为指定的二维点
//返回值:当输入的二维点在矩形区域内时,返回1,否则返回0
//在这里我们将亮度 _FlowBrightness * IsInRect的返回值,使得在流光区域内的返回亮度值,不在的返回0
half brightness = IsInRect(half4(realXPos , realYPos , _FlowWidth, _FlowThickness), uv) * _FlowBrightness;
//【新增的行】将流光区域平滑(使得越靠近区域右侧,流光强度越接近1,越靠近区域左侧,流光强度越接近0)
brightness *= smoothstep(0, _FlowWidth, uv.x - realXPos + _FlowWidth * 0.5);
//将流光颜色叠加到主颜色,如果不在流光区域内,则上方求得的亮度为0,则叠加无效
color.rgb += color.a * brightness * _FlowColor;
这里的uv.x - realXPos + _FlowWidth * 0.5
计算,将uv的x坐标映射到了(0,1),使得位于流光区域最左侧
的点映射为0
,流光区域最右侧
的点映射为1
,从而生成了一个平滑区间的效果,将此平滑区间 * brightness ,便将叠加的流光颜色进行了平滑效果。
流光动画
要实现流光动画,很明显我们只需要改变输入的_FlowPos
值即可,不需要借助_Time
参数,直接添加一个动画播放器:
到此,这个简单的边框流动效果就基本完结了,如果还有不明白的地方可以查阅源码,当然,博客中的代码不一定与源码完全相同,这里的本意只是介绍思路,具体的实现过程中可能有一些语句上的优化,当然核心算法是一样的。