[ShaderStaff] 圆角矩形效果实现
操作系统:Windows8.1
显卡:Nivida GTX965M
开发工具:Unity2017.3 | Shader
最近在制作一款APP,其中需要对矩形图片资源的展现进行圆角化,看了一下网上的方案大多是基于正方形,然而在实际场景中,需要圆角化的图片往往还包括矩形,即宽高不相等,如果用技术参数来解释,便是 width 和 height 的 aspect 非 1。所以本文将会简要说明如何实现矩形圆角化Shader。
首先看下效果组图。
通过图示我们观察到,常规的基于正方形体的圆角化效果基本符合我们预期,即当半径达到 0.50 最大值的时候变成了圆形。除此之外,长方形虽然围绕Y方向进行了一定程度的拉伸 (在这里高度为宽度的2倍),但算法对中心点进行了修正,最终确保了正确的圆角化效果。
Basic Conception
效果已经看完了,那么如何设计实现呢,我们在这里将圆角化分两个部分阐述原理。首先了解基于正方形的圆角化思路。如下图所示,如果选择上下左右四个角作为圆角化目标,则分别需要按照上下左右四个圆形区域进行mask渲染,最终只保留图示中绿色区域及黄色区域。如下图所示,当 半径 r = 0.25 时及 r = 0.5 时圆角化处理后的保留渲染区域,特别的当r= 0.5时可以联想四个角度区域的原型发生了重叠。
在继续深入讨论实现之前,我们温习一下UV坐标系,这是在贴图采样过程中遵循的坐标系,具体如下图所示:
UV坐标范围是收敛在 [0, 1] 之间,如果当前UV坐标是 (0.1, 0.1) 取得颜色值是绿色,如果当前UV坐标是 (0.2, 0.8) 取得颜色值就是粉色,如果当前UV值是 (0.7, 0.2) 取得颜色值就是蓝色,如果当前UV值是 (0.9, 0.9) 取得颜色值就是黄色。如果要索取更多的信息可以进入 WIKI 咨询。
搞清概念后,我们只需要简单的在Shader中针对四个方向分别定义圆点,并根据圆点坐标及半径来判断当前UV是否在圆半径范围内,如果在圆半径范围内应用采样,反之填充透明色或者丢弃当前片元。几个主要代码片段如下:
在shader中定义新的变量用于保存自定义半径信息,在这里默认为0.25:
Properties { _MainTex ("Texture", 2D) = "white" {} _Radius ("_Radius", Range(0, 0.5)) = 0.25 }
同时片段着色器编写主要判定逻辑,如下:
fixed4 frag (v2f i) : SV_Target { // UV采样数据 fixed4 col = tex2D(_MainTex, i.uv); // 用于保存像素与圆心距离 half dist = 0; // 默认圆心坐标 half2 center = half2(0, 0); // 默认右上角圆心坐标 half2 TRC = half2(1, 1); // 默认右下角圆心坐标 half2 BRC = half2(1, 0); // 默认左下角圆心坐标 half2 BLC = half2(0, 0); // 默认左上角圆心坐标 half2 TLC = half2(0, 1); // 根据输入半径修正右上角圆心坐标 center = half2(TRC.x - _Radius, TRC.y - _Radius); if (true) { // 判定当前UV是否在圆外侧 if (center.x < i.uv.x && center.y < i.uv.y) { // 计算当前UV点与圆心距离 dist = sqrt( pow(center.x - i.uv.x, 2) + pow(center.y - i.uv.y, 2) ); // 将距离与圆半径比较,如果不在范围内,丢弃该片段 if (dist > _Radius) { discard; } } } return col; }
该代码已经在注释中描述的比较清晰了,没有说清楚的是一个 if (true) 语句,该语句用于通过外部传递的信息决定是否对当前角进行圆角化处理。主要更新代码如下:
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_Radius ("_Radius", Range(0, 0.5)) = 0.25
[MaterialToggle] _TR ("_TopRightCorner", Float) = 1
[MaterialToggle] _BR ("_BottomRightCorner", Float) = 1
[MaterialToggle] _BL ("_BottomLeftCorner", Float) = 1
[MaterialToggle] _TL ("_TopLeftCorner", FLoat) = 1
}
将开关变量分别替换掉 if(true) 后运行程序,如果一切顺利,运行效果如下图所示。
通过图示我们看到已经可以通过编辑材质来控制图片资源的圆角显示规则了。
Rectangle implementation
矩形的圆角化功能是基于上文正方形圆角化功能扩展的,所谓扩展的点就在于利用非 1 : 1 的宽高比进行已有算法实现的修正,看起来有点难以理解,没有关系,我们看代码说话。
// 修正圆心X坐标位置 half sfx0 = 1; // 修正UV点与圆心距离 half sfx1 = 1; // 修正圆心Y轴坐标位置 half sfy0 = 1; // 修正UV点与圆心距离 half sfy1 = 1; // 当矩形的width > height 时计算圆心、UV修正系数 if (_scale.x > _scale.y) { // 计算UV坐标空间下,圆心X坐标对应缩放系数 sfx0 = _scale.y/_scale.x; sfx1 = (_scale.y/ pow(_scale.x,2)); } else { // 计算UV坐标空间下,圆心Y坐标对应缩放系数 sfy0 = _scale.x/_scale.y; sfy1 = (_scale.x/ pow(_scale.y,2)); }
同时圆角化用于计算圆心到UV采样点的距离需要根据缩放的比例进行调整,具体代码如下。
center = half2((_TRC.x - (_Radius * sfx0)),(_TRC.y - (_Radius * sfy0) )); if (_TR == 1 ) { if (center.x < IN.texcoord.x && center.y < IN.texcoord.y) { dist = sqrt( pow( center.x - IN.texcoord.x , 2 ) / sfx1 + pow( center.y - IN.texcoord.y , 2 ) / sfy1); if (dist > _Radius)
{ discard; } } }
如果一些顺利,将该部分修改更新到四个圆角计算部分,最后通过外部传递参数控制即可,在这里以Unity3D材质编辑属性为例,输入图片资源的缩放比例即可。
Summary
整体的实现思路还是比较清晰的,在实际使用中如果结合UGUI的ScrollView子元素,可能出现遮罩的问题,只要将模版缓冲区相关的Shader设置代码补全即可。