NGUI源码解析之 UIDrawCall(二)UIPanel 裁剪

文章说明

在阅读这篇文章的时候,希望你是已经看了 UIDrawCall(一)的,所以第二篇主要来讲一下关于渲染调用阶段的函数,其实这一章和 UIPanel 裁剪有关,最好是先阅读一下 UIPanel 源码再来看会比较清晰。

如果文章中有写的不对的地方,欢迎大家指出,谢谢。

源码分析

UIPanel 裁剪介绍

在进行源码讲解之前,先来点小菜,了解一下 “UIPanel” 组件的裁剪部分。

那么究竟什么是裁剪,就是我们只需要显示我们想要看到的那部分内容,对于不想看到的那些多余的内容,我们就需要将他们裁剪掉,对于 UIDrawcall 来讲,接下来讲到的裁剪都是和 UIPanel 的这个 Clipping 属性相关,关于 UIPanel 更多详细的内容我们在 UIPanel 专用篇中详细介绍,这里不过多阐述。

如图,重点关注一下 UIPanel 的 Clipping 的属性有四个可以选择的选项。除了第一个 None,剩下三个都是与裁剪相关。

在这里插入图片描述
我们看到三个裁剪选择:

  • Texture Mask 贴图裁剪
    在这里插入图片描述
  • Soft Clip 软裁剪
    在这里插入图片描述
  • Constrain But Dont Clip (这个裁剪我还是不太了解,基本也没用过,这里不多介绍)

渲染调用阶段

1. 主要函数

OnWillRenderObject() :这个函数是用于做一些渲染之前的一些设置的,一般用于设置材质的参数 还有 裁剪信息。

SetClipping(): 这是设置裁剪信息的核心代码。

裁剪部分主要分为三部分:

  1. 贴图裁剪,主要就是 Texture Mask 贴图裁剪
  2. 其他裁剪,主要就是 Soft Clip 软裁剪
  3. 遗留功能,这个一般不会走进去,这里不讨论

首先重点介绍一下 第二部分 “其他裁剪”,主要就是 soft Clip 。看懂了第二种裁剪,贴图裁剪也就明白了,先看源码:

