uGUI之MaskableGraphic

实际ui控件的抽象基类

在需要绘制的ui组件的抽象类的基础添加了 IMaskable,IClippable, IMaterialModifier 功能,

主要 IMaskable,IClippable功能,可被mask裁剪。

  1. MaskableGraphic

    继承自 Graphic, IClippable, IMaskable, IMaterialModifier,实际的控件的基类

    这部分主要是计算 mask和 clip区域, 基于 模板

    1. 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);
          }
    2. 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();
          }
    3. 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);
          }
    4. 实现细节

      主要功能是 Clipable 和mask

      1. mask 实现

      主要通过 材质模板属性Stencil的操作

      StencilMaterial.RemoveStencilMaterial.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;
                  }
              } 
      1. 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);
              }
          }


文章作者: Yonggang Long
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Yonggang Long !
 上一篇
2022-08-10 Yonggang Long
下一篇 
2022-08-10 Yonggang Long
  目录