当前位置:   article > 正文

Unity Graphic功能,实现UGUI上三角形,四边形,圆环的绘制

unity graphic

前言

这篇简单的纪录下利用Graphic类,实现UGUI圆环的绘制。效果图如下:

github目录:https://github.com/luckyWjr/Demo

 

Unity如何绘制图形

我们知道一个图形是由N个顶点,互相连成线,然后填充起来。如三角形有三个顶点,四边形有四个,而圆形可以理解为很多很多个顶点。Unity绘制图形的时候同样需要知道这些顶点信息,而区别在于这些看起来无缝连接的形状,在Untiy中是由一个个三角形拼接起来的。

也就是说,Unity只能绘制出三角形,然后由一个个三角形来组成更复杂的形状。例如四边形是两个三角形组成(下图中是ABC和ADC两个三角形,当然也可以是ABD和BDC两个组成),五边形则是三个,而圆则可以由N个三角形组成,如下图:

要绘制三角形那就需要三个顶点,例如上图中的ABC,Unity提供了UIVertex类来纪录顶点的信息,例如坐标,颜色,uv,顶点法线等。除了三个顶点信息外,我们还需要知道其绘制顺序,如是A->B->C还是A->C->B或者其他。这个顺序会影响到三角形面的朝向(Unity是左手坐标系,绘制方向则是左手手指弯曲的方向,面的朝向即左手大拇指朝向,因此ABC的顺序面朝向屏幕前的我们,ACB的顺序则是朝向屏幕后)由于UGUI默认的Shader:UI/Default,设置了Cuff Off,也就是双面渲染,所以我们看不出差别。

注:Unity内置Shader下载路径:https://unity3d.com/get-unity/download/archive

 

Graphic

前面讲了大致的理念,那么知道顶点信息后如何实现图形的绘制,就是由Graphic类来帮助我们实现。如下图,我们需要自定义一个类继承Graphic,然后重新其OnPopulateMesh方法。(UIGI中的Image也是继承于此)

  1. public class Triangle : Graphic
  2. {
  3. protected override void OnPopulateMesh(VertexHelper vh)
  4. {
  5. }
  6. }

注:文件名和类名必须相同,否则在给GameObject挂该组件的时候,会报错:cant add script component because the script class cannot be found...

注:若想要支持RectMask2D功能,则改为继承MaskableGraphic即可

在OnPopulateMesh方法中提供了VertexHelper参数,通过它我们就可以实现添加顶点等操作了。

添加顶点

VertexHelper.AddVert(Vector3 position, Color32 color, Vector2 uv0)

参数是顶点坐标,颜色以及uv,也可以直接添加我们前面提到的UIVertex:

VertexHelper.AddVert(UIVertex v)

在VertexHelper中会有如下List来存放这些顶点的相关信息,添加一个顶点对应的List.Count + 1

  1. List<Vector3> m_Positions;
  2. List<Color32> m_Colors;
  3. List<Vector2> m_Uv0S;
  4. List<Vector2> m_Uv1S;
  5. List<Vector2> m_Uv2S;
  6. List<Vector2> m_Uv3S;
  7. List<Vector3> m_Normals;
  8. List<Vector4> m_Tangents;

添加三角面

VertexHelper.AddTriangle(int idx0, int idx1, int idx2)

参数是三个顶点存储在VertexHelper中的下标,同时也代表了其绘制顺序,idx0 -> idx1 -> idx2。

在VertexHelper中有如下List来存放这些下标,添加一个三角面对应的List.Count + 3

private List<int> m_Indices;

当前顶点数量

VertexHelper.currentVertCount

对应 m_Positions.Count,添加了几个顶点数量就是几

当前索引数量

VertexHelper.currentIndexCount

对应 m_Indices.Count,添加了几次三角面数量就是N * 3

因此正常的绘制一个四边形,有四个顶点,两个三角面,因此currentVertCount = 4,currentIndexCount = 6

清除所有顶点及索引信息

VertexHelper.Clear()

设置贴图

除了重写OnPopulateMesh方法外,我们还可以重写mainTexture属性来设置贴图,例如

  1. [SerializeField] Sprite m_image;
  2. public override Texture mainTexture => m_image == null ? s_WhiteTexture : m_image.texture;

s_WhiteTexture即是Texture2D.whiteTexture,也就是纯白的图。

SetDirty

如果我们在运行时动态修改了一些属性,需要重新绘制。例如修改了贴图,或者绘制圆的时候,修改了填充比例。我们可以调用下面方法来触发。

  1. //调用后会在下一帧重新执行OnPopulateMesh
  2. SetVerticesDirty();
  3. //调用后会在下一帧重新设置material以及Texture
  4. SetMaterialDirty();
  5. //调用后会重新布局
  6. SetLayoutDirty();
  7. //以上全部调用
  8. SetAllDirty();

 

绘制一个三角形

