alexprey alexprey

Добавил скриншоты на главную, позже загружу дневники разработки игры, где можно будет посмотреть с чего все начиналось :)

alexprey alexprey

EfReeZe, о да, тогда было знатное время и игра казалась сделано круто :D До сих пор иногда играю на телефоне от скуки)

EfReeZe EfReeZe

Это был наш далёкий 2014...

Jusper Jusper

Запись трансляции на YouTube

Jusper Jusper

Случайно сделали битву сварщиков
https://devtribe.ru/p/slash-polygon

Jusper Jusper

Внезапно на сайте потерялся 15-й выпуск. Перевыложили.

Jusper Jusper

Я вам еще не закончил писать обратную связь по боевке, есть много вещей которые немного смутили, но они поправимы. Завтра, если перестанут дергать - чиркану. Спасибо за демку!

Jusper Jusper

ruggaraxe,

Да, в этом плане все ок. Логично, что графен на старой машине, если не упарываться, не взлетит. Но я рад, что это было не 5 фпс, как даже в некоторых АА (типа Pillars of Eternity в некоторых схватках...

Jusper Jusper

ruggaraxe,

Подкреплю ее к публикации.

ruggaraxe ruggaraxe

Jusper, вот ссылка на анкету (я затупил со ссылкой с топике, сорри)
https://docs.google.com/forms/d/e/1FAIpQLSd_Wn53lJFrnfGpWI2IX...

ruggaraxe ruggaraxe

Jusper, честно говоря, да на 800х600 даже не проверяли... :) сорри. Ориентировались на FullHD и выше. Хотя над интерфейсом конечно же надо еще хорошенько поработать.
Тултипы постараемся сделать обязательно к следующей версии...

GenElCon GenElCon

Jusper,

Наверное. В прошлом они сделали Endless Legend - посмотри и сразу станет ясно в какую сторону они работают.

Jusper Jusper

GenElCon,

Я не очень понял по трейлеру геймплей. Это что-то типа цивы? Или это RTS?

GenElCon GenElCon

Humankind от разработчиков Endless Legends (и Space, но тут важно именно Legends).
А также согревающие сердца олдов трейлеры Port Royal 4 и Knights of Honor.

Jusper Jusper

Похвалю темную атмосферу, отличную работу с освещением, тенями и шейдерами. Происходящее на экране корректно задает атмосферу до тех пор, пока не начнется сам экшон, об это напишу далее. Управление персонажем отзывчивое...

Jusper Jusper

Первое, оно же самое тяжелое - UI. Я конечно, понимаю, что 800x600 совсем уже не в моде (завтра проверю на нормальной широформатной машине). Заблюренный текст я еще прочитать могу, но вот конкретно размер его крайне мал...

...
Jusper Jusper

