Mariya Mariya

Dreaman, Спасибо! =)

Dreaman Dreaman

Mariya, законченный вариант реально классно выглядит! Молодец :)

Mariya Mariya

Всем привет!
Сегодня хочу показать законченный вариант домика Сырны.

alexprey alexprey

StarPlosion: Битва с пиратами за планету

Wings' might Wings' might

Всем привет)
Добавил на этой неделе начальное окно, новую валюту и достижения:

alexprey alexprey

shadeborn, может быть они нашли способ как обойти полноценный запуск этих сервисов при запуске игры? Если так, то почему бы и не использовать единый клиент?) В любом случае, чем дальше все идет, там это все больше становится похоже на эту шутку

...
shadeborn shadeborn

Это всё круто, но...зачем? Ок есть Стим. Хорошо, теперь еще и ЕГС есть. Благо, комп не зашкварен Оригином...но у людей и он стоит. И для работы игр внутри этих сервисов нужны, собсно, эти сервисы. Так поверх этих сервисов нужно накатить еще один...

Devion Devion

согласен, есть кейсы которые без них вообще не делаются, не знать или избегать их определенно неправильно

Так точно, либо любое изменение в объекте на стороне редактора.

alexprey alexprey

Devion, только рад за дополнение, Спасибо)

хм, а вот это интересно, не пробовал никогда такой кейс.

Devion Devion

лёш, ты же не против если я дополню? )

скриптаблы, для понимания могут храниться на уровне ассета и на уровне сцены.

Mariya Mariya

Всем привет!
Начали работу над мебелью в домик Сырны, и первым сделали чайный столик с чайным сервизом. А так же продолжаем работу над анимациями.

Wings' might Wings' might

Всем привет)
За неделю в игру было добавлено меню настроек, переделана старая локация, добавлены новые враги и повышена производительности

alexprey alexprey

А мне кажется это такой особый хитрый ход, хотя с другой стороны как разработчики успеют к этому подготовиться получше 🤔

Dreaman Dreaman

Всем привет!
Для проекта "Mental State" разработано новое устройство, которое уже полностью функционирует внутри игрового мира. Оно носит название "Репульсивер". Совместно с очередными большими воротами это устройство образует новую головоломку...

...
Mariya Mariya

Всем привет!
На этой неделе мы научили Сырну летать!

Tartal Tartal

alexprey, кастомизации - создание внешности персонажа? Я всегда любил это, но не думаю, что это будет к месту в мясном шутере)
EfimovMax, да, есть немного)

Tartal Tartal

Jusper, точно не помню, уже как полгода точно) Я вроде в Дискорде немного обсуждал эту тему. Пока немножко попробовал движок - мне очень нравится. Ну, в конце концов, он идеально подходит под жанры, с которыми я хочу работать...

alexprey alexprey

Первая тема крутая очень!

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

#7 Shadows

#7 Shadows
статья не дописана, пока не для публикации

Тени

Содержание

1 Вступление
2 Краткая теория
2.1 ShadowMapping
2.2 Рендер Depth Texture
2.3 Рендер Shadow Map
2.4 Сборка Теней
2.5 Настройка Теней в unity
2.6 Алгоритм фильтрации Percentage Closer Filtering
3 Практика. Пишем шейдер
3.1 Отбрасывание теней
3.2 Получение теней

Вступление

Всем доброго времени суток! За окном декабрь, а значит самое время писать шейдеры! Текущим объектом нашего исследования были выбраны тени. Для понимания того, что тут вообще будет происходить, вам необходимо знать, что такое вертесный и фрагментный шейдеры.

Краткая теория

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

#7 Shadows  — Unity — DevTribe: Разработка игр (Unity, Шейдеры)

Прямой поддержки полутени в юнити нет , но ее можно попробовать имитировать с помощью алгоритмов фильтрации.

Unity рендерит реалтайм тени с помощью самого распространенного на данный момент алгоритма - Shadow Mapping.

Shadow Mapping

Shadow Mapping - это алгоритм рендеринга динамических теней, где информация о тенях хранится в текстуре. Главным его достоинством является быстрая отрисовка теней геометрически-сложных объектов.

