模仿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过渡的)。
- 模仿NGUI实现SoftClip(二)
- 模仿NGUI实现SoftClip(一)
- NGUI裁剪粒子的实现,完全仿照原有的NGUI SoftClip逻辑思路,可多层裁剪
- NGUI UIPanel在softClip下不起作用的问题
- NGUI学习(二)
- NGUI的工作总结(二)
- unity ngui学习(二)
- Unity NGUI实现2048(二)逻辑分析
- Unity3d NGUI doesn't have a clipped shader version for SoftClip 和 AlphaClip错误的解决方法
- [MD]模仿百度手机助手动态折线图/MPAndroidCharts实现(二)
- iOS项目模仿之喜马拉雅(二)—— TabBar实现
- Qt/C++ 项目实战模仿酷狗之实现 换肤(二)
- NGUI实用操练(二)如何实现窗口拖动?【NGUI2.6.3】
- 模仿OSO的论坛(二)
- Unity3D UNET 模仿局域网游戏(二)
- NGUI学习笔记(二)UISprite
- unity学习之NGUI(二)
- 【从零开始学NGUI 】 (二)Label
- java进阶
- java二分查找算法(折半查找算法)实例
- jquery插件效果
- BigDecimal数据类型(初始化,加减乘除,基本操作)
- 谈谈数据库的ACID
- 模仿NGUI实现SoftClip(二)
- MATLAB入门
- 226. Invert Binary Tree
- ACM 吝啬的国度
- HBase实战
- 支付网关的设计
- PyTorch contiguous 的概念
- Java并发工具类CountDownLatch
- maven 打包 jar包运行出错