Gpahic 的作用
Graphic 是 Unity最基础的图形基类。主要负责UGUI的显示部分。
由上图可以看你出我们经常使用的Image,Text,都是继承自Graphic。
Graphic的渲染流程
在Graphic的源码中有以下属性
[NonSerialized] private CanvasRenderer m_CanvasRenderer;
Graphic会收集渲染所需要的数据如:顶点,材质,等信息。然后对 CanvasRenderer设置 材质(Material)、贴图(Texture)、网格 让其进行渲染到屏幕中。
以下是Graphic的源码:
using System;
#if UNITY_EDITOR
using System.Reflection;
#endif
using System.Collections.Generic;
using UnityEngine.Events;
using UnityEngine.EventSystems;
using UnityEngine.Serialization;
using UnityEngine.UI.CoroutineTween;
namespace UnityEngine.UI
{
///
/// Base class for all UI components that should be derived from when creating new Graphic types.
///
[DisallowMultipleComponent]
[RequireComponent(typeof(RectTransform))]
[ExecuteAlways]
public abstract class Graphic
: UIBehaviour,
ICanvasElement
{
static protected Material s_DefaultUI = null;
static protected Texture2D s_WhiteTexture = null;
///
/// Default material used to draw UI elements if no explicit material was specified.
///
static public Material defaultGraphicMaterial
{
get
{
if (s_DefaultUI == null)
s_DefaultUI = Canvas.GetDefaultCanvasMaterial();
return s_DefaultUI;
}
}
// Cached and saved values
[FormerlySerializedAs("m_Mat")]
[SerializeField] protected Material m_Material;
[SerializeField] private Color m_Color = Color.white;
[NonSerialized] protected bool m_SkipLayoutUpdate;
[NonSerialized] protected bool m_SkipMaterialUpdate;
///
/// Base color of the Graphic.
///
///
public virtual Color color { get { return m_Color; } set { if (SetPropertyUtility.SetColor(ref m_Color, value)) SetVerticesDirty(); } }
[SerializeField] private bool m_RaycastTarget = true;
///
/// Should this graphic be considered a target for raycasting?
///
public virtual bool raycastTarget
{
get
{
return m_RaycastTarget;
}
set
{
if (value != m_RaycastTarget)
{
if (m_RaycastTarget)
GraphicRegistry.UnregisterRaycastGraphicForCanvas(canvas, this);
m_RaycastTarget = value;
if (m_RaycastTarget && isActiveAndEnabled)
GraphicRegistry.RegisterRaycastGraphicForCanvas(canvas, this);
}
}
}
[SerializeField]
private Vector4 m_RaycastPadding = new Vector4();
///
/// Padding to be applied to the masking
/// X = Left
/// Y = Bottom
/// Z = Right
/// W = Top
///
public Vector4 raycastPadding
{
get { return m_RaycastPadding; }
set
{
m_RaycastPadding = value;
}
}
[NonSerialized] private RectTransform m_RectTransform;
[NonSerialized] private CanvasRenderer m_CanvasRenderer;
[NonSerialized] private Canvas m_Canvas;
[NonSerialized] private bool m_VertsDirty;
[NonSerialized] private bool m_MaterialDirty;
[NonSerialized] protected UnityAction m_OnDirtyLayoutCallback;
[NonSerialized] protected UnityAction m_OnDirtyVertsCallback;
[NonSerialized] protected UnityAction m_OnDirtyMaterialCallback;
[NonSerialized] protected static Mesh s_Mesh;
[NonSerialized] private static readonly VertexHelper s_VertexHelper = new VertexHelper();
[NonSerialized] protected Mesh m_CachedMesh;
[NonSerialized] protected Vector2[] m_CachedUvs;
// Tween controls for the Graphic
[NonSerialized]
private readonly TweenRunner m_ColorTweenRunner;
protected bool useLegacyMeshGeneration { get; set; }
// Called by Unity prior to deserialization,
// should not be called by users
protected Graphic()
{
if (m_ColorTweenRunner == null)
m_ColorTweenRunner = new TweenRunner();
m_ColorTweenRunner.Init(this);
useLegacyMeshGeneration = true;
}
///
/// Set all properties of the Graphic dirty and needing rebuilt.
/// Dirties Layout, Vertices, and Materials.
///
public virtual void SetAllDirty()
{
// Optimization: Graphic layout doesn't need recalculation if
// the underlying Sprite is the same size with the same texture.
// (e.g. Sprite sheet texture animation)
if (m_SkipLayoutUpdate)
{
m_SkipLayoutUpdate = false;
}
else
{
SetLayoutDirty();
}
if (m_SkipMaterialUpdate)
{
m_SkipMaterialUpdate = false;
}
else
{
SetMaterialDirty();
}
SetVerticesDirty();
}
///
/// Mark the layout as dirty and needing rebuilt.
///
///
/// Send a OnDirtyLayoutCallback notification if any elements are registered. See RegisterDirtyLayoutCallback
///
public virtual void SetLayoutDirty()
{
if (!IsActive())
return;
LayoutRebuilder.MarkLayoutForRebuild(rectTransform);
if (m_OnDirtyLayoutCallback != null)
m_OnDirtyLayoutCallback();
}
///
/// Mark the vertices as dirty and needing rebuilt.
///
///
/// Send a OnDirtyVertsCallback notification if any elements are registered. See RegisterDirtyVerticesCallback
///
public virtual void SetVerticesDirty()
{
if (!IsActive())
return;
m_VertsDirty = true;
CanvasUpdateRegistry.RegisterCanvasElementForGraphicRebuild(this);
if (m_OnDirtyVertsCallback != null)
m_OnDirtyVertsCallback();
}
///
/// Mark the material as dirty and needing rebuilt.
///
///
/// Send a OnDirtyMaterialCallback notification if any elements are registered. See RegisterDirtyMaterialCallback
///
public virtual void SetMaterialDirty()
{
if (!IsActive())
return;
m_MaterialDirty = true;
CanvasUpdateRegistry.RegisterCanvasElementForGraphicRebuild(this);
if (m_OnDirtyMaterialCallback != null)
m_OnDirtyMaterialCallback();
}
protected override void OnRectTransformDimensionsChange()
{
if (gameObject.activeInHierarchy)
{
// prevent double dirtying...
if (CanvasUpdateRegistry.IsRebuildingLayout())
SetVerticesDirty();
else
{
SetVerticesDirty();
SetLayoutDirty();
}
}
}
protected override void OnBeforeTransformParentChanged()
{
GraphicRegistry.UnregisterGraphicForCanvas(canvas, this);
LayoutRebuilder.MarkLayoutForRebuild(rectTransform);
}
protected override void OnTransformParentChanged()
{
base.OnTransformParentChanged();
m_Canvas = null;
if (!IsActive())
return;
CacheCanvas();
GraphicRegistry.RegisterGraphicForCanvas(canvas, this);
SetAllDirty();
}
///
/// Absolute depth of the graphic, used by rendering and events -- lowest to highest.
///
///
/// The depth is relative to the first root canvas.
///
/// Canvas
/// Graphic - 1
/// Graphic - 2
/// Nested Canvas
/// Graphic - 3
/// Graphic - 4
/// Graphic - 5
///
/// This value is used to determine draw and event ordering.
///
public int depth { get { return canvasRenderer.absoluteDepth; } }
///
/// The RectTransform component used by the Graphic. Cached for speed.
///
public RectTransform rectTransform
{
get
{
// The RectTransform is a required component that must not be destroyed. Based on this assumption, a
// null-reference check is sufficient.
if (ReferenceEquals(m_RectTransform, null))
{
m_RectTransform = GetComponent();
}
return m_RectTransform;
}
}
///
/// A reference to the Canvas this Graphic is rendering to.
///
///
/// In the situation where the Graphic is used in a hierarchy with multiple Canvases, the Canvas closest to the root will be used.
///
public Canvas canvas
{
get
{
if (m_Canvas == null)
CacheCanvas();
return m_Canvas;
}
}
private void CacheCanvas()
{
var list = ListPool
1.SetAllDirty、SetLayoutDirty
Graphic使用了脏标记模式(游戏编程模式里面有这个设计模式),每当一些属性发生改变后不会立即更新属性,而是将这些变化的属性打上标记,然后CanvasUpdateRegistry.RegisterCanvasElementForGraphicRebuild 方法会向一个队列中添加一个元素,并在某一个时刻 (在 OnEnable、Reset、OnDidApplyAnimationProperties、OnValidate、OnTransformParentChanged等等事件中,需要更新表现时都会调用SetAllDirty,发送对应的事件。给 CanvasUpdateRegistry 标记需要被Rebuild) 一起更新。
2.Rebuild
执行 Rebuild 函数。 这时候会根据Dirty的标识(SetAllDirty里面将标识设置为true,这里设置为false)来调用 UpdateGeometry、UpdateMaterial。
3.更新材质 UpdateMaterial. (直接给CanvasRenderer设置值)
4.更新顶点数据 UpdateGeometry。
- 根据代码我们可以看到首先调用 OnPopulateMesh 来生成Mesh数据。 Image、RawImage以及Text都对这个函数进行重写来生成特定的顶点数据。
- 获取当前GameObject上的所有实现了 IMeshModifier 接口的组件并调用 ModifyMesh 方法来修改Mesh数据
- 给 CanvasRenderer 设置更新后的数据
Graphic的事件: GraphicRegistry
GraphicRegistry是负责让Canvas知道 Graphic 是否被操作了,是否需要渲染内容了,从上面源码中可以看到 OnEnable、OnCanvasHierarchyChanged、OnTransformParentChanged
中注册了自己
GraphicRegistry.RegisterGraphicForCanvas(canvas, this);
在 OnDisable中取消注册
GraphicRegistry.UnregisterGraphicForCanvas(currentCanvas, this);
通过 Graphic 给 GraphicRegistry 注册完成后,在 GraphicRaycaster 中的 Raycast函数中会进行获取的操作。
- GraphicRaycaster 获取 GraphicRegistry 中所注册的 Graphic 并调用 Graphic 中的 Raycast 方法。
- 获取Graphic所挂载的transform上的所有Components检测是否实现了ICanvasRaycastFilter, 对所有实现了 ICanvasRaycastFilter 接口的调用 IsRaycastLocationValid
- IsRaycastLocationValid 按照对应实现来进行检测。