unity 字体_【Unity源码学习】Text源码-细品(1)

前言

老大要去生孩子了,临走还不忘关怀我,让我把Text文本组件源码读了,回来要考察我! 学习的动力+1 SoarDText组件,我们自己的扩展部分包括UGUI的源码(现在有一个乱码的 bug,要是能分析出来就再好不过了); Spine的运行库源码; 二选一,自己抽时间去读,读的透透的,捋清楚里面的所有功能、细节、逻辑、算法,每一行代码都给我读懂,从原理到实现,问啥你都能知道的那个程度。 有兴趣挑战一下吗? 我这个人总喜欢功利性学习,让我们带着问题去看源码吧!

问题:

  1. 字体文件是怎么使用的?封装在C里,没有找到。
  2. Material文件上没有挂texture,字体文件是怎么使用上这个文件的?fontMaterial.mainTexture = fontTexture;
  3. Text文本的居中,居左等对齐方式实现

接下来我们拿一个制作的艺术字来学习字体的制作和使用过程

一、 BMFont工具使用

  1. 详细教程: 图片导入教程
  2. 设置教程
Options-Texture-设置图集大小
bit depth 设置真彩和黑白
Texture-png

3. 使用教程

新建txt,输入需要使用的艺术字,以UTF-8格式保存
Edit - Selctect Form Files- 选择刚才的文本
Edit - OpenImageManager -
鼠标悬停bmfont,右下角可以显示字体ID
Image - ImportImage - 选择要导入的图片 - 输入ID - 确定

c4d8ae0e99097526f8f72c002046f85d.png

二、导入unity

  1. BM Font Maker插件
  2. 新建一个材质Material,和字体文件Custom Font,然后把生成的.png和.fnt文件拖入插件
  3. 就能生成我们游戏中需要用到的字体文件

28d65bc1b8873c9cb460d82c3a62eb07.png

BM Font Maker工具解析了.fnt文件,转换成unity可以识别的CharacterInfo信息,存到.fontsettings文件里

ffd50a2910652eafa98bb6ac7f9c72d4.png

我们拿1【Ascii码:49】这个美术字来做例子,分析一下这个字体文件的使用

fb337b3f8992ab97c535a034f749b672.png

88947854456d2d1a454169329f242089.png
Unity UV是以左上角为0,0点的

三、字体使用

Text

UGUI的Text就是 位图字体,先通过 TTF字体将字体形状生成在位图中,接着就是将正确的UV设置给字体的Mesh,这和前面介绍的Image组件几乎一样了。

63a543519d32580404a752eed5787da9.png

新建一个Text组件,设置我们自己制作的艺术字,触发FontUpdateTracker注册的textureRebuilt事件,然后调用到Text.cs的FontTextureChanged方法,里面有SetAllDirty会将m_MaterialDirty设置成true,这样,text在下次rebuild的时候会调用UpdateMaterial

Graphic.cs
        protected virtual void UpdateMaterial()
        {
            if (!IsActive())
                return;

            canvasRenderer.materialCount = 1;
            canvasRenderer.SetMaterial(materialForRendering, 0);
            canvasRenderer.SetTexture(mainTexture);
        }

至于这2个神秘的材质和纹理实现是这样的

Text.cs
        public override Texture mainTexture
        {
            get
            {
                if (font != null && font.material != null && font.material.mainTexture != null)
                    return font.material.mainTexture;

                if (m_Material != null)
                    return m_Material.mainTexture;

                return base.mainTexture;
            }
        }
Graphic.cs
        public virtual Material materialForRendering
        {
            get
            {
                var components = ListPool<Component>.Get();
                GetComponents(typeof(IMaterialModifier), components);

                var currentMat = material;
                for (var i = 0; i < components.Count; i++)
                    currentMat = (components[i] as IMaterialModifier).GetModifiedMaterial(currentMat);
                ListPool<Component>.Release(components);
                return currentMat;
            }
        }

Text的材质是拿的MaskableGraphic的GetModifiedMaterial的材质,是一个可以遮罩的材质,然后纹理拿的是字体文件材质的纹理

