Рано или поздно возможностей стандартных шейдеров вам станет не хватать, и тогда вам на помощь придут Surface Shaders. По сути это надстройка над обычными шейдерами, которая позволяет вам писать более понятный и компактный код.
В Surface Shader вы можете управлять освещением, тенями, путями отрисовки (rendering path) используя все тот же Cg / HLSL код.
Вот полная версия шейдера, откуда я буду брать куски для рассмотрения
Первая строка это путь шейдера
По этому пути он будет доступен в инспекторе.
Далее идут Properties, это параметры которые вы сможете задать в инспекторе. Каждый параметр имеет название переменной, описание, тип и значение по-умолчанию.
Типы данных:
name («display name», Range (min, max)) = number
Диапазон значений типа float от min до max, в инспекторе отобразится как слайдер
name («display name», Color) = (number,number,number,number)
Значение типа цвет, значение по-умолчанию должно быть RGBA float от 0 до 1. В инспекторе отобразится как color picker
name («display name», 2D) = «def_col» { options }
Описывает текстуру. В инспекторе будет как текстура
name («display name», Rect) = «def_col» { options }
Описывает текстуру с размером не 2n. В инспекторе будет как текстура
name («display name», Cube) = «def_col» { options }
Описывает Cubemap текстуру. В инспекторе будет как текстура
name («display name», Float) = number
Просто float, в инспекторе будет как поле ввода с цифрой
name («display name», Vector) = (number,number,number,number)
Описывает вектор
Значение по-умолчанию (def_col) для типов Rect, 2D и Cubemap может быть пустой, либо: «white», «black», «gray», «bump». Оно указывает какого цвета пиксели будут по-умолчанию внутри текстуры.
Вот что мы увидим в итоге в инспекторе:

Далее пишется SubShader. Когда юнити пытается отрисовать объект, она ищет первый подходящий SubShader в списке это шейдера. Если ни один SubShader не найден, то произойдет ошибка. Например это бывает нужно в ситуации когда вы хотите реализовать возможности Shader Model 3.0, но оставить возможность играть людям со старой видеокартой.
Внутри Surface SubShader находятся теги SubShader'а и собственно сам код.
Тэг RenderType = «Opaque» означает что мы собираемся отрисовать непрозрачный объект. Подробнее по поводу тэгов можно почитать тут и тут
В коде SurfaceShader'а вы можете описать три функции (в принципе можно и больше, но редко нужно):
Для примера напишем шейдер Diffuse Bumped Specular с морфингом. Функцию расчета освещения в рамках данной статьи я описывать не буду, мы созьмем стандартный BlinnPhong.
CGPROGRAM — директива объявляющая что мы пишем на языке Cg (завершается директивой ENDCG).
Второй строкой мы объявляем что:
Теперь объявляем переменные, которые будут использоваться в коде:
Юнити позаботится о том, чтобы данные из параметров шейдера, объявленных вверху, попали в эти переменные. Важно назвать их так же как и в параметрах шейдера.
А вот и процедура вертексной части нашего шейдера. Для примера возьмем координаты текущего вертекса и прибавим к ним нормаль умноженную на текущие координаты и коэффициент из параметров шейдера. Конечно получится какая-то хрень, но для примера вполне достаточно. Слайдером Amount вы можете регулировать степень искажения объекта.
Пример:

В официальной документации есть пример опухшего солдата. Они просто подняли все вертексы вдоль нормали:

