大神们unity3d遮罩shader里面的圆形遮罩怎么做

使用透明度实现Mask遮罩的Unity Shader - 简书
使用透明度实现Mask遮罩的Unity Shader
原创文章,转载请注明出处
你好,用你的shader后在pc编辑器上是没有问题的,在5.4环境下打包到手机上也没问题,但是升级到5.5后打包到手机(ios,android的还没试)上就显示为粉红色,不知道博主有没有解决方案
感谢提醒,有时间我会修正一下
关于遮罩的需求
将矩形的图片做成圆角矩形、圆形
常用实现方式
使用UGUI自带的Mask组件实现
使用网上一搜一大把的简单切割shader实现
存在的问题
这些实现方案容易出现锯齿问题,见下图
常见实现方案的锯齿
锯齿的由来
由于屏幕都是像素化的,所以看似圆滑的曲线,在屏幕上其实都是有锯齿的,但由于人眼的识别能力有限,所以当锯齿小到一定程度的时候就会认为曲线是圆滑的。然而,由于分辨率问题,我们很难做到锯齿足够小,所以需要在产生锯齿的边缘添加颜色过渡,避免明显的分界线,从而使人眼忽略锯齿问题。
现有方案的锯齿原因
由于现有方案是简单的判断遮罩的边界:
UGUI的Mask组件是通过透明度判断,如果遮罩某像素点透明度达到一个值,则该像素点对应的颜色透明度不变,否则透明度置为0
,使用遮罩的透明度作为掩码写入缓冲区,之后只渲染缓冲区为1的区域。Implementation
Masking is implemented using the stencil buffer of the GPU.The first Mask element writes a 1 to the stencil buffer All elements below the mask check when rendering, and only render to areas where there is a 1 in the stencil bufferNested Masks will write incremental bit masks into the buffer, this means that renderable children need to have the logical & of the stencil values to be rendered.
简单切割shader的实现是通过边缘检测来判断该点是否显示,不显示则透明度置为0
发现了吗,这些方案都是非黑即白的的方案,根本实现不了颜色过渡,所以存在锯齿的现象
我给出的方案是使用透明度叠加来做
1.制作一张PS做的带透明度过渡的遮罩2.编写shader,获取这张遮罩的透明度,将该透明度和目标图片的透明度进行混合
使用遮罩前
制作材质球
添加材质球
使用遮罩后
其他形状的遮罩
shader代码
Shader "ImageEffect/MaskIcon"
Properties
[PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
_Mask ("Base (RGB)", 2D) = "white" {}
_Color ("Tint", Color) = (1,1,1,1)
_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
[Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip ("Use Alpha Clip", Float) = 0
"Queue"="Transparent"
"IgnoreProjector"="True"
"RenderType"="Transparent"
"PreviewType"="Plane"
"CanUseSpriteAtlas"="True"
Ref [_Stencil]
Comp [_StencilComp]
Pass [_StencilOp]
ReadMask [_StencilReadMask]
WriteMask [_StencilWriteMask]
Lighting Off
ZWrite Off
ZTest [unity_GUIZTestMode]
Blend SrcAlpha OneMinusSrcAlpha
ColorMask [_ColorMask]
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "UnityUI.cginc"
#pragma multi_compile __ UNITY_UI_ALPHACLIP
struct a2v
fixed2 uv : TEXCOORD0;
half4 vertex : POSITION;
float4 color
struct v2f
fixed2 uv : TEXCOORD0;
half4 vertex : SV_POSITION;
float4 color
sampler2D _MainT
sampler2D _M
v2f vert (a2v i)
o.vertex = mul(UNITY_MATRIX_MVP, i.vertex);
o.color = i.color * _C
fixed4 frag (v2f i) : COLOR
half4 color = tex2D(_MainTex, i.uv) * i.
half4 mask = tex2D(_Mask, i.uv);
color.a *= mask.a;全文解析圆形Image组件的实现原理,取关键代码介绍算法细节,源码已经上传Github,欢迎下载试用。
一、Unity原生Image组件实现圆形图片的缺陷
Mask渲染消耗
许多游戏项目里免不了有很多图片是以圆形形式展示的,如头像,技能Icon等,一般做法是使用Image组件,再加上一个圆形的Mask。实现非常简单,但因为影响效率,许多关于ui方面的Unity效率优化文章,都会建议开发者少用Mask。
使用Mask会额外消耗多一个Drawcall来创建Mask,做像素剔除。
Mask不利于层级合并。原本同一图集里的ui可以合并层级,仅需一个Drawcall渲染,如果加入Mask,就会将一个ui整体分割成了Mask下的子ui与其他ui,两者只能各自进行层级合并,至少要两个Drawcall。Mask用得多了,一个ui整体会被分割得四分五裂,就会严重影响层次合并的效率了。
无法精确点击
Image+Mask的实现的圆形,点击判断不精确,点击到圆形外的四个边角仍会触发点击,虽然可以通过另外设置eventAlphaThreshold实现像素级判断,但这个方法有天生缺陷,并不是好的选择。
二、应运而生的CircleImage组件
了解了原有做法的缺陷后,我们希望自制圆形Image组件,解决这些问题,并且尽量简单易用。
虽说少用Mask,但游戏项目里总免不了有些图片要以圆形形式显示,不得不用,怎么办?转而从渲染层面思考,Image组件默认以矩形形式渲染,如果有办法定制一个特殊Image组件,重新写入圆形形状的渲染顶点、三角面片信息,根本不需要Mask就能渲染出圆形Image。
我们看到的屏幕显示,是通过GPU渲染出来的,而GPU渲染以三角面片为最小单元。所有的图形画面,本质是由无数三角面片组成的,例如矩形是由两个直角三角面片组成的;圆形可以由若干个相同的以圆心为顶点的等腰三角面片组成正多边形,近似模拟出来。三角面片分得多了,多边形的边越多,夹角越大,就越近似圆形。
绿色圆圈由60个等腰三角面片构成,黄色圆圈由10个等腰三角形面片构成
另一种精确点击方案
组件不再以像素Alpha值判断是否点击,而是用Ray-Crossing算法计算点击点是否在落多边形内,来实现精确点击。
三、组件实现
Unity引擎并不开源,好在其中是开源的,简单看下Image代码:
public class Image : MaskableGraphic, ISerializationCallbackReceiver, ILayoutElement, ICanvasRaycastFilter
Image类继承自MaskableGraphic,实现了ISerializationCallbackReceiver, ILayoutElement, ICanvasRaycastFilter这三个接口。最关键的是MaskableGraphic类,MaskableGraphic负责绘制逻辑,MaskableGraphic继承自Graphic,Graphic里有个OnPopulateMesh函数,这正是我们需要的函数。
当UI元素生成顶点数据时会调用OnPopulateMesh(VertexHelper vh)函数,我们只要继承改写OnPopulateMesh函数,将原先的矩形顶点数据清除,改写入圆形顶点数据,这样渲染出来的自然是圆形图片。
我们希望这个圆形Image组件,能够自定义某些参数,比如自定义圆形等分面数(即由多少个三角形组成这个圆形),自定义圆形填充比例等。
由于Unity的限制,继承UnityEngine基类的派生类不能在Inspector里显示自定义参数。为了解决这点,我们再造个小轮子,新建BaseImage类来代替Image类。原Image源码有近千行代码,BaseImage对其进行了部分精简,只支持Simple Image Type,并去掉了eventAlphaThreshold的相关代码。经过删减,得到一个百行代码的BaseImage类,精简版Image就完成了。
接着,新建CircleImage类继承BaseImage,重写OnPopulateMesh方法。
protected override void OnPopulateMesh(VertexHelper vh)
OnPopulateMesh方法的VertexHelper参数,保存着原来的顶点信息,因为要重新传入顶点信息,需先调用Clear方法,清除VertexHelper原有顶点信息。在计算顶点前,通过DataUtility.GetOuterUV(overrideSprite)获取贴图uv信息,简单计算获得中心点,缩放等信息。
protected override void OnPopulateMesh(VertexHelper vh)
vh.Clear();
Vector4 uv = overrideSprite != null ? DataUtility.GetOuterUV(overrideSprite) : Vector4.
float uvCenterX = (uv.x + uv.z) * 0.5f;
float uvCenterY = (uv.y + uv.w) * 0.5f;
float uvScaleX = (uv.z - uv.x) /
float uvScaleY = (uv.w - uv.y) /
知道了等分面片数segements,我们可以算出每个面片的顶点夹角,面片数segements与填充比例fillPercent相乘,就知道要用多少个面片来显示圆形/扇形
float degreeDelta = (float)(2 * Mathf.PI / segements);
int curSegements = (int)(segements * fillPercent);
通过RectTransform获取矩形宽高,计算出半径
float tw = rectTransform.rect.
float th = rectTransform.rect.
float outerRadius = rectTransform.pivot.x *
已经有了半径,夹角信息,根据圆形点坐标公式(radius * cosA,radius * sinA)可以算出顶点坐标,每次迭代新建UIVertex,将求出的坐标,color,uv等参数传入,再将UIVertex传给VertexHelper。重复迭代n次,VertexHelper就获得了多边形顶点及圆心点信息了。
计算顶点、指定三角形
float curDegree = 0;
UIVertex uiV
int verticeC
int triangleC
Vector2 curV
curVertice = Vector2.
verticeCount = curSegements + 1;
uiVertex = new UIVertex();
uiVertex.color =
uiVertex.position = curV
uiVertex.uv0 = new Vector2(curVertice.x * uvScaleX + uvCenterX, curVertice.y * uvScaleY + uvCenterY);
vh.AddVert(uiVertex);
for (int i = 1; i & verticeC i++)
float cosA = Mathf.Cos(curDegree);
float sinA = Mathf.Sin(curDegree);
curVertice = new Vector2(cosA * outerRadius, sinA * outerRadius);
curDegree += degreeD
uiVertex = new UIVertex();
uiVertex.color =
uiVertex.position = curV
uiVertex.uv0 = new Vector2(curVertice.x * uvScaleX + uvCenterX, curVertice.y * uvScaleY + uvCenterY);
vh.AddVert(uiVertex);
outterVertices.Add(curVertice);
知道了所有顶点信息,仍不足以渲染图形,因为GPU还不知道顶点之间的关系,不知道这些顶点分成了多少个三角面片,所以还需要把所有三角形信息一一告诉GPU。VertexHelper是通过AddTriangle接口接受三角形信息:
public void AddTriangle(int idx0, int idx1, int idx2)
接口的传入参数并不是UIVertex类型,而是int类型的索引值。哪来的索引?还记得之前往VertexHelper传入了一堆顶点吗?按照传入顺序,第一个顶点,索引记为0,依次类推。每次传入三个顶点的索引,就记录下了一个三角形。
需要注意,GPU 默认是做backface culling(背面剔除)的,GPU只渲染正对屏幕的三角面片,当GPU认为某个三角面片是背对屏幕时,直接丢弃该三角面片,不做渲染。那么GPU怎么判断我们传入的某个三角形是正对屏幕,还是背对屏幕?答案是通过三个顶点的时针顺序,当三个顶点是呈顺时针时,判定为正对屏幕;呈逆时针时,判定为背对屏幕。
左边的图中指定顶点的顺序是顺时针的,右边是逆时针的
VertexHelper收到的第一个顶点是圆心,且算法是按逆时针方向,迭代计算出的多边形顶点,并依次传给VertexHelper。因此按(i, 0, i+1)(i&=1)的规律取索引,就可以保证顶点顺序是顺时针的。
triangleCount = curSegements*3;
for (int i = 0, vIdx = 1; i & triangleCount - 3; i += 3, vIdx++)
vh.AddTriangle(vIdx, 0, vIdx+1);
if (fillPercent == 1)
//首尾顶点相连
vh.AddTriangle(verticeCount - 1, 0, 1);
到这里为止,我们已经完成了绘制圆形的工作了。
考虑还有可能要以圆环形式显示,组件也做了支持。圆环的情况稍微复杂:顶点集没有圆心顶点了,只有内环、外环顶点;三角形集也不是简单的切饼式分割,采用一种比较直观的三角形划分,让内外环相邻的顶点类似一根鞋带那样互相连接,来划分三角形。
定义fill、thickness变量确定是否填充图形、圆环宽度
[Tooltip("是否填充圆形")]
public bool fill =
[Tooltip("圆环宽度")]
public float thickness = 5;
计算顶点、指定三角形
float tw = rectTransform.rect.
float th = rectTransform.rect.
float outerRadius = rectTransform.pivot.x *
float innerRadius = rectTransform.pivot.x * tw -
float curDegree = 0;
UIVertex uiV
int verticeC
int triangleC
Vector2 curV
verticeCount = curSegements*2;
for (int i = 0; i & verticeC i += 2)
float cosA = Mathf.Cos(curDegree);
float sinA = Mathf.Sin(curDegree);
curDegree += degreeD
curVertice = new Vector3(cosA * innerRadius, sinA * innerRadius);
uiVertex = new UIVertex();
uiVertex.color =
uiVertex.position = curV
uiVertex.uv0 = new Vector2(curVertice.x * uvScaleX + uvCenterX, curVertice.y * uvScaleY + uvCenterY);
vh.AddVert(uiVertex);
innerVertices.Add(curVertice);
curVertice = new Vector3(cosA * outerRadius, sinA * outerRadius);
uiVertex = new UIVertex();
uiVertex.color =
uiVertex.position = curV
uiVertex.uv0 = new Vector2(curVertice.x * uvScaleX + uvCenterX, curVertice.y * uvScaleY + uvCenterY);
vh.AddVert(uiVertex);
outterVertices.Add(curVertice);
triangleCount = curSegements*3*2;
for (int i = 0, vIdx = 0; i & triangleCount - 6; i += 6, vIdx += 2)
vh.AddTriangle(vIdx+1, vIdx, vIdx+3);
vh.AddTriangle(vIdx, vIdx + 2, vIdx + 3);
if (fillPercent == 1)
//首尾顶点相连
vh.AddTriangle(verticeCount - 1, verticeCount - 2, 1);
vh.AddTriangle(verticeCount - 2, 0, 1);
圆形Image的像素级点击判断
虽然我们完成了圆形Image的绘制,但Unity还是以图片矩形包围盒来判断点击。点击圆形之外4个边角区域,仍会判定点击,在要求精确点击的场景下就有问题了。
Unity本身提供了像素级点击判断方案,通过设置eventAlphaThreshold属性(在5.4以上版本中改为alphaHitTestMinimumThreshold),根据点击像素点是否已超过Alpha阈值来判定是否触发点击。然而这个美好的方案却有天生缺陷,要求传入图片Texture Type不能为默认的Sprite,需设置为Advanced,且需勾选上Read/Write Enabled,这样会导致图片占用双倍内存,且不能合并入图集。
综合效率和易用性,设置eventAlphaThreshold都不是一个合适的方案,那么有没有别的办法实现精确的点击判断?有的,换个角度思考,我们只需要考虑点击区域是在多边形之内,还是之外就可以了。这个问题早有人研究,抽象严谨地说,这个问题可以描述为“如何判定一点是否在给定顶点的不规则封闭区域内”,知乎上有。拾前人牙慧,我们选用Ray-Crossing算法来判定屏幕点击是否落在多边形内。
Ray-Crossing算法
Ray-Crossing算法大概思路是从指定点p发出一条射线,与多边形相交,假若交点个数是奇数,说明点p落在多边形内,交点个数为偶数说明点p在多边形外。算法结论乍看难以理解,但在逻辑上是可证的。假设有条射线,从起始点向无穷远处延伸,无穷远处必定处于多边形之外;而射线从起始点出发与多边形相交的过程中,射线尾端状态是呈二态性交替变化的,即在“多边形外&-&多边形内”两种状态里交替变化,已知延长线的状态,通过交点个数就可以倒推出起始点的状态。
射线选取哪个方向并没有限制,但为了实现起来方便,考虑屏幕点击点为点p,向水平方向右侧发出射线的情况,那么顶点v1,v2组成的线段与射线若有交点q,则点q必定满足两个条件:
v2.y & q.y = p.y & v1.y
我们根据这两个条件,逐一跟多边形线段求交点,并统计交点个数,最后判断奇偶即可得知点击点是否在圆形内。
public override bool IsRaycastLocationValid(Vector2 screenPoint, Camera eventCamera)
Sprite sprite = overrideS
if (sprite == null)
RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, screenPoint, eventCamera, out local);
return Contains(local, outterVertices, innerVertices);
private bool Contains(Vector2 p, List&Vector3& outterVertices, List&Vector3& innerVertices)
var crossNumber = 0;
RayCrossing(p, innerVertices, ref crossNumber);//检测内环
RayCrossing(p, outterVertices, ref crossNumber);//检测外环
return (crossNumber & 1) == 1;
/// &summary&
/// 使用RayCrossing算法判断点击点是否落在多边形里
/// &/summary&
/// &param name="p"&&/param&
/// &param name="vertices"&&/param&
/// &param name="crossNumber"&&/param&
private void RayCrossing(Vector2 p, List&Vector3& vertices, ref int crossNumber)
for (int i = 0, count = vertices.C i & i++)
var v1 = vertices[i];
var v2 = vertices[(i + 1) % count];
//点击点水平线必须与两顶点线段相交
if (((v1.y &= p.y) && (v2.y & p.y))
|| ((v1.y & p.y) && (v2.y &= p.y)))
//只考虑点击点右侧方向,点击点水平线与线段相交,且交点x & 点击点x,则crossNumber+1
if (p.x & v1.x + (p.y - v1.y) / (v2.y - v1.y) * (v2.x - v1.x))
crossNumber += 1;
至此,一个能够灵活地以圆形,扇形,圆环形式展现图片的CircleImage组件就完成了,无须使用Mask,无须消耗额外Drawcall,不影响图集合并效率,且能实现精确点击。重新设置顶点,点击判断等逻辑的时间复杂度为O(n),与设置面片数相关,面片数最大支持设置到100,这个量级对运算效率几乎无影响,实际上,面片数设置为30已能达到较好效果。
阅读(...) 评论()后使用快捷导航没有帐号?
只需一步,快速开始
&加载中...
查看: 1610|回复: 8
遮罩流光效果
TA的其他好贴
马上注册,加入CGJOY,让你轻松玩转CGJOY。
才可以下载或查看,没有帐号?
10-35-01-823.gif (677.96 KB, 下载次数: 2)
10:49 上传
模型实际效果
材质范例.jpg (10.85 KB, 下载次数: 2)
10:50 上传
mask层就是黑白遮罩层贴图,liudong是你想要实现的光影效果层
(4.31 KB, 售价: 5 张CG券)
10:50 上传
点击文件名下载附件
售价: 5 张CG券 &
如何让别人关注你?
谢谢楼主分享!!!
本楼回复(<span id="dp_count_)
如何让别人关注你?
楼主这么屌,你家里人造吗?
本楼回复(<span id="dp_count_)
如何让别人关注你?
本楼回复(<span id="dp_count_)
如何让别人关注你?
感謝分享這麼好的資源!
本楼回复(<span id="dp_count_)
如何让别人关注你?
常常吸取前輩的實務經驗才能增長自己的實力!
本楼回复(<span id="dp_count_)
如何让别人关注你?
好好好哈哈哈呵呵呵
本楼回复(<span id="dp_count_)
如何让别人关注你?
顶一下,谢谢分享。。。。
本楼回复(<span id="dp_count_)
如何让别人关注你?
好棒棒喔~~
本楼回复(<span id="dp_count_)
如何让别人关注你?
Powered by【图片】NGUI 圆形头像遮罩 列表,求助【unity3d吧】_百度贴吧
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&签到排名:今日本吧第个签到,本吧因你更精彩,明天继续来努力!
本吧签到人数:0成为超级会员,使用一键签到本月漏签0次!成为超级会员,赠送8张补签卡连续签到:天&&累计签到:天超级会员单次开通12个月以上,赠送连续签到卡3张
关注:72,709贴子:
NGUI 圆形头像遮罩 列表,求助收藏
现在用NGUI 在做一个界面&排行榜&有一个item 列表.使用scrollview scrollview 的UIPanel
的Clipping 用soft clip . 其中每个item有个头像 要求是圆形的.但是图像资源是正方形.用了NGUI 的UIPanel 的 clipping
Text Mask 遮罩方式.实现了圆形遮罩.就是创建了一个UIPanel ,并设置 clipping
为Text Mask.把头像的sprite 添加到其子对象.但是 当滑动列表的时候.
这个头像 不会被scrollview 的UIPanel 正常切割如图:画的红色线框是 scrollview 的soft clip 范围.正常切割 应该如图中 金色奖杯 被切割.而头像却超出了切割框一定距离之后才会隐藏.求解怎么解决这个问题..或者
有其他 方案来完成这个功能.
没人回答自己回答一下,初步估计是render queue
渲染队列 的原因!
之前也遇到了这个问题,用ngui的自带shader改一下就好了,分享给你
1、新建一个材质球,Mask选择你的遮罩形状2、手动或者用代码添加需要遮罩的UITextureUISprite应该也可以的,你试试
这是材质球:我用的是:Unlit - Transparent Colored MultiMask
(其他几个也试过)这是Texture这是最终效果```...还是没得到想要的结果```求助
刚才试了一下,3.8.2里面自带了一个这样的shader,它用的是alpha通道的mask
用楼上这个方法貌似终于解决了!!!!!!!!!!!!!大感谢啊啊啊啊啊啊啊啊啊啊啊!!!!!!!!!!!!!!!!
登录百度帐号推荐应用}

我要回帖

更多关于 unity 圆形遮罩shader 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信