• UGUI 文字效果实现(ShadowGradientOutline)


    http://www.cnblogs.com/zsb517/p/6565446.html

    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系统进行扩展,期待。

  • 相关阅读:
    立即执行函数
    刷题-函数-闭包-返回函数
    刷题-js对象-属性遍历
    并发——无缓冲通道,带缓冲的通道,通道的多路复用,关闭通道
    并发——轻量级线程,通道,单向通道
    包——基本概念,自定义包,创建包,导出包中的标志符
    接口——嵌套,接口和类型间的转换,空接口类型,类型分支
    接口——定义,实现接口的条件,类型与接口的关系,类型断言
    结构体——内嵌,初始化内嵌结构体,内嵌结构体成员名字冲突
    结构体——方法和接收器,为任意类型添加方法
  • 原文地址:https://www.cnblogs.com/alps/p/7773483.html
Copyright © 2020-2023  润新知