Dzeste Dzeste

Имхо, 4 самый крутой, интересная форма маски и приятные очки. 5 не понравился больше всего из за бесцветных линз, как-то не смотрится

Sporwiev Sporwiev

alexprey, помогите пожалуйста, у меня много чего подчеркивает красным, несмотря на то что код я копировал
FindEmptyState()

AstronBH AstronBH

Всем привет! Я разработчик игры Beast Hour, асимметричного PvP-хоррора от 3-го лица с паркуром и монстрами. Мы работаем над подземельем и окружением. Как вы думаете, что за эксперименты тут происходили...

...
AstronBH AstronBH

Всем привет! Я разработчик игры Beast Hour, асимметричного PvP-хоррора от 3-го лица с паркуром и монстрами. Что думаете о нашем проекте?
https://store.steampowered.com/app/1563250/Beast_Hour/

Jusper Jusper

Zemlaynin,

Ну то бишь в основе проекта ты все равно один :) Красава)

Zemlaynin Zemlaynin

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

Zemlaynin Zemlaynin

Jusper, делать контент... Секрета особого нет, труд, еще раз труд и стремление :)
Да и ребята мне помогают в плане моделей и художественных концептов.

iRediKurou iRediKurou

Jusper, сменили движок 1 октября 2020.
Аудитория ещё с нами :)

Jusper Jusper

Сколько вы уже делаете проект?
Аудитория не потеряала интерес еще?

Jusper Jusper

Вопрос на миллион. Как вы не устаете делать контент? :)
Есть какая-то система?

tihironrrr tihironrrr

Так, ну, я просто подкорректирую цвета, а указать его не проблема, даже приятно

Jusper Jusper

Как же без Slash Polygon на этой неделе.

SimonSn50 SimonSn50

Игра в разработке: #MajorDiscoBall

...
valentina_pazii valentina_pazii

uScript Personal Learning Edition не действительна
Погода: World Manager API (WAPI) - ссылка не действительна.

Jusper Jusper

Zemlaynin, у нас временно просто включена защита от череды запросов. Кароч, поправил, не парься. На то и премодерация.

Zemlaynin Zemlaynin

Jusper, Спасибо :)
А то у меня инет говенный на работе, по десять раз приходится заливать и через раз проходит.

Jusper Jusper

Zemlaynin,

Я поправил при публикации.

Zemlaynin Zemlaynin

Ого, а как картинки поправились :)

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

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

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

Справка