Что еще есть внутри:
Тут можно прочитать полную статью по вертексным шейдерам.
В структуре Input вы можете попросить у шейдера дополнительные переменные, которые вам понадобятся для расчетов в процедуре поверхности. Мы попросили UV координаты обеих текстур. Переменная должна называться uv_НазваниеТекстуры для первых UV координат и uv2_НазваниеТекстуры соответственно для вторых.
Полный список того, что там можно указать вы найдете здесь.
Вот и сама процедура отрисовки поверхности:
В нее передается структура Input, а внутри надо заполнить структуру SurfaceOutput, которая имеет следующий вид:
Заканчиваем шейдер строкой
Это значит, что если шейдер по каким-то причинам не заработает на клиентской машине, то нужно откатиться до шейдера Specular.
Полная версия шейдера из примера
Материалы использованные для написания статьи:
http://unity3d.com/support/documentation/Components/SL-SurfaceShaders
http://unity3d.com/support/documentation/Components/SL-SurfaceShaderExamples.html
http://unity3d.com/support/documentation/Components/SL-SubShader
http://unity3d.com/support/documentation/Components/SL-SubshaderTags
http://unity3d.com/support/documentation/Components/SL-ShaderReplacement.html
В Surface Shader вы можете управлять освещением, тенями, путями отрисовки (rendering path) используя все тот же Cg / HLSL код.
Создание шейдера с нуля
Вот полная версия шейдера, откуда я буду брать куски для рассмотрения
Первая строка это путь шейдера
Shader "AgasperShaders/TestShader" {
По этому пути он будет доступен в инспекторе.
Properties
Далее идут Properties, это параметры которые вы сможете задать в инспекторе. Каждый параметр имеет название переменной, описание, тип и значение по-умолчанию.
Properties { _Color ("Main Color", Color) = (1,1,1,1) _SpecColor ("Specular Color", Color) = (0.5, 0.5, 0.5, 1) _Shininess ("Shininess", Range (0.03, 1)) = 0.078125 _MainTex ("Base (RGB) Gloss (A)", 2D) = "white" {} _BumpMap ("Normalmap", 2D) = "bump" {} _Amount ("Extrusion Amount", Range(-1,1)) = 0.5 }
Типы данных:
name («display name», Range (min, max)) = number
Диапазон значений типа float от min до max, в инспекторе отобразится как слайдер
name («display name», Color) = (number,number,number,number)
Значение типа цвет, значение по-умолчанию должно быть RGBA float от 0 до 1. В инспекторе отобразится как color picker
name («display name», 2D) = «def_col» { options }
Описывает текстуру. В инспекторе будет как текстура
name («display name», Rect) = «def_col» { options }
Описывает текстуру с размером не 2n. В инспекторе будет как текстура
name («display name», Cube) = «def_col» { options }
Описывает Cubemap текстуру. В инспекторе будет как текстура
name («display name», Float) = number
Просто float, в инспекторе будет как поле ввода с цифрой
name («display name», Vector) = (number,number,number,number)
Описывает вектор
Значение по-умолчанию (def_col) для типов Rect, 2D и Cubemap может быть пустой, либо: «white», «black», «gray», «bump». Оно указывает какого цвета пиксели будут по-умолчанию внутри текстуры.
Вот что мы увидим в итоге в инспекторе:

Subshaders
Далее пишется SubShader. Когда юнити пытается отрисовать объект, она ищет первый подходящий SubShader в списке это шейдера. Если ни один SubShader не найден, то произойдет ошибка. Например это бывает нужно в ситуации когда вы хотите реализовать возможности Shader Model 3.0, но оставить возможность играть людям со старой видеокартой.
Внутри Surface SubShader находятся теги SubShader'а и собственно сам код.
Тэг RenderType = «Opaque» означает что мы собираемся отрисовать непрозрачный объект. Подробнее по поводу тэгов можно почитать тут и тут
SubShader { Tags { "RenderType" = "Opaque" } //code }
Собственно сам код
В коде SurfaceShader'а вы можете описать три функции (в принципе можно и больше, но редко нужно):
- Фукцию расчета вертексов
- Фукцию отрисовки поверхности
- Фукцию расчета освещения
Для примера напишем шейдер Diffuse Bumped Specular с морфингом. Функцию расчета освещения в рамках данной статьи я описывать не буду, мы созьмем стандартный BlinnPhong.
CGPROGRAM #pragma surface surf BlinnPhong vertex:vert
CGPROGRAM — директива объявляющая что мы пишем на языке Cg (завершается директивой ENDCG).
Второй строкой мы объявляем что:
- процедура отрисовки поверхности называется surf
- в шейдере будет использоваться свет типа BlinnPhong (еще бывает Lambert, Unlit или своя процедура)
- процедура изменения вертексов называется vert
Теперь объявляем переменные, которые будут использоваться в коде:
sampler2D _MainTex; sampler2D _BumpMap; fixed4 _Color; half _Shininess; float _Amount;
Юнити позаботится о том, чтобы данные из параметров шейдера, объявленных вверху, попали в эти переменные. Важно назвать их так же как и в параметрах шейдера.
void vert (inout appdata_full v) { v.vertex.xyz += v.normal * v.vertex.xyz * _Amount ; }
А вот и процедура вертексной части нашего шейдера. Для примера возьмем координаты текущего вертекса и прибавим к ним нормаль умноженную на текущие координаты и коэффициент из параметров шейдера. Конечно получится какая-то хрень, но для примера вполне достаточно. Слайдером Amount вы можете регулировать степень искажения объекта.
Пример:

В официальной документации есть пример опухшего солдата. Они просто подняли все вертексы вдоль нормали:
v.vertex.xyz += v.normal * _Amount ;

Что еще есть внутри:
- float4 vertex — координаты текущего вертекса
- float3 normal — нормаль к поверхности в текущей точке
- float4 texcoord — UV координаты первой текстуры
- float4 texcoord1 — UV координаты второй текстуры
- float4 tangent — тангенс вектор
- float4 color — цвет вертекса
Тут можно прочитать полную статью по вертексным шейдерам.
struct Input { float2 uv_MainTex; float2 uv_BumpMap; };
В структуре Input вы можете попросить у шейдера дополнительные переменные, которые вам понадобятся для расчетов в процедуре поверхности. Мы попросили UV координаты обеих текстур. Переменная должна называться uv_НазваниеТекстуры для первых UV координат и uv2_НазваниеТекстуры соответственно для вторых.
Полный список того, что там можно указать вы найдете здесь.
Вот и сама процедура отрисовки поверхности:
void surf (Input IN, inout SurfaceOutput o) { //Получаем цвет точки по текущим UV координатам. fixed4 tex = tex2D(_MainTex, IN.uv_MainTex); //Смешиваем с оттенком из параметров o.Albedo = tex.rgb * _Color.rgb; //Степень отражения будет зависеть от яркости точки o.Gloss = tex.rgb; //Размытие будет зависеть от параметров шейдера o.Specular = _Shininess; //Распаковываем нормаль из соответствующей текстуры o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap)); }
В нее передается структура Input, а внутри надо заполнить структуру SurfaceOutput, которая имеет следующий вид:
struct SurfaceOutput { half3 Albedo; //Альбедо поверхности (Цвет) half3 Normal; //Нормаль поверхности half3 Emission; //Эмиссия (используется для расчета отражения) half Specular; //"Размытие" отблеска в данной точке (зависит от направления камеры (dot(viewDir, Normal)) half Gloss; //Сила отблеска в данной точке half Alpha; //Прозрачность в данной точке (не будет использоваться в "RenderType" = "Opaque") };
Заканчиваем шейдер строкой
FallBack "Specular"
Это значит, что если шейдер по каким-то причинам не заработает на клиентской машине, то нужно откатиться до шейдера Specular.
Полная версия шейдера из примера
Материалы использованные для написания статьи:
http://unity3d.com/support/documentation/Components/SL-SurfaceShaders
http://unity3d.com/support/documentation/Components/SL-SurfaceShaderExamples.html
http://unity3d.com/support/documentation/Components/SL-SubShader
http://unity3d.com/support/documentation/Components/SL-SubshaderTags
http://unity3d.com/support/documentation/Components/SL-ShaderReplacement.html
