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

#8 UnityCode Shadows

#8 UnityCode Shadows

UnityCode Shadows

Содержание

1 . Вступление
2 . Краткая теория
2 .1 Кубические карты
2.2 Ключевые слова
2.3 Директива multi_compile
2.4 Forward shadows
3 . Практика
3.1 Постановка задач.
3.2 ShadowCaster
3.3 ForwardBase
3.4 ForwardAdd
3.5 Мой Результат
4 . Заключение

Вступление

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

В прошлой ( тык ) статье мы создали шейдер, принимающий и отбрасывающий тени от directional light'а. В этот раз мы будем создавать тени для directional и point light'а, используя для реализации directional light теней встроенный в юнити код. А почему бы нам не сделать все самим, вручную, как мы это делали раньше ? Ну, на это есть несколько причин:

  • Проще реализовать
  • Интеграция с остальным кодом от unity
  • Юнити не дает доступ к "чистой" карте теней при реализации теней от point light'а + автоматически сэмплирует ту, что предоставляет с поправкой на теневые каскады
  • Меньше кода

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

Реализация теней от directional light'а и теней, появившихся в результате point light'а, различается. Это происходит из-за того, что направление света у directional light'а всего одно, а point light светит во все стороны вокруг себя. При таком типе освещения одной текстурой не обойдешься, поэтому, используются кубические карты(cubemaps).

Кубические карты

Кубическая карта представляет собой развёртку шести граней куба, каждая грань которого содержит текстуру. Каждая текстура отображает вид окружения, которое видно из одной точки зрения в шести направлениях ( определение из википедии). В нашем случае каждая грань куба будет содержать карту глубины соответствующей ей стороны от point light'а.

#8 UnityCode Shadows  — Unity — DevTribe: Разработка игр

Переменная с типом кубической карты в CG прописывается как samplerCUBE
В ShaderLab в блоке Properties переменной -кубической карте соответствует запись _Cube("Refl Cube",Cube) = "white"{}

Для того, чтобы сэмплировать(получить цвет) кубическую карту, нужно использовать функцию texCUBE(samplerCUBE sc, float3 vec)

sc - кубическая карта
vec - вектор, на основе которого высчитывется цвет.

Ключевые слова

Нам нужно сделать шейдер, который будет в зависимости от ситуации рендерить тени от directional light'а и от point light'а. Как это реализовать ? Да очень просто - при помощи ключевых слов! Они добавляют вариативности шейдеру. У них есть два состояние: ключевое слово объявлено или не объявлено.

Объявить ключевое слово можно при помощи директивы #define KEYWORD
Пример:

#define SHADOWS_SCREEN 

Здесь ключевым словом является SHADOWS_SCREEN

Проверить, объявлено то или иное ключевое слово можно при помощи директивы #if defined (KEYWORD)

Пример:

 
//Если SHADOWS_SCREEN объявлено, то будет исполнен код внутри блока #if --- #endif

#if defined (SHADOWS_SCREEN) 
...
// какой-то код 
 ...
#endif  

Устанавливать ключевые слова можно не только внутри шейдера, но и из C# скриптов с помощью функций EnableKeyword(string keyword) и DisableKeyword(string keyword)

Пример:

    public Material mat;

	void Start ()
    {

        if(mat!=null)
        {
            mat.EnableKeyword("SHADOWS_SCREEN"); // включаем 
            mat.DisableKeyword("ANOTHER_WORD"); // отключаем 
        }
		
    }

Директива multi_compile

Директива multi_compile используется для того, чтобы движок мог скомпилировать шейдер с определенными параметрами, тем самым обеспечивая несколько вариантов одного и того же шейдера.

Пример:

#pragma multi_compile FANCY_STUFF_OFF FANCY_STUFF_ON

Такая директивая будет производить 2 варианта шейдера: один с кейвордом FANCY_STUFF_OFF и другой с кейвордом FANCY_STUFF_ON. Один из них будет активирован на основе Material или глобальных ключевых слов шейдера. Если ни одно из двух ключевых слов не включено, тогда будет использовано первое (“off”).

Forward shadows

Как мы помним из этой статьи, в forward rendering'е освещение разбито на 2 прохода:

  • Forward Base (Directional Light + AmbientOcclusion)
  • Forward Additional (Все остальные источники света)

Значит, вполне логично и то, что тени от этих источников обрабатываются так-же: в forward base проходе тени от directional light'а, а в forward additional тени от остальных источников света.

Для того, чтобы Unity правильно правильно предоставляла нам текстуру теней, в каждый проход нужно добавить соответствующую ему multi_compile директиву:

  • Forward Base #pragma multi_compile_fwdbase_fullshadow
  • Forward Additional #pragma multi_compile_fwdadd_fullshadow

Практика

Постановка задач.

  • Написать шейдер, работающий в режиме forward rendering
  • Реализовать диффузное освещение с бликами, Ambient Occlusion, SH.
  • Реализовать отбрасывание теней для directional и point light'ов.
  • Реализовать прием теней от directional и point light'а.

Для начала создадим скелет шейдера (forward base проход,forward add проход и shadowcaster)

Shader "DevTribe/CompeteShadows"
{
	Properties
	{
	}
	SubShader
	{
		Tags 
		{ 
			"RenderType"="Opaque"
		}
		
		Pass
		{
	
			
			v2f vert ()
			{
			}
			fixed4 frag (v2f i) : SV_Target
			{
			}
			ENDCG
		}
		Pass
		{		
			
			
			v2f vert ()
			{
			}
			fixed4 frag (v2f i) : SV_Target
			{
			}
			ENDCG
		}
		
		Pass
		{
			Tags{"LightMode" = "ShadowCaster"}
			Zwrite On
			ZTest Less
			Cull Off
			CGPROGRAM 
			#pragma target 3.0
			#pragma vertex vert
			#pragma fragment frag 
			#include "UnityCG.cginc"
			
			v2f vert ()
			{
			}
			fixed4 frag (v2f i) : SV_Target
			{
			}
		}
	}
	
}

Теперь начнем разбираться с каждым проход по отдельности. И первым подопытным у нас станет ShadowCaster - проход, который используется для записи информации в Z баффер, чтобы потом на его основе рассчитать глубину.

В прошлом шейдере для directional light'а мы использовали вот такую реализацию:

			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

Сейчас нам необходимо реализация так же и для point light'. Как ее сделать ? Откроем frame debugger(Window->Frame Debugger) и посмотрим, как это делает движок со стандартным шейдером.

рендер глубины для directional light'а — #8 UnityCode Shadows  — Unity — DevTribe: Разработка игр
рендер глубины для directional light'а

Судя по картинке выше, для рендера глубины directional light'а, движок использует проход ShadowCaster с объявленным ключевым словом SHADOWS_DEPTH

Нам нужно сделать точно так-же. Идем в наш код и заключаем текущую реализацию глубины в блок #if defined(SHADOWS_DEPTH) --- #endif:

				#if defined(SHADOWS_DEPTH)
				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;
				}
				#endif
				

Отлично! Теперь смотрим, как стандартный шейдер пишет point light.

рендер глубины для point light'а — #8 UnityCode Shadows  — Unity — DevTribe: Разработка игр
рендер глубины для point light'а

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

Идем в shadowcaster и создаем блок if defined(SHADOWS_CUBE) --- endif. Но, прежде чем приступать к реализации, нужно подумать, а, как,собственно, реализовать запись информации о глубине для кубической текстуры ? В теории я уже говорил, что для сэмплирования кубической карты нужен вектор, указывающий на положение точки в пространстве. Так вот, для записи информации в кубическую карту так-же необходим вектор, исходящий из точки к источнику света.

