UGUI 文字效果實現(Shadow\Gradient\Outline)


NGUI直接在UILabel組件中接入了Shadow、Gradient和outline選項,但在UGUI中是通過另外的組件單獨提供,比如outline、shader等。這篇文章主要記錄這幾個文字效果實現的思路和流程。

1. 實現思路

1. UGUI源碼分析

UGUI 的 Text 渲染的過程是由 TextGenerator 產生頂點數據,配合字體產生的貼圖最終顯示在屏幕上 . 下圖為Text組件的繼承樹。
mark
UGUI中很多渲染相關的組件都是繼承自Graphics,而Graphics還在Canvas繪制之前進行重建。Graphics中定義了渲染框架,核心代碼如下。Text、Image組件的需要自己實現 protected virtual void OnPopulateMesh(VertexHelper vh) 方法來填充需要的數據。Unity提供了IMeshModifier接口供外部使用,如果在Text組件所在物體中存在IMeshModifier類型的組件,則會調用ModifyMesh方法允許你獲得渲染數據。也就是說可以通過這種方式進行mesh、貼圖等數據的修改。

 protected virtual void UpdateGeometry()
     {
         if (useLegacyMeshGeneration)
             DoLegacyMeshGeneration();
         else
             DoMeshGeneration();
     }

     private void DoMeshGeneration()
     {
         if (rectTransform != null && rectTransform.rect.width >= 0 && rectTransform.rect.height >= 0)
	         //獲取數據
             OnPopulateMesh(s_VertexHelper);
         else
             s_VertexHelper.Clear(); // clear the vertex helper so invalid graphics dont draw.

         var components = ListPool<Component>.Get();
         GetComponents(typeof(IMeshModifier), components);

		 //檢測是否存在IMeshModifier接口類型組件
         for (var i = 0; i < components.Count; i++)
             ((IMeshModifier)components[i]).ModifyMesh(s_VertexHelper);

         ListPool<Component>.Release(components);
		 //填充渲染數據
         s_VertexHelper.FillMesh(workerMesh);
         canvasRenderer.SetMesh(workerMesh);
     }

2. 實現接口

Unity提供了BaseMeshEffect類型,繼承自IMeshModifier,提供 public abstract void ModifyMesh(VertexHelper vh); 接口。不過從4.7到現在這個接口修改了很多次,5.3版本是這個,5.5版本似乎又做了修改。文字的各種特效就可以通過這個接口獲得渲染數據並進行修改即可。

2. 顏色漸變(Gradient)

漸變其實就是根據需要進行漸變的方向和顏色修改頂點的顏色值。 如果只考慮上下方向的漸變,可以計算字符上下最大和最小值,然后進行插值即可計算出需要的顏色。對於多方向的漸變實現稍稍麻煩點,原理類似。核心代碼如下:

 public override void ModifyMesh(VertexHelper vh)
 {
     var vertexList = new List<UIVertex>();
     vh.GetUIVertexStream(vertexList);
     int count = vertexList.Count;
     
     ApplyGradient(vertexList, 0, count);
     vh.Clear();
     vh.AddUIVertexTriangleStream(vertexList);
 }

 private void ApplyGradient(List<UIVertex> vertexList, int start, int end)
 {
     float bottomY = vertexList[0].position.y;
     float topY = vertexList[0].position.y;
     for (int i = start; i < end; ++i) {
         float y = vertexList[i].position.y;
         if (y > topY) {
             topY = y;
         } else if (y < bottomY) {
             bottomY = y;
         }
     }

     float uiElementHeight = topY - bottomY;
     for (int i = start; i < end; ++i) {
         UIVertex uiVertex = vertexList[i];
         uiVertex.color = Color32.Lerp(bottomColor, topColor, (uiVertex.position.y - bottomY)/uiElementHeight);
         vertexList[i] = uiVertex;
     }
 }

3. 陰影(Shadow)

UGUI 中默認帶有Shadow的組件,也是對ModifyMesh進行重載。然后將網格數據復制一份並向指定方向移動指定像素,然后填充到頂點數據中。也就是說,Shadow實現是通過增加頂點數據實現的。

// X y 為 shadow大小
protected void ApplyShadowZeroAlloc(List<UIVertex> verts, Color32 color, int start, int end, float x, float y)
{
    UIVertex vt;
    var neededCapacity = verts.Count + end - start;
    if (verts.Capacity < neededCapacity)
        verts.Capacity = neededCapacity;
    for (int i = start; i < end; ++i)
    {
        vt = verts[i];
        verts.Add(vt);
        Vector3 v = vt.position;
        v.x += x;
        v.y += y;
        vt.position = v;
        var newColor = color;
        if (m_UseGraphicAlpha)
            newColor.a = (byte)((newColor.a * verts[i].color.a) / 255);
        vt.color = newColor;
        verts[i] = vt;
    }
}

4. 勾邊 (Outline)

1. 基於Shadow

outline的實現傳統的做法是在4個方向或者8個方向進行Shodow操作。換句話說頂點數需要增加8倍,大量使用請謹慎。

public override void ModifyMesh(VertexHelper vh)
 {
     var verts = ListPool<UIVertex>.Get();
     vh.GetUIVertexStream(verts);
     var neededCpacity = verts.Count * 5;
     if (verts.Capacity < neededCpacity)
         verts.Capacity = neededCpacity;
     var start = 0;
     var end = verts.Count;
     ApplyShadowZeroAlloc(verts, effectColor, start, verts.Count, effectDistance.x, effectDistance.y);
     start = end;
     end = verts.Count;
     ApplyShadowZeroAlloc(verts, effectColor, start, verts.Count, effectDistance.x, -effectDistance.y);
     start = end;
     end = verts.Count;
     ApplyShadowZeroAlloc(verts, effectColor, start, verts.Count, -effectDistance.x, effectDistance.y);
     start = end;
     end = verts.Count;
     ApplyShadowZeroAlloc(verts, effectColor, start, verts.Count, -effectDistance.x, -effectDistance.y)
     vh.Clear();
     vh.AddUIVertexTriangleStream(verts);
     ListPool<UIVertex>.Release(verts);
 }

2 . 效果擴展

4方向Outlin美術經常不滿足的其效果,所以有時候會需要8方向描邊的outline8, 甚至更多。基於shadow方式的outline實現可以參考開源代碼:https://github.com/n-yoda/unity-vertex-effects 。換湯不換葯吧,越好的效果頂點增加的越多。
幾種效果的圖示,中文字邊緣比較明顯。Circle的邊緣更圓滑一些,Shadow操作的次數和8方向相同。
mark

3. 基於Mesh實現

基於Shadow的實現方式內存占用比較高,當然還有別的思路,可以參考網頁中描述的方式。其詳細流程:

  • 提取文字UV區域
  • 擴大文字繪圖區域並記錄增長量
  • 在pixel處理階段,在每個像素點,對周圍區域(受增長量以及原有uv區域控制)進行采樣並作為這個點的顏色和alpha
  • 對原始紋理、alpha以及擴大后的區域進行融合
    mark

5.結論

outline的幾種實現方式根據具體需求使用,雲風在博客中也給出一種優化策略可參考
總之,UGUI對字體效果的支持不算很好,像圖文混排等等都需要自己做擴展,據說收購了TexmeshPro對自身text系統進行擴展,期待。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM