Добавлен lehanru,
опубликован
Vertex and Fragment Shader
В этой статье мы напишем простой вертексно-пиксельный шейдер, который
будет натягивать текстуру(логотип XGM) на объект(в моем случае на
сферу).
Итак, приступим.
Для начала создадим папку, где будем хранить шейдеры. Назовем ее
Shaders. Далее создадим шейдер: правый клик по папке
Shaders>Create>Shader>Unlit Shader. Назовем шейдер xgmSkin.
будет натягивать текстуру(логотип XGM) на объект(в моем случае на
сферу).
Итак, приступим.
Для начала создадим папку, где будем хранить шейдеры. Назовем ее
Shaders. Далее создадим шейдер: правый клик по папке
Shaders>Create>Shader>Unlit Shader. Назовем шейдер xgmSkin.
Перед нами открылось окно редактора с содержанием стандартного Unlit
Shader'а. Выделяем все и смело удаляем. Начнем, как говориться, "с чистого листа".
Из предыдущей статьи мы уяснили, что в Unity шейдер пишется внутри
SubShader'а. Так-что давайте сначала напишем "фундамент".
Shader'а. Выделяем все и смело удаляем. Начнем, как говориться, "с чистого листа".
Из предыдущей статьи мы уяснили, что в Unity шейдер пишется внутри
SubShader'а. Так-что давайте сначала напишем "фундамент".
Shader "MyShaders/xgmSkin" // путь и имя шейдера в выпадающем окне
материала
{
Properties // отображаемые в инспекторе свойства
{
}
SubShader
{
Pass
{
CGPROGRAM
// тут будет CG код
CGEND
}
}
Fallback "Diffuse"
}
Начнем заполнять блок Properties. В нем мы обозначаем внешние
переменные, которые будут видны в окне материала в инспекторе. Здесь нам
многого не нужно, только текстурка. Пишем:
переменные, которые будут видны в окне материала в инспекторе. Здесь нам
многого не нужно, только текстурка. Пишем:
Properties
{
_xgmTex("Texture",2D) = "white" {}
}
Ну и всё, никаких тэгов писать не будем, нам подходят настройки по
дефолту, так что сразу спускаемся в блок CGPROGRAM.
В статье по теории я писал, что мы можем назначать, какие данные примет и
вернет шейдер при помощи семантики. Для удобства записи в CG программе
эти данные объединяют в структуры. Давайте опишем входящую(vertexInput) и
выходящую(vertexOutput) структуры для вертексного шейдера:
дефолту, так что сразу спускаемся в блок 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.
#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, что бы движок сам передал ей значение.
отдельную структуру для него у нас нет. Поэтому,назначаем семантику
прямо для самого возвращаемого значения функции 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)
какого-либо объекта, нужно создать новый материал и назначить наш шейдер
в выпадающем окне материала(если вы не забыли, наш шейдер находится в MyShaders>xgmSkin)
Теперь выберем нужную текстуру в окне материала.
Вуаля!
Вуаля!
Теперь,думаю,стоит немного усложнить наш шейдер.Добавим в блок
Properties цвет, что-бы иметь возможность вручную управлять цветом.
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;
}
1=(1,1,1,1); 0 = (0,0,0,0)
В итоге,если мы установим _Color на зеленый цвет, у нас получилась
такая вот сфера, у которой мы можем менять цвет текстуры логотипа по
нашему желанию.)
В итоге,если мы установим _Color на зеленый цвет, у нас получилась
такая вот сфера, у которой мы можем менять цвет текстуры логотипа по
нашему желанию.)
Итак, теперь мы, вроде, умеем делать простые-препростые шейдеры. Давайте
капнем немного глубже и сделаем нашей сфере дифузное освещение.
Вот формула, по которой рассчитывается освещенность для точки:
dif = max(0,(n*l)), где n - нормаль, l - направление света.
капнем немного глубже и сделаем нашей сфере дифузное освещение.
Вот формула, по которой рассчитывается освещенность для точки:
dif = max(0,(n*l)), где n - нормаль, l - направление света.
Заранее скажу, что рассчитывать освещение мы будет в мировых
координатах, следовательно, нормаль, которая дается в объектных
координатах придется переводить в мировые. Обычная матрица, которую мы
используем для частиц тут не подойдет, так-что для таких случаев
припасена встроенная инверсная матрица _World2Object.
Так-же хочу сказать о том, что в ShaderLab есть встроенная переменная
_WorldSpaceLightPos0. По названию можно понять, что это вектор, который
содержит мировые координаты источника света, но тут есть одна хитрость.
Если на карте используется только 1 источник света ( Direction light),
то тогда в эту переменную записывается вектор освещения, а если больше,
то ,действительно, положение источника света. В данном случае у нас один
источник, так что эта переменная нам подходит.
Еще кое-что.Как мы все знаем, семантика TEXCOORD используется для передачи
uv координат. Но, т.к. их может быть несколько штук, например,
TEXCOORD1,TEXCOORD2,TEXCOORD3..., ее удобно использовать, что-бы
передать нужные данные из вертексного шейдера в пиксельный, т.к. у
переменных во входящих/выходящих струтурах обязательно должна семантика.
Ну вот теперь вроде всё. Поехали!
Добавим в vertexInput строчку, которая будет запрашивать нормаль:
координатах, следовательно, нормаль, которая дается в объектных
координатах придется переводить в мировые. Обычная матрица, которую мы
используем для частиц тут не подойдет, так-что для таких случаев
припасена встроенная инверсная матрица _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'а, то заметим, что участки с
противоположной стороны от света затеняться.
направление лучей у Direction light'а, то заметим, что участки с
противоположной стороны от света затеняться.
Полный код шейдера
Shader "MyShaders/xgmSkin"
{
Properties
{
_xgmTex("Texture",2D) = "white" {}
_Color ("LogoColor",Color) = (0,0,0,0)
}
{
Properties
{
_xgmTex("Texture",2D) = "white" {}
_Color ("LogoColor",Color) = (0,0,0,0)
}
SubShader
{
Pass
{
Cull Off
{
Pass
{
Cull Off
CGPROGRAM
#pragma vertex vert говорим имя у вертексного шейдера
#pragma fragment frag говорим имя пиксельного шейдера
#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;
{
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
{
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"
}
}
Fallback "Diffuse"
}
Итог:
To be continued ...
P.S. спонсоры данных статьей - кофе и свободное время. Спасибо им за то
что они есть.
P.S. спонсоры данных статьей - кофе и свободное время. Спасибо им за то
что они есть.
Содержание
`
ОЖИДАНИЕ РЕКЛАМЫ...
Комментарии пока отсутcтвуют.
Чтобы оставить комментарий, пожалуйста, войдите на сайт.