Нам далеко не всегда нужно раскидывать во все стороны мозгами, чтобы реализовать ту или иную фичу. Очень часто они уже реализованы кем-то до нас. Поэтому можно просто подсмотреть, как это работает у других. В unity шейдерах, если мы хотим узнать, как реализованы те или иные вещи, нужно просто пройти в папку, где у вас установлен unity->Editor->Data->CGIncludes. Здесь расположены подключаемые файлы. В них, если покопаться, можно найти многое. Так и с данным примером, в "UnityCG.cginc" ( самый используемые подключаемый файл) можно найти нужный нам код

#8 UnityCode Shadows  — Unity — DevTribe: Разработка игр

Пишем реализацию для point light'а:

				#if defined(SHADOWS_CUBE)
				struct appdata 
				{
					float4 vertex:POSITION;
				};
				struct v2f 
				{
					float4 pos:SV_POSITION;
					float3 lightVec:TEXCOORD0;
				};

				v2f vert(appdata v)
				{
					v2f o;
					o.pos = UnityObjectToClipPos(v.vertex);
					o.lightVec = mul(unity_ObjectToWorld, v.vertex).xyz - _LightPositionRange.xyz;
					return o;
				}

				float4 frag(v2f i):SV_Target
				{
					float depth = length(i.lightVec) + unity_LightShadowBias.x;
					depth *= _LightPositionRange.w;
					return UnityEncodeCubeShadowDepth(depth);
				}
				#endif

_LightPositionRange - переменная, содержащая в xyz положение источника света, а в w обратное значение range источника ( если range = 10, то LightPositionRange .w = 0.1)

{
#ifdef UNITY_USE_RGBA_FOR_POINT_SHADOWS
return EncodeFloatRGBA (min(z, 0.999));
#else
return z;
#endif
}

Encoding/decoding [0..1) floats into 8 bit/channel RGBA. Note that 1.0 will not be encoded properly.

inline float4 EncodeFloatRGBA( float v )
{
float4 kEncodeMul = float4(1.0, 255.0, 65025.0, 16581375.0);
float kEncodeBit = 1.0/255.0;
float4 enc = kEncodeMul * v;
enc = frac (enc);
enc -= enc.yzww * kEncodeBit;
return enc;
}

ShadowCaster готов! Теперь реализуем directional shadow в forward base проходе.

Пропишем необходимые инструкции.

Tags {"LightMode" = "ForwardBase"}
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#pragma target 3.0
			// make fog work
			#pragma multi_compile_fog
			#pragma multi_compile_fwdbase_fullshadow
			#include "UnityCG.cginc"
			#include "AutoLight.cginc"

Для того, чтобы в переменную _ShadowMapTexture движок прислал верную текстуру теней, мы прописали директиву

 #pragma multi_compile_fwdbase_fullshadow

И для использования встроенных макросов подключаем AutoLight

#include "AutoLight.cginc"

Теперь создадим структуры для данных:

			struct appdata
			{
				float4 vertex : POSITION; 
				float2 uv : TEXCOORD0;
				float4 normal:NORMAL;

			};

			struct v2f
			{
				float2 uv : TEXCOORD0;
				UNITY_FOG_COORDS(1)
				float4 vertex : SV_POSITION;
				float3 worldPos:TEXCOORD2;
				float3 screenPos:TEXCOORD3;
				float3 normal:TEXCOORD4;
				float4 _ShadowCoord :TEXCOORD5;
			};

Поднимемся вверх и пропишем в свойствах шейдера необходимые внешние переменные:

Properties
	{
		_MainTex ("Texture", 2D) = "white" {}
		_Color ("Color", Color) = (1,1,1,1)
		_SpecColor("Specual Color", Color) = (1,1,1,1)
		_AO ("Ambient Occlusion Power", Float) = 0.5
		_Shininess("Shininess", Float) = 10
	} 

И сразу-же пропишем их в шейдере:

			uniform float4 _Color;
			uniform float4 _SpecColor;
			uniform float _AO;
			uniform float _Shininess;
			sampler2D _MainTex;
			float4 _MainTex_ST;

