LEARN X · ЗА 16 МИН

ShaderLab (Unity)

ShaderLab за 16 минут: структура шейдера Unity, Properties, SubShader, Pass, HLSL-блоки, вершинные и фрагментные функции, текстуры и блендинг — на примерах.

ShaderLab — это декларативный язык-обёртка Unity, который описывает структуру шейдера: его свойства, проходы рендеринга и теги. Сама математика цвета пишется внутри на HLSL/Cg. Ниже — весь язык в комментариях к рабочему коду.

Что такое ShaderLab

ShaderLab задаёт «скелет» материала, а внутри него живут блоки шейдерного кода на HLSL.

// ShaderLab НЕ считает пиксели сам — он описывает структуру:
//   Shader   — корневой блок, путь и имя в списке материалов
//     Properties — настройки, видимые в инспекторе Unity
//     SubShader  — вариант рендеринга (Unity выберет первый подходящий)
//       Pass     — один проход отрисовки (внутри — код на HLSL)
//
// Цвет каждого пикселя вычисляет уже HLSL-код внутри Pass.
// ShaderLab лишь говорит "когда", "с какими настройками" и "что показывать".

Структура шейдера

Минимальный каркас любого шейдера выглядит так.

// Имя в кавычках = путь в выпадающем меню материала (Shader -> ...)
Shader "Learn/UnlitBasic"
{
    Properties
    {
        // здесь объявляем настраиваемые параметры
    }

    SubShader
    {
        // теги и проходы рендеринга
        Pass
        {
            // блок HLSL-кода
        }
    }

    // запасной шейдер, если ни один SubShader не подошёл железу:
    Fallback "Diffuse"
}

Свойства (Properties)

Properties — это мост между инспектором Unity и кодом шейдера.

Properties
{
    // _Имя ("Подпись в инспекторе", Тип) = ЗначениеПоУмолчанию

    _Color    ("Tint",        Color)     = (1, 1, 1, 1)   // RGBA-цвет
    _MainTex  ("Texture",     2D)        = "white" {}     // текстура, дефолт — белая
    _Glossy   ("Glossiness",  Float)     = 0.5            // одно число
    _Cutoff   ("Alpha Cutoff", Range(0, 1)) = 0.5         // ползунок от 0 до 1
    _Strength ("Strength",    Range(0, 5)) = 1.0          // ползунок с границами
}

// Частые типы:
//   Color    — цвет RGBA, значение (r, g, b, a)
//   2D       — текстура, дефолт строкой: "white" / "black" / "gray" / "bump"
//   Float    — вещественное число
//   Range(a, b) — число-ползунок в диапазоне [a, b]
//   Vector   — четырёхкомпонентный вектор (x, y, z, w)

Теги (Tags)

Теги сообщают Unity, как и когда рисовать объект: к какому типу он относится и в каком порядке попадёт в очередь.

SubShader
{
    Tags
    {
        // RenderType — категория для замены шейдеров и эффектов
        "RenderType" = "Opaque"        // непрозрачный объект

        // Queue — порядок отрисовки (меньше число = раньше)
        "Queue" = "Geometry"           // обычная геометрия (= 2000)
        // другие очереди:
        //   "Background"  (1000) — скайбоксы
        //   "Geometry"    (2000) — непрозрачное
        //   "AlphaTest"   (2450) — отсечение по альфе
        //   "Transparent" (3000) — прозрачное, рисуется после непрозрачного
        //   "Overlay"     (4000) — поверх всего (UI, блики)
    }
    // ...
}

Блоки HLSL

Внутри Pass настоящий код шейдера заключают между HLSLPROGRAM и ENDHLSL.

Pass
{
    HLSLPROGRAM
    // #pragma указывает, какие функции — вершинная и фрагментная
    #pragma vertex   vert      // вершинная функция называется vert
    #pragma fragment frag      // фрагментная функция называется frag

    // подключаем встроенные функции и макросы Unity:
    #include "UnityCG.cginc"

    // ... структуры и функции vert / frag ...

    ENDHLSL
}
// Примечание: в старых проектах вместо HLSLPROGRAM встречается
// CGPROGRAM ... ENDCG — это почти тот же Cg/HLSL-диалект.

Вершинная и фрагментная функции

Вершинная функция переводит вершины в координаты экрана, фрагментная возвращает цвет пикселя.

// Вершинный шейдер: на вход — вершина модели, на выход — данные для растеризатора
v2f vert (appdata v)
{
    v2f o;
    // переводим позицию вершины из модели в координаты клипа (экрана)
    o.vertex = UnityObjectToClipPos(v.vertex);
    o.uv     = v.uv;            // передаём UV дальше
    return o;
}

// Фрагментный шейдер: на вход — интерполированные данные, на выход — цвет
fixed4 frag (v2f i) : SV_Target
{
    // возвращаем сплошной красный (R=1, G=0, B=0, A=1)
    return fixed4(1, 0, 0, 1);
}

Структуры вход-выход и семантики

Семантики (слова после двоеточия) сообщают GPU, что означает каждое поле.

