Unity3D / Введение в Surface Shaders

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

В Surface Shader вы можете управлять освещением, тенями, путями отрисовки (rendering path) используя все тот же Cg / HLSL код.

Создание шейдера с нуля


Первая строка это путь шейдера
Shader "AgasperShaders/TestShader1" {

По этому пути он будет доступен в инспекторе.

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 ;


image

Что еще есть внутри:
  • 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


Если вы ведёте свой блог, микроблог, либо участвуете в какой-то популярной социальной сети, то вы можете быстро поделиться данной заметкой со своими друзьями и посетителями. Для этого воспользуйтесь предлагаемыми ниже кнопками:


Блог: http://romanlovetext.blogspot.com/