Unity UI Text에 그라데이션 및 그레이스케일 효과 구현하기

Unity의 기본 Text 컴포넌트는 단일 색상만을 지원하지만, OnPopulateMesh 메서드를 오버라이드하면 텍스트의 각 정점(Vertex) 데이터를 직접 수정하여 그라데이션이나 그레이스케일 같은 시각적 효과를 부여할 수 있습니다.

CustomGradientText 구현

아래 코드는 Text 클래스를 확장하여 전체 텍스트 또는 개별 문자 단위로 그라데이션을 적용하고, 텍스트 전체를 회색조로 변경하는 기능을 포함합니다.

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

public class CustomGradientText : Text
{
    [SerializeField] private bool useGrayscale = false;
    [SerializeField] private bool useGradient = false;
    [SerializeField] private bool isGlobalGradient = true;
    [SerializeField] private Color colorTop = Color.white;
    [SerializeField] private Color colorBottom = Color.black;

    public bool IsGrayscale
    {
        get => useGrayscale;
        set
        {
            if (useGrayscale != value)
            {
                useGrayscale = value;
                SetVerticesDirty();
            }
        }
    }

    protected override void OnPopulateMesh(VertexHelper vh)
    {
        base.OnPopulateMesh(vh);

        if (useGrayscale)
        {
            ApplyGrayscale(vh);
        }
        else if (useGradient)
        {
            ApplyGradient(vh);
        }
    }

    private void ApplyGrayscale(VertexHelper vh)
    {
        int count = vh.currentVertCount;
        UIVertex vertex = new UIVertex();
        Color32 gray = new Color32(128, 128, 128, 255);

        for (int i = 0; i < count; i++)
        {
            vh.PopulateUIVertex(ref vertex, i);
            vertex.color = gray;
            vh.SetUIVertex(vertex, i);
        }
    }

    private void ApplyGradient(VertexHelper vh)
    {
        int count = vh.currentVertCount;
        if (count == 0) return;

        UIVertex vertex = new UIVertex();

        if (!isGlobalGradient)
        {
            // 각 문자별 개별 그라데이션 (4개의 정점 단위)
            for (int i = 0; i < count; i += 4)
            {
                SetVertexColor(vh, i, colorTop);     // Left Top
                SetVertexColor(vh, i + 1, colorTop); // Right Top
                SetVertexColor(vh, i + 2, colorBottom); // Right Bottom
                SetVertexColor(vh, i + 3, colorBottom); // Left Bottom
            }
        }
        else
        {
            // 텍스트 전체 영역 기준 그라데이션
            float minY = float.MaxValue;
            float maxY = float.MinValue;

            for (int i = 0; i < count; i++)
            {
                vh.PopulateUIVertex(ref vertex, i);
                minY = Mathf.Min(minY, vertex.position.y);
                maxY = Mathf.Max(maxY, vertex.position.y);
            }

            float height = maxY - minY;
            for (int i = 0; i < count; i++)
            {
                vh.PopulateUIVertex(ref vertex, i);
                float normalizedY = (vertex.position.y - minY) / height;
                vertex.color = Color32.Lerp(colorBottom, colorTop, normalizedY);
                vh.SetUIVertex(vertex, i);
            }
        }
    }

    private void SetVertexColor(VertexHelper vh, int index, Color color)
    {
        if (index >= vh.currentVertCount) return;
        UIVertex v = new UIVertex();
        vh.PopulateUIVertex(ref v, index);
        v.color = color;
        vh.SetUIVertex(v, index);
    }
}

인스펙터 커스터마이징

사용자 정의 컴포넌트의 가독성을 높이기 위해 전용 에디터 클래스를 생성하여 특정 옵션이 활성화될 때만 관련 설정이 보이도록 구성합니다.

using UnityEngine;
using UnityEditor;
using UnityEditor.UI;
using System;

[CustomEditor(typeof(CustomGradientText))]
[CanEditMultipleObjects]
public class GradientTextInspector : Editor
{
    private Editor baseEditor;
    private SerializedProperty propGrayscale;
    private SerializedProperty propGradient;
    private SerializedProperty propGlobal;
    private SerializedProperty propTop;
    private SerializedProperty propBottom;

    private void OnEnable()
    {
        Type textEditorType = Type.GetType("UnityEditor.UI.TextEditor, UnityEditor.UI");
        if (textEditorType != null)
        {
            baseEditor = CreateEditor(targets, textEditorType);
        }

        propGrayscale = serializedObject.FindProperty("useGrayscale");
        propGradient = serializedObject.FindProperty("useGradient");
        propGlobal = serializedObject.FindProperty("isGlobalGradient");
        propTop = serializedObject.FindProperty("colorTop");
        propBottom = serializedObject.FindProperty("colorBottom");
    }

    private void OnDisable()
    {
        if (baseEditor != null) DestroyImmediate(baseEditor);
    }

    public override void OnInspectorGUI()
    {
        if (baseEditor != null) baseEditor.OnInspectorGUI();

        serializedObject.Update();
        EditorGUILayout.Space();
        EditorGUILayout.LabelField("Custom Effects", EditorStyles.boldLabel);
        
        EditorGUILayout.PropertyField(propGrayscale);
        EditorGUILayout.PropertyField(propGradient);

        if (propGradient.boolValue)
        {
            EditorGUI.indentLevel++;
            EditorGUILayout.PropertyField(propGlobal);
            EditorGUILayout.PropertyField(propTop);
            EditorGUILayout.PropertyField(propBottom);
            EditorGUI.indentLevel--;
        }

        if (serializedObject.ApplyModifiedProperties())
        {
            (target as CustomGradientText).SetVerticesDirty();
        }
    }
}

VertexHelper의 주요 인터페이스

텍스트 렌더링 최적화와 효과 구현을 위해 VertexHelper에서 제공하는 주요 메서드들은 다음과 같습니다.

  • PopulateUIVertex: 특정 인덱스의 정점 데이터를 가져옵니다.
  • SetUIVertex: 수정된 정점 데이터를 다시 메시에 적용합니다.
  • currentVertCount: 현재 메시에 포함된 전체 정점의 개수를 반환합니다.
  • AddUIVertexQuad: 4개의 정점을 이용해 사각형(문자 하나 단위)을 추가합니다.

텍스트의 정점 좌표는 기본적으로 해당 UI 요소의 Pivot을 기준으로 하는 로컬 좌표계를 사용합니다. 따라서 그라데이션 계산 시 vertex.position.y 값을 활용하면 텍스트의 높이에 따른 정확한 보간 처리가 가능합니다.

태그: Unity uGUI VertexHelper UIEffects TextGradient

9월 25일 02:29에 게시됨