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"
}