Jusper Jusper

Мы попали в подборку #MadeWithUnity
На 4:58

Loya Loya

Func подчеркивает красным. Подскажите какую библиотеку нужно подключить?

Jusper Jusper

Normanof,

Автор довольно давно не заходил.

Normanof Normanof

Куда можно задать вопрос по поводу игры?
Как сделать так, чтобы карта двигалась за персонажем? т.е. персонаж - всегда в центре, а при движении в любом направлении, карта тоже двигалась так, чтобы персонаж снова оказывался в центре...

Jusper Jusper

Довольно сражений! Поработаем над внутрянкой. Как насчет экипировки? 🗡

Jusper Jusper

Так-то мы вчера и такое сделали.

Jusper Jusper

alexprey, ты предлагаешь добавить это самое?

alexprey alexprey

Jusper, если так, то не только на бороде уж останавливаться 🤣

Jusper Jusper

Всю недельную работу можно склеить в один бодрый видос:

...
Rummy_Games Rummy_Games

Доброго субботнего вечера! Сегодня мы ходим поделиться наработками нашего 3D-моделлера в рамках #saturdayscreenshot. Концепты космического инженера.

...
Razz Razz

Это последний уровень, где я свечу скриншотами и гифками. Босс и весь следующий уровень - секрет

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

#6 Surface Shader

Surface Shader

Всем доброго времени суток! В этой статье мы разберемся с тем, как работают поверхностные (surface) шейдеры, а также напишем что-нибудь своё.Чтобы понимать, что тут происходит, вы должны освоить эту и вот эту вот статью.

Вступление

Думаю мало кто не согласится с тем, что написание вертексных и пиксельных шейдеров работа достаточно трудоёмкая. Для того, чтобы написать простой шейдер с поддержкой карты нормалей и бликов, придется вручную написать 400 строк кода. И всё потому, что реализация содержит множество технических аспектов, которые нужно учитывать (forward/deffered lighting, множественные проходы, различные матрицы переходов и тд). Поэтому разработчики движка создали упрощенный вид шейдера - поверхностный, который выполняет все те-же действия за кулисами, позволяя нам добиваться нужных эффектов без столь громоздкого кода.

Немного теории

!Начнем! с того, что это хоть и упрощенный, но всё-таки шейдер, следовательно, его мы пишем на языке *CG* внутри блока CGPROGRAM...ENDCG, который, в свою очередь, находится внутри блока SubShader{}, но в отличие от вертексных/пиксельных шейдеров, поверхностный не нужно помещать внутрь блока прохода *Pass{}*.
Вот как выглядит шаблон поверхностного шейдера:

 Shader "MyShaders/ExampleShader"
{
 Properties
{...}
SubShader
{
 CGPROGRAM 
	#pragma surface surfaceFunction lightModel
	
	struct Input
	{
	 ...
	}; 
	void surfaceFunction (Input IN, inout SurfaceOutput o)
	{...}
ENDCG
}
Fallback "Diffuse"
}

!Разберем! все по порядку:
#pragma surface surfaceFunction .. - идентификация поверхностного шейдера.
lightModel - указываем, какую модель освещения мы будем использовать.
Другими словами, строчка #pragma surface surf Lambert говорит компилятору, что функция surfaceFunction - это поверхностный шейдер, который использует модель освещения
lightModel. // Встроенные в юнити модели освещения: Lambert, BlinnPhong, Standard.
struct Intput{} - задаем структуру с входными данными
void surfaceFunction (Input IN, inout SurfaceOutput o){} - собственно, сама шейдерная функция. На вход принимает входную структуру Input IN. Внутри функции шейдера мы производим вычисления и заполняем все поля специальной выходной структуры SurfaceOutput o.

Входная структура

!Входная! структура в поверхностном шейдере может содержать следующие переменные:
* float3 viewDir - содержит направление взгляда
* float4 SomeName : COLOR - содержит интерполированный цвет
* float4 screenPos - содержит экранные координаты
* float3 worldPos - содержит мировые координаты точки
* float3 worldRefl - содержит направление отражения вектора взгляда от данной точки в мировых координатах //(если не задана карта нормалей)
* float3 worldNormal - содержит направление вектора нормали в мировых координатах // (если не задана карта нормалей)
* float3 worldRefl; INTERNAL_DATA - содержит направление отражения вектора взгляда от данной точки в мировых координатах //(если задана карта нормалей, используется вместе с функцией WorldReflectionVector (IN, o.Normal) )
* float3 worldNormal; INTERNAL_DATA - содержит направление вектора нормали в мировых координатах // (если задана карта нормалей, используется вместе с функцией WorldNormalVector (IN, o.Normal) )

