[Shader 着色器]解读Unity中的CG编写Shader系列4——unity中的圆角矩形shader

博主空间

点击打开链接


上篇文章中我们掌握了表面剔除和剪裁模式 

这篇文章将利用这些知识实现一个简单的,但是又很常用的例子:把一张图片做成圆角矩形 



例3:圆角矩形Shader  


好吧我承认在做这个例子的时候走了不少弯路,由于本人对矩阵的知识掌握已经悉数还给老师,所以一开始用了一些笨办法计算圆角矩形区域。 
我们知道TEXTCOORD0是一个以对象为坐标系的坐标,并且范围在该坐标的第一象限,取值为(0,0)到(1,1) 
那么我们把每一张图片都看做一张1X1大小的矩形 
我们要在1X1大小的矩形中擦除4个角,应该是这样: 
 
以左上角为例,我们做一个辅助圆内切于这个角,半径为0.1,那么我们将这个圆擦掉3/4,剩下的黄色弧线与这个角形成的区域就是我们要擦除的区域: 
 
这个道理很简单,那么我们对4个角分别擦除掉这样的区域就能得到一个半径为10%原图尺寸的圆角矩形 


我一开始走的弯路就在于计算这个区域,是用4个圆的方程来算呢还是用距离来计算 


由于给出4个圆的方程太过于复杂,我这里直接给出计算外点距离算法的示意图: 


 
首先在这个大矩形的内部以4个圆心为顶点做出一个内矩形,心算得边长为0.8 
其次忽略内矩形内部的点,在这个红色矩形内给定任意一个蓝色矩形的外点p  ,只要能够得到p到蓝色矩形的距离,距离大于0.1(半径),那么就在圆角矩形外部,直接擦除,如图: 
 
我们看p1,p2,p3,3个点到蓝色矩形的距离分别是p1的距离<半径,根号2倍半径>p2的距离>半径,p3的距离等于半径 
所以p2在圆角矩形外,p1,p3在圆角矩形内部或边缘,我们将p2擦除掉 
其中p3的距离恰好是 p3到4条直线的距离最小值 
p1同理 
而p2的距离不能再这样计算,而应该是计算p2到4个顶点距离的最小值 




按这个算法只要算出所有外点中的点到蓝色矩形区域的距离,然后与半径判断大小,大于则discard就能得到圆角矩形 


一开始我按照这个思路得出一个极端无脑的方法: 


给定任意一点p,求p到4条直线,4个顶点的距离,然后在8个距离中求最小值作为最终的距离拿来与半径比较 


兴奋地写完代码知道我错了,检查了很久才明白,像p3这种点,算出来的8个距离中,最小距离并不是到顶点的距离,而是到两条边的延长线的距离 


于是最终8距离求最小值算法以失败告终 


还是得老老实实分情况 
那么有几种情况呢 
其实只有1种,但是先按正常逻辑分为2种: 
 
我们来看绿色区域和紫色区域的外点们 
1、当外点在紫色区域时,距离应是点到4顶点的距离最小值 
2、当外点在绿色区域时,距离应是点到4条直线的距离最小值 
按照这个思路那么我们可以对整个 坐标系内的任意点(x,y)进行判断: 
1、如果点在白色区域或者绿色区域内,  还计算个毛线距离啊,肯定是不discard啊~~~~(之前我傻乎乎的还真去算了) 


2、紫色区域内计算点到4顶点距离,然后取最小值,然后将大于0.1的部分剔除掉 


最后我们需要将这个0.1作为变量提取出来,不能写死,这样可以在Inspector中方便调节,或者在script中去设置,也就是给我们的shader定义一个float或rang型的属性 


最后代码为: 


复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
Shader  "Custom/RoundRect"  {
     Properties {
         //两种内容模式,图片模式
         _MainTex ( "Base (RGB)" , 2D) =  "white"  {}
         //纯色模式
         //_MainColor ("Color", COLOR) = (1,1,1,1)
         //圆角半径,默认为0.1
         _RoundRadius( "Radius" , float ) = 0.1
     }
     SubShader {
           
         Pass{
             CGPROGRAM
   
             #pragma fragment frag
               
             #include "UnityCG.cginc"
             //获取3个属性 并传值到CG代码段
             sampler2D _MainTex;
             float  _RoundRadius;
             float4 _MainColor;
               
             //片段着色器输入结构体(可省略)
             struct  FragInput{
                 float2 texcoord:TEXCOORD0;
                   
             };
   
             //片段着色器入口函数
             float4 frag(FragInput input) : COLOR
             {
                   
                   
                 float4 c=tex2D(_MainTex,input.texcoord); //将图片信息按坐标转换成颜色
                 //float4 c=_MainColor;  //纯色
                   
                 //x,y两个变元,区间均为[0,1]
                 float  x=input.texcoord.x;
                 float  y=input.texcoord.y;
                   
                 //4条直线的常数部分
                 float  xt=1-_RoundRadius;
                 float  xb=_RoundRadius;
                 float  yl=_RoundRadius;
                 float  yr=1-_RoundRadius;
                 //如果(x,y)不在4条直线构成的矩形中(上图的白色区域)
                 if (!(x<xt&&x>xb&&y>yl&&y<yr))
                 {
                   
                     //如果(x.y)不在上图的绿色区域
                     if (!((x<xt&&x>xb) || (y>yl&&y<yr) ))
                         //数学不好,好像判断的复杂了,如果您可以直接写出紫色区域
                         //的不等式组那么可以简单点
                 {
                 //计算四个顶点的坐标
                 float2 plb=float2(_RoundRadius,_RoundRadius);
                 float2 plt=float2(_RoundRadius,1-_RoundRadius);
                 float2 prt=float2(1-_RoundRadius,1-_RoundRadius);
                 float2 prb=float2(1-_RoundRadius,_RoundRadius);
               
                 //计算x,y分别到4个顶点的距离
                 float  distlb= sqrt ( pow ((x-plb.x),2)+ pow ((y-plb.y),2));
                 float  distlt= sqrt ( pow ((x-plt.x),2)+ pow ((y-plt.y),2));
                 float  distrt= sqrt ( pow ((x-prt.x),2)+ pow ((y-prt.y),2));
                 float  distrb= sqrt ( pow ((x-prb.x),2)+ pow ((y-prb.y),2));
                   
                 //对4个距离取最小值
                 float  dist=min(distlb,distlt);
                 dist=min(dist,distrt);
                 dist=min(dist,distrb);
                   
                 //将大于半径的表面剔除
                 if (dist>_RoundRadius)
                         discard;
   
                 }
                   
                   
                 }
   
                 return  c;
   
             }
             ENDCG
         }
   
           
     }
     FallBack  "Diffuse"
}



 




最后运行的效果,不同Radius不同尺寸的图片进行圆角矩形剔除 


当radius设为0.25时我们可以得到一个圆,所以我们的RoundRadius属性可以设置为一个0.01~0.25的rang 


由于我是windows系统,mac os下好像有点问题,不知道是不是省略了顶点着色器的问题,发现原因后再来补正 




如果我的博客对您有帮助或者您有任何疑问,欢迎加入重庆u3d交流QQ群我会给您解答:68994667,也可以加群与我们一同交流技术
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值