Шейдер с эффектом светящегося контура в Unity

Шейдеры в Unity всегда казались мне чем-то вроде тёмного искусства, пока я не решил добавить в свою игру эффект светящегося контура для объектов. Представьте: персонаж подсвечивается, как будто его окружает неоновая аура, — выглядит круто и сразу привлекает внимание. В этой статье расскажу, как я создал такой шейдер, с какими проблемами столкнулся и как вы можете повторить это без боли. Спойлер: это проще, чем кажется!

Зачем нужен светящийся контур?

Эффект glowing outline часто используется в играх, чтобы выделить интерактивные объекты, показать выбранного персонажа или просто добавить стиля. Когда я работал над небольшой 3D-игрой, заказчик попросил сделать так, чтобы ключевые предметы «светились» при наведении. Стандартные материалы Unity не давали нужного эффекта, и я полез изучать шейдеры.

Для этого проекта я выбрал подход с написанием шейдера вручную, хотя Unity предлагает Shader Graph для визуального создания эффектов. Код даёт больше контроля, и мне хотелось разобраться, как всё работает под капотом.

Шаг 1: Понимание идеи

Светящийся контур обычно создаётся с помощью двух проходов (passes) в шейдере:

  • Первый проход рисует сам объект с обычной текстурой.
  • Второй проход рисует «надутую» версию объекта, которая создаёт контур, с применением яркого цвета.

Я вдохновлялся туториалами, но многие из них были либо слишком сложными, либо устаревшими. После пары часов экспериментов я собрал рабочий шейдер.

Шаг 2: Пишем шейдер

Вот пример шейдера, который добавляет светящийся контур к объекту. Он написан на ShaderLab с использованием HLSL для логики рендеринга:

Shader "Custom/GlowOutline"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _OutlineColor ("Outline Color", Color) = (1, 1, 0, 1)
        _OutlineWidth ("Outline Width", Float) = 0.01
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }

        // Первый проход: рисуем контур
        Pass
        {
            Cull Front // Игнорируем лицевые грани
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
            };

            float _OutlineWidth;
            fixed4 _OutlineColor;

            v2f vert (appdata v)
            {
                v2f o;
                // "Надуваем" модель, смещая вершины вдоль нормалей
                v.vertex += float4(v.normal * _OutlineWidth, 0);
                o.vertex = UnityObjectToClipPos(v.vertex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                return _OutlineColor;
            }
            ENDCG
        }

        // Второй проход: рисуем основной объект
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                return tex2D(_MainTex, i.uv);
            }
            ENDCG
        }
    }
}

Этот шейдер создаёт жёлтый контур вокруг объекта. Вы можете настроить цвет и ширину контура через инспектор Unity (параметры _OutlineColor и _OutlineWidth).

Шаг 3: Применяем шейдер

Чтобы использовать шейдер:

  1. Создайте новый материал в Unity.
  2. Выберите шейдер Custom/GlowOutline в выпадающем меню.
  3. Назначьте материал на объект (например, куб или персонажа).
  4. Настройте текстуру, цвет и ширину контура в инспекторе.

Когда я впервые применил этот шейдер к модели, контур выглядел слишком толстым. Пришлось уменьшить _OutlineWidth до 0.005, чтобы получить аккуратный эффект. Это научило меня: всегда тестируйте шейдер на реальных моделях, а не на тестовых сферах!

Шаг 4: Оптимизация и проблемы

Мой первый шейдер работал, но на сложных моделях с большим числом полигонов контур выглядел неровно. Причина? Нормали модели были неравномерными. Я решил это, экспортировав модель из Blender с опцией «Smooth Normals».

Еще одна проблема — производительность. Контурный проход может быть тяжелым для мобильных устройств. Чтобы оптимизировать:

  • Используйте шейдер только для ключевых объектов.
  • Уменьшайте _OutlineWidth, чтобы снизить нагрузку на рендеринг.
  • Тестируйте на целевой платформе (я проверял на старом iPhone, и пришлось упростить шейдер).

Советы для начинающих

Мой опыт с шейдерами научил меня нескольким вещам:

  • Начните с простого: Попробуйте изменить цвет или прозрачность объекта, чтобы понять структуру шейдера.
  • Изучайте примеры: Unity Asset Store и GitHub полны бесплатных шейдеров. Разберите их код, чтобы понять, как они работают.
  • Не бойтесь ошибок: Мой первый шейдер выдавал чёрный экран, потому что я забыл включить текстуру. Дебаггинг — часть процесса.

Если хотите ускорить обучение, рекомендую книгу The Book of Shaders (есть бесплатная версия онлайн) и туториалы на канале Freya Holmér. Они помогли мне понять, как шейдеры обрабатывают свет и текстуры.

Итог

Создание шейдера с эффектом светящегося контура оказалось не таким уж сложным, а результат добавил моей игре вау-эффект. Теперь я использую этот шейдер для выделения объектов, и заказчик в восторге. Шейдеры — это не только про красивые картинки, но и про умение решать задачи, которые стандартные инструменты Unity не тянут.