Чтобы задать uv координаты, нужно подписать префикс uv_ к имени названию текстуры. Например, float 2 uv_mainTexture

Выходная структура

!Выходная! структура содержит в себе параметры поверхности, которые мы можем заполнить в поверхностном шейдере. Юнити предлагает нам две структуры на выбор:
Стандартная, используется для моделей освещения Lambert'а и BlinnPhonga'а.

struct SurfaceOutput
{
    fixed3 Albedo;  // цвет
    fixed3 Normal;  // нормаль в пространстве касательных ( из карты нормалей) 
    fixed3 Emission; // свечение
    half Specular;  // сила спекуляра в пределах от 0 до 1
    fixed Gloss;    // интенсивности спеуляра
    fixed Alpha;    // прозрачность
};

Более физически корректные, используются для моделей Standard

struct SurfaceOutputStandard
{
    fixed3 Albedo;      //  цвет
    fixed3 Normal;      // нормаль в пространстве касательных ( из карты нормалей)
    half3 Emission; // свечение
    half Metallic;      // 0 - не метал, 1 - метал
    half Smoothness;    // 0 - грубая поверхность, 1 - гладкая поверхность
    half Occlusion;     // влияние на цвет окружения
    fixed Alpha;        // прозрачность
};

-

Дополнительные модификации

-
!Дополнительные! модификации задаются в директиве препроцессора после указания модели освещения.

Кастомные функции ===

* vertex:vertexFunction - функция для изменения вертексного шейдера
* finalcolor:ColorFunction - функция для изменения финального цвета
* finalgbuffer:ColorFunction - функция для изменения контента G-буффера в отсроченном освещении

Тени и тесселяция ==

* fullforwardshadows - добавляет тени для всех источников света в Forward rendering освещении
* tessellate:TessFunction - функция, которая вычисляет параметры тесселяции. (для работы требует DX11или выше)

Функции генерации кода ==

!Обычно! поверхностный шейдер реализует все возможные сценарии освещения/затенения/лайтмаппинга. Однако, в некоторых случаях это нам совсем не нужно. Для отключения ненужных опций шейдера существуют специальные команды. Вот некоторые из них:
* exclude_path:deferred, exclude_path:forward, exclude_path:prepass - отключение генерации прохода модели освещения
* noshadow - отключение всех теней
* noambient - отключение влияния ambient lighting или light probes
* nolightmap - отлючение всех лайтмапов
* noforwardadd - отключение добавочного прохода в forward rendering

Пишем шейдер

!Ну! вот и всё, пока нам этих знаний должно хватить. Настал тот самый момент, когда мы можем сделать свой велосипед применить полученные знания на практике.
Создадим новый поверхностный шейдер: Create->Shader->Standard Surface Shader
Я назову его "FirstSurface"
Делаем двойной клик по шейдеру и смотрим, что у нас тут:

#6 Surface Shader — Unity — DevTribe: инди-игры, разработка, сообщество

!Да!, собственно, ничего особенного. Этот шейдер берет значения из инспектора и прямиком транслирует их в шейдерную функцию. Думаю, стоит сделать из него что-нибудь более интересное. Задумка состоит в том, чтобы мы сделалии ледяную сферу с трещинами, в которых будем время от времени проявляться лава.
Ингредиенты:
* Сфера
* Текстура льда
* Текстура лавы
* Наспех сделанная карта нормалей льда

Начнем модифицировать шейдер. Сперва добавим в окно свойств недостающие переменные:

Properties 
	{
		_Color ("Color", Color) = (1,1,1,1)
		_MainTex ("Albedo (RGB)", 2D) = "white" {} // текстура льда
		_NormalMap("NormalMap",2D) = "white"{} // карта нормалей льда
		_SecondTex ("Lava_Texture",2D) = "white"{} // текстура лавы
		_Glossiness ("Smoothness", Range(0,1)) = 0.5
		_Metallic ("Metallic", Range(0,1)) = 0.0
		_Str ("Strenght", Range(0,10)) = 1 // переменная для регулировки силы
	}

Следующим нашим действием будет добавление объявление внешних переменных из окна свойств

sampler2D _NormalMap;
sampler2D _SecondTex;
half _Str;

и добавлением переменных с uv координатами в входную структуру Input

struct Input 
		{
			float2 uv_MainTex;
			float2 uv_NormalMap;
			float2 uv2_SecondTex;
		};
заметьте, что к внешним переменным не обязательно обозначать как !uniform!, хотя, по канону, это желательно делать

