Vertexand and Fragmend Shader часть 2
Всем доброго времени суток! Надеюсь вы успешно переварили предыдущие
статьи по шейдерам ?)
Во второй части мы сделаем несколько более-сложный шейдер. Будем "добивать" тему с освещением: добавим блики иосвещенность, зависящую от нескольких источников света и сделаем втором сделаем эффект обводки(outline).
Прежде чем начнем писать сам шейдер, я хотел бы сделать небольшой ликбез
в способы ренера света, имеющиеся в юнити, а точнее по одному из них - упреждающему типу рендера (Forward Rendering).
статьи по шейдерам ?)
Во второй части мы сделаем несколько более-сложный шейдер. Будем "добивать" тему с освещением: добавим блики иосвещенность, зависящую от нескольких источников света и сделаем втором сделаем эффект обводки(outline).
Прежде чем начнем писать сам шейдер, я хотел бы сделать небольшой ликбез
в способы ренера света, имеющиеся в юнити, а точнее по одному из них - упреждающему типу рендера (Forward Rendering).
Forward Rendering Path
Если мы сейчас создадим какой-нибудь дополнительный источник света, например point light, и поднесем его к нашей сфере, то мы не увидим никакого эффекта. Никакая из частей сферы не станет светлее,а всё потому, что мы описали в нашем шейдере освещение, зависящее только от одного источника света - Directional Light'а.Если мы хотим, чтобы объект мог взаимодействовать с несколькими источниками света, нужно как-то указать шейдеру эти источники и способы их ренера.Благо в движке этот момент предусмотрен. Юнити предлагает нам на выбор несколько способов рендера света.
Способ рендера(Render Path) - это набор инструкций для движка,определяющих,каким образом обрабатывать рендер света и теней. Unity поддерживает четыре типа рендера: Deferred,Forward,Legacy Deferred,Vertex Lit. Каждый из них имеет свои преимущества и недостатки.
Сравнение и краткое описание каждого из них вы можете найти здесь. Итак, упреждающий рендер.
Способ рендера(Render Path) - это набор инструкций для движка,определяющих,каким образом обрабатывать рендер света и теней. Unity поддерживает четыре типа рендера: Deferred,Forward,Legacy Deferred,Vertex Lit. Каждый из них имеет свои преимущества и недостатки.
Сравнение и краткое описание каждого из них вы можете найти здесь. Итак, упреждающий рендер.
При упреждающем рендеринге, некоторое количество самых ярких источников света, влияющих на каждый объект, рендерится в режиме полной попиксельной подсветки. Далее до 4 точечных источников света рассчитываются повертексно. Остальные источники света рассчитываются по сферическим гармоникам (Spherical Harmonics, SH), которые значительно быстрее, но выдают усреднённый результат.
- Источники света всегда считаются повертексными или SH, если значение их свойства Render Mode (режим рендера) установлено в Not Important (не важно).
- Самый яркий из направленных источников света всегда является попиксельным.
- Источники света всегда считаются попиксельными, если значение их свойства Render Mode установлено в Important (важно).
- Если в результате вышеперечисленного, количество источников света будет меньше текущего Pixel Light Count (количества пиксельных источников освещения), указанного в настройках качества, тогда в порядке убывания яркости, попиксельно отрендерится ещё некоторое количество источников света.
Для более полной картины, давайте рассмотрим пример авто-сортировки источников света, приводимый в документации юнити:
Если предположить, что в данном примере на объект действуют восемь одинаковых ( с одинаковым цветом и интенсивностью) источников света, а так-же на источниках выставлен Auto render-mode, то тогда они будут
сортироваться для данного объекта именно так:
сортироваться для данного объекта именно так:
Наиболее интенсивные источники света(A,B,C,D) будут рендерится в режиме per-pixel. Наименее яркие источники (D-G) будут рендерится в режиме per-vertex, а остальные (G,H) в SH режиме.Учтите, что группы источников света перекрывают друг друга. Например, последний попиксельный источник света переходит в повертексный режим освещения, поэтому будет меньше резких изменений освещения при перемещении объектов и источников света.
Если включен Directional Light, он считается движком самым ярким источником света
Если включен Directional Light, он считается движком самым ярким источником света
Вот как происходит рендеринг каждого объекта в упреждающем ренеринге
- Базовый проход применяет один направленный попиксельный источник света( обычно Directional Light) и все повертексные/SH источники света.
- Остальные попиксельные источники света рендерятся в дополнительных проходах, один проход на один
Ну, надеюсь, теперь стало понятно. Мы с вами не станем делать полное Forward освещение, а выполним расчет только попиксельных источников. Т.е. Directionl Light + добавочные, наиболее яркие,источники света.
Что бы вручную указать, в каком режиме рендерить источник, нужно в компоненте Light, который есть у любого источника света, найти параметр Render Mode и выбрать нужное для нас значение:
Important - свет всегда просчитывается в per-pixel режиме.
Not Important - свет всегда просчитывается в per-vertex режиме.
Auto - использовался в примере, автоматически сортирует источники в зависимости от их яркости и близости к объекту.
Что бы вручную указать, в каком режиме рендерить источник, нужно в компоненте Light, который есть у любого источника света, найти параметр Render Mode и выбрать нужное для нас значение:
Important - свет всегда просчитывается в per-pixel режиме.
Not Important - свет всегда просчитывается в per-vertex режиме.
Auto - использовался в примере, автоматически сортирует источники в зависимости от их яркости и близости к объекту.
Обычно упреждающий рендеринг стоит в юнити по умолчанию, но, на всякий случай, следует еще раз указать движку тип рендера. Для этого идем в настройки рендера (Edit > Project Settings>Player) и ставим значение Forward.
Phong Lighting
Одной из самых распространенных моделей освещения, поддерживающая блики,является модель освещения Фонга. Она высчитывает освещение по формуле:
или, как она будет записана у нас :
I = pow(max(0,dot(refl,view)),_Shininess)
refl= reflect(l,view)
l - вектор света
refl - направление отраженного света
Shininess - коэффициент блеска
view - направление взгляда
I = pow(max(0,dot(refl,view)),_Shininess)
refl= reflect(l,view)
l - вектор света
refl - направление отраженного света
Shininess - коэффициент блеска
view - направление взгляда
Ну что-же, с теорией вроде-бы разобрались,самое время переходить к практике! Берем наш старый диффузный шейдер и начинаем творить.
Для начала нужно добавить недостающие параметры в блок Properties:
Для начала нужно добавить недостающие параметры в блок Properties:
Properties
{
_xgmTex("Texture",2D) = "white" {}
_Color ("LogoColor",Color) = (0,0,0,0)
_SpecColor("Specular Color",Color) = (0,0,0,0) // цвет блика
_Shininess ("Shinines",Float) = 10 // коэффициент блеска материала
}
Итак, сейчас мы делаем базовый проход, где будем рассчитывать самый яркий источник света ( в моем случае Directioal Light). Поставим нужный тег в Pass'е, что бы указать движку, что данный проход является базовым.
Pass
{
Tags{"LightMode" = "ForwardBase"} // базовый проход
...
}
Далее, объявим наши новые внешние переменные в CG коде:
uniform sampler2D _xgmTex;
uniform float4 _Color;
uniform float4 _SpecColor;
uniform float _Shininess;
Теперь, если внимательно посмотреть на формулу, то можно заметить, что нам не хватает вектора отраженного света и вектора взгляда. Вычислить отраженный вектор мы сможем с помощью функции reflect(float3 vec, float3 norm), где vec - вектор освещения, norm - вектор нормали.Оба этих вектора у нас уже есть. Вектор взгляда мы сможем вычислить, вычитая из мировых координат камеры, которые содержатся во встроенной переменной _WorldSpaceCameraPos, мировые координаты текущей точки. Надеюсь вы помните, что получить мировые координаты точки можно путем перемножения объектных координат на мировую матрицу( в юнити является так-же встроенной переменной - _Object2World).
Так-же, в этом шейдере мы не будем инвертировать цвета((1-col)), что бы нагляднее видеть блики.
Итак, для начала добавим в структуру vertexOutput переменную для мировых координат точки:
Так-же, в этом шейдере мы не будем инвертировать цвета(
Итак, для начала добавим в структуру vertexOutput переменную для мировых координат точки:
struct vertexOutput
{
...
float3 worldPos:TEXCOORD2;
};
Далее перебираемся в вертексный шейдер и перемножаем объектные координаты на мировую матрицу:
vertexOutput vert(vertexInput v)
{
...
o.worldPos=mul(_Object2World,v.vertex); // переводим координаты из пространства модели в мировое
return o;
}
Ну а теперь самое интересное - расчеты освещения в пиксельном шейдере :
fixed4 frag(vertexOutput v):SV_Target
{
fixed4 col = tex2D(_xgmTex,v.uv); // берем цвет из текстуры по UV координатам
float3 n = normalize(v.worldNormal); // нормализуем вектор нормали
float3 viewDir = normalize(_WorldSpaceCameraPos-v.worldPos); // вектор взгляда
float3 l; // направление света
float atten; // коэффициент затенения
if(_WorldSpaceLightPos0.w == 0) // Directional light
{
l = normalize(_WorldSpaceLightPos0.xyz);
atten = 1.0; // нет затенения
}else{ // point/spot light
l = _WorldSpaceLightPos0.xyz - v.worldPos.xyz;
atten = 1/length(l);
l = normalize(l);
}
float3 dif = atten*col* max(0.0,dot(n,l)); // рассчитываем цвет освещенного пикселя
float3 spec = float3(0,0,0);
if(dot(l,n)>0.0)
{
float3 refl = reflect(-l,n);
spec = atten * _SpecColor * pow(max(0.0,dot(refl,viewDir)),_Shininess);
}
return fixed4(dif+spec,1.0);
}
Итак, мы сделали первый проход. Теперь Directioal Light создает блик на нашей сфере. Задав красный цвет для спекуляра и покрутив Directional Light я получил такой-вот блик.
Код
Shader "MyShaders/xgmSkin"
{
Properties
{
_xgmTex("Texture",2D) = "white" {}
_Color ("LogoColor",Color) = (0,0,0,0)
_SpecColor("Specular Color",Color) = (0,0,0,0) // цвет блика
_Shininess ("Shinines",Float) = 10 // коэффициент блеска материала
}
SubShader
{
Pass
{
Tags{"LightMode"="ForwardBase"} // базовый проход
Cull Off
CGPROGRAM
#pragma vertex vert // говорим имя у вертексного шейдера
#pragma fragment frag // говорим имя пиксельного шейдера
uniform sampler2D _xgmTex;
uniform float4 _Color;
uniform float4 _SpecColor;
uniform float _Shininess;
struct vertexInput
{
float4 vertex:POSITION;
float2 uv:TEXCOORD0;
float3 norm:NORMAL;
};
struct vertexOutput
{
float4 position:SV_POSITION;
float2 uv:TEXCOORD0;
float3 worldNormal:TEXCOORD1;
float3 worldPos:TEXCOORD2;
};
vertexOutput vert(vertexInput v)
{
vertexOutput o; // возвращаемая структура
o.position = mul(UNITY_MATRIX_MVP,v.vertex); // переводим координаты из пространства модели в проекционное
o.uv = v.uv; // просто передаем uv координаты
o.worldNormal = mul(_World2Object,v.norm);
o.worldPos = mul(_Object2World,v.vertex); // переводим координаты из пространства модели в мировое
return o;
}
fixed4 frag(vertexOutput v):SV_Target
{
fixed4 col = tex2D(_xgmTex,v.uv); // берем цвет из текстуры по UV координатам
float3 n = normalize(v.worldNormal); // нормализуем вектор нормали
float3 viewDir = normalize(_WorldSpaceCameraPos-v.worldPos); // вектор взгляда
float3 l; // направление света
float atten; // коэффициент затенения
if(_WorldSpaceLightPos0.w == 0) // Directional light
{
l = normalize(_WorldSpaceLightPos0.xyz);
atten = 1.0; // нет затенения
}else{ // point/spot light
l = _WorldSpaceLightPos0.xyz - v.worldPos.xyz;
atten = 1/length(l);
l = normalize(l);
}
float3 dif = atten*col* max(0.0,dot(n,l)); // рассчитываем цвет освещенного пикселя
float3 spec = float3(0,0,0);
if(dot(l,n)>0.0)
{
float3 refl = reflect(-l,n);
spec = atten * _SpecColor * col * pow(max(0.0,dot(refl,viewDir)),_Shininess);
}
return fixed4(dif+spec,1.0);
}
ENDCG
}
}
Fallback "Diffuse"
}
Теперь напишем второй проход, где будем обрабатывать добавочные источники.
Создаем новый Pass после предыдущего,ставим ему тег добавочного прохода и добавляем смешивание(Blend), что бы этот Pass не перекрывал предыдущий:
Создаем новый Pass после предыдущего,ставим ему тег добавочного прохода и добавляем смешивание(Blend), что бы этот Pass не перекрывал предыдущий:
Pass
{
Tags{"LightMode" = "ForwardAdd"} // добавочный проход
Blend One One
}
Blend One One = складываем цвет текущего пикселя с цветом предыдущего
Т.к. предыдущий Pass у нас получился универсальным, весь CG код из него можно скопировать в этот и всё будет работать, т.к. мы сделали проверку переменной _WorldSpaceLightPos0 и в зависимости от того, какие данные она содержит,посчитали освещение.
Ну, в принципе вот и всё. У нас готов шейдер, который взаимодействует с несколькими источниками света и бликует.
Итог:
Т.к. предыдущий Pass у нас получился универсальным, весь CG код из него можно скопировать в этот и всё будет работать, т.к. мы сделали проверку переменной _WorldSpaceLightPos0 и в зависимости от того, какие данные она содержит,посчитали освещение.
Ну, в принципе вот и всё. У нас готов шейдер, который взаимодействует с несколькими источниками света и бликует.
Итог:
Финальный код
Shader "MyShaders/xgmSkin"
{
Properties
{
_xgmTex("Texture",2D) = "white" {}
_Color ("LogoColor",Color) = (0,0,0,0)
_SpecColor("Specular Color",Color) = (0,0,0,0) цвет блика
_Shininess ("Shinines",Float) = 10 коэффициент блеска материала
}
{
Properties
{
_xgmTex("Texture",2D) = "white" {}
_Color ("LogoColor",Color) = (0,0,0,0)
_SpecColor("Specular Color",Color) = (0,0,0,0) цвет блика
_Shininess ("Shinines",Float) = 10 коэффициент блеска материала
}
SubShader
{
Pass
{
Tags{"LightMode"="ForwardBase"} базовый проход
Cull Off
{
Pass
{
Tags{"LightMode"="ForwardBase"} базовый проход
Cull Off
CGPROGRAM
#pragma vertex vert говорим имя у вертексного шейдера
#pragma fragment frag говорим имя пиксельного шейдера
uniform sampler2D _xgmTex;
uniform float4 _Color;
uniform float4 _SpecColor;
uniform float _Shininess;
struct vertexInput
{
float4 vertex:POSITION;
float2 uv:TEXCOORD0;
float3 norm:NORMAL;
};
struct vertexOutput
{
float4 position:SV_POSITION;
float2 uv:TEXCOORD0;
float3 worldNormal:TEXCOORD1;
float3 worldPos:TEXCOORD2;
};
vertexOutput vert(vertexInput v)
{
vertexOutput o; возвращаемая структура
o.position = mul(UNITY_MATRIX_MVP,v.vertex); переводим координаты из пространства модели в проекционное
o.uv = v.uv; просто передаем uv координаты
o.worldNormal = mul(_World2Object,v.norm);
o.worldPos = mul(_Object2World,v.vertex); переводим координаты из пространства модели в мировое
return o;
}
fixed4 frag(vertexOutput v):SV_Target
{
fixed4 col = tex2D(_xgmTex,v.uv); берем цвет из текстуры по UV координатам
float3 n = normalize(v.worldNormal); нормализуем вектор нормали
float3 viewDir = normalize(_WorldSpaceCameraPos-v.worldPos); вектор взгляда
float3 l; направление света
float atten; коэффициент затенения
if(_WorldSpaceLightPos0.w == 0) Directional light
{
l = normalize(_WorldSpaceLightPos0.xyz);
atten = 1.0; нет затенения
}else{ point/spot light
l = _WorldSpaceLightPos0.xyz - v.worldPos.xyz;
atten = 1/length(l);
l = normalize(l);
}
float3 dif = atten*col* max(0.0,dot(n,l)); рассчитываем цвет освещенного пикселя
float3 spec = float3(0,0,0);
if(dot(l,n)>0.0)
{
float3 refl = reflect(-l,n);
spec = atten * _SpecColor * col * pow(max(0.0,dot(refl,viewDir)),_Shininess);
}
return fixed4(dif+spec,1.0);
}
ENDCG
#pragma vertex vert говорим имя у вертексного шейдера
#pragma fragment frag говорим имя пиксельного шейдера
uniform sampler2D _xgmTex;
uniform float4 _Color;
uniform float4 _SpecColor;
uniform float _Shininess;
struct vertexInput
{
float4 vertex:POSITION;
float2 uv:TEXCOORD0;
float3 norm:NORMAL;
};
struct vertexOutput
{
float4 position:SV_POSITION;
float2 uv:TEXCOORD0;
float3 worldNormal:TEXCOORD1;
float3 worldPos:TEXCOORD2;
};
vertexOutput vert(vertexInput v)
{
vertexOutput o; возвращаемая структура
o.position = mul(UNITY_MATRIX_MVP,v.vertex); переводим координаты из пространства модели в проекционное
o.uv = v.uv; просто передаем uv координаты
o.worldNormal = mul(_World2Object,v.norm);
o.worldPos = mul(_Object2World,v.vertex); переводим координаты из пространства модели в мировое
return o;
}
fixed4 frag(vertexOutput v):SV_Target
{
fixed4 col = tex2D(_xgmTex,v.uv); берем цвет из текстуры по UV координатам
float3 n = normalize(v.worldNormal); нормализуем вектор нормали
float3 viewDir = normalize(_WorldSpaceCameraPos-v.worldPos); вектор взгляда
float3 l; направление света
float atten; коэффициент затенения
if(_WorldSpaceLightPos0.w == 0) Directional light
{
l = normalize(_WorldSpaceLightPos0.xyz);
atten = 1.0; нет затенения
}else{ point/spot light
l = _WorldSpaceLightPos0.xyz - v.worldPos.xyz;
atten = 1/length(l);
l = normalize(l);
}
float3 dif = atten*col* max(0.0,dot(n,l)); рассчитываем цвет освещенного пикселя
float3 spec = float3(0,0,0);
if(dot(l,n)>0.0)
{
float3 refl = reflect(-l,n);
spec = atten * _SpecColor * col * pow(max(0.0,dot(refl,viewDir)),_Shininess);
}
return fixed4(dif+spec,1.0);
}
ENDCG
}
Pass
{
Tags{"LightMode" = "ForwardAdd"} дополнительный проход
Blend One One
CGPROGRAM
#pragma vertex vert говорим имя у вертексного шейдера
#pragma fragment frag говорим имя пиксельного шейдера
uniform sampler2D _xgmTex;
uniform float4 _Color;
uniform float4 _SpecColor;
uniform float _Shininess;
struct vertexInput
{
float4 vertex:POSITION;
float2 uv:TEXCOORD0;
float3 norm:NORMAL;
};
struct vertexOutput
{
float4 position:SV_POSITION;
float2 uv:TEXCOORD0;
float3 worldNormal:TEXCOORD1;
float3 worldPos:TEXCOORD2;
};
vertexOutput vert(vertexInput v)
{
vertexOutput o; возвращаемая структура
o.position = mul(UNITY_MATRIX_MVP,v.vertex); переводим координаты из пространства модели в проекционное
o.uv = v.uv; просто передаем uv координаты
o.worldNormal = mul(_World2Object,v.norm);
o.worldPos = mul(_Object2World,v.vertex); переводим координаты из пространства модели в мировое
return o;
}
fixed4 frag(vertexOutput v):SV_Target
{
fixed4 col = tex2D(_xgmTex,v.uv); берем цвет из текстуры по UV координатам
float3 n = normalize(v.worldNormal); нормализуем вектор нормали
float3 viewDir = normalize(_WorldSpaceCameraPos-v.worldPos); вектор взгляда
float3 l; направление света
float atten; коэффициент затенения
if(_WorldSpaceLightPos0.w == 0) Directional light
{
l = normalize(_WorldSpaceLightPos0.xyz);
atten = 1.0; нет затенения
}else{ point/spot light
l = _WorldSpaceLightPos0.xyz - v.worldPos.xyz;
atten = 1/length(l);
l = normalize(l);
}
float3 dif = atten*col* max(0.0,dot(n,l)); рассчитываем цвет освещенного пикселя
float3 spec = float3(0,0,0);
if(dot(l,n)>0.0)
{
float3 refl = reflect(-l,n);
spec = atten * _SpecColor * col * pow(max(0.0,dot(refl,viewDir)),_Shininess);
}
return fixed4(dif+spec,1.0);
}
ENDCG
}
}
Fallback "Diffuse"
}
{
Tags{"LightMode" = "ForwardAdd"} дополнительный проход
Blend One One
CGPROGRAM
#pragma vertex vert говорим имя у вертексного шейдера
#pragma fragment frag говорим имя пиксельного шейдера
uniform sampler2D _xgmTex;
uniform float4 _Color;
uniform float4 _SpecColor;
uniform float _Shininess;
struct vertexInput
{
float4 vertex:POSITION;
float2 uv:TEXCOORD0;
float3 norm:NORMAL;
};
struct vertexOutput
{
float4 position:SV_POSITION;
float2 uv:TEXCOORD0;
float3 worldNormal:TEXCOORD1;
float3 worldPos:TEXCOORD2;
};
vertexOutput vert(vertexInput v)
{
vertexOutput o; возвращаемая структура
o.position = mul(UNITY_MATRIX_MVP,v.vertex); переводим координаты из пространства модели в проекционное
o.uv = v.uv; просто передаем uv координаты
o.worldNormal = mul(_World2Object,v.norm);
o.worldPos = mul(_Object2World,v.vertex); переводим координаты из пространства модели в мировое
return o;
}
fixed4 frag(vertexOutput v):SV_Target
{
fixed4 col = tex2D(_xgmTex,v.uv); берем цвет из текстуры по UV координатам
float3 n = normalize(v.worldNormal); нормализуем вектор нормали
float3 viewDir = normalize(_WorldSpaceCameraPos-v.worldPos); вектор взгляда
float3 l; направление света
float atten; коэффициент затенения
if(_WorldSpaceLightPos0.w == 0) Directional light
{
l = normalize(_WorldSpaceLightPos0.xyz);
atten = 1.0; нет затенения
}else{ point/spot light
l = _WorldSpaceLightPos0.xyz - v.worldPos.xyz;
atten = 1/length(l);
l = normalize(l);
}
float3 dif = atten*col* max(0.0,dot(n,l)); рассчитываем цвет освещенного пикселя
float3 spec = float3(0,0,0);
if(dot(l,n)>0.0)
{
float3 refl = reflect(-l,n);
spec = atten * _SpecColor * col * pow(max(0.0,dot(refl,viewDir)),_Shininess);
}
return fixed4(dif+spec,1.0);
}
ENDCG
}
}
Fallback "Diffuse"
}
Готово. Можно смахнуть пот с задумчивого лица и заварить чашечку чая, хотя говорят, что настоящие проггеры пьют только кофе.Но сильно расслабляться не стоит, ибо сейчас мы преступаем к написанию OutLine шейдера.
OutLine Shader
Ну, чего тянуть? Давайте сразу взглянем на результат )
Вот это и есть outline шейдер. Он создаем эффект того, что объект обведен линией.Ладно,хватит слов,приступим к написанию!
Добавим новые внешние переменные в блок Properties
Добавим новые внешние переменные в блок Properties
Properties
{
_xgmTex("Texture",2D) = "white" {}
_Color ("LogoColor",Color) = (0,0,0,0)
_SpecColor("Specular Color",Color) = (0,0,0,0) // цвет блика
_Shininess ("Shinines",Float) = 10 // коэффициент блеска материала
_OutColor("Outline Color",Color) = (0,0,0,0) // цвет обводки
_Outline("Outline",Range(0.01,0.05)) = 0.05 // размер
}
Создаем еще один Pass. Его нужно расположить сверху, над всем остальными проходами. В этом Pass'е мы будем "раздувать" наш объект. Ставим ему отсечение полигонов спереди, что-бы "раздутая" сфера из этого прохода не перекрывал собой всю сферу целиком, которая будет рендериться в последующих проходах.
Pass
{
Cull Front // отсекаем полигоны спереди
ZTest Always // делаем Wall Hack "эффект "
}
Далее, в CG программе объявляем вертексный и пиксельный шейдеры,подключаем "UnityCG.cginc",содержащий в себе много полезных функций,которые нам понадобятся в этом проходе, а так-же, указываем внешние переменные,которые мы объявили ранее:
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
uniform float _Outline;
uniform float4 _OutColor;
Для того, что-бы "раздуть" объект, многого не требуется. Нам будет нужна только нормаль, ну и координаты вершины. Напишем входную и выходную структуры для вертексного шейдера:
struct vertexInput
{
float4 vertex:POSITION;
float3 normal:NORMAL;
};
struct vertexOutput
{
float4 pos:SV_POSITION;
float4 col:COLOR;
};
А теперь пришла очередь главному герою появится в коде, итак, дамы и господа, вертексный шейдер!
vertexOutput vert (vertexInput v)
{
vertexOutput o;
o.pos = mul(UNITY_MATRIX_MVP,v.vertex);
float3 viewNorm = mul((float3x3)UNITY_MATRIX_IT_MV,v.normal); // получаем нормаль в видовых координатах
float2 NormXY= TransformViewToProjection(viewNorm.xy); // функция из CGinclude. Переводим xy координаты нормали в проекционное пространство
o.pos.xy += NormXY * _Outline; // "раздуваем модел"
o.col = _OutColor;
return o;
}
Уже почти готово, осталось только задать цвет в пиксельном шейдере:
fixed4 frag (vertexOutput v):SV_Target
{
return v.col;
}
Всё! Шейдер готов.
Полный код
Shader "MyShaders/xgmSkin"
{
Properties
{
_xgmTex("Texture",2D) = "white" {}
_Color ("LogoColor",Color) = (0,0,0,0)
_SpecColor("Specular Color",Color) = (0,0,0,0) // цвет блика
_Shininess ("Shinines",Float) = 10 // коэффициент блеска материала
_OutColor("Outline Color",Color) = (0,0,0,0) // цвет обводки
_Outline("Outline",Range(0.01,0.05)) = 0.05 // размер обводки
}
SubShader
{
Tags{"RenderType" = "Transparent"}
Pass
{
Cull Front
ZTest Always
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
uniform float _Outline;
uniform float4 _OutColor;
struct vertexInput
{
float4 vertex:POSITION;
float3 normal:NORMAL;
};
struct vertexOutput
{
float4 pos:SV_POSITION;
float4 col:COLOR;
};
vertexOutput vert (vertexInput v)
{
vertexOutput o;
o.pos = mul(UNITY_MATRIX_MVP,v.vertex);
float3 viewNorm = mul((float3x3)UNITY_MATRIX_IT_MV,v.normal); // получаем нормаль в видовых координатах
float2 viewNormXY= TransformViewToProjection(viewNorm.xy); // функция из CGinclude.Переводим координаты нормали в проекционное простанство
o.pos.xy += viewNormXY * _Outline; // "раздуваем модель"
o.col = _OutColor;
return o;
}
fixed4 frag (vertexOutput v):SV_Target
{
return v.col;
}
ENDCG
}
Pass
{
Tags{"LightMode"="ForwardBase"} // базовый проход
Cull Off
CGPROGRAM
#pragma vertex vert // говорим имя у вертексного шейдера
#pragma fragment frag // говорим имя пиксельного шейдера
uniform sampler2D _xgmTex;
uniform float4 _Color;
uniform float4 _SpecColor;
uniform float _Shininess;
struct vertexInput
{
float4 vertex:POSITION;
float2 uv:TEXCOORD0;
float3 norm:NORMAL;
};
struct vertexOutput
{
float4 position:SV_POSITION;
float2 uv:TEXCOORD0;
float3 worldNormal:TEXCOORD1;
float3 worldPos:TEXCOORD2;
};
vertexOutput vert(vertexInput v)
{
vertexOutput o; // возвращаемая структура
o.position = mul(UNITY_MATRIX_MVP,v.vertex); // переводим координаты из пространства модели в проекционное
o.uv = v.uv; // просто передаем uv координаты
o.worldNormal = mul(_World2Object,v.norm);
o.worldPos = mul(_Object2World,v.vertex); // переводим координаты из пространства модели в мировое
return o;
}
fixed4 frag(vertexOutput v):SV_Target
{
fixed4 col = tex2D(_xgmTex,v.uv); // берем цвет из текстуры по UV координатам
float3 n = normalize(v.worldNormal); // нормализуем вектор нормали
float3 viewDir = normalize(_WorldSpaceCameraPos-v.worldPos); // вектор взгляда
float3 l; // направление света
float atten; // коэффициент затенения
if(_WorldSpaceLightPos0.w == 0) // Directional light
{
l = normalize(_WorldSpaceLightPos0.xyz);
atten = 1.0; // нет затенения
}else{ // point/spot light
l = _WorldSpaceLightPos0.xyz - v.worldPos.xyz;
atten = 1/length(l);
l = normalize(l);
}
float3 dif = atten*col* max(0.0,dot(n,l)); // рассчитываем цвет освещенного пикселя
float3 spec = float3(0,0,0);
if(dot(l,n)>0.0)
{
float3 refl = reflect(-l,n);
spec = atten * _SpecColor* pow(max(0.0,dot(refl,viewDir)),_Shininess);
}
return fixed4(dif+spec,1.0);
}
ENDCG
}
Pass
{
Tags{"LightMode" = "ForwardAdd"} // дополнительный проход
Blend One One
CGPROGRAM
#pragma vertex vert // говорим имя у вертексного шейдера
#pragma fragment frag // говорим имя пиксельного шейдера
uniform sampler2D _xgmTex;
uniform float4 _Color;
uniform float4 _SpecColor;
uniform float _Shininess;
struct vertexInput
{
float4 vertex:POSITION;
float2 uv:TEXCOORD0;
float3 norm:NORMAL;
};
struct vertexOutput
{
float4 position:SV_POSITION;
float2 uv:TEXCOORD0;
float3 worldNormal:TEXCOORD1;
float3 worldPos:TEXCOORD2;
};
vertexOutput vert(vertexInput v)
{
vertexOutput o; // возвращаемая структура
o.position = mul(UNITY_MATRIX_MVP,v.vertex); // переводим координаты из пространства модели в проекционное
o.uv = v.uv; // просто передаем uv координаты
o.worldNormal = mul(_World2Object,v.norm);
o.worldPos = mul(_Object2World,v.vertex); // переводим координаты из пространства модели в мировое
return o;
}
fixed4 frag(vertexOutput v):SV_Target
{
fixed4 col = tex2D(_xgmTex,v.uv); // берем цвет из текстуры по UV координатам
float3 n = normalize(v.worldNormal); // нормализуем вектор нормали
float3 viewDir = normalize(_WorldSpaceCameraPos-v.worldPos); // вектор взгляда
float3 l; // направление света
float atten; // коэффициент затенения
if(_WorldSpaceLightPos0.w == 0) // Directional light
{
l = normalize(_WorldSpaceLightPos0.xyz);
atten = 1.0; // нет затенения
}else{ // point/spot light
l = _WorldSpaceLightPos0.xyz - v.worldPos.xyz;
atten = 1/length(l);
l = normalize(l);
}
float3 dif = atten*col* max(0.0,dot(n,l)); // рассчитываем цвет освещенного пикселя
float3 spec = float3(0,0,0);
if(dot(l,n)>0.0)
{
float3 refl = reflect(-l,n);
spec = atten * _SpecColor * pow(max(0.0,dot(refl,viewDir)),_Shininess);
}
return fixed4(dif+spec,1.0);
}
ENDCG
}
}
Fallback "Diffuse"
}
Как всё выглядит в игре:
to be continued...