В вертексном шейдере, т.к. мы знаем, что у нас есть directional light, которые будет обрабатываться в forward base проходе, мы не будем писать #if defined(DIRECTIONAL) -- #endif, а просто переведем _ShadowCoord в screen space:

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.normal = normalize(mul(unity_WorldToObject, float4(v.normal.xyz, 0)).xyz);
				o.screenPos = ComputeScreenPos(o.vertex);
				o.worldPos = mul(unity_ObjectToWorld, v.vertex);
				o._ShadowCoord = ComputeScreenPos(o.vertex);
				return o;
			}

В фрагментном шейдере мы не будем вручную сэмплировать и фильтровать текстуру теней, а используем встроенный макрос SHADOW_ATTENUATION, который прописан в "AutoLighta".

#define SHADOW_ATTENUATION(a) unitySampleShadow(a._ShadowCoord)

unitySampleShadow прописан в подключаемом файле UnityShadowLibrary.cginc, которая сэмплирует тень в зависимости от ключевых слов, объявленных в шейдере.

Фрагментный шейдер должен обработать свет и тень от directional light'а + освещение от окружающей среды (Ambient Occlusion (AO) и Spherical Harmonics (SH) ). Какие данные для этого нужны:

  • цвет из текстуры (col)
  • тень (atten)
  • направление света (lightDir)
  • диффузное освещение (diffuse) + окружение (AO, SH), блики (spec)

Фрагментный шейдер:

			fixed4 frag (v2f i) : SV_Target
			{
				// sample the texture
				fixed4 col = tex2D(_MainTex, i.uv);
				// apply fog
				UNITY_APPLY_FOG(i.fogCoord, col);
				// Sample Shadows
				fixed atten = SHADOW_ATTENUATION(i);
				// if w == 0 then this is dir light, else point 
				float3 lightDir = normalize(_WorldSpaceLightPos0.xyz - i.worldPos.xyz *_WorldSpaceLightPos0.w);
				fixed3 diffuse = col*max(0.0, dot(i.normal, lightDir))*_Color*atten;
				fixed3 AO = (UNITY_LIGHTMODEL_AMBIENT.rgb) * _AO;
				fixed3 SH = ShadeSH9(float4(i.normal,1))* _AO;
				// Phong spec model
				
				float3 viewDir = normalize(_WorldSpaceCameraPos - i.worldPos);
				float3 refl = reflect(-lightDir,normalize(i.normal));
				fixed3 spec = col*_SpecColor* pow(max(0.0, dot(refl, viewDir)), _Shininess)*atten;
				return fixed4(SH+diffuse+AO+spec,1);
			}

Все, Forward base pass готов.

Перейдем к реализации point light теней в forward add pass'е.

Пропишем настройки:

				Blend One One
				ZWrite Off
				Cull Off
				Tags{ "LightMode" = "ForwardAdd"  }
				CGPROGRAM
				#pragma vertex vert
				#pragma fragment frag
				#pragma multi_compile_fwdadd_fullshadow
				#include "AutoLight.cginc"
				#include "UnityCG.cginc"

Здесь мы говорим движку, что это addition pass, а это значит, что в _ShadowMapTexture нужно подать теневую кубмапу.

#pragma multi_compile_fwdadd_fullshadow 

Структуры:

	struct appdata
			{
				float4 vertex : POSITION;
				float2 uv : TEXCOORD0;
				float4 normal:NORMAL;

			};

			struct v2f
			{
				float2 uv : TEXCOORD0;
				float4 vertex : SV_POSITION;
				float3 worldPos:TEXCOORD1;
				float3 screenPos:TEXCOORD2;
				float3 normal:TEXCOORD3;
				float4 _ShadowCoord :TEXCOORD4;
			};