ruggaraxe, я поиграл на старом маке 2012 года (Macbook Pro, Intel HD 4000), рад что с учетом довольно нагруженной по свету и теням картинке игруля не лагает как последняя сволочь (лагает конечно, но очень терпимо...

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

Guassian Blur

Guassian Blur

Размытие по Гауссу

Размытие по Гауссу(Guassian Blur) - это всеми известный алгоритм размытия изображения, который очень часто используется в фотошопе или других растровых редакторах.

Если вбить в википедии формулу Гауссовского размытия, то нам выдаст что-то подобное

формула Гаусса — Guassian Blur  — Unity — DevTribe: Разработка игр (blur, shader, Unity, размытие, Шейдеры, шейдеры, юнити)
формула Гаусса

Не очень-то приятная формула. Но если мы посмотрим на график ее функции, получим вот что

Guassian Blur  — Unity — DevTribe: Разработка игр (blur, shader, Unity, размытие, Шейдеры, шейдеры, юнити)

А это ни что иное, как нормальное распределение, которое показывает, что наибольшее значение функция принимает где-то посередине, а по краям ее значения являются небольшими.

Теперь суть гаусовского алгоритма : мы берем какой-либо пиксель изображения и считаем его центральным. Затем суммируем его цвет + цвета всех его соседей. Количество этих самых соседей, цвета которых мы суммируем, определяется kernel картой. Они могут быть 3х3, 4х4, 6х6 и т.д. Но суммируем не просто бездумно ( как это делалось в статье с PCF размытием), а сначала умножаем цвет на определенный коэффициент в зависимости от удаленности пикселя от центра. Прямо как в нормальном распределении.
Коэффициенты удаленности от центра закодированы в матрице свертки, которая выглядит вот так:

Guassian Blur  — Unity — DevTribe: Разработка игр (blur, shader, Unity, размытие, Шейдеры, шейдеры, юнити)

Как вы видите, центральный элемент матрицы, который соответствует центральному пикселю, имеет наибольшее значение 4, а самые удаленные от него элементы имеют наименьшее значение 1.
Если просуммировать все элементы матрицы, то получится коэффициент для нормализации этой матрицы, который равен 16. То есть, для того, чтобы значение наших цветов было в рамках от 0..1, после всех манипуляций нужно будет поделить цвет на 16.

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

С теорией вроде разобрались, переходим к практике.

Пишем шейдер

Первым делом нужно подготовить почву. Размывать мы будем изображение в тот момент, когда оно уже прошло все основные этапы графического конвеера Unity и готово выводится на экран. Т.е. наш эффект будет будет применяться к текстуре. Такие эффекты называются постэффектами (Post Effects/ Image Effects).
В движке есть функция, которая вызывается в момент рендера изображения на экран.
void OnRenderImage(RenderTexture src, RenderTexture dest) {}
RenderTexture src - это текстура, которая поступает с графического конвеера
RenderTexture dest - текстура, которая будет выведена на экран.

Переносить содержимое одной рендер текстуры на другую можно при помощи функции
public static void Blit(Texture source, RenderTexture dest, Material mat)
source - источник
dest - разультат
mat - материал, который будет применен к source, после чего source будет записана в dest

Напишем небольшой скрипт, который повесим на камеру:

using UnityEngine;

public class Image_Effets : MonoBehaviour {

    public Material mat;

    RenderTexture rt;

	void Start () {
        rt = new RenderTexture(Screen.width, Screen.height, 1);
        mat.SetTexture("_MainTex", rt);
	}


    void OnRenderImage(RenderTexture src, RenderTexture dest)
    {
        rt = src;
        mat.SetTexture("_MainTex", rt);
        Graphics.Blit(src, dest, mat);   
    }
}

mat - материал, на котором будет висеть шейдер размытия
rt - рендер текстура, которую передаем в материал для обработки шейдером

Вешаем скрипт на камеру и идем писать шейдер. Я назвал шейдер просто Blur.

Важно понимать, что в шейдере постэффекта мы работает с текстурой, которая передается в _MainTex и состоит целиком из пикселей. Это значит, что вся работа будет происходить в пиксельном шейдере, который применится ко всем пикселям изображения.

Итак, блок свойств:

Shader "Hidden/Blur"
{
	Properties
	{
		_MainTex ("Texture", 2D) = "white" {}
		_BlurDev("Avarage_Koef", Range(0,50)) = 16
		_uvOffset("Offset",Range(0,0.01)) = 0.05
		_useBlur ("Use Blur", Int) = 1
	}

_BlurDev - переменная для регулировки коэффициента деления
_uvOffset - переменная для смещения по uv коордиантам
_useBlur - для переключения между размытым и не размытым изображением

Далее идут стандартные преобразования, здесь нет ничего необычного:

SubShader
	{
		// No culling or depth
		Cull Off 
		//ZWrite Off ZTest Always
	
	Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			
			#include "UnityCG.cginc"

			struct appdata
			{
				float4 vertex : POSITION;
				float2 uv : TEXCOORD0;
			};

			struct v2f
			{
				float2 uv : TEXCOORD0;
				float4 vertex : SV_POSITION;
			};

			v2f vert (appdata v)
			{
				v2f o;
				o.vertex = UnityObjectToClipPos(v.vertex);
				o.uv = v.uv;
								
				return o;
			}

Опишем внешние переменные

			uniform sampler2D _MainTex;
			uniform float4 _MainTex_TexelSize;
			uniform float _BlurDev;
			uniform float _uvOffset;
			uniform int _useBlur;

В переменную float4 _MainTex_TexelSize движок передает размер текселей _MainTex. ( они равны 1/width, 1/height)
Далее введем Гаусовскую матрицу, которую будем использовать для умножения на цвета пикселей

float3x3 GM = float3x3
			( 1.0,2.0,1.0,
			  2.0,4.0,2.0,
			  1.0,2.0,1.0
			);

И напишем функцию размытия

float4 guassianBlur(sampler2D tex, float2 uv, float2 tex_size)
			{
				float4 result = 0;
				tex_size+= _uvOffset;
				result = 
				tex2D ( tex, uv + float2(-1.0,1.0) * tex_size )* GM[0][0] + 
				tex2D ( tex, uv + float2(0.0,1.0) * tex_size) * GM[0][1]  + 
				tex2D ( tex, uv + float2(1.0,1.0) * tex_size )* GM[0][2]  + 
				tex2D ( tex, uv + float2(-1.0,0.0) * tex_size) * GM[1][0]  + 
				tex2D ( tex, uv + float2(0.0,0.0) * tex_size) * GM[1][1]  + 
				tex2D ( tex, uv + float2(1.0,0.0) * tex_size) * GM[1][2]  + 
				tex2D ( tex, uv + float2(-1.0,-1.0) * tex_size )* GM[2][0]  + 
				tex2D ( tex, uv + float2(0.0,-1.0) * tex_size)* GM[2][1]  + 
				tex2D ( tex, uv + float2(1.0,-1.0) * tex_size) * GM[2][2] ;
				return result/_BlurDev;

			}

result - переменная, которая хранит сумму всех окрестных пикселей.
Суммирование идет по часовой стрелки, начиная верхнего левого пикселя.
Например, одна строчка tex2D ( tex, uv + float2(-1.0,1.0) * tex_size )* GM[0][0] значит, что мы берем цвет из tex, по координатам uv(это центр) + левый верхний угол float2(-1.0,1.0), который умножается на размер текселя. Потом вот этот цвет домнажаем на значение веса из матрицы, у которого левая верхняя позиция (GM[0][0]). И так для каждого пикселя.
И теперь вызываем эту функцию из фрагментного шейдера. У меня еще есть условия для включение и отключения размытия.

float4 frag (v2f i) : SV_Target
			{
				float4 col = 1;
				if(_useBlur > 1) col = guassianBlur(_MainTex,i.uv,_MainTex_TexelSize.xy);
				else col = tex2D(_MainTex, i.uv);
				col.rgb = col.rgb;
				return col;
			}
			ENDCG
		}
	}
}
до — Guassian Blur  — Unity — DevTribe: Разработка игр (blur, shader, Unity, размытие, Шейдеры, шейдеры, юнити)
до
после — Guassian Blur  — Unity — DevTribe: Разработка игр (blur, shader, Unity, размытие, Шейдеры, шейдеры, юнити)
после
со смещением по uv — Guassian Blur  — Unity — DevTribe: Разработка игр (blur, shader, Unity, размытие, Шейдеры, шейдеры, юнити)
со смещением по uv
Shader "Hidden/Blur"
{
	Properties
	{
		_MainTex ("Texture", 2D) = "white" {}
		_BlurDev("Avarage_Koef", Range(0,50)) = 16
		_uvOffset("Offset",Range(0,0.01)) = 0.05
		_useBlur ("Use Blur", Int) = 1
	}
	SubShader
	{
		// No culling or depth
		Cull Off 
		//ZWrite Off ZTest Always
	

		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			
			#include "UnityCG.cginc"

			struct appdata
			{
				float4 vertex : POSITION;
				float2 uv : TEXCOORD0;
			};

			struct v2f
			{
				float2 uv : TEXCOORD0;
				float4 vertex : SV_POSITION;
			};

			v2f vert (appdata v)
			{
				v2f o;
				o.vertex = UnityObjectToClipPos(v.vertex);
				o.uv = v.uv;
								
				return o;
			}


			
			uniform sampler2D _MainTex;
			uniform float4 _MainTex_TexelSize;
			uniform float _BlurDev;
			uniform float _uvOffset;
			uniform int _useBlur;
			float3x3 GM = float3x3
			( 1.0,2.0,1.0,
			  2.0,4.0,2.0,
			  1.0,2.0,1.0
			);

			float4 guassianBlur(sampler2D tex, float2 uv, float2 tex_size)
			{
				float4 result = 0;
				tex_size+= _uvOffset;
				result = 
				tex2D ( tex, uv + float2(-1.0,1.0) * tex_size )* GM[0][0] + 
				tex2D ( tex, uv + float2(0.0,1.0) * tex_size) * GM[0][1]  + 
				tex2D ( tex, uv + float2(1.0,1.0) * tex_size )* GM[0][2]  + 
				tex2D ( tex, uv + float2(-1.0,0.0) * tex_size) * GM[1][0]  + 
				tex2D ( tex, uv + float2(0.0,0.0) * tex_size) * GM[1][1]  + 
				tex2D ( tex, uv + float2(1.0,0.0) * tex_size) * GM[1][2]  + 
				tex2D ( tex, uv + float2(-1.0,-1.0) * tex_size )* GM[2][0]  + 
				tex2D ( tex, uv + float2(0.0,-1.0) * tex_size)* GM[2][1]  + 
				tex2D ( tex, uv + float2(1.0,-1.0) * tex_size) * GM[2][2] ;
				return result/_BlurDev;

			}


			float4 frag (v2f i) : SV_Target
			{
				float4 col = 1;
				if(_useBlur > 1) col = guassianBlur(_MainTex,i.uv,_MainTex_TexelSize.xy);
				else col = tex2D(_MainTex, i.uv);
				col.rgb = col.rgb;
				return col;
			}
			ENDCG
		}
	}
}

