Vertex and Fragment Shader
В этой статье мы напишем простой вертексно-пиксельный шейдер, который
будет натягивать текстуру(логотип XGM) на объект(в моем случае на
сферу).
Итак, приступим.
Для начала создадим папку, где будем хранить шейдеры. Назовем ее
Shaders. Далее создадим шейдер: правый клик по папке
Shaders>Create>Shader>Unlit Shader. Назовем шейдер xgmSkin.
Перед нами открылось окно редактора с содержанием стандартного Unlit
Shader'а. Выделяем все и смело удаляем. Начнем, как говориться, "с чистого листа".
Из предыдущей статьи мы уяснили, что в Unity шейдер пишется внутри
SubShader'а. Так-что давайте сначала напишем "фундамент".
Shader "MyShaders/xgmSkin" // путь и имя шейдера в выпадающем окне материала { Properties // отображаемые в инспекторе свойства { } SubShader { Pass { CGPROGRAM // тут будет CG код CGEND } } Fallback "Diffuse" }
Начнем заполнять блок Properties. В нем мы обозначаем внешние
переменные, которые будут видны в окне материала в инспекторе. Здесь нам
многого не нужно, только текстурка. Пишем:
Properties { _xgmTex("Texture",2D) = "white" {} }
Ну и всё, никаких тэгов писать не будем, нам подходят настройки по
дефолту, так что сразу спускаемся в блок CGPROGRAM.
В статье по теории я писал, что мы можем назначать, какие данные примет и
вернет шейдер при помощи семантики. Для удобства записи в CG программе
эти данные объединяют в структуры. Давайте опишем входящую(vertexInput) и
выходящую(vertexOutput) структуры для вертексного шейдера:
struct vertexInput // входящие данные { float4 vertex:POSITION; // позиция вершины в пространстве модели float2 uv:TEXCOORD0; // uv координаты данной вершины }; struct vertexOutput // выходящие данные { float4 position:SV_POSITION; // позиция float2 uv:TEXCOORD0; // uv координаты };
Данные описаны, теперь самое время писать векртексный шейдер. Ой, чуть не забыл)
Для того, что бы обозначить функцию как вертексный или пиксельный
шейдер, мы должны назначить имена для этих функций в директиве #pragma.
#pragma vertex vert #pragma fragmet frag
А вот собственно сам вертексный шейдер:
vertexOutput vert(vertexInput v) { vertexOutput o; // создаем возвращаемую структуру o.position = mul(UNITY_MATRIX_MVP,poss); // переводим координатыиз пространства модели в проекционное o.uv = v.uv; // просто передаем uv координаты return o; }
Пиксельный шейдер возвращает только цвет, так что нужды создавать
отдельную структуру для него у нас нет. Поэтому,назначаем семантику
прямо для самого возвращаемого значения функции frag(Семантика для цвета
COLOR или SV_Target ). Так же нам нужно вытащить текстуру из
Properties.Напомню, что имя внешней переменной должно быть такое-же, как в
Properties, что бы движок сам передал ей значение.
uniform sampler2D _xgmTex; // внешняя текстура fixed4 frag(vertexOutput v):SV_Target { fixed4 col = tex2D(_xgmTex,v.uv);// берем цвет из текстуры по UV координатам return col; }
Ну вот и всё, наш шейдер готов!
Shader "MyShaders/xgmSkin" { Properties { _xgmTex("Texture",2D) = "white" {} } SubShader { Pass { CGPROGRAM #pragma vertex vert // говорим имя у вертексного шейдера #pragma fragment frag // говорим имя пиксельного шейдера struct vertexInput { float4 vertex:POSITION; float2 uv:TEXCOORD0; }; struct vertexOutput { float4 position:SV_POSITION; float2 uv:TEXCOORD0; }; vertexOutput vert(vertexInput v) { vertexOutput o; // возвращаемая структура o.position = mul(UNITY_MATRIX_MVP,v.vertex); // переводим координаты из пространства модели в проекционное o.uv = v.uv; // просто передаем uv координаты return o; } uniform sampler2D _xgmTex; // внешняя текстура fixed4 frag(vertexOutput v):SV_Target { fixed4 col = tex2D(_xgmTex,v.uv); // берем цвет из текстуры по UV координатам return col; } ENDCG } } Fallback "Diffuse" }
Пора применить его на объекте! Для того что-бы применить шейдер для
какого-либо объекта, нужно создать новый материал и назначить наш шейдер
в выпадающем окне материала(если вы не забыли, наш шейдер находится в MyShaders>xgmSkin)
Теперь выберем нужную текстуру в окне материала.
Вуаля!
Теперь,думаю,стоит немного усложнить наш шейдер.Добавим в блок
Properties цвет, что-бы иметь возможность вручную управлять цветом.
_Color ("LogoColor",Color) = (0,0,0,0)
И объявим его внутри шейдера в том-же месте, где объявляли текстуру
uniform fixed4 _Color;
Надеюсь,вы читали первую статью, ну вот ту часть про цвет ? В общем сейчас мы
хотим поколдовать с цветом.
В итоге я изменил одну строчку в пиксельном шейдере
fixed4 frag(vertexOutput v):SV_Target { fixed4 col = tex2D(_xgmTex,v.uv); // берем цвет из текстуры по UV координатам return (1-col)*_Color; }
В итоге,если мы установим _Color на зеленый цвет, у нас получилась
такая вот сфера, у которой мы можем менять цвет текстуры логотипа по
нашему желанию.)
Итак, теперь мы, вроде, умеем делать простые-препростые шейдеры. Давайте
капнем немного глубже и сделаем нашей сфере дифузное освещение.
Вот формула, по которой рассчитывается освещенность для точки:
dif = max(0,(n*l)), где n - нормаль, l - направление света.
Заранее скажу, что рассчитывать освещение мы будет в мировых
координатах, следовательно, нормаль, которая дается в объектных
координатах придется переводить в мировые. Обычная матрица, которую мы
используем для частиц тут не подойдет, так-что для таких случаев
припасена встроенная инверсная матрица _World2Object.
Так-же хочу сказать о том, что в ShaderLab есть встроенная переменная
_WorldSpaceLightPos0. По названию можно понять, что это вектор, который
содержит мировые координаты источника света, но тут есть одна хитрость.
Если на карте используется только 1 источник света ( Direction light),
то тогда в эту переменную записывается вектор освещения, а если больше,
то ,действительно, положение источника света. В данном случае у нас один
источник, так что эта переменная нам подходит.
Еще кое-что.Как мы все знаем, семантика TEXCOORD используется для передачи
uv координат. Но, т.к. их может быть несколько штук, например,
TEXCOORD1,TEXCOORD2,TEXCOORD3..., ее удобно использовать, что-бы
передать нужные данные из вертексного шейдера в пиксельный, т.к. у
переменных во входящих/выходящих струтурах обязательно должна семантика.
Ну вот теперь вроде всё. Поехали!
Добавим в vertexInput строчку, которая будет запрашивать нормаль:
float3 norm:NORMAL;
И в vertexOutput переменную для хранения мировых координат нормали:
float3 worldNormal:TEXCOORD1;
Далее идем в вертексный шейдер и переводим нормаль из объектного в
мировые координаты:
o.worldNormal = mul(_World2Object,v.norm);
Ну и рассчитаем силу освещения в пиксельном шейдере:
fixed4 frag(vertexOutput v):SV_Target { fixed4 col = tex2D(_xgmTex,v.uv); // берем цвет из текстуры по UV координатам float3 l = normalize(_WorldSpaceLightPos0); // нормализуем вектор освещения float3 n = normalize(v.worldNormal); // нормализуем вектор нормали float dif = max(0.0,dot(n,l)); // рассчитываем освещенность пикселя return ((1-col)*_Color)*dif; }
Ну вот как-то так. Теперь, если мы перейдем в юнити и изменим
направление лучей у Direction light'а, то заметим, что участки с
противоположной стороны от света затеняться.
Shader "MyShaders/xgmSkin"
{
Properties
{
_xgmTex("Texture",2D) = "white" {}
_Color ("LogoColor",Color) = (0,0,0,0)
}
SubShader
{
Pass
{
Cull Off
CGPROGRAM
#pragma vertex vert // говорим имя у вертексного шейдера
#pragma fragment frag // говорим имя пиксельного шейдера
struct vertexInput
{
float4 vertex:POSITION;
float2 uv:TEXCOORD0;
float3 norm:NORMAL;
};
struct vertexOutput
{
float4 position:SV_POSITION;
float2 uv:TEXCOORD0;
float3 worldNormal:TEXCOORD1;
};
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); // переводим нормаль в мировые к-ты
return o;
}
uniform sampler2D _xgmTex; // внешняя текстура
uniform fixed4 _Color;
fixed4 frag(vertexOutput v):SV_Target
{
fixed4 col = tex2D(_xgmTex,v.uv); // берем цвет из текстуры по UV координатам
float3 l = normalize(_WorldSpaceLightPos0); // нормализуем вектор освещения
float3 n = normalize(v.worldNormal); // нормализуем вектор нормали
float dif = max(0.0,dot(n,l)); // рассчитываем освещенность пикселя
return ((1-col)*_Color)*dif;
}
ENDCG
}
}
Fallback "Diffuse"
}
Итог:
http://devtribe.ru/ext-files/562/174812/qwerty.unity3d
To be continued ...
P.S. спонсоры данных статьей - кофе и свободное время. Спасибо им за то что они есть.Смотрите также:
Комментарии
Здесь еще никто не оставил комментарий
CollectableItemData.cs
[CreateMenuItem(fileName = "newItem", menuName = "Data/Items/Collectable", order = 51]