Внешние переменные:

			uniform samplerCUBE _ShadowMapTexture;
			uniform float4 _ShadowMapTexture_TexelSize;
			uniform float4 _Color;
			uniform float4 _SpecColor;
			uniform float _Shininess;
			uniform float4 _LightColor0;
			sampler2D _MainTex;
			float4 _MainTex_ST;

uniform samplerCUBE _ShadowMapTexture ; - кубическая карта теней.

В вертексном шейдере в качестве теневых координат будет использоваться вектор для теневой кубмапы:

	v2f vert(appdata v)
			{
				v2f o;
				o.vertex = UnityObjectToClipPos(v.vertex);
				o.uv = TRANSFORM_TEX(v.uv, _MainTex);
				o.normal = normalize(mul(unity_WorldToObject, float4(v.normal.xyz, 0)).xyz);
				o.screenPos = ComputeScreenPos(o.vertex);
				o.worldPos = mul(unity_ObjectToWorld, v.vertex);
				o._ShadowCoord = float4(o.worldPos.xyz - _LightPositionRange.xyz,1);
				return o;
			}
			

Сейчас нужно подумать, что будет из себя представлять пиксельный шейдер. Он должен обрабатывать освещение и тени от point light'а. Какие данные для этого требуются ?

  • направление и расстояние от света до точки (lightDir)
  • цвет из текстуры
  • линейное затенение ( Не путать с тенью от объекта. Это затенение от расстояние до источника света) (1/length(lightDir))
  • тень от объекта (shadow)
  • диффузное освещение (diffuse), блик (spec)

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

			 float HabrCubeFiltering(float3 vec, float toLight_distance)
			{
				float downscale = 32.0f;
				toLight_distance *= 0.92;
				// Случайный вектор
				const float3 rndseed = float3(12.9898, 78.233, 45.5432);
				float3 randomvec = float3(dot(vec, rndseed), dot(vec.yzx, rndseed), dot(vec.zxy, rndseed));
				randomvec = frac(sin(randomvec) * 43758.5453);

				// Вот эти вектора для смещений
				float3 xvec = normalize(cross(vec, randomvec));
				float3 yvec = normalize(cross(vec, xvec));
				float3 vec1 = xvec / downscale;
				float3 vec2 = yvec / downscale;

				float4 shadowVals;

				// Выборки из кубмапы
				shadowVals.x = SampleCubeDistance(vec + vec1);
				shadowVals.y = SampleCubeDistance(vec + vec2);
				shadowVals.z = SampleCubeDistance(vec - vec1);
				shadowVals.w = SampleCubeDistance(vec - vec2);

				// Смешиваем
				half4 shadows = (shadowVals < toLight_distance.xxxx) ? _LightShadowData.rrrr : 1.0f;
				return dot(shadows, 0.25);
			}
			inline float SampleCubeDistance(float3 vec)
			{
				// DX9 with SM2.0, and DX11 FL 9.x do not have texture LOD sampling.
#if ((SHADER_TARGET < 25) && defined(SHADER_API_D3D9)) || defined(SHADER_API_D3D11_9X)
				return UnityDecodeCubeShadowDepth(texCUBE(_ShadowMapTexture, vec));
#else
				return UnityDecodeCubeShadowDepth(texCUBElod(_ShadowMapTexture, float4(vec, 0)));
#endif
			}
			fl