2. NGUI 源码:

	//这个函数是用于做一些渲染之前的一些设置的,一般用于设置材质的参数 还有 裁剪信息
	void OnWillRenderObject ()
	{
		//渲染前再次检查材质是否需要重建
		UpdateMaterials();

		if (mBlock != null) mRenderer.SetPropertyBlock(mBlock);
		if (onRender != null) onRender(mDynamicMat ?? mMaterial);
		if (mDynamicMat == null || mClipCount == 0) return;
		//如果有发生裁剪,才会走下来,接下来的部分全部是针对 panel 裁剪的设置部分

		if (mTextureClip)
		{
			//这里设置的是 贴图的 Mask 裁剪,原理是:使用一张 Mask(遮罩) 图片,shader 中在根据裁剪区域和像素 alpha 设置裁剪
			Vector4 cr = panel.drawCallClipRange;
			//panel 的 clipSoftness,看代码并没有使用到这些
			Vector2 soft = panel.clipSoftness;
			Vector2 sharpness = new Vector2(1000.0f, 1000.0f);
			if (soft.x > 0f) sharpness.x = cr.z / soft.x;
			if (soft.y > 0f) sharpness.y = cr.w / soft.y;

			//在材质中设置 _ClipRange0 的数值为 (-cr.x / cr.z, -cr.y / cr.w, 1f / cr.z, 1f / cr.w)
			mDynamicMat.SetVector(ClipRange[0], new Vector4(-cr.x / cr.z, -cr.y / cr.w, 1f / cr.z, 1f / cr.w));
			//在材质中设置 裁剪贴图信息 _ClipTex 
			mDynamicMat.SetTexture("_ClipTex", clipTexture);
			//最终 _ClipRange0 和 _ClipTex 都会被传递到 shader 中对应的变量
		}
		else if (!mLegacyShader)
		{
			//如果有裁剪设置,并且不是 mask 裁剪,一般都会走到这里,用于给 Soft Clip (软裁剪)设置裁剪范围的
			UIPanel currentPanel = panel;

			//从自己开始遍历所有的父Panel,凡是支持裁剪的,都要设置裁剪范围,直到 currentPanel 为 null
			//之前的 只有三种 “Transparent Colored”shader 就与这个 panel 裁剪嵌套相关最多支持3层叠加裁剪
			for (int i = 0; currentPanel != null; )
			{
				if (currentPanel.hasClipping)
				{
					//这个里面计算裁剪范围和父Panel 与 当前DrawCall所属的Panel的角度
					float angle = 0f;
					Vector4 cr = currentPanel.drawCallClipRange;

					// Clipping regions past the first one need additional math
					//从叠加的第2个裁剪区域开始,需要增加一些额外的运算,只会显示叠加裁剪重叠的部分
					if (currentPanel != panel)
					{
						//对于父子节点中多个 使用  Soft Clip 的 UIPanel,他们的显示区域是他们叠加中重叠部分的范围,所以要计算这个角度
						Vector3 pos = currentPanel.cachedTransform.InverseTransformPoint(panel.cachedTransform.position);
						cr.x -= pos.x;
						cr.y -= pos.y;

						Vector3 v0 = panel.cachedTransform.rotation.eulerAngles;
						Vector3 v1 = currentPanel.cachedTransform.rotation.eulerAngles;
						Vector3 diff = v1 - v0;
						//把角度限制在-180和180之间
						diff.x = NGUIMath.WrapAngle(diff.x);
						diff.y = NGUIMath.WrapAngle(diff.y);
						diff.z = NGUIMath.WrapAngle(diff.z);

						// unity 是左手坐标系,所以我们正常看到的平面都是垂直于z轴的
						// 所以这里防止界面绕着 x轴 或者 y轴旋转 90度,使界面 完全横向水平于z轴,或者完全纵向水平于z轴,
						if (Mathf.Abs(diff.x) > 0.001f || Mathf.Abs(diff.y) > 0.001f)
							Debug.LogWarning("Panel can only be clipped properly if X and Y rotation is left at 0", panel);
						//因为我们看到的界面是垂直于z轴的,所以只有绕着z轴旋转是有效角度
						angle = diff.z;
					}

					// Pass the clipping parameters to the shader

					///这里就是真正设置裁剪的地方了
					// cr就是this.panel的 drawCallClipRange,而这个drawCallClipRange的各个参数意义是这样的: 
					// ( x:中心点X坐标 y:中心点Y坐标 z:panel width的一半, w:panel height的一半)
					// currentPanel.clipSoftness 软裁剪设置的渐变边缘
					// angle 是与父Panel的角度 
					// 如果是单层:angle 是0
					// 如果是多层叠加:angle 会被使用
					SetClipping(i++, cr, currentPanel.clipSoftness, angle);
				}
				currentPanel = currentPanel.parentPanel;
			}
		}
		else // Legacy functionality
		{
			//这个是遗留功能,通常不会走到这里,这里不多解释
			Vector2 soft = panel.clipSoftness;
			Vector4 cr = panel.drawCallClipRange;
			Vector2 v0 = new Vector2(-cr.x / cr.z, -cr.y / cr.w);
			Vector2 v1 = new Vector2(1f / cr.z, 1f / cr.w);

			Vector2 sharpness = new Vector2(1000.0f, 1000.0f);
			if (soft.x > 0f) sharpness.x = cr.z / soft.x;
			if (soft.y > 0f) sharpness.y = cr.w / soft.y;

			mDynamicMat.mainTextureOffset = v0;
			mDynamicMat.mainTextureScale = v1;
			mDynamicMat.SetVector("_ClipSharpness", sharpness);
		}
	}