// appdata — то, что приходит из меша в вершинную функцию
struct appdata
{
    float4 vertex : POSITION;    // позиция вершины в пространстве модели
    float2 uv     : TEXCOORD0;   // координаты текстуры (UV)
};

// v2f (vertex-to-fragment) — то, что вершинная функция передаёт фрагментной
struct v2f
{
    float2 uv     : TEXCOORD0;    // UV интерполируются по треугольнику
    float4 vertex : SV_POSITION;  // ОБЯЗАТЕЛЬНАЯ позиция в координатах клипа
};

// Ключевые семантики:
//   POSITION    — входная позиция вершины
//   TEXCOORD0   — набор UV (TEXCOORD1, TEXCOORD2... для доп. данных)
//   SV_POSITION — итоговая позиция вершины для растеризатора (System Value)
//   SV_Target   — итоговый цвет пикселя, который пишется в кадр

Uniform-свойства в коде

Чтобы прочитать Properties в HLSL, их объявляют как переменные с тем же именем.

// Имена должны ТОЧНО совпадать с именами в блоке Properties
sampler2D _MainTex;     // текстура _MainTex (тип 2D)
float4    _MainTex_ST;  // авто-переменная: tiling (xy) и offset (zw) текстуры
fixed4    _Color;       // цвет _Color (тип Color)
float     _Glossy;      // число _Glossy (тип Float / Range)

// Соответствие типов ShaderLab -> HLSL:
//   Color / Vector   -> float4 (или fixed4 для цвета)
//   2D               -> sampler2D
//   Float / Range    -> float
// fixed/half/float — разная точность; fixed для цветов, float для координат

Текстурирование

Текстуру читают функцией tex2D по UV-координатам; tiling и offset применяет TRANSFORM_TEX.

v2f vert (appdata v)
{
    v2f o;
    o.vertex = UnityObjectToClipPos(v.vertex);
    // TRANSFORM_TEX применяет _MainTex_ST: масштаб (tiling) и сдвиг (offset)
    o.uv = TRANSFORM_TEX(v.uv, _MainTex);
    return o;
}

fixed4 frag (v2f i) : SV_Target
{
    // tex2D(текстура, UV) — берём цвет текселя по координате i.uv
    fixed4 texColor = tex2D(_MainTex, i.uv);
    // умножаем на тинт _Color (покомпонентно RGBA)
    return texColor * _Color;
}
// TRANSFORM_TEX(uv, _MainTex) разворачивается в:
//   uv * _MainTex_ST.xy + _MainTex_ST.zw

Встроенные функции и матрицы

UnityCG.cginc даёт готовые преобразования и удобные переменные.

// Преобразование позиции модель -> клип (заменяет mul с матрицами):
o.vertex = UnityObjectToClipPos(v.vertex);
// эквивалент: mul(UNITY_MATRIX_MVP, v.vertex)

// _Time — встроенный вектор времени (x = t/20, y = t, z = t*2, w = t*3)
float wave = sin(_Time.y * 2.0);     // колебание во времени

// Полезные функции HLSL:
float a = lerp(0.0, 1.0, 0.3);       // линейная интерполяция -> 0.3
float b = saturate(wave);            // зажать в диапазон [0, 1]
float c = dot(float3(0,1,0), float3(0,1,0)); // скалярное произведение -> 1
float d = frac(_Time.y);             // дробная часть

// Матрицы Unity: UNITY_MATRIX_MVP, _Object2World (модель -> мир) и др.

Прозрачность и блендинг

Для полупрозрачности меняют очередь, отключают запись глубины и включают смешивание цветов.

SubShader
{
    // прозрачные объекты рисуются после непрозрачных
    Tags { "RenderType" = "Transparent" "Queue" = "Transparent" }

    Pass
    {
        // Blend: как смешать новый цвет (Src) с уже нарисованным (Dst)
        // классическая альфа-прозрачность:
        Blend SrcAlpha OneMinusSrcAlpha
        // итог = Src.rgb * Src.a + Dst.rgb * (1 - Src.a)

        ZWrite Off       // не писать в буфер глубины (иначе прозрачность ломается)
        // Cull Off      // рисовать обе стороны граней (по желанию)

        // ... HLSLPROGRAM ...
    }
}
// Другие режимы Blend:
//   Blend One One            — аддитивный (свечение, огонь)
//   Blend One OneMinusSrcColor — мягкое сложение

Типичный шейдер целиком

Неосвещённый (unlit) шейдер с текстурой и цветовым тинтом — рабочая заготовка для большинства задач.

Shader "Learn/UnlitTextureTint"
{
    Properties
    {
        _MainTex ("Texture", 2D)    = "white" {}
        _Color   ("Tint",    Color) = (1, 1, 1, 1)
    }
    SubShader
    {
        Tags { "RenderType" = "Opaque" "Queue" = "Geometry" }

        Pass
        {
            HLSLPROGRAM
            #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;
            fixed4    _Color;

            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
            {
                // цвет текстуры, умноженный на тинт
                fixed4 col = tex2D(_MainTex, i.uv) * _Color;
                return col;
            }
            ENDHLSL
        }
    }
    Fallback "Unlit/Texture"
}
Поддержать проект