当我们进行文本输入的时候,会触发布局和顶点的重建

        public virtual string text
        {
            get
            {
                return m_Text;
            }
            set
            {

                if (String.IsNullOrEmpty(value))
                {
                    if (String.IsNullOrEmpty(m_Text))
                        return;
                    m_Text = "";
                    SetVerticesDirty();
                }
                else if (m_Text != value)
                {
                    m_Text = value;
                    SetVerticesDirty();
                    SetLayoutDirty();
                }
            }
        }

TextGenerator虽然开源了C#源码,但是具体实现层层封装,最后都藏到了“Modules/TextRendering/TextGenerator.h”难过,看不到源码。只能靠大概猜想实现:

cachedTextGenerator.PopulateWithErrors(text, settings, gameObject);

应该是根据传入的text,去索引NFont.fontsettings文件的Character Rects,找到对应的索引,输出顶点和UV。

然后拿着这个顶点和UV去重建Mesh,然后根据UV信息,找到纹理上的相应位置,和大小,扣出UV,绘制到mesh上,这个具体实现应该是在shader上实现的,细节有点不懂。。。

public class Text : MaskableGraphic, ILayoutElement
{
    //...略
    //字体生成器
    public TextGenerator cachedTextGenerator
    {
        get { return m_TextCache ?? (m_TextCache = (m_Text.Length != 0 ? new TextGenerator(m_Text.Length) : new TextGenerator())); }
    }
    readonly UIVertex[] m_TempVerts = new UIVertex[4];
    protected override void OnPopulateMesh(VertexHelper toFill)
    {
        if (font == null)
            return;
        m_DisableFontTextureRebuiltCallback = true;
        Vector2 extents = rectTransform.rect.size;
        //获取字体的生成规则设置
        var settings = GetGenerationSettings(extents);
        //根据待填充字体、生成规则,生成顶点信息
        cachedTextGenerator.PopulateWithErrors(text, settings, gameObject);
        //取到cachedTextGenerator.verts的顶点信息
        IList<UIVertex> verts = cachedTextGenerator.verts;
        float unitsPerPixel = 1 / pixelsPerUnit;
        int vertCount = verts.Count - 4;
        if (vertCount <= 0)
        {
            toFill.Clear();
            return;
        }
        Vector2 roundingOffset = new Vector2(verts[0].position.x, verts[0].position.y) * unitsPerPixel;
        roundingOffset = PixelAdjustPoint(roundingOffset) - roundingOffset;
        toFill.Clear();
        if (roundingOffset != Vector2.zero)
        {
            for (int i = 0; i < vertCount; ++i)
            {
                int tempVertsIndex = i & 3;
                //填充顶点信息
                m_TempVerts[tempVertsIndex] = verts[i];
                //设置字体偏移
                m_TempVerts[tempVertsIndex].position *= unitsPerPixel;
                m_TempVerts[tempVertsIndex].position.x += roundingOffset.x;
                m_TempVerts[tempVertsIndex].position.y += roundingOffset.y;
                if (tempVertsIndex == 3)
                    //填充UI顶点面片
                    toFill.AddUIVertexQuad(m_TempVerts); 
            }
        }
        else
        {
            for (int i = 0; i < vertCount; ++i)
            {
                int tempVertsIndex = i & 3;
                //填充顶点信息
                m_TempVerts[tempVertsIndex] = verts[i];
                //设置字体偏移
                m_TempVerts[tempVertsIndex].position *= unitsPerPixel;
                if (tempVertsIndex == 3)
                    toFill.AddUIVertexQuad(m_TempVerts);//填充UI顶点面片
            }
        }
        m_DisableFontTextureRebuiltCallback = false;
    }

有个问题

为啥自己做的艺术字没有mesh呀,源生text都有mesh,有明白的大佬帮忙解惑一下

b200ffa37a2f43037c651e5b531aac51.png

拓展

UGUI的Text Effect包含了字体的描边和阴影,它是如何实现的呢?

Graphic.cs(部分代码):
private void DoMeshGeneration()
    {
        if (rectTransform != null && rectTransform.rect.width >= 0 && rectTransform.rect.height >= 0)
            //在继承类中实现具体的元素信息
            OnPopulateMesh(s_VertexHelper);
        else
            s_VertexHelper.Clear(); 
        var components = ListPool<Component>.Get();
        //获取当前对象是否有IMeshModifier接口
        //Text的描边和阴影都是通过IMeshModifier的ModifyMesh()实现出来的
        GetComponents(typeof(IMeshModifier), components);
        //将Text的网格信息传入ModifyMesh()进行修改
        for (var i = 0; i < components.Count; i++)
            ((IMeshModifier)components[i]).ModifyMesh(s_VertexHelper);
        ListPool<Component>.Release(components);
        //最终Text的Mesh信息保存在s_VertexHelper对象中。
        //通过FillMesh()方法将网格构建出来
        s_VertexHelper.FillMesh(workerMesh);
        //提交网格信息,开始合并网格
        canvasRenderer.SetMesh(workerMesh);
    }

字体一旦添加描边以后会在原有基础上多绘制4遍,所以在UGUI中最好使用阴影Shadow来代替描边Outline。毕竟阴影只会多画一遍。

04b87c9f88ae290f975fd8f6d4003968.png
outline

3d640f8971175613e832a37fef855d96.png
shadow
//shadow.cs 
        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;
            }
        }