И, собсвенно, сам фрагментый шейдер:

			fixed4 frag(v2f i) :SV_Target
			{

				// sample the texture
			fixed4 col = tex2D(_MainTex, i.uv);
			// apply fog
			UNITY_APPLY_FOG(i.fogCoord, col);
			float3 lightDir = _WorldSpaceLightPos0.xyz - i.worldPos.xyz *_WorldSpaceLightPos0.w;
			float toLight_distance = (length(i._ShadowCoord) * _LightPositionRange.w);
			float shadow = HabrCubeFiltering(-lightDir, toLight_distance);
			//float shadow = texCUBE(_ShadowMapTexture, -lightDir);
			float atten = (shadow*2+1)/(3+length(lightDir));
			lightDir = normalize(lightDir);
			// if w == 0 then this is dir light, else point 

			fixed3 diffuse = col*max(0.0, dot(i.normal, lightDir))*_Color*atten;
			// Phong spec model
			lightDir = normalize(lightDir);
			float3 viewDir = normalize(_WorldSpaceCameraPos - i.worldPos);
			float3 refl = reflect(-lightDir, i.normal);
			fixed3 spec = ((_LightColor0*0.2)+(0.5*_SpecColor))* pow(max(0.0, dot(refl, viewDir)), _Shininess)*atten;
			return fixed4(diffuse + spec,1);
			} 

Готово!