Для того, чтобы разобраться, как юнити реализует данный алгоритм, создадим простую сцену: куб, две сферы, бросающих на него тень, камера и источник направленного света (directional light).

#7 Shadows  — Unity — DevTribe: Разработка игр (Unity, Шейдеры)

Теперь посмотрим, как происходит рендер одного кадра нашей сцены. В этом нам поможет инструмент для отладки frame debugger. Откываем Windows->FrameDebugger.
Нажимаем кнопку Enable.
Перед нами появилось окно с какими-то непонятным вещами внутри.

#7 Shadows  — Unity — DevTribe: Разработка игр (Unity, Шейдеры)

Разложим все по полочкам.
1 . Ползунок, показывающий номер шага. В моем случае номер шага равен 4, а всего 24 шага. Хотя правильно называть это не шагами, а draw call'ми.
2 . Окно вызовов отрисовки (draw call'ов). Здесь находятся сгруппированные draw call'ы.

Это запрос отрисовки, направленный графическому API. Чем запросов больше, тем выше нагрузка.
Для оптимизации draw call'ы однотипных объектов объединяют (батчат / batching)

3 . Окно информации о draw call'е, выбранном во втором окне. Здесь есть данные о шейдере, pass'е(проходе), ключевых словах, смешивании, текстурах, векторах, матрицах, текстурах и т.д.

Что же нам показывает frame debugger ?

Рендер depth texture

Прежде, чем рендерить геометрию, unity делает дополнительный проход, в котором создает depth texture(текстуру глубины). Это текстура, в которой каждый пиксель содержит информацию о "глубине" обектов на сцене.

текстура глубины нашей сцены — #7 Shadows  — Unity — DevTribe: Разработка игр (Unity, Шейдеры)
текстура глубины нашей сцены
  • Размер текстуры глубины совпадает с разрешением экрана
  • Значения пикселей в текстуре глубины находятся в диапазоне от 0 до 1 с нелинейным распространением. 0 - далеко от камеры, черный цвет. 1 - близко к камере, белый цвет.
  • Показывает только видимые объекты. Т.е. если один закрыт другим объектом, то информация о нем не будет занесена в текстуру глубины.
  • Рассчитывается с "точки зрения" камеры в clip space //(пространство камеры)

Рендер shadow map

Далее по списку идет рендер карты теней, которая используется для shadow mapping'а.

Идея Shadow mapping очень проста - мы поместим камеру в положение источника света и отрендерим для нее depth map. Её мы будем использовать для проверки видимости объектов. По сути, shadow map будет показывать, какое расстояние прошел луч от источника света, прежде чем столкнулся с объектом. Если расстояние(в shadow_space) от источника света до точки меньше, чем расстояние, закодированное для этой-же точки в shadow map, то это значит, что эта точка затенена.

#7 Shadows  — Unity — DevTribe: Разработка игр (Unity, Шейдеры)

Т.к. directional light распространяется равномерно и независимо от расстояния, то для него будет использоваться ортогональная камера. Плюс ко всем, directinal light олицетворяет собой солнце, а значит, по идее, расстояние от него до каждой точки должно быть одинаковым, а shadow map очень большой. На самом деле это не так. Расстояние для рендера Shadow map выбирается в зависимости от положения камеры.

Вот так примерно выглядит shadow map для нашей сцены.

shadow map — #7 Shadows  — Unity — DevTribe: Разработка игр (Unity, Шейдеры)
shadow map
  • Размер текстуры задается в настройках теней
  • Значения пикселей в текстуре глубины находятся в диапазоне от 0 до 1 с нелинейным распространением. 0 - далеко от камеры, черный цвет. 1 - близко к камере, белый цвет.
  • Показывает только видимые объекты. Т.е. если один закрыт другим объектом, то информация о нем не будет занесена в текстуру глубины.
  • Рассчитывается с "точки зрения" источника света в clip space
  • Рендерится для каждого отдельного источника света

А почему их четыре ? Это все потому, что мы используем четыре теневых каскада в сцене. Подробнее о них мы поговорим чуть позже.

Сборка теней

в frame debuggere этот этап назван collect shadows, и я не знаю, как это лучше перевести

Итак, что мы имеем ? У нас есть карта глубины с точки зрения камеры, у нас есть карта глубины с точки зрения источника света. Конечно, они сохранены в разных системах отсчета ( clip spaces), но мы знаем пространственные отношения и направления "взгляда" обоих точек зрения. А это в свою очередь означает, что мы можем сравнивать глубину depth map и shadow map. Другими словами, мы можем сравнивать два вектора(вектор, выходящий из источника света и вектор, выходящий из камеры). Если они, заканчиваются в одной точке и эта точка затенена, то мы сохраняем цвет тени в отдельную текстуру. Если же вектор света или вектор взгляда сталкиваются по пути с каким-либо объектом и не достиюте точки, то она не затенена или ее не видно, следовательно, нам не нужно сохранять значение тени в ней. В итоге получается текстура с тенями. На значения, сохраненные в ней, умножается цвета объетов во время рендера сцены.

В итоге мы имеем такой алгоритм shadow mapping'а в юнити:

  1. Создание карты глубины камеры (depth map)
  2. Создание карт глубины всех источников света (shadow map)
  3. Вычисление теней
  4. Сборка теней

Настройка теней

Теперь, когда мы знаем, как происходит ренер теней, можно перейти к изучению их настроек.

Открываем Edit->Project Settings->Quality

#7 Shadows  — Unity — DevTribe: Разработка игр (Unity, Шейдеры)

В Quality Setting мы устанавливаем параметры для всех источников света. Тем не менее, некоторые из них можно будет переопределить уже на каждом конкретном источнике света в зависимости от его задачи.
1 . Shadows - этот параметр отвечает за качество теней.

  • Diable Shadows - тени не будут рендерится
  • Hard Shadows Only - будет происходить рендер только жестких(hard) теней
  • Hard and Soft Shadows - будет происходить рендер мягких(soft) и жестких(hard) теней в зависимости от настроек источника света

2 . Shadow Resolution - настройка разрешения карты теней. Чем больше разрешение, тем качественнее на сцене будут тени

  • Low Resolution - размер карты теней с низким разрешением (1024x1024 )
  • Medium Resolution - размер карты теней со средний разрешением (2048x2048 )
  • High Resolution - размер карты теней с высоким разрешением (4096x4096)
  • Very Hight Resolution - размер карты теней с очень высоким разрешением (не особо понятно, в чем разница, размер карты теней 4096x4096)

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

#7 Shadows  — Unity — DevTribe: Разработка игр (Unity, Шейдеры)
#7 Shadows  — Unity — DevTribe: Разработка игр (Unity, Шейдеры)

Чем больше текстура, тем качественнее тени, но и ресурсов будет использоваться больше. Особенно это касается памяти. А зачем нам рендерить качественные тени для объектов, находящихся далеко от камеры ? Для них вполне бы подошли текстуры с маленьким разрешением. Эту проблему решают теневые каскады.

Под теневыми каскадами подразумевают разбиение пространства на каскады в зависимости от удаления до камеры. Каждый каскад уже реализует свою карту теней. Именно для них и рендерится несколько теневых карт с разным разрешением.

теневые каскады — #7 Shadows  — Unity — DevTribe: Разработка игр (Unity, Шейдеры)
теневые каскады

Как видно на картинке выше, тень дальней сферы, проходя через границу теневого каскада, немного теряет в качестве.

3 . Shadow Projection - параметр, на основе которого выбирается теневой каскад. По умолчанию используется Stable fit.

  • Close Fit - в зависимости от глубины камеры
  • Stable Fit - в зависимости от расстояния до камеры

-

4 . Shadow Distance - расстояние от камеры, после которого тени не будут рендерится. По умолчанию стоит 150. При установки значения shadow distance = 8, тень от дальней сферы уже не рендерилась. Так же, при маленьком shadow distance необходимость в теневых каскадах отпадает.
Чем меньше shadow distance, тем быстрее рендерится сцена. У меня количество draw call'ов уменьшилось с 24 до 16.

shadow distance 8 — #7 Shadows  — Unity — DevTribe: Разработка игр (Unity, Шейдеры)
shadow distance 8

5 . Shadowmask Mode - в каком режиме будет работать shadowmask (текстура, в которой сохранены тени от статических объектов на другие статические объекты. Используется в mixed-lighting'е)

  • Shadowmask - статические объекты отбрасывают всегда запеченные тени
  • Distance Shadowmask - использование реал-тайм теней до shadow distance, а статических после нее

6 . Shadow Near Plane Offset - смещение ближнего плана(near plane) камеры, которая рендерит теневую карту. По умолчанию равно 3.

7 . Shadow Cascades - количество теневых каскадов.

  • No Cascades - без теневых каскадов, тени ограничиваются только shadow distance
  • Two Cascades - два теневых каскада
  • Four Cascades - четыре теневых каскада

8 . Cascade splits - Панель, где можно настроить расстояние, на котором активируется и выключается каждый из каскадов. Расстояние считается в процентах от shadow distance.

#7 Shadows  — Unity — DevTribe: Разработка игр (Unity, Шейдеры)

Это были общие настройки для теней. Также в unity можно настроить отображение теней для каждого отдельного источника света.

#7 Shadows  — Unity — DevTribe: Разработка игр (Unity, Шейдеры)

1 . Shadow Type - тип теней.

  • Hard Shadows - жесткие тени
  • Soft Shadows - мягкие тени

-
2 . Strength - коэффициент, на который умножаются тени при их сборке. 0 - нет теней, 1- черные тени.

3 . Resolution - разрешение карты теней для данного источника света.

  • Use Quality Settings - использует значение, которое мы выставили в Quality Settings
  • Low Resolution - размер карты теней для данного источника света 512x512
  • Medium Resolution - размер карты теней для данного источника света 1024x1024
  • High Resolution - размер карты теней для данного источника света 2048x2048
  • Very Hight Resolution - размер карты теней для данного источника света 4096x4096

4 . Bias - коэффициент, который добавляется к глубине. Он как-бы немного вдавливает тень. По умолчанию равен 0.05. Если выставить слишком большое значение, тень слишком далеко отодвинется от объекта и будет казаться, что она отдельно от него, если слишком маленькое, появится эффект shadow acne.

5 . Normal Bias - делает все то-же самое, что и bias, только выравнивание идет по нормалям объекта. Если установить слишком высокое значение, то тень станет слишком "вжатой в себя" или очень узкой.

6 . Near Plane - смещение ближнего плана камеры для данного источника света.

Алгоритм фильтрации Percentage Closer Filtering (PCF)

PCF - это алгоритм фильтрации, основанный на усреднении значений между соседними текселями тени в shadow map. В нем для каждого пикселя мы берем значения соседних пикселей в Shadow Map и усредняем их. Получается своеобразный переход из затененной в освещенную область. Узнать поподробнее о PCF, а также о других алгоритмах можно здесь

#7 Shadows  — Unity — DevTribe: Разработка игр (Unity, Шейдеры)

Пишем шейдер

!Переходим! к практике!

Обозначим задачи.

  1. Предмет должен отбрасывать тени
  2. Шейдер должен принимать тени от directional light'а
  3. Тени должны быть мягкими

Создадим новый материал, для него создадим новый Unlit шейдер и применим его к сфере.
Как видно на изображении ниже, сфера перестала отбрасывать тень, т.к. в unlit шейдере нет её реализации.

#7 Shadows  — Unity — DevTribe: Разработка игр (Unity, Шейдеры)

Отбрасывание теней (Casting Shadow)

Итак, перейдем к реализации отбрасывания теней. Если мы сейчас откроем frame debugger, то увидим, что наша unlit сфера не рендерится ни в shadowmap, ни в depth map. Это происходит потому, что в шейдере unlit сферы нет прохода с тэгом "ShadowCaster", из которого движок берет информацию о глубине на основе положения объекта.

Добавим дополнительный проход в наш шейдер:

		Pass
		{
			Tags{"LightMode"="ShadowCaster"}
			Blend One Zero
			CGPROGRAM 
			#pragma target 3.0 
			#pragma vertex vert 
			#pragma fragment frag 
                        #include "UnityCG.cginc"
			float4 vert(float4 vertex:POSITION):SV_POSITION
			{
				return mul(UNITY_MATRIX_MVP,vertex);
			}

			float4 frag():SV_Target
			{
				return 0;
			}
			ENDCG
		}
	}

В этом проходе нам важна только позиция, поэтому просто переведем положение объекта из object space в clip space.
Ну, собственно, вот и все. Теперь наш шейдер пишет глубину, а это значит, что объект отбрасывает тень.
Но тут возникает небольшая проблема: у нас не производится смещения в зависимости от bias'а и normal bias'а.

Чтобы правильно скорректировать положение сферы в зависимости от bias'а, мы применим функции, реализованные в " UnityCG.cginc":

float4 UnityClipSpaceShadowCasterPos(float3 vertex, float3 normal)
{
	float4 clipPos;
    
    // Important to match MVP transform precision exactly while rendering
    // into the depth texture, so branch on normal bias being zero.
    if (unity_LightShadowBias.z != 0.0)
    {
		float3 wPos = mul(_Object2World, float4(vertex,1)).xyz;
		float3 wNormal = UnityObjectToWorldNormal(normal);
		float3 wLight = normalize(UnityWorldSpaceLightDir(wPos));

		// apply normal offset bias (inset position along the normal)
		// bias needs to be scaled by sine between normal and light direction
		// (http://the-witness.net/news/2013/09/shadow-mapping-summary-part-1/)
		//
		// unity_LightShadowBias.z contains user-specified normal offset amount
		// scaled by world space texel size.

		float shadowCos = dot(wNormal, wLight);
		float shadowSine = sqrt(1-shadowCos*shadowCos);
		float normalBias = unity_LightShadowBias.z * shadowSine;

		wPos -= wNormal * normalBias;

		clipPos = mul(UNITY_MATRIX_VP, float4(wPos,1));
    }
    else
    {
        clipPos = mul(UNITY_MATRIX_MVP, float4(vertex,1));
    }
	return clipPos;
}
float4 UnityApplyLinearShadowBias(float4 clipPos)
{
	clipPos.z += saturate(unity_LightShadowBias.x/clipPos.w);
	float clamped = max(clipPos.z, clipPos.w*UNITY_NEAR_CLIP_VALUE);
	clipPos.z = lerp(clipPos.z, clamped, unity_LightShadowBias.y);
	return clipPos;
}

Изменим наш вертексный шейдер:

			float4 vert(float4 vertex:POSITION, float3 normal:NORMAL):SV_POSITION
			{
				float4 clipPos = UnityClipSpaceShadowCasterPos(vertex.xyz,normal);
				return UnityApplyLinearShadowBias(clipPos);
			}

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

#7 Shadows  — Unity — DevTribe: Разработка игр (Unity, Шейдеры)

Получение теней (Receiving Shadows)

Для того, чтобы узнать, затенена ли точка, мы должны взять значение глубины из shadow map, сравнить его с расстоянием(в shadow space) от источника света до этой точки, и если оно больше, то точка затенена.

Возвращаемся в наш первый проход(pass), и введем новую переменную _ShadowCoord, в которую мы в будущем запишем координаты точки в screen space. А почему нам не подойдет clip space ? Да все потому, что каждая точка в shadow map, доступ к которой мы имеем по средствам uv координат, отражает положение соответствующей ей позиции на сцене. UV координаты находятся в пределах от 0 до 1, а clip space координаты от -1 до 1. Поэтому, если напрямую запрашивать информацию о глубине из shadow map при помощи clip space координат, мы получим ошибку. Итак, мы добавили в структуру v2f переменную _ShadowCoord.

			struct v2f
			{
				float2 uv : TEXCOORD0;
				UNITY_FOG_COORDS(1)
				float4 vertex : SV_POSITION;
				float4 _ShadowCoord:TEXCOORD2;
				float4 wPos:TEXCOORD3; // мировые координаты точки 

			}

Теперь спускаемся в вертексный шейдер и присваиваем переменной значение. Для того, чтобы значение было в screen space, нужно привести переменную к виду, где минимальному значению clip space (-1) будет соответствовать минимально значение screen space (0). Это делается при помощи функции ComputeScreenPos(float4 clip_pos), объявленной в "UnityCG.cginc"

inline float4 ComputeNonStereoScreenPos (float4 pos) {
	float4 o = pos * 0.5f;
	#if defined(UNITY_HALF_TEXEL_OFFSET)
		o.xy = float2(o.x, o.y * _ProjectionParams.x) +
			o.w * _ScreenParams.zw;
	#else
		o.xy = float2(o.x, o.y * _ProjectionParams.x) + o.w;
	#endif
	o.zw = pos.zw;
	return o;
}

inline float4 ComputeScreenPos (float4 pos) {
	float4 o = ComputeNonStereoScreenPos(pos);
	#ifdef UNITY_SINGLE_PASS_STEREO
		o.xy = TransformStereoScreenSpaceTex(o.xy, pos.w);
	#endif
	return o;
}

Сейчас векртексный шейдер должен выглядеть как-то так

			v2f vert (appdata v)
			{
				v2f o;
				o.vertex = UnityObjectToClipPos(v.vertex);
				o.uv = TRANSFORM_TEX(v.uv, _MainTex);
				UNITY_TRANSFER_FOG(o,o.vertex);
				o.wPos = mul(_Object2World,v.vertex);
				o._ShadowCoord = ComputeScreenPos(o.vertex);
				return o;
			}

Следующим шагом станет сравнивание расстояния до точки и глубины, соответствующей этой точке. Для этого нам нужно достать shadow map, которая объявлена в подключаемом файле "UnityShadowLibrary.cginc". Поднимаемся выше, и подключаем этот файл в наш шейдер.

#include "UnityShadowLibrary.cginc"

И сразу же скажем шейдеру, что мы хотим получить извне shadow map

uniform sampler2D _ShadowMapTexture;

Во фрагментном шейдере создадим переменную для теневых координат, к которым применим проекцию путем деления xy на w.

float2 shadow_coords = i._ShadowCoord.xy/i._ShadowCoord.w;

Если не делать проекцию, то мы получим неправильное значение из shadow map.

#7 Shadows  — Unity — DevTribe: Разработка игр (Unity, Шейдеры)

Достаем значение глубины из shadow map:

float map_dist = tex2D(_ShadowMapTexture,shadow_coords).r;

Достаем расстояние до объекта в shadow space ( система кординат с точки зрения источника света) при помощи предоставляемой unity матрицы unity_WorldToShadow[x], которая переводит из мировых в "теневые" координаты. X - номер каскада. Мы будем считать тень только для самого первого каскада.

float shadow_dist = mul(unity_WorldToShadow[0],i.wPos);

Создадим переменную тени

float shadow = 1; 
if(shadow_dist>map_dist) shadow = 0;
...
return col*shadow;

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

#7 Shadows  — Unity — DevTribe: Разработка игр (Unity, Шейдеры)

Получились довольно грубые тени с резким переходом.

#7 Shadows  — Unity — DevTribe: Разработка игр (Unity, Шейдеры)

Исправлять это дело мы будет при помощи алгоритма фильтрации PCF. Но для этого нам нужно запросить у движка еще одну переменную - размер текселя shadow map.

uniform float2 _ShadowMapTexture_TexelSize;

И, собственно, написать саму функцию фильтрации:

float PCF (sampler2D shadowMap, float2 uv)
			{
				float result = 0.0;
				for(int x=-3; x<=3;x++)
					for(int y=-3;y<=3;y++)
					{
						float2 offset = float2(x,y)*_ShadowMapTexture_TexelSize;
						result+= tex2D(shadowMap,uv+offset).r;
					}
					return result/20;
			}

Здесь для сэмплирования мы взяли 3x3 текселя. Коэффициент, на который нужно делить результат я подбирал "на глаз", т.к. для каждого размера куба он будет резличен.
А теперь применим эту функцию в фрагментном шейдере:

if(shadow_dist>map_dist) shadow = PCF(_ShadowMapTexture,shadow_coords);

И вот что получается:

#7 Shadows  — Unity — DevTribe: Разработка игр (Unity, Шейдеры)

Было/стало

Ну, в принципе, вот и все. Первый блин готов. Но он имеет ряд недостатков:

  1. Получает тени только от directional light'а
  2. Используется достаточно дорогой PCF
  3. Не реализовывает CSM (Cascaded shadow mapping)
Shader "Unlit/Shadows"
{
	Properties
	{
		_MainTex ("Texture", 2D) = "white" {}
	}
	SubShader
	{
		Tags { "RenderType"="Opaque" }
		LOD 100

		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			// make fog work
			#pragma multi_compile_fog

			#include "AutoLight.cginc"
			#include "UnityCG.cginc"
			#include "UnityShadowLibrary.cginc"
			struct appdata
			{
				float4 vertex : POSITION;
				float2 uv : TEXCOORD0;
			};

			struct v2f
			{
				float2 uv : TEXCOORD0;
				UNITY_FOG_COORDS(1)
				float4 vertex : SV_POSITION;
				float4 _ShadowCoord:TEXCOORD2;
				float4 wPos:TEXCOORD3;
				SHADOW_COORDS(2)
			};

			uniform sampler2D _ShadowMapTexture;
			uniform float2 _ShadowMapTexture_TexelSize;
			sampler2D _MainTex;
			float4 _MainTex_ST;
			
			v2f vert (appdata v)
			{
				v2f o;
				o.vertex = UnityObjectToClipPos(v.vertex);
				o.uv = TRANSFORM_TEX(v.uv, _MainTex);
				UNITY_TRANSFER_FOG(o,o.vertex);
				o._ShadowCoord = ComputeScreenPos(o.vertex);
				o.wPos = mul(unity_ObjectToWorld,v.vertex);
				return o;
			}

			float PCF (sampler2D shadowMap, float2 uv)
			{
				float result = 0.0;
				for(int x=-3; x<=3;x++)
					for(int y=-3;y<=3;y++)
					{
						float2 offset = float2(x,y)*_ShadowMapTexture_TexelSize;
						result+= tex2D(shadowMap,uv+offset).r;
					}
					return result/20;
			}

			fixed4 frag (v2f i) : SV_Target
			{
				// sample the texture
				fixed4 col = tex2D(_MainTex, i.uv);
				float2 shadow_coords = i._ShadowCoord.xy/i._ShadowCoord.w;
				float map_dist = tex2D(_ShadowMapTexture,shadow_coords).r;
				float shadow_dist = mul(unity_WorldToShadow[0],i.wPos);
				float shadow=1;
				if(shadow_dist>map_dist) shadow = PCF(_ShadowMapTexture,shadow_coords);
				UNITY_APPLY_FOG(i.fogCoord, col);
				return col*shadow;
			}
			ENDCG
		}
		Pass
		{
			Tags{"LightMode"="ShadowCaster"}
			Blend One Zero
			CGPROGRAM 
			#pragma target 3.0 
			#pragma vertex vert 
			#pragma fragment frag 
			#include "UnityCG.cginc"
			float4 vert(float4 vertex:POSITION, float3 normal:NORMAL):SV_POSITION
			{
				float4 clipPos = UnityClipSpaceShadowCasterPos(vertex.xyz,normal);
				return UnityApplyLinearShadowBias(clipPos);
			}

			float4 frag():SV_Target
			{
				return 0;
			}
			ENDCG
		}
	}
}

Как видите, это не совсем рабочий вариант шейдера, и он во много уступает встроенному в юнити шейдеру, но для понимания процесса рендера теней сойдет. В следующих статьях мы попробуем исправить его недостатки.
Если есть вопросы, пишите в личку или в комментариях, постараюсь ответить.
Удачи!



Статья огонь! Максимально исчерпывающая ^^

Шикарно, как и ранее.
Как раз есть, где применить. Спасибо!