Unity 3D: Основы программирования шейдеров #4 Vertex and Fragment Shader 2

Основы программирования шейдеров. #1 Краткая теория

Vertexand and Fragmend Shader часть 2

Всем доброго времени суток! Надеюсь вы успешно переварили предыдущие
статьи по шейдерам ?)
Во второй части мы сделаем несколько более-сложный шейдер. Будем "добивать" тему с освещением: добавим блики иосвещенность, зависящую от нескольких источников света и сделаем втором сделаем эффект обводки(outline).
Прежде чем начнем писать сам шейдер, я хотел бы сделать небольшой ликбез
в способы ренера света, имеющиеся в юнити, а точнее по одному из них - упреждающему типу рендера (Forward Rendering).

Forward Rendering Path


Если мы сейчас создадим какой-нибудь дополнительный источник света, например point light, и поднесем его к нашей сфере, то мы не увидим никакого эффекта. Никакая из частей сферы не станет светлее,а всё потому, что мы описали в нашем шейдере освещение, зависящее только от одного источника света - Directional Light'а.Если мы хотим, чтобы объект мог взаимодействовать с несколькими источниками света, нужно как-то указать шейдеру эти источники и способы их ренера.Благо в движке этот момент предусмотрен. Юнити предлагает нам на выбор несколько способов рендера света.
Способ рендера(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) и все повертексные/SH источники света.
  • Остальные попиксельные источники света рендерятся в дополнительных проходах, один проход на один
источник света.
Ну, надеюсь, теперь стало понятно. Мы с вами не станем делать полное Forward освещение, а выполним расчет только попиксельных источников. Т.е. Directionl Light + добавочные, наиболее яркие,источники света.
Что бы вручную указать, в каком режиме рендерить источник, нужно в компоненте 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 - направление взгляда
Ну что-же, с теорией вроде-бы разобрались,самое время переходить к практике! Берем наш старый диффузный шейдер и начинаем творить.
Для начала нужно добавить недостающие параметры в блок 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 переменную для мировых координат точки:
      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
   {
    Tags{"LightMode" = "ForwardAdd"} // добавочный проход
    Blend One One 
   } 
Blend One One = складываем цвет текущего пикселя с цветом предыдущего
Т.к. предыдущий 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 коэффициент блеска материала
}
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
}
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"
}
Готово. Можно смахнуть пот с задумчивого лица и заварить чашечку чая, хотя говорят, что настоящие проггеры пьют только кофе.Но сильно расслабляться не стоит, ибо сейчас мы преступаем к написанию OutLine шейдера.

OutLine Shader


Ну, чего тянуть? Давайте сразу взглянем на результат )
Вот это и есть outline шейдер. Он создаем эффект того, что объект обведен линией.Ладно,хватит слов,приступим к написанию!
Добавим новые внешние переменные в блок 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"
 } 
Как всё выглядит в игре:
Unity Logo Запустить плеер:
qwerty.unity3d

to be continued...

Просмотров: 1 893

Комментарии пока отсутcтвуют