模仿NGUI实现SoftClip(二)

来源:互联网 发布:知乎 十号胖狐狸 编辑:程序博客网 时间:2024/06/05 00:59

书接上文,这篇文章我们要在上文的基础上实现SoftClip效果。


上图是最终效果,内层白框里面正常显示,外层白框外完全透明,两层白框间透明过渡。


首先我们要在ClipPanel脚本上添加过softWidth和softHeight这两个参数,用来定位内框,修改后的脚本如下:

using UnityEngine;using System.Collections;using System.Collections.Generic;public class ClipPanel : MonoBehaviour{public float clipWidth = 4f; // 显示区域宽度public float clipHeight = 3f; // 显示区域高度public float offsetHor = 0; // 显示区域水平偏移public float offsetVer = 0; // 显示区域垂直偏移public float softWidth = 0; // 显示区域边缘Alpha过渡宽度public float softHeight = 0; // 显示区域边缘Alpha过渡高度// 在Scene窗口中绘制两个白框用来定位显示区域和过渡区域private void OnDrawGizmos(){Vector3 panelPointLB = new Vector3(offsetHor - clipWidth * 0.5f, offsetVer - clipHeight * 0.5f);Vector3 panelPointRT = new Vector3(offsetHor + clipWidth * 0.5f, offsetVer + clipHeight * 0.5f);Vector3 worldPointLB = transform.TransformPoint(panelPointLB);Vector3 worldPointRT = transform.TransformPoint(panelPointRT);Vector3 worldPointLT = new Vector3(worldPointLB.x, worldPointRT.y, worldPointRT.z);Vector3 worldPointRB = new Vector3(worldPointRT.x, worldPointLB.y, worldPointLB.z);Gizmos.DrawLine(worldPointLB, worldPointLT);Gizmos.DrawLine(worldPointRB, worldPointRT);Gizmos.DrawLine(worldPointLB, worldPointRB);Gizmos.DrawLine(worldPointLT, worldPointRT);Vector3 panelSoftPointLB = panelPointLB + new Vector3(softWidth, softHeight);Vector3 panelSoftPointRT = panelPointRT - new Vector3(softWidth, softHeight);Vector3 worldSoftPointLB = transform.TransformPoint(panelSoftPointLB);Vector3 worldSoftPointRT = transform.TransformPoint(panelSoftPointRT);Vector3 worldSoftPointLT = new Vector3(worldSoftPointLB.x, worldSoftPointRT.y, worldSoftPointRT.z);Vector3 worldSoftPointRB = new Vector3(worldSoftPointRT.x, worldSoftPointLB.y, worldSoftPointLB.z);Gizmos.DrawLine(worldSoftPointLB, worldSoftPointLT);Gizmos.DrawLine(worldSoftPointRB, worldSoftPointRT);Gizmos.DrawLine(worldSoftPointLB, worldSoftPointRB);Gizmos.DrawLine(worldSoftPointLT, worldSoftPointRT);Gizmos.DrawLine(worldSoftPointLB, worldPointLB);Gizmos.DrawLine(worldSoftPointLT, worldPointLT);Gizmos.DrawLine(worldSoftPointRB, worldPointRB);Gizmos.DrawLine(worldSoftPointRT, worldPointRT);}}
然后在ClipDrawer脚本里,也需要对该新加的数据做一些处理并传递给Shader,修改后的脚本如下:
using UnityEngine;using System.Collections;using System.Collections.Generic;public class ClipDrawer : MonoBehaviour{public ClipPanel panel;private void OnWillRenderObject(){if (panel != null){// 从panel里取得裁切窗口数据,转化为窗口的左下角、右上角两个坐标点(基于panel的本地坐标)Vector3 panelPointLB = new Vector3(panel.offsetHor - panel.clipWidth * 0.5f, panel.offsetVer - panel.clipHeight * 0.5f);Vector3 panelPointRT = new Vector3(panel.offsetHor + panel.clipWidth * 0.5f, panel.offsetVer + panel.clipHeight * 0.5f);// 把这两个点转化为世界坐标点Vector3 worldPointLB = panel.transform.TransformPoint(panelPointLB);Vector3 worldPointRT = panel.transform.TransformPoint(panelPointRT);// 把这两个点转化为ClipDrawer的本地坐标点Vector3 localPointLB = transform.InverseTransformPoint(worldPointLB);Vector3 localPointRT = transform.InverseTransformPoint(worldPointRT);// 恢复为窗口尺寸和偏移数据Vector2 localSize = new Vector2(localPointRT.x - localPointLB.x, localPointRT.y - localPointLB.y);Vector2 localOffset = (localPointLB + localPointRT) * 0.5f;// 合并数据到一个Vector4中并等待发送Vector4 clipRange = new Vector4(localSize.x, localSize.y, localOffset.x, localOffset.y);// 计算‘显示区域尺寸’和‘Soft区域尺寸’的比值(Soft区域在两侧都存在,所以要除以2个Soft尺寸)float softWidthRatio = panel.softWidth < 0.000001f ? 10000 : panel.clipWidth / panel.softWidth * 0.5f;float softHeightRatio = panel.softHeight < 0.000001f ? 10000 : panel.clipHeight / panel.softHeight * 0.5f;Vector4 clipSoftRatio = new Vector4(softWidthRatio, softHeightRatio, 0, 0);Renderer r = GetComponent<Renderer>();Material mat = r.materials[0];// 把 ClipRange 数据发送给Shader,一共4条数据:窗口宽度、窗口高度、窗口水平偏移、窗口垂直偏移(注意这些数据都基于本地坐标)mat.SetVector("_ClipRange", clipRange);// 把 ClipSoftRatio 数据发送给Shader,一共2条数据:窗口宽度比Soft宽度、窗口高度比Soft高度mat.SetVector("_ClipSoftRatio", clipSoftRatio);}}}
传给Shader的是一个叫做_ClipSoftRatio的向量数据,它记录了窗口区域和过渡区域的比值,后面会解释为什么要传递这个值。


Shader内容变化也不大,先把代码贴出来:

Shader "Unlit/SoftClip"{Properties{_MainTex("Texture", 2D) = "white" {}_ClipRange("ClipRange", Vector) = (0, 0, 0, 0)_ClipSoftRatio("ClipSoftRatio", Vector) = (0, 0, 0, 0)}SubShader{// 因为需要操控alpha,所以需要设置渲染类型为透明Tags{ "RenderType" = "Transparent" }Pass{// 因为是透明Shader,需要关闭深度写入并开启alpha混合ZWrite OffBlend SrcAlpha OneMinusSrcAlphaCGPROGRAM#pragma vertex vert#pragma fragment frag#include "UnityCG.cginc"struct appdata{float4 vertex : POSITION;float2 uv : TEXCOORD0;};struct v2f{float2 uv : TEXCOORD0;float4 vertex : SV_POSITION;// 该二维数组记录该点的xy坐标与显示区域的关系float2 relation : TEXCOORD1;// 该二维数组记录该点的 显示区域尺寸 和 Soft区域 的比值float2 softRatio : TEXCOORD2;};sampler2D _MainTex;float4 _MainTex_ST;float4 _ClipRange = float4(0, 0, 0, 0);float4 _ClipSoftRatio = float4(0, 0, 0, 0);v2f vert(appdata v){v2f o;o.vertex = UnityObjectToClipPos(v.vertex);o.uv = TRANSFORM_TEX(v.uv, _MainTex);// 将顶点的本地坐标值和窗口尺寸相除,如果点在窗口内,结果会落在(-1,1)区间内。这行代码一定要理解o.relation = (v.vertex.xy - _ClipRange.zw) / (_ClipRange.xy * 0.5);o.softRatio = _ClipSoftRatio.xy;return o;}fixed4 frag(v2f i) : SV_Target{// 优化relation,结果若是大于0就表示点在窗口内,否则点在窗口外(显示区域中心点的值为1,区域边缘为0,区域外都是负数,线性递减)float2 relation1 = float2(1, 1) - abs(i.relation.xy);// 乘以softRatio,此时relation等于1的点不再位于区域中心,而是Soft过渡区域的最内边float2 relation2 = relation1 * i.softRatio;// 不需要分别检查x和y两个坐标,选择其中较小的值做检查即可float relation3 = min(relation2.x, relation2.y);// 把数据约束到[0, 1]范围内float relation4 = clamp(relation3, 0, 1);// sample the texturefixed4 col = tex2D(_MainTex, i.uv);// 把输出颜色乘以relation4,得到想要的结果col.a *= relation4;return col;}ENDCG}}}
最后解说一下原理:

我们只看x的值,假定Alpha过渡区域宽度是显示区域宽度的1/4,那么vert函数中算出来的relation值的分布应该入下图所示,显示区域内最左侧为-1,最右侧为1,线性增长。

后续计算出的relation1的值如下图所示,从左到右几个关键点的值分别是0、0.25、1、0.25、0,此时已经距离最终结果很近了。如果我们把relation1的值直接赋给输出颜色,就会得到一个透明度从0到1又回到0的显示区域(过渡区域跟显示区域一样大)。


而我们需要的是下图这样的值,只要把上面的值乘以4(显示区域和过渡区域的比值)再把所有大于1的值都压到1 就能得到,是不是很简单?


把一步步处理得到的relation值乘以最终输出颜色的Alpha,就得到了我们的SoftClip效果!


备注:

    本文的实现机制跟NGUI基本上一致,NGUI自带的带数字后缀的Shader都是支持Soft Clip功能的Shader,后缀数字表示可同时被几个Panel裁剪(最大为3)。

    我们传给Shader的Soft参数是“显示区域和过渡区域的比值”,所以如果过渡区域大小为0的时候,为了避免除数为0我们需要特殊处理(也不应该是负数),所以一旦发现过渡区域值小于等于0,就赋值一个很大的固定比值(本文选了10000而NGUI选了1000)。这也导致了一个小问题,就算把过渡区域设为0,也还是有个很小的过渡区域,大家可以在NGUI上测试一下(把使用SoftClip的Panel的size设的巨大而softness设为0,然后看边缘裁切其实是有Alpha过渡的)。





原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 下蹲腿的筋酸痛怎么办 躺平了 腿酸痛怎么办 500上下蹲后腿痛怎么办 蹲完马步后站不起来怎么办? 车在水里熄火了怎么办 脚磕到了很疼怎么办 破腹产4年了腰疼怎么办 蹲起之后腿疼怎么办 深蹲起跳伤腰部怎么办 蹲起膝盖有响声怎么办 腰突然不能弯了怎么办 蚂蚱吃了会过敏怎么办 孕妇能吃蚂蚱菜怎么办 孕妇吃了蚂蚁菜怎么办 白果很硬的时候怎么办 有痔疮吃了胡椒怎么办 吃紫菜多了难受怎么办 四川泡菜太酸了怎么办 孕妇吃了白花菜怎么办 怀孕吃了马扎菜怎么办 被铁钉扎伤了脚怎么办 风扇吹得肩膀疼怎么办 胳膊肌肉那块肿了怎么办 生完孩子臀部变宽怎么办 3岁宝宝肋骨外翻怎么办 胸肌正面不明显侧面看才有怎么办 小孩胸肌骨突出外翻怎么办 衣柜隔层板坏了怎么办 科三路线记不住怎么办 喂奶以后胸变小了怎么办 健身完肩膀缝疼怎么办 生过孩子胯部宽怎么办 无肩带文胸往下滑怎么办 内衣肩带老是往下滑怎么办 乳房发育一边大一边小怎么办 母猫乳房有硬块怎么办 19岁乳晕很大乳头很小怎么办 做完俯卧撑胳膊特别痛怎么办 电压低风扇转不动怎么办 小孩不配合康复锻炼怎么办 跑步机安全开关脱落怎么办