根据前面的介绍,要绘制一个三角形就很简单了,添加三个顶点,添加一个三角面即可

  1. [ExecuteInEditMode]
  2. public class Triangle : Graphic
  3. {
  4. public Vector2 positionA;
  5. public Vector2 positionB;
  6. public Vector2 positionC;
  7. protected override void OnPopulateMesh(VertexHelper vh)
  8. {
  9. vh.Clear();
  10. vh.AddVert(positionA, Color.white, Vector2.zero);
  11. vh.AddVert(positionB, Color.red, Vector2.zero);
  12. vh.AddVert(positionC, Color.green, Vector2.zero);
  13. vh.AddTriangle(0, 1, 2);
  14. }
  15. }

在Canvas中添加一个GameObject,挂上我们的组件,然后随便设置三个点的坐标即可,效果如下

 

绘制一个四边形

同样的四边形也就很简单了,四个顶点,两个三角面。不过在这边顺便讲一下前面没有提到的uv属性,先来看完整的代码

  1. [ExecuteInEditMode]
  2. public class Quadrangle : Graphic
  3. {
  4. public Vector2 positionLeftTop;
  5. public Vector2 positionRightTop;
  6. public Vector2 positionRightBottom;
  7. public Vector2 positionLeftBottom;
  8. [SerializeField] Sprite m_image;
  9. public override Texture mainTexture => m_image == null ? s_WhiteTexture : m_image.texture;
  10. UIVertex[] m_vertexes = new UIVertex[4];
  11. Vector2[] m_uvs = new Vector2[4];
  12. protected override void Start()
  13. {
  14. m_uvs[0] = new Vector2(0, 1);
  15. m_uvs[1] = new Vector2(1, 1);
  16. m_uvs[2] = new Vector2(1, 0);
  17. m_uvs[3] = new Vector2(0, 0);
  18. }
  19. protected override void OnPopulateMesh(VertexHelper vh)
  20. {
  21. vh.Clear();
  22. for (int i = 0; i < 4; i++)
  23. {
  24. m_vertexes[i].color = color;
  25. m_vertexes[i].uv0 = m_uvs[i];
  26. }
  27. m_vertexes[0].position = positionLeftTop;
  28. m_vertexes[1].position = positionRightTop;
  29. m_vertexes[2].position = positionRightBottom;
  30. m_vertexes[3].position = positionLeftBottom;
  31. vh.AddVert(m_vertexes[0]);
  32. vh.AddVert(m_vertexes[1]);
  33. vh.AddVert(m_vertexes[2]);
  34. vh.AddVert(m_vertexes[3]);
  35. vh.AddTriangle(0, 2, 1);
  36. vh.AddTriangle(0, 3, 2);
  37. }
  38. }

相比之前,我们添加了Sprite的设置,同时也为顶点添加了uv属性,效果图如下

正方形(就像Image组件了):   不规则形状:

 

顶点的uv属性

uv简单来说就是一个二维坐标(u和v的取值范围都是 0-1 ),代表着一张贴图每个像素的位置信息。uv(0,0)就代表图片的左下角,uv(1,1)代表图片的右上角。这样上述代码m_uvs中的四个uv值就很好理解了,分别是左上,右上,右下,左下。

接着我们的正方形正好是四个顶点,每个顶点设置相应的uv值,那么在该顶点上就会显示图片中相应uv的像素信息了。两个顶点之间的颜色同样也对应着图片中两点之间的像素信息。(顶点的color值要设置为Graphic中的color属性)

 

绘制圆和圆环