Shader "DevTribe/CompeteShadows"
{
	Properties
	{
		_MainTex ("Texture", 2D) = "white" {}
		_Color ("Color", Color) = (1,1,1,1)
		_SpecColor("Specual Color", Color) = (1,1,1,1)
		_AO ("Ambient Occlusion Power", Float) = 0.5
		_Shininess("Shininess", Float) = 10
	}
	SubShader
	{
		Tags 
		{ 
			"RenderType"="Opaque"
		}
		LOD 100
		

		Pass
		{
			Tags {"LightMode" = "ForwardBase"}
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#pragma target 3.0
			// make fog work
			#pragma multi_compile_fog
			#pragma multi_compile_fwdbase_fullshadow
			#include "UnityCG.cginc"
			#include "AutoLight.cginc"

			struct appdata
			{
				float4 vertex : POSITION; 
				float2 uv : TEXCOORD0;
				float4 normal:NORMAL;

			};

			struct v2f
			{
				float2 uv : TEXCOORD0;
				UNITY_FOG_COORDS(1)
				float4 vertex : SV_POSITION;
				float3 worldPos:TEXCOORD2;
				float3 screenPos:TEXCOORD3;
				float3 normal:TEXCOORD4;
				float4 _ShadowCoord :TEXCOORD5;
			};

			uniform float4 _Color;
			uniform float4 _SpecColor;
			uniform float _AO;
			uniform float _Shininess;
			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.normal = normalize(mul(unity_WorldToObject, float4(v.normal.xyz, 0)).xyz);
				o.screenPos = ComputeScreenPos(o.vertex);
				o.worldPos = mul(unity_ObjectToWorld, v.vertex);
				o._ShadowCoord = ComputeScreenPos(o.vertex);
				return o;
			}
			
			fixed4 frag (v2f i) : SV_Target
			{
				// sample the texture
				fixed4 col = tex2D(_MainTex, i.uv);
				// apply fog
				UNITY_APPLY_FOG(i.fogCoord, col);
				// Sample Shadows
				fixed atten = SHADOW_ATTENUATION(i);
				// if w == 0 then this is dir light, else point 
				float3 lightDir = normalize(_WorldSpaceLightPos0.xyz - i.worldPos.xyz *_WorldSpaceLightPos0.w);
				fixed3 diffuse = col*max(0.0, dot(i.normal, lightDir))*_Color*atten;
				fixed3 AO = (UNITY_LIGHTMODEL_AMBIENT.rgb) * _AO;
				fixed3 SH = ShadeSH9(float4(i.normal,1))* _AO;
				// Phong spec model
				
				float3 viewDir = normalize(_WorldSpaceCameraPos - i.worldPos);
				float3 refl = reflect(-lightDir,normalize(i.normal));
				fixed3 spec = col*_SpecColor* pow(max(0.0, dot(refl, viewDir)), _Shininess)*atten;
				return fixed4(SH+diffuse+AO+spec,1);
			}
			ENDCG
		}
			Pass
			{
				Blend One One
				ZWrite Off
				Cull Off
				Tags{ "LightMode" = "ForwardAdd"  }
				CGPROGRAM
				#pragma vertex vert
				#pragma fragment frag
				#pragma multi_compile_fwdadd_fullshadow
				#include "AutoLight.cginc"
				#include "UnityCG.cginc"


			uniform samplerCUBE _ShadowMapTexture;
			uniform float4 _ShadowMapTexture_TexelSize;
			uniform float4 _Color;
			uniform float4 _SpecColor;
			uniform float _Shininess;
			uniform float4 _LightColor0;
			sampler2D _MainTex;
			float4 _MainTex_ST;

			struct appdata
			{
				float4 vertex : POSITION;
				float2 uv : TEXCOORD0;
				float4 normal:NORMAL;

			};

			struct v2f
			{
				float2 uv : TEXCOORD0;
				float4 vertex : SV_POSITION;
				float3 worldPos:TEXCOORD1;
				float3 screenPos:TEXCOORD2;
				float3 normal:TEXCOORD3;
				float4 _ShadowCoord :TEXCOORD4;
			};

			//================================ФУНКЦИИ ДЛЯ ФИЛЬТРАЦИИ=============================
			inline float SampleCubeDistance(float3 vec)
			{
				// DX9 with SM2.0, and DX11 FL 9.x do not have texture LOD sampling.
#if ((SHADER_TARGET < 25) && defined(SHADER_API_D3D9)) || defined(SHADER_API_D3D11_9X)
				return UnityDecodeCubeShadowDepth(texCUBE(_ShadowMapTexture, vec));
#else
				return UnityDecodeCubeShadowDepth(texCUBElod(_ShadowMapTexture, float4(vec, 0)));
#endif
			}


			float HabrCubeFiltering(float3 vec, float toLight_distance)
			{
				float downscale = 32.0f;
				toLight_distance *= 0.92;
				// Случайный вектор
				const float3 rndseed = float3(12.9898, 78.233, 45.5432);
				float3 randomvec = float3(dot(vec, rndseed), dot(vec.yzx, rndseed), dot(vec.zxy, rndseed));
				randomvec = frac(sin(randomvec) * 43758.5453);

				// Вот эти вектора для смещений
				float3 xvec = normalize(cross(vec, randomvec));
				float3 yvec = normalize(cross(vec, xvec));
				float3 vec1 = xvec / downscale;
				float3 vec2 = yvec / downscale;

				float4 shadowVals;

				// Выборки из кубмапы
				shadowVals.x = SampleCubeDistance(vec + vec1);
				shadowVals.y = SampleCubeDistance(vec + vec2);
				shadowVals.z = SampleCubeDistance(vec - vec1);
				shadowVals.w = SampleCubeDistance(vec - vec2);

				// Смешиваем
				half4 shadows = (shadowVals < toLight_distance.xxxx) ? _LightShadowData.rrrr : 1.0f;
				return dot(shadows, 0.25);
			}
			//=======================================================================================
			v2f vert(appdata v)
			{
				v2f o;
				o.vertex = UnityObjectToClipPos(v.vertex);
				o.uv = TRANSFORM_TEX(v.uv, _MainTex);
				o.normal = normalize(mul(unity_WorldToObject, float4(v.normal.xyz, 0)).xyz);
				o.screenPos = ComputeScreenPos(o.vertex);
				o.worldPos = mul(unity_ObjectToWorld, v.vertex);
				o._ShadowCoord = float4(o.worldPos.xyz - _LightPositionRange.xyz,1);
				return o;
			}

			fixed4 frag(v2f i) :SV_Target
			{

				// sample the texture
			fixed4 col = tex2D(_MainTex, i.uv);
			// apply fog
			UNITY_APPLY_FOG(i.fogCoord, col);
			float3 lightDir = _WorldSpaceLightPos0.xyz - i.worldPos.xyz *_WorldSpaceLightPos0.w;
			float toLight_distance = (length(i._ShadowCoord) * _LightPositionRange.w);
			float shadow = HabrCubeFiltering(-lightDir, toLight_distance);
			//float shadow = texCUBE(_ShadowMapTexture, -lightDir);
			float atten = (shadow*2+1)/(3+length(lightDir));
			lightDir = normalize(lightDir);
			// if w == 0 then this is dir light, else point 

			fixed3 diffuse = col*max(0.0, dot(i.normal, lightDir))*_Color*atten;
			// Phong spec model
			lightDir = normalize(lightDir);
			float3 viewDir = normalize(_WorldSpaceCameraPos - i.worldPos);
			float3 refl = reflect(-lightDir, i.normal);
			fixed3 spec = ((_LightColor0*0.2)+(0.5*_SpecColor))* pow(max(0.0, dot(refl, viewDir)), _Shininess)*atten;
			return fixed4(diffuse + spec,1);
			}

				ENDCG
			}
		
		Pass
		{
			Tags{"LightMode" = "ShadowCaster"}
			Zwrite On
			ZTest Less
			Cull Off
			CGPROGRAM 
			#pragma target 3.0
			#pragma multi_compile_shadowcaster
			#pragma vertex vert
			#pragma fragment frag 
			#include "UnityCG.cginc"

			//============SHADOWS CUBE========================
				#if defined(SHADOWS_CUBE)
				struct appdata 
				{
					float4 vertex:POSITION;
				};
				struct v2f 
				{
					float4 pos:SV_POSITION;
					float3 lightVec:TEXCOORD0;
				};

				v2f vert(appdata v)
				{
					v2f o;
					o.pos = UnityObjectToClipPos(v.vertex);
					o.lightVec = mul(unity_ObjectToWorld, v.vertex).xyz - _LightPositionRange.xyz;
					return o;
				}

				float4 frag(v2f i):SV_Target
				{
					float depth = length(i.lightVec) + unity_LightShadowBias.x;
					depth *= _LightPositionRange.w;
					return UnityEncodeCubeShadowDepth(depth);
				}
				#endif
			//=========== END SHADOWS CUBE==================

			//=========== SHADOWS DEPTH====================
				#if defined(SHADOWS_DEPTH)
				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;
				}
				#endif
			//=========== END SHADOWS DEPTH===============
			ENDCG

		}
	}
}

