Unity shader 实现图片带圆角和边线border
Posted 长江很多号
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Unity shader 实现图片带圆角和边线border相关的知识,希望对你有一定的参考价值。
1 前言
基本诉求:想要一张图,显示时有圆角,且还能有boarder。图可以是纯色,也可以是图片。
在android,绘制这样的图非常简单,在xml声明一下就行。
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#FFFFFFFF"/>
<corners android:radius="25dp"/>
<stroke android:width="3dp" android:color="#FFff0000"/>
</shape>
圆角一般设置一个半径radius,边线border一般设置线宽&颜色即可。
在Unity,就麻烦一些,要借助shader或者脚本实现,本文着重讨论一下用shader怎么实现。
2 圆角的实现
先研究左下角怎么抠掉。
当x < r 且 y < r
,则可以分为上图的区域A和区域B,其中区域A是需要设置alpha = 0
的区域。区域B则保持纹理颜色。
怎么找出区域A呢?
首先,圆角的中心点,坐标为(r, r)
,则这个区域内的任意一点(x, y)
, 距离中心点的距离arc_size
为
如果arc_size
大于圆的半径r
,则是在区域A,否则,在区域B。
开根号浪费计算,可以都加个平方。
即,从代码上,可以这么写:
//左下角
if (x < r && y < r)
arc_size = (x - r) * (x - r) + (y - r) * (y - r);
if (arc_size > r * r)
color.a = 0;
补充一点,是x
的范围是0~width
, y
的范围是0 ~ height
。
由于shader的顶点坐标是归一化的,即0到1,所以需要归一化坐标,乘于view的宽和高。
float x = IN.texcoord.x * width;
float y = IN.texcoord.y * height;
3 border的实现
边界,可以分8个区域,具体如下
分两种,一种是拐弯区域,一种是直线区域。
3.1 拐弯区域
以左下角为例子,我们放大区域看看。
分三部分,一个是镂空区域,一个是线区域,一个是线内区域。3者距离中心点不一样,所以我们还是可以根据某个点距离中心点(r, r) 来判断。
来,上代码:
half4 color = IN.color;
if (x < r && y < r)//左下角区域
//离中心点的距离(的平方)
arc_size = (x - r) * (x - r) + (y - r) * (y - r);
if (arc_size > r * r) //超过圆的半径,则透明度为0
color.a = 0;
else if (border_width > 0 && arc_size > (r - border_width) * (r - border_width))
//要求有边线,且距离大于r - border_width, 为线的实际区域
color = border_color;
3.2 直线区域
直线就比较简单了,以下边中间的线为例子。区域范围是x > r && x < (width - r)
。
现在,只需要保证y < border_width
,则是线的实际区域,否则,为原始像素。
所以,代码如下
half4 color = IN.color;
//....
if (border_width > 0)
//下边直线区域
if (x > r && x < (width - r) && y < border_width)
color = border_color;
4 完整shader代码
Shader "Custom/UI/RoundConorNew"
Properties
[PerRendererData] _MainTex("Sprite Texture", 2D) = "white"
_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
_RoundedRadius("Rounded Radius", Range(0, 256)) = 64
_Width("View Width", Float) = 200
_Height("View Height", Float) = 200
_BorderWidth("Border Width", Float) = 1
_BorderColor("Boader Color", Color) = (1, 0, 0, 1)
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
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "UnityUI.cginc"
#pragma multi_compile __ UNITY_UI_ALPHACLIP
struct appdata_t
float4 vertex :
POSITION;
float4 color :
COLOR;
float2 texcoord :
TEXCOORD0;
;
struct v2f
float4 vertex :
SV_POSITION;
fixed4 color :
COLOR;
half2 texcoord :
TEXCOORD0;
float4 worldPosition :
TEXCOORD1;
;
fixed4 _TextureSampleAdd;
float4 _ClipRect;
float _RoundedRadius;
float _Width;
float _Height;
float _BorderWidth;
float4 _BorderColor;
float4 _MainTex_TexelSize;//纹理的大小,可能没有纹理,只有顶点颜色
v2f vert(appdata_t IN)
v2f OUT;
OUT.worldPosition = IN.vertex;
OUT.vertex = UnityObjectToClipPos(OUT.worldPosition);
OUT.texcoord = IN.texcoord;
OUT.color = IN.color;
return OUT;
sampler2D _MainTex;
fixed4 frag(v2f IN) : SV_Target
half4 color = IN.color;
if (_MainTex_TexelSize.z > 0)
//有纹理,则颜色从纹理读取, 并叠加顶点颜色
color = (tex2D(_MainTex, IN.texcoord)) * IN.color;
//float width = _MainTex_TexelSize.z;
//float height = _MainTex_TexelSize.w;
float width = _Width;
float height = _Height;
if (width <= 0 && _MainTex_TexelSize.z > 0)
//如果没定义宽度,而纹理又定义了宽度,则从纹理宽度读取
width = _MainTex_TexelSize.z;
if (height <= 0 && _MainTex_TexelSize.w > 0)
//同上
height = _MainTex_TexelSize.w;
float border_width = _BorderWidth;
half4 border_color = _BorderColor;
float x = IN.texcoord.x * width;
float y = IN.texcoord.y * height;
float r = _RoundedRadius;
float arc_size = 0;
//左下角
if (x < r && y < r)
arc_size = (x - r) * (x - r) + (y - r) * (y - r);
if (arc_size > r * r)
color.a = 0;
else if (border_width > 0 && arc_size > (r - border_width) * (r - border_width))
color = border_color;
//左上角
if (x < r && y >(height - r))
arc_size = (x - r) * (x - r) + (y - (height - r)) * (y - (height - r));
if (arc_size > r * r)
color.a = 0;
else if (border_width > 0 && arc_size > (r - border_width) * (r - border_width))
color = border_color;
//右下角
if (x > (width - r) && y < r)
arc_size = (x - (width - r)) * (x - (width - r)) + (y - r) * (y - r);
if (arc_size > r * r)
color.a = 0;
else if (border_width > 0 && arc_size > (r - border_width) * (r - border_width))
color = border_color;
//右上角
if (x > (width - r) && y > (height - r))
arc_size = (x - (width - r)) * (x - (width - r)) + (y - (height - r)) * (y - (height - r));
if (arc_size > r * r)
color.a = 0;
else if (border_width > 0 && arc_size > (r - border_width) * (r - border_width))
color = border_color;
if (border_width > 0)
//下边直线区域
if (x > r && x < (width - r) && y < border_width)
color = border_color;
//上边直线区域
if (x > r && x < (width - r) && (height - y) < border_width)
color = border_color;
//左边直线区域
if (y > r && y < (height - r) && x < border_width)
color = border_color;
//右边直线区域
if (y > r && y < (height - r) && x > (width - border_width))
color = border_color;
return color;
ENDCG
5 shader使用方式
5.1 创建shader
在Unity 开发环境中,Assets的某个子目录下,右键,Create - Shader - Unlit Shader
,将创建一个不带光照的shader文件,但无所谓,我们都要删除。双击打开,把上面第4节的代码黏贴覆盖。
5.2 创建材质Material
右键,Create - Material
,新建一个材质。
然后,材质的shader选择为Custom/UI/RoundConorNew
,也就是5.1新建的shader名字。
接着,调整参数,主要有5个参数需要关心:
Rounded Radius
就是圆角的半径。
View Width & View Height
是目标UI的宽高。
Border Width
是边线宽度。
Border Color
是边线颜色。
如果不要边线,把Border Width设置为0即可。
5.3 应用
在Unity的中,新建一个Image,然后把材质选为上面5.2新建的材质即可。
以上是关于Unity shader 实现图片带圆角和边线border的主要内容,如果未能解决你的问题,请参考以下文章
SimpleRoundedImage-不使用mask实现圆角矩形图片