Вот мы и добрались до самой шейдерной функции. Первым делом назначим нашу карту нормалей:

o.Normal = UnpackNormal(tex2D(_NormalMap,IN.uv_NormalMap));
float3 norm = o.Normal;

Затем создадим переменные для льда и лавы:

fixed4 lava = tex2D(_SecondTex,(IN.uv2_SecondTex));
fixed4 ice = tex2D(_MainTex,IN.uv_MainTex);

Теперь нам стоит подумать как добиться, чтобы лава была только в трещинах. Какая переменная у нас отвечает за высоту поверхности ? Правильно, нормаль. Путем плясок с бубном было выяснено, что за трещины отвечает xy компонента вектора нормали. Интерполируем цвет между цветом льда и красным с использованием длинны norm.xy:

fixed4 col = lerp(ice,fixed4(1,0,0,0),length(norm.xy)); 

Вот что у нас получилось:

#6 Surface Shader — Unity — DevTribe: инди-игры, разработка, сообщество

Как-то слабовато. Умножим length(norm.xy) на _Str=2 и посмотрим что получиться:

#6 Surface Shader — Unity — DevTribe: инди-игры, разработка, сообщество

Уже намного лучше. Заменим красный цвет на лаву:

fixed4 col = lerp(ice,lava,length(norm.xy)*_Str);

Ну, и последний штрих - добавить мерцание. Здесь я буду использовать переменную _SinTime.w, т.к. она изменяется в пределах от 0 до 1, что и требуется. Еще немного плясок с бубном и вот что я вывел:

fixed4 c = lerp(ice * _Color,max(lava-0.5,lava*(_SinTime.w+_CosTime.x)*length(norm.xy)*_Str),length(norm.xy)*_Str);

При желании, можно поиграться со значениями и формулой, чтобы получить более интересный результат.

Что получилось у меня:

Shader "Custom/FirstSurface" {
	Properties 
	{
		_Color ("Color", Color) = (1,1,1,1)
		_MainTex ("Albedo (RGB)", 2D) = "white" {}
		_NormalMap("NormalMap",2D) = "white"{}
		_SecondTex ("Lava_Texture",2D) = "white"{}
		_Glossiness ("Smoothness", Range(0,1)) = 0.5
		_Metallic ("Metallic", Range(0,1)) = 0.0
		_Str ("Strenght", Range(0,10)) = 1
	}
	SubShader 
	{
		Tags { "RenderType"="Opaque" }
		LOD 200
		
		CGPROGRAM
		// Physically based Standard lighting model, and enable shadows on all light types
		#pragma surface surf Standard fullforwardshadows

		// Use shader model 3.0 target, to get nicer looking lighting
		#pragma target 3.0

		sampler2D _MainTex;
		sampler2D _NormalMap;
		sampler2D _SecondTex;
		//float4 _SecondTex_ST;

		struct Input 
		{
			float2 uv_MainTex;
			float2 uv_NormalMap;
			float2 uv2_SecondTex;
		};

		half _Glossiness;
		half _Metallic;
		fixed4 _Color;
		half _Str;
		void surf (Input IN, inout SurfaceOutputStandard o) {
			// Albedo comes from a texture tinted by color
			o.Normal = UnpackNormal(tex2D(_NormalMap,IN.uv_NormalMap));
			float3 norm = o.Normal;
			fixed4 lava = tex2D(_SecondTex,(IN.uv2_SecondTex));
			fixed4 ice = tex2D(_MainTex,IN.uv_MainTex);
			fixed4 col = lerp(ice * _Color,max(lava-0.5,lava*(_SinTime.w+_CosTime.x)*length(norm.xy)*_Str),length(norm.xy)*_Str);
			o.Albedo = col.rgb;
			// Metallic and smoothness come from slider variables
			o.Metallic = _Metallic;
			o.Smoothness = _Glossiness;
			o.Alpha = col.a;
		}
		ENDCG
	}
	FallBack "Diffuse"
}

Если есть вопросы, буду рад ответить.
Удачи!

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


Комментарии



Спасибо.
Благодаря тебе и курсу на Udemy, я запрограммировал свой первый Surface шейдер.

а можно ссылку на курс на Udemy?

E.S., сейчас курс поделился на 2, раньше было и 3D и 2D в одном.
https://www.udemy.com/share/1000PU/

Jusper, спасибо, я думал по шейдерам курс) Но я там и по шейдерам нашел парочку.

E.S., не там чисто на игру трехмерную был.
Я в рамках нее экспериментировал.

Справка