Результат:

#8 UnityCode Shadows  — Unity — DevTribe: Разработка игр

Заключение

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

Если есть вопросы, пишите в личку или в комментариях, постараюсь ответить.

Удачи!



  • 1
  • 2 (Текущая страница)

Если вкратце, то компромисс, про который я говорил, это лишь небольшое усовершенствование классического сдвига по глубине. А второй метод - это сдвиг по нормали (он то и используется в 3D Builder). Очевидный метод, но почему-то статей по нему я не нашел.

DENj, сдвиг по нормали - это normal bias, если поискать в интернете, можно найти его описание, правда на инглише. Но если сделаешь статью на русском, то это будет полезно ! А так вся борьба с самозатенением - это усовершенствование метода сдвига. Вот один из лучших, и который юзается в последних версиях юнити - Receiver Plane Depth Bias. Тут PDF, который все это описывает.

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

DENj,

при котором вместо глубины записывается список полигонов

Ого, про такой подход к рендерингу теней я еще не слышал. Слышал про разные функции расчета глубины для улучшения точности и качества теней, но такой подход... Есть желание поделится информацией об этом?

alexprey, этот метод используется в Unreal 4, я его не пытался реализовывать. Как по мне слишком дорого как в плане памяти так и в плане производительности.

DENj, вах, а я почему-то не нарыл про них инфу, когда искал алгоритмы сглажиавния. Наверное, из-за того, что искал в контексте юнити. А как гибридный метод с полигонами называется на английском ? Хочется почитать )

lehanru, Hybrid Ray-Traced Shadows.

Большое человеческое спасибо
вот и появилась у меня настольная книга по шейдерам! )

  • 1
  • 2 (Текущая страница)