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

#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 (Текущая страница)
Справка