圆的话就是由N个三角形组成,圆环则是把这些组成圆的三角形切了一刀,也就是变成了一个四边形。由于代码写了注释,这里就不详细介绍了,效果图见文章最上方。

  1. [ExecuteInEditMode]
  2. // Changed to maskableGraphic so it can be masked with RectMask2D
  3. public class Annulus : MaskableGraphic
  4. {
  5. public enum ShapeType
  6. {
  7. Annulus,//圆环
  8. Circle,//圆
  9. }
  10. [SerializeField] Sprite m_image;
  11. public ShapeType shapeType;
  12. public float innerRadius = 10;//圆环内径,为0即是圆
  13. public float outerRadius = 20;//圆环外径
  14. [Range(0, 1)] [SerializeField] float m_fillAmount;//填充值
  15. [Range(0, 720)] public int segments = 360;//片数,越大锯齿越不明显
  16. [SerializeField] Image.Origin360 m_originType;//填充的起点
  17. public bool m_isClockwise = true;//填充方向,是否是顺时针填充
  18. public override Texture mainTexture => m_image == null ? s_WhiteTexture : m_image.texture;
  19. float m_originRadian = -1;//根据m_originType设置相关弧度(-1表示还没设置过对应的值)
  20. public float fillAmount
  21. {
  22. get => m_fillAmount;
  23. set
  24. {
  25. m_fillAmount = value;
  26. SetVerticesDirty();
  27. }
  28. }
  29. public Sprite image
  30. {
  31. get => m_image;
  32. set
  33. {
  34. if (m_image == value)
  35. return;
  36. m_image = value;
  37. SetVerticesDirty();
  38. SetMaterialDirty();
  39. }
  40. }
  41. public Image.Origin360 originType
  42. {
  43. get => m_originType;
  44. set
  45. {
  46. if (m_originType == value)
  47. return;
  48. m_originType = value;
  49. SetOriginRadian();
  50. SetVerticesDirty();
  51. }
  52. }
  53. public bool isClockwise
  54. {
  55. get => m_isClockwise;
  56. set
  57. {
  58. if (m_isClockwise != value)
  59. {
  60. m_isClockwise = value;
  61. SetVerticesDirty();
  62. }
  63. }
  64. }
  65. UIVertex[] m_vertexes = new UIVertex[4];
  66. Vector2[] m_uvs = new Vector2[4];
  67. Vector2[] m_positions = new Vector2[4];
  68. protected override void Start()
  69. {
  70. if (m_originRadian == -1)
  71. SetOriginRadian();
  72. m_uvs[0] = new Vector2(0, 1);
  73. m_uvs[1] = new Vector2(1, 1);
  74. m_uvs[2] = new Vector2(1, 0);
  75. m_uvs[3] = new Vector2(0, 0);
  76. }
  77. protected override void OnPopulateMesh(VertexHelper vh)
  78. {
  79. vh.Clear();
  80. //m_fillAmount == 0,什么也不绘制
  81. if (m_fillAmount == 0) return;
  82. #if UNITY_EDITOR
  83. SetOriginRadian();
  84. #endif
  85. //每个面片的角度
  86. float degrees = 360f / segments;
  87. //需要绘制的面片数量
  88. int count = (int)(segments * m_fillAmount);
  89. float cos = Mathf.Cos(m_originRadian);
  90. float sin = Mathf.Sin(m_originRadian);
  91. //计算外环起点,例如m_originRadian = 0,x = -outerRadius,y = 0,所以起点是Left(九点钟方向)
  92. float x = -outerRadius * cos;
  93. float y = outerRadius * sin;
  94. Vector2 originOuter = new Vector2(x, y);
  95. //计算内环起点
  96. x = -innerRadius * cos;
  97. y = innerRadius * sin;
  98. Vector2 originInner = new Vector2(x, y);
  99. for (int i = 1; i <= count; i++)
  100. {
  101. //m_positions[0] 当前面片的外环起点
  102. m_positions[0] = originOuter;
  103. //当前面片的弧度 + 起始弧度 = 终止弧度
  104. float endRadian = i * degrees * Mathf.Deg2Rad * (isClockwise ? 1 : -1) + m_originRadian;
  105. cos = Mathf.Cos(endRadian);
  106. sin = Mathf.Sin(endRadian);
  107. //m_positions[1] 当前面片的外环终点
  108. m_positions[1] = new Vector2(-outerRadius * cos, outerRadius * sin);
  109. //m_positions[2] 当前面片的内环终点
  110. //m_positions[3] 当前面片的内环起点
  111. if (shapeType == ShapeType.Annulus)
  112. {
  113. m_positions[2] = new Vector2(-innerRadius * cos, innerRadius * sin);
  114. m_positions[3] = originInner;
  115. }
  116. else
  117. {
  118. m_positions[2] = Vector2.zero;
  119. m_positions[3] = Vector2.zero;
  120. }
  121. // 设置顶点的颜色坐标以及uv
  122. for (int j = 0; j < 4; j++)
  123. {
  124. m_vertexes[j].color = color;
  125. m_vertexes[j].position = m_positions[j];
  126. m_vertexes[j].uv0 = m_uvs[j];
  127. }
  128. //当前顶点数量
  129. int vertCount = vh.currentVertCount;
  130. //如果是圆只需要添加三个顶点,创建一个三角面
  131. vh.AddVert(m_vertexes[0]);
  132. vh.AddVert(m_vertexes[1]);
  133. vh.AddVert(m_vertexes[2]);
  134. //参数即三角面的顶点绘制顺序
  135. vh.AddTriangle(vertCount, vertCount + 2, vertCount + 1);
  136. // 如果是圆环就需要添加第四个顶点,并再创建一个三角面
  137. if (shapeType == ShapeType.Annulus)
  138. {
  139. vh.AddVert(m_vertexes[3]);
  140. vh.AddTriangle(vertCount, vertCount + 3, vertCount + 2);
  141. }
  142. //当前面片的终点就是下个面片的起点
  143. originOuter = m_positions[1];
  144. originInner = m_positions[2];
  145. }
  146. }
  147. //m_originType改变的时候需要重新设置m_originRadian
  148. void SetOriginRadian()
  149. {
  150. switch (m_originType)
  151. {
  152. case Image.Origin360.Left:
  153. m_originRadian = 0 * Mathf.Deg2Rad;
  154. break;
  155. case Image.Origin360.Top:
  156. m_originRadian = 90 * Mathf.Deg2Rad;
  157. break;
  158. case Image.Origin360.Right:
  159. m_originRadian = 180 * Mathf.Deg2Rad;
  160. break;
  161. case Image.Origin360.Bottom:
  162. m_originRadian = 270 * Mathf.Deg2Rad;
  163. break;
  164. }
  165. }
  166. }

知道原理,我们就可以进行一些魔改,例如fillmount小于1的时候,用别的颜色来填充等,大家可以自由发挥。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/在线问答5/article/detail/881893
推荐阅读
相关标签
  

闽ICP备14008679号