Dreaman Dreaman

Jusper, спасибо! Рад, что понравилось :)

Dreaman Dreaman

В новом помещении космической станции появилось очень важное устройство. По своей сути оно является энергетическим реактором, снабжающим станцию энергией.

...
iRediKurou iRediKurou

Jusper, UE сам движок увесистый это да. При билде требования на много ниже, но если болт забить то видюху пожгет тоже.

Блюпринтами можно реализовать самую элементарную логику. В проекте на С++ можно делать ноды для блюпринтов...

...
Jusper Jusper

С учетом того, каждая поделка на Unreal лагает как тварь если у тебя не топ PC, я бы тут поспорил. Без скиллов оптимизации все это графонистое будет играться только на топовом железе, а поверь, владельцы топового железа будут играть в блокбастеры...

...
iRediKurou iRediKurou

Jusper, если коротко: Unity ушла не туда по развитию.

Если подробнее:

  1. XYZ на курсе GameDesing на обучении используется UE. Потому, что он на много удобен для прототипирования (очень много из коробки).
  2. ...
Jusper Jusper

iRediKurou,

А чем, кстати, Unity не пошла?

iRediKurou iRediKurou

Ynomomento, Добрый день. Переносим игру с Unity на UE4. Потестировать прототип без сервера можно будет всем. Сейчас переписываются игровые механики и делается OutBlock. Подробнее на странице группы в ВК...

Ynomomento Ynomomento

Здравствуйте! можно ли уже поиграть в этот шедевр?) какими либо способами

Jusper Jusper

6 ударов в колокол и 1 крик Вильгельма.
Slash Polygon снова с вами.

Jusper Jusper

Я не до конца уверен, но, вероятнее всего, у тебя каждый раз определяется переменная enemy_hp = 100, потом ты от нее отнимаешь 20 и получаешь всегда на выходе 80. Оператор отрабатывает каждый раз, но при это значение переменной берет дефолтное ...

ternox92 ternox92
Мой новый интерфейс
Мой новый интерфейс
...
ondadesign ondadesign

Я купил, ща буду проходить))))))))))))))))
Если честно, привлекла музыка из трейлера)

Dreaman Dreaman

Недавно ко мне поступили новые замечания по поводу сложных моментов в игре.
И мной были сделаны некоторые изменения, которые должны облегчить прохождение игры.

TheDarkestRed TheDarkestRed

Позвали дракона на вечеринку 🎮 🥳 🔥 🐉

https://vk.com/the_darkestred

TheDarkestRed TheDarkestRed

Поработали над окружением 🎮 🏛
https://vk.com/the_darkestred

Zemlaynin Zemlaynin

alexprey, не, все на Java у меня.

alexprey alexprey

Концепты очень хорошо выглядят!

Так, я думал у вас плюсы)

Логотип проекта Unity

#4 Vertex and Fragment Shader 2

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 (количества пиксельных источников освещения), указанного в настройках качества, тогда в порядке убывания яркости, попиксельно отрендерится ещё некоторое количество источников света.

Для более полной картины, давайте рассмотрим пример авто-сортировки источников света, приводимый в документации юнити:

#4 Vertex and Fragment Shader 2 — Unity — DevTribe: инди-игры, разработка, сообщество

Если предположить, что в данном примере на объект действуют восемь одинаковых ( с одинаковым цветом и интенсивностью) источников света, а так-же на источниках выставлен Auto render-mode, то тогда они будут
сортироваться для данного объекта именно так:

#4 Vertex and Fragment Shader 2 — Unity — DevTribe: инди-игры, разработка, сообщество

Наиболее интенсивные источники света(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 - использовался в примере, автоматически сортирует источники в зависимости от их яркости и близости к объекту.

#4 Vertex and Fragment Shader 2 — Unity — DevTribe: инди-игры, разработка, сообщество

Обычно упреждающий рендеринг стоит в юнити по умолчанию, но, на всякий случай, следует еще раз указать движку тип рендера. Для этого идем в настройки рендера (Edit > Project Settings>Player) и ставим значение Forward.

#4 Vertex and Fragment Shader 2 — Unity — DevTribe: инди-игры, разработка, сообщество

Phong Lighting

Одной из самых распространенных моделей освещения, поддерживающая блики,является модель освещения Фонга. Она высчитывает освещение по формуле:

#4 Vertex and Fragment Shader 2 — Unity — DevTribe: инди-игры, разработка, сообщество

или, как она будет записана у нас :
I = pow(max(0,dot(refl,view)),_Shininess)
refl= reflect(l,view)
l - вектор света
refl - направление отраженного света
Shininess - коэффициент блеска
view - направление взгляда

#4 Vertex and Fragment Shader 2 — Unity — DevTribe: инди-игры, разработка, сообщество

Ну что-же, с теорией вроде-бы разобрались,самое время переходить к практике! Берем наш старый диффузный шейдер и начинаем творить.
Для начала нужно добавить недостающие параметры в блок 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 я получил такой-вот блик.

#4 Vertex and Fragment Shader 2 — Unity — DevTribe: инди-игры, разработка, сообщество
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 и в зависимости от того, какие данные она содержит,посчитали освещение.
Ну, в принципе вот и всё. У нас готов шейдер, который взаимодействует с несколькими источниками света и бликует.
Итог:

#4 Vertex and Fragment Shader 2 — Unity — DevTribe: инди-игры, разработка, сообщество

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

Ну, чего тянуть? Давайте сразу взглянем на результат )

#4 Vertex and Fragment Shader 2 — Unity — DevTribe: инди-игры, разработка, сообщество

Вот это и есть 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"
 } 

Как всё выглядит в игре:
http://devtribe.ru/ext-files/562/177368/qwerty.unity3d


to be continued...

Смотрите также:


Комментарии



статья уже пригодилась

Справка