SetClipping 和 Awake 相关代码如下:

	static int[] ClipRange = null;
	static int[] ClipArgs = null;

	/// <summary>
	/// Set the shader clipping parameters.
	/// </summary>
	//设置裁剪需要的相关信息
	void SetClipping (int index, Vector4 cr, Vector2 soft, float angle)
	{
		angle *= -Mathf.Deg2Rad;

		//如果 soft.x 和 soft.y 都小于等于0的话,就拿默认值
		Vector2 sharpness = new Vector2(1000.0f, 1000.0f);
		if (soft.x > 0f) sharpness.x = cr.z / soft.x; 
		if (soft.y > 0f) sharpness.y = cr.w / soft.y;

		//ClipRange 数组在 Awake 被写死了,命名分别是:_ClipRange0 _ClipRange1 _ClipRange2  (这些名字都是在 shader 中定义对应的变量名)
		//ClipArgs 数组在 Awake 也是被写死了,命名分别是: _ClipArgs0 _ClipArgs1 _ClipArgs2 _ClipArgs3
		//所以如果你们项目中对于这些不够用的话,扩展也要按照这个规则走,(如果不拓展的话,超过这个范围的多重叠加裁剪就不会设置信息,所以要拓展就要增加自定义 shader)
		if (index < ClipRange.Length)
		{
			mDynamicMat.SetVector(ClipRange[index], new Vector4(-cr.x / cr.z, -cr.y / cr.w, 1f / cr.z, 1f / cr.w));
			mDynamicMat.SetVector(ClipArgs[index], new Vector4(sharpness.x, sharpness.y, Mathf.Sin(angle), Mathf.Cos(angle)));
		}
	}

	// Unity 5.4 bug work-around: http://www.tasharen.com/forum/index.php?topic=14839.0
	static int dx9BugWorkaround = -1;

	/// <summary>
	/// Cache the property IDs.
	/// </summary>

	void Awake ()
	{
		if (dx9BugWorkaround == -1)
		{
			var pf = Application.platform;
#if !UNITY_5_5_OR_NEWER
			dx9BugWorkaround = ((pf == RuntimePlatform.WindowsPlayer || pf == RuntimePlatform.XBOX360) &&
#else
			dx9BugWorkaround = ((pf == RuntimePlatform.WindowsPlayer) &&
#endif
				SystemInfo.graphicsShaderLevel < 40 && SystemInfo.graphicsDeviceVersion.Contains("Direct3D")) ? 1 : 0;
		}

		if (ClipRange == null)
		{
			ClipRange = new int[]
			{
				Shader.PropertyToID("_ClipRange0"),  //这些名字都是在 shader 中定义的变量
				Shader.PropertyToID("_ClipRange1"),
				Shader.PropertyToID("_ClipRange2"),
				Shader.PropertyToID("_ClipRange4"),  //shader 中我没找到这个变量(诶不知道是不是遗留功能还没做,不过也好奇为啥这个不是3是4,哇,我不会搞到盗版源码了吧)
			};
		}

		if (ClipArgs == null)
		{
			ClipArgs = new int[]
			{
				Shader.PropertyToID("_ClipArgs0"),  //这些名字都是在 shader 中定义的变量
				Shader.PropertyToID("_ClipArgs1"),
				Shader.PropertyToID("_ClipArgs2"),
				Shader.PropertyToID("_ClipArgs3"),   //shader 中我没找到这个变量(诶不知道是不是遗留功能还没做)
			};
		}
	}

3. shader 源码分析:

这里我们只分析单层 clip ,多层叠加裁剪感兴趣的可以自己去阅读,懂了原理没什么太大问题哈,我对 shadr 也是刚刚入门,如果解释不对的地方,欢迎大神指出。接下来这张图是整篇文章最重要的部分,核心讲解裁剪的原理:

在这里插入图片描述

“Hidden/Unlit/Transparent Colored 1 ” shader 部分源码如下:

			sampler2D _MainTex;
			float4 _ClipRange0 = float4(0.0, 0.0, 1.0, 1.0);  //从 NGUI 中设置的信息传递进来
			float2 _ClipArgs0 = float2(1000.0, 1000.0);      //从 NGUI 中设置的信息传递进来

			struct appdata_t
			{
				float4 vertex : POSITION;
				half4 color : COLOR;
				float2 texcoord : TEXCOORD0;
				UNITY_VERTEX_INPUT_INSTANCE_ID
			};

			struct v2f
			{
				float4 vertex : SV_POSITION;
				half4 color : COLOR;
				float2 texcoord : TEXCOORD0;
				float2 worldPos : TEXCOORD1;
				UNITY_VERTEX_OUTPUT_STEREO
			};

			v2f o;

			v2f vert (appdata_t v)
			{
				UNITY_SETUP_INSTANCE_ID(v);
				UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
				o.vertex = UnityObjectToClipPos(v.vertex);
				o.color = v.color;
				o.texcoord = v.texcoord;
				o.worldPos = v.vertex.xy * _ClipRange0.zw + _ClipRange0.xy;
				return o;
			}

			half4 frag (v2f IN) : SV_Target
			{
				// Softness factor
				float2 factor = (float2(1.0, 1.0) - abs(IN.worldPos)) * _ClipArgs0;
			
				// Sample the texture
				half4 col = tex2D(_MainTex, IN.texcoord) * IN.color;
				//核心裁剪代码
				col.a *= clamp( min(factor.x, factor.y), 0.0, 1.0);
				return col;
			}

“Hidden/Unlit/Transparent Colored (TextureClip)” shader 部分源码如下:

			sampler2D _MainTex;
			sampler2D _ClipTex;
			float4 _ClipRange0 = float4(0.0, 0.0, 1.0, 1.0);

			struct appdata_t
			{
				float4 vertex : POSITION;
				float2 texcoord : TEXCOORD0;
				half4 color : COLOR;
				UNITY_VERTEX_INPUT_INSTANCE_ID
			};

			struct v2f
			{
				float4 vertex : SV_POSITION;
				float2 texcoord : TEXCOORD0;
				float2 clipUV : TEXCOORD1;
				half4 color : COLOR;
				UNITY_VERTEX_OUTPUT_STEREO
			};

			v2f vert (appdata_t v)
			{
				v2f o;
				UNITY_SETUP_INSTANCE_ID(v);
				UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
				o.vertex = UnityObjectToClipPos(v.vertex);
				o.color = v.color;
				o.texcoord = v.texcoord;
				//这个计算方式是为了将 [-1, 0]范围内的点转换成 [0,0.5],将(0, 1]范围的点转化成 (0.5,1],这样就将[-1,1]的点转化成[0,1]
				//这个是计算 裁剪 贴图中每个顶点所在的像素的位置,根据我之前的 mesh 文章,已知,uv坐标是只有[0,1]的
				//这样计算完成后,方便在片段着色器中去寻找该像素所对应的 裁剪 color 值
				o.clipUV = (v.vertex.xy * _ClipRange0.zw + _ClipRange0.xy) * 0.5 + float2(0.5, 0.5);
				return o;
			}

			half4 frag (v2f IN) : SV_Target
			{
				//tex2D(sampler2D tex, float2 s)函数,找到贴图上 对应的uv点,直接使用颜色信息来进行着色 
				//这是CG程序中用来在一张贴图中对一个点进行采样的方法,返回值是 float4 类型的颜色值。
				//这里对 _MainTex 在输入点上进行了采样,将 贴图中 像素颜色 赋值给 采样点的颜色,包括透明度。
				half4 col = tex2D(_MainTex, IN.texcoord) * IN.color;
				//这是设置裁剪 的最终代码,如果这个注视调,就没有裁剪效果了
				col.a *= tex2D(_ClipTex, IN.clipUV).a;
				return col;
			}

4. 多重裁剪的例子

这里我使用两个 soft Clip panel 进行裁剪,并且修改了 第二个 panel 的 rotation 的 z 轴坐标,使其有旋转的效果,如图所示,看了这个图后,再去看多重裁剪的代码,就会更加理解代码中的含义,修改 z 轴坐标,就可以明白为什么代码中要计算 angle 。

在这里插入图片描述

5. 结果展示

未裁剪

这里我们设置了一个 520 * 520 大小的贴图,如下是没有裁剪的效果:

在这里插入图片描述

Soft Clip 裁剪

单层 Soft Clip 裁剪效果:

在这里插入图片描述

Texture Mask 裁剪

贴图裁剪效果如下:

在这里插入图片描述

修改

修改 “Hidden/Unlit/Transparent Colored 1” shader 中,将 片段着色器 设置 alpha 的代码注释掉,观察裁剪是否还有效果:

			half4 frag (v2f IN) : SV_Target
			{
				// Softness factor
				float2 factor = (float2(1.0, 1.0) - abs(IN.worldPos)) * _ClipArgs0;
			
				// Sample the texture
				half4 col = tex2D(_MainTex, IN.texcoord) * IN.color;
				//注释掉这行代码
				/*col.a *= clamp( min(factor.x, factor.y), 0.0, 1.0);*/
				return col;
			}

结果如下,很明显,失去裁剪效果:

在这里插入图片描述

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值