반응형

TextmeshPro기반의 BitmapFont 사용할때 SDF쉐이더가 아닌 Sprite쉐이더를 사용합니다.

해당 쉐이더에는 Shadow기능이 없어 별도 Shadow 컴포넌트를 제작했습니다.

(MeshRenderer에서만 사용가능)

 

using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

/// <summary>
/// TextMeshPro - MeshRenderer전용 Shadow 효과
/// </summary>
[ExecuteInEditMode]
public class TMPSpriteShadow : MonoBehaviour
{
    [SerializeField] private Color m_EffectColor = new Color(0f, 0f, 0f, 0.5f);
    [SerializeField] private Vector2 m_EffectDistance = new Vector2(1f, -1f);
    [SerializeField] private bool m_UseGraphicAlpha = true;

    private TMP_Text m_TextComponent;

    private void Awake()
    {
        m_TextComponent = GetComponent<TMP_Text>();
    }

    private void OnEnable()
    {
        if (m_TextComponent == null) m_TextComponent = GetComponent<TMP_Text>();
        TMPro_EventManager.TEXT_CHANGED_EVENT.Add(ON_TEXT_CHANGED);     // TMP 수정 이벤트 콜백 등록
        SetVerticesDirty();
    }

    private void OnDisable()
    {
        TMPro_EventManager.TEXT_CHANGED_EVENT.Remove(ON_TEXT_CHANGED);      // TMP 수정 이벤트 콜백 삭제
        SetVerticesDirty();
    }

    // Inspector 값 수정시 콜백 호출
    private void OnValidate()
    {
        SetVerticesDirty();
    }

    protected virtual void LateUpdate()
    {
        if (isActiveAndEnabled && m_TextComponent)
        {
            if (m_TextComponent.havePropertiesChanged)
            {
                SetVerticesDirty(); // TMP컴포넌트 수정시 호출
            }
        }
    }

    // TMP 텍스트 변화 콜백 함수
    private void ON_TEXT_CHANGED(Object obj)
    {
        TMP_TextInfo textInfo = m_TextComponent.textInfo;
        if (m_TextComponent != obj || textInfo.characterCount - textInfo.spaceCount <= 0)
            return;

        VertexHelper s_VertexHelper = new VertexHelper();
        for (int i=0;i<textInfo.meshInfo.Length;i++)
        {
            Mesh mesh = textInfo.meshInfo[i].mesh;
            if (mesh == null) continue;
            FillVertexHelper(s_VertexHelper, mesh); // VertexHelper 수동 구성
            ModifyMesh(s_VertexHelper); // TMP Mesh 수정
            s_VertexHelper.FillMesh(mesh);
        }
    }

    // TMP 오브젝트 Mesh 수정 (그림자 메쉬 추가)
    private void ModifyMesh(VertexHelper vh)
    {
        if (!isActiveAndEnabled || vh.currentVertCount <= 0)
            return;

        List<UIVertex> s_Verts = new List<UIVertex>();
        vh.GetUIVertexStream(s_Verts);
        int start = 0;
        int end = s_Verts.Count;

        ApplyShadow(s_Verts, m_EffectColor, start, end, m_EffectDistance, m_UseGraphicAlpha);

        vh.Clear();
        vh.AddUIVertexTriangleStream(s_Verts);
        s_Verts.Clear();
    }

    // Mesh 버텍스 정보 갱신
    private void SetVerticesDirty()
    {
        if (m_TextComponent == null) return;
       
        for(int i=0;i< m_TextComponent.textInfo.meshInfo.Length;i++)
        {
            TMP_MeshInfo info = m_TextComponent.textInfo.meshInfo[i];
            Mesh mesh = info.mesh;
            if (mesh == null) continue;
            mesh.Clear();
            mesh.vertices = info.vertices;
            mesh.uv = info.uvs0;
            mesh.colors32 = info.colors32;
            mesh.triangles = info.triangles;
        }
        m_TextComponent.havePropertiesChanged = true;
    }

    // VertexHelper 수동 구성
    private void FillVertexHelper(VertexHelper vh, Mesh mesh)
    {
        List<Vector2> s_Uv0 = new List<Vector2>();
        List<Vector3> s_Vertices = new List<Vector3>();
        List<int> s_Indices = new List<int>();
        List<Color32> s_Colors = new List<Color32>();
        mesh.GetVertices(s_Vertices);
        mesh.GetColors(s_Colors);
        mesh.GetUVs(0, s_Uv0);
        mesh.GetIndices(s_Indices, 0);

        vh.Clear();
        for (int i = 0; i < s_Vertices.Count; i++)
        {
            vh.AddVert(s_Vertices[i], s_Colors[i], s_Uv0[i]);
        }
        for (int i = 0; i < s_Indices.Count; i += 3)
        {
            vh.AddTriangle(s_Indices[i], s_Indices[i + 1], s_Indices[i + 2]);
        }
    }


    private void ApplyShadow(List<UIVertex> verts, Color color, int start, int end, Vector2 effectDistance, bool useGraphicAlpha)
    {
        if (color.a <= 0)
            return;

        ApplyShadowZeroAlloc(verts, color, start, end, effectDistance.x, effectDistance.y, useGraphicAlpha);
    }

    // Shadow 버텍스 추가
    private void ApplyShadowZeroAlloc(List<UIVertex> verts, Color color, int start, int end, float x, float y, bool useGraphicAlpha)
    {
        int count = end - start;
        int neededCapacity = verts.Count + count;
        if (verts.Capacity < neededCapacity)
            verts.Capacity = neededCapacity;

        UIVertex vt = default(UIVertex);
        for (int i = 0; i < count; ++i)
        {
            vt = verts[i];
            verts.Add(vt);

            Vector3 v = vt.position;
            vt.position.Set(v.x + x, v.y + y, v.z);

            Color vertColor = color;
            vertColor.a = useGraphicAlpha ? color.a * vt.color.a / 255 : color.a;
            vt.color = vertColor;
            verts[i] = vt;
        }
    }
}

 

 

 

참고자료

- https://github.com/mob-sakai/MeshEffectForTextMeshPro

 

반응형

WRITTEN BY
CatDarkGame
Technical Artist dhwlgn12@gmail.com

,