Сейчас мы реализовали только матричное размытие, хотя существуют и другие ( четкость, эрозия и т.д.) Такие матрицы называются матричными фильтрами. Подробнее про них можно почитать тут

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

Если есть вопросы, буду рад ответить )

Удачи!



А мне вот кст не особо нравится размытие по гауссу из за двоения изображения. В итоге пользовался варицией сжатия и расширения исходной текстуры. Это конеяно затратнее по ресурсам но результат был без двоения.

Поэтому, я решил попробовать новый формат таких вот небольших целевых статей, которые можно написать за один-два вечера. Надеюсь будет полезно и раз такой формат попроще, то и выходить статьи должны почаще

Формат прекрасен и очень полезен.

alexprey, если ты про последнее фото, то там не совсем чистое гауссовское размытие. Чисто гауссовское это вторая фотка. Двоение происходит только при извращении типо как со смещением пикселей kernel мапы по uv.

alexprey, для усиления размытия нужно увеличивать матрицу свертывания

lehanru, хммм, так вот оно в чем дело, в моей криворукости тогда 😂

alexprey, алгоритм размытия по Гауссу что приведен в статье - это в общем и целом алгоритм фильтрации (так работают обычный фильтра для фото). Просто у каждого конкретного фильтра свое конкретное ядро (матрица с конкретными значениями), в данной статье это ядро Гаусса. Но не об этом.

Так вот, если прочитать еще раз принцип алгоритма, то увидишь, что на значение пикселя нового изображения с тем или иным весом (которые как раз и указываются в матрице - ядре фильтра) влияют значения его близлежащих пикселей, который попали в это своеобразное окно подсчета.

Меньше окно -> больше влияние самых-самых соседних пикселей -> каскадный эффект.
Больше окно -> меньше влияние самых-самых соседних пикселей.

Справка