uGUI之MaskableGraphic
实际ui控件的抽象基类
在需要绘制的ui组件的抽象类的基础添加了 IMaskable,IClippable, IMaterialModifier 功能,
主要 IMaskable,IClippable功能,可被mask裁剪。
MaskableGraphic
继承自 Graphic, IClippable, IMaskable, IMaterialModifier,实际的控件的基类
这部分主要是计算 mask和 clip区域, 基于 模板
IClippable
主要的接口 RecalculateClipping,Cull,SetClipRect
public interface IClippable { /// <summary> /// GameObject of the IClippable object /// </summary> GameObject gameObject { get; } /// <summary> /// Will be called when the state of a parent IClippable changed. /// </summary> void RecalculateClipping(); /// <summary> /// The RectTransform of the clippable. /// </summary> RectTransform rectTransform { get; } /// <summary> /// Clip and cull the IClippable given a specific clipping rect /// </summary> /// <param name="clipRect">The Rectangle in which to clip against.</param> /// <param name="validRect">Is the Rect valid. If not then the rect has 0 size.</param> void Cull(Rect clipRect, bool validRect); /// <summary> /// Set the clip rect for the IClippable. /// </summary> /// <param name="value">The Rectangle for the clipping</param> /// <param name="validRect">Is the rect valid.</param> void SetClipRect(Rect value, bool validRect); }IMaskable接口实现
主要接口 RecalculateMasking
public interface IMaskable { /// <summary> /// Recalculate masking for this element and all children elements. /// </summary> /// <remarks> /// Use this to update the internal state (recreate materials etc). /// </remarks> void RecalculateMasking(); }IMaterialModifier
主要接口 GetModifiedMaterial
public interface IMaterialModifier { /// <summary> /// Perform material modification in this function. /// </summary> /// <param name="baseMaterial">The material that is to be modified</param> /// <returns>The modified material.</returns> Material GetModifiedMaterial(Material baseMaterial); }实现细节
主要功能是 Clipable 和mask
- mask 实现
主要通过 材质
模板属性Stencil的操作StencilMaterial.Remove和StencilMaterial.Add进行缓存//StencilMaterial.cs private class MatEntry { public Material baseMat; public Material customMat; public int count; public int stencilId; public StencilOp operation = StencilOp.Keep; public CompareFunction compareFunction = CompareFunction.Always; public int readMask; public int writeMask; public bool useAlphaClip; public ColorWriteMask colorMask; }public static Material Add(Material baseMat, int stencilID, StencilOp operation, CompareFunction compareFunction, ColorWriteMask colorWriteMask) { return Add(baseMat, stencilID, operation, compareFunction, colorWriteMask, 255, 255); } /// <summary> /// Add a new material using the specified base and stencil ID. /// </summary> public static Material Add(Material baseMat, int stencilID, StencilOp operation, CompareFunction compareFunction, ColorWriteMask colorWriteMask, int readMask, int writeMask) { ///检查是否有标准的模板选项 // _Stencil,_StencilOp,_StencilComp,_StencilReadMask,_StencilWriteMask,_ColorMask if ((stencilID <= 0 && colorWriteMask == ColorWriteMask.All) || baseMat == null) return baseMat; if (!baseMat.HasProperty("_Stencil")) { Debug.LogWarning("Material " + baseMat.name + " doesn't have _Stencil property", baseMat); return baseMat; } if (!baseMat.HasProperty("_StencilOp")) { Debug.LogWarning("Material " + baseMat.name + " doesn't have _StencilOp property", baseMat); return baseMat; } if (!baseMat.HasProperty("_StencilComp")) { Debug.LogWarning("Material " + baseMat.name + " doesn't have _StencilComp property", baseMat); return baseMat; } if (!baseMat.HasProperty("_StencilReadMask")) { Debug.LogWarning("Material " + baseMat.name + " doesn't have _StencilReadMask property", baseMat); return baseMat; } if (!baseMat.HasProperty("_StencilWriteMask")) { Debug.LogWarning("Material " + baseMat.name + " doesn't have _StencilWriteMask property", baseMat); return baseMat; } if (!baseMat.HasProperty("_ColorMask")) { Debug.LogWarning("Material " + baseMat.name + " doesn't have _ColorMask property", baseMat); return baseMat; } // 从缓冲读取 for (int i = 0; i < m_List.Count; ++i) { MatEntry ent = m_List[i]; if (ent.baseMat == baseMat && ent.stencilId == stencilID && ent.operation == operation && ent.compareFunction == compareFunction && ent.readMask == readMask && ent.writeMask == writeMask && ent.colorMask == colorWriteMask) { ++ent.count; return ent.customMat; } } // 设置模板选项 并加入缓存 var newEnt = new MatEntry(); newEnt.count = 1; newEnt.baseMat = baseMat; newEnt.customMat = new Material(baseMat); newEnt.customMat.hideFlags = HideFlags.HideAndDontSave; newEnt.stencilId = stencilID; newEnt.operation = operation; newEnt.compareFunction = compareFunction; newEnt.readMask = readMask; newEnt.writeMask = writeMask; newEnt.colorMask = colorWriteMask; newEnt.useAlphaClip = operation != StencilOp.Keep && writeMask > 0; newEnt.customMat.name = string.Format("Stencil Id:{0}, Op:{1}, Comp:{2}, WriteMask:{3}, ReadMask:{4}, ColorMask:{5} AlphaClip:{6} ({7})", stencilID, operation, compareFunction, writeMask, readMask, colorWriteMask, newEnt.useAlphaClip, baseMat.name); newEnt.customMat.SetInt("_Stencil", stencilID); newEnt.customMat.SetInt("_StencilOp", (int)operation); newEnt.customMat.SetInt("_StencilComp", (int)compareFunction); newEnt.customMat.SetInt("_StencilReadMask", readMask); newEnt.customMat.SetInt("_StencilWriteMask", writeMask); newEnt.customMat.SetInt("_ColorMask", (int)colorWriteMask); newEnt.customMat.SetInt("_UseUIAlphaClip", newEnt.useAlphaClip ? 1 : 0); if (newEnt.useAlphaClip) newEnt.customMat.EnableKeyword("UNITY_UI_ALPHACLIP"); else newEnt.customMat.DisableKeyword("UNITY_UI_ALPHACLIP"); m_List.Add(newEnt); return newEnt.customMat; }public static void Remove(Material customMat) { if (customMat == null) return; for (int i = 0; i < m_List.Count; ++i) { MatEntry ent = m_List[i]; if (ent.customMat != customMat) continue; if (--ent.count == 0) { Misc.DestroyImmediate(ent.customMat); ent.baseMat = null; m_List.RemoveAt(i); } return; } }- Clip 实现
主要通过Rect区域(向上递归), Clipping.FindCullAndClipWorldRect
//RectMask2D.cs public virtual void PerformClipping() { if (ReferenceEquals(Canvas, null)) { return; } //TODO See if an IsActive() test would work well here or whether it might cause unexpected side effects (re case 776771) // if the parents are changed // or something similar we // do a recalculate here if (m_ShouldRecalculateClipRects) { //获取符合条件的 RectMasks MaskUtilities.GetRectMasksForClip(this, m_Clippers); m_ShouldRecalculateClipRects = false; } // get the compound rects from // the clippers that are valid bool validRect = true; //遍历获取最小rect区域(xMin, yMin, Width, height) Rect clipRect = Clipping.FindCullAndClipWorldRect(m_Clippers, out validRect); // If the mask is in ScreenSpaceOverlay/Camera render mode, its content is only rendered when its rect // overlaps that of the root canvas. // 1. 可裁剪的相机模式 2. 物体区域和裁剪区域没有重合部分 // 满足条件则进行设置裁剪区域进行裁剪 RenderMode renderMode = Canvas.rootCanvas.renderMode; bool maskIsCulled = (renderMode == RenderMode.ScreenSpaceCamera || renderMode == RenderMode.ScreenSpaceOverlay) && !clipRect.Overlaps(rootCanvasRect, true); // if (maskIsCulled) { // Children are only displayed when inside the mask. If the mask is culled, then the children // inside the mask are also culled. In that situation, we pass an invalid rect to allow callees // to avoid some processing. clipRect = Rect.zero; validRect = false; } if (clipRect != m_LastClipRectCanvasSpace) { foreach (IClippable clipTarget in m_ClipTargets) { clipTarget.SetClipRect(clipRect, validRect); } foreach (MaskableGraphic maskableTarget in m_MaskableTargets) { maskableTarget.SetClipRect(clipRect, validRect); maskableTarget.Cull(clipRect, validRect); } } else if (m_ForceClip) { foreach (IClippable clipTarget in m_ClipTargets) { clipTarget.SetClipRect(clipRect, validRect); } foreach (MaskableGraphic maskableTarget in m_MaskableTargets) { maskableTarget.SetClipRect(clipRect, validRect); if (maskableTarget.canvasRenderer.hasMoved) maskableTarget.Cull(clipRect, validRect); } } else { foreach (MaskableGraphic maskableTarget in m_MaskableTargets) { if (maskableTarget.canvasRenderer.hasMoved) maskableTarget.Cull(clipRect, validRect); } } m_LastClipRectCanvasSpace = clipRect; m_ForceClip = false; }//向上遍历 获取符合条件的 RectMask2D public static void GetRectMasksForClip(RectMask2D clipper, List<RectMask2D> masks) { masks.Clear(); List<Canvas> canvasComponents = ListPool<Canvas>.Get(); List<RectMask2D> rectMaskComponents = ListPool<RectMask2D>.Get(); clipper.transform.GetComponentsInParent(false, rectMaskComponents); if (rectMaskComponents.Count > 0) { clipper.transform.GetComponentsInParent(false, canvasComponents); for (int i = rectMaskComponents.Count - 1; i >= 0; i--) { if (!rectMaskComponents[i].IsActive()) continue; bool shouldAdd = true; for (int j = canvasComponents.Count - 1; j >= 0; j--) { if (!IsDescendantOrSelf(canvasComponents[j].transform, rectMaskComponents[i].transform) && canvasComponents[j].overrideSorting) { shouldAdd = false; break; } } if (shouldAdd) masks.Add(rectMaskComponents[i]); } } ListPool<RectMask2D>.Release(rectMaskComponents); ListPool<Canvas>.Release(canvasComponents); }// Cliping.cs ///返回最小区域的 Rect。 (xMin, yMin, Width, height) public static Rect FindCullAndClipWorldRect(List<RectMask2D> rectMaskParents, out bool validRect) { if (rectMaskParents.Count == 0) { validRect = false; return new Rect(); } Rect current = rectMaskParents[0].canvasRect; float xMin = current.xMin; float xMax = current.xMax; float yMin = current.yMin; float yMax = current.yMax; for (var i = 1; i < rectMaskParents.Count; ++i) { current = rectMaskParents[i].canvasRect; if (xMin < current.xMin) xMin = current.xMin; if (yMin < current.yMin) yMin = current.yMin; if (xMax > current.xMax) xMax = current.xMax; if (yMax > current.yMax) yMax = current.yMax; } validRect = xMax > xMin && yMax > yMin; if (validRect) return new Rect(xMin, yMin, xMax - xMin, yMax - yMin); else return new Rect(); }internal class RectangularVertexClipper { readonly Vector3[] m_WorldCorners = new Vector3[4]; readonly Vector3[] m_CanvasCorners = new Vector3[4]; // 返回相对于Canvas 的Corner的Rect public Rect GetCanvasRect(RectTransform t, Canvas c) { if (c == null) return new Rect(); t.GetWorldCorners(m_WorldCorners); var canvasTransform = c.GetComponent<Transform>(); for (int i = 0; i < 4; ++i) m_CanvasCorners[i] = canvasTransform.InverseTransformPoint(m_WorldCorners[i]); return new Rect(m_CanvasCorners[0].x, m_CanvasCorners[0].y, m_CanvasCorners[2].x - m_CanvasCorners[0].x, m_CanvasCorners[2].y - m_CanvasCorners[0].y); } }