//Outline继承Shadow,调用了4次ApplyShadowZeroAlloc
    public class Outline : Shadow
    {
        protected Outline()
        {}

        public override void ModifyMesh(VertexHelper vh)
        {
            if (!IsActive())
                return;

            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);
        }
    }
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
网格重建是一种常见的计算几何算法,用于从点云或体素数据中重建几何形状。在Unity中,网格重建算法可以应用于许多应用程序,如建筑信息建模、虚拟现实和游戏开发等领域。 Unity的网格重建算法主要涉及以下几个步骤: 1. 读取点云或体素数据,将其转换为Unity中的网格数据结构。 2. 对网格数据进行预处理,如去除重复点、移除离群点等。 3. 利用网格重建算法对网格数据进行重建,生成新的网格数据。 4. 将新的网格数据保存到Unity中,并进行后续处理,如纹理映射、光照计算等。 下面是一个基于Unity源码的简单网格重建示例代码: ```csharp using UnityEngine; using System.Collections; using System.Collections.Generic; public class MeshRebuilder : MonoBehaviour { public float radius = 1f; // 重建半径 private MeshFilter meshFilter; private Mesh mesh; void Start () { meshFilter = GetComponent<MeshFilter> (); mesh = meshFilter.mesh; List<Vector3> vertices = new List<Vector3> (); List<int> indices = new List<int> (); // 读取点云数据 Vector3[] points = GetPointCloudData (); // 预处理点云数据 points = PreprocessPointCloud (points); // 网格重建算法 for (int i = 0; i < points.Length; i++) { for (int j = i + 1; j < points.Length; j++) { if (Vector3.Distance (points [i], points [j]) < radius) { // 生成新的网格数据 int index1 = vertices.IndexOf (points [i]); int index2 = vertices.IndexOf (points [j]); if (index1 < 0) { vertices.Add (points [i]); index1 = vertices.Count - 1; } if (index2 < 0) { vertices.Add (points [j]); index2 = vertices.Count - 1; } indices.Add (index1); indices.Add (index2); } } } // 将新的网格数据保存到Unity中 mesh.vertices = vertices.ToArray (); mesh.SetIndices (indices.ToArray (), MeshTopology.Lines, 0); mesh.RecalculateBounds (); mesh.RecalculateNormals (); } // 读取点云数据 private Vector3[] GetPointCloudData () { // TODO: 从外部文件或其他数据源读取点云数据 return null; } // 预处理点云数据 private Vector3[] PreprocessPointCloud (Vector3[] points) { // TODO: 去除重复点、移除离群点等 return points; } } ``` 以上示例代码只是一个简单的网格重建实现,实际应用中需要根据具体需求进行优化和调整。同时,网格重建算法也存在一些局限性,如对于大规模点云数据的处理效率较低等问题。因此,在实际应用中需要根据具体场景选择合适的算法和工具库。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值