Рано или поздно возможностей стандартных шейдеров вам станет не хватать, и тогда вам на помощь придут 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