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'а.

Переменная с типом кубической карты в 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'а, движок использует проход 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.

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

Пишем реализацию для 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
}
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
}
}
}
Результат:

Заключение
Как вы видите, чем больше фич в шейдере хочешь реализовать, тем больше строк кода приходится писать. А это чревато багами и ошибками. Поэтому, чтобы научиться писать шейдеры, настоятельно всем рекомендую лазить по разным подключаемым файлам, стандартным шейдерам и использовать встроенный код. Хотя однозначно нельзя сказать, т.к. на днях разрабы юнити завезли в бету новый инструмент для графического создания шейдеров (Shader Graph tool), который, возможно, может очень сильно повлиять на процесс создания шейдеров в Unity, и тогда надобность в рысканье по лабиринтам стандартного кода отпадет.
Если есть вопросы, пишите в личку или в комментариях, постараюсь ответить.
Удачи!
Смотрите также:
Комментарии
- 1
- 2 (Текущая страница)
Если вкратце, то компромисс, про который я говорил, это лишь небольшое усовершенствование классического сдвига по глубине. А второй метод - это сдвиг по нормали (он то и используется в 3D Builder). Очевидный метод, но почему-то статей по нему я не нашел.
DENj, сдвиг по нормали - это normal bias, если поискать в интернете, можно найти его описание, правда на инглише. Но если сделаешь статью на русском, то это будет полезно ! А так вся борьба с самозатенением - это усовершенствование метода сдвига. Вот один из лучших, и который юзается в последних версиях юнити - Receiver Plane Depth Bias. Тут PDF, который все это описывает.
lehanru, не только усовершенствование метода сдвига. Есть еще метод с идентификаторами полигонов, еще какая-то студия демонстрировала свой метод с использованием двух шадоу мапов, экспоненциальные тени, не забываем и про гибридные рейкастинг тени - улучшение шадоумапов, при котором вместо глубины записывается список полигонов.
DENj,
при котором вместо глубины записывается список полигонов
Ого, про такой подход к рендерингу теней я еще не слышал. Слышал про разные функции расчета глубины для улучшения точности и качества теней, но такой подход... Есть желание поделится информацией об этом?
alexprey, этот метод используется в Unreal 4, я его не пытался реализовывать. Как по мне слишком дорого как в плане памяти так и в плане производительности.
DENj, вах, а я почему-то не нарыл про них инфу, когда искал алгоритмы сглажиавния. Наверное, из-за того, что искал в контексте юнити. А как гибридный метод с полигонами называется на английском ? Хочется почитать )
Большое человеческое спасибо
вот и появилась у меня настольная книга по шейдерам! )
- 1
- 2 (Текущая страница)


<a href= http://mosros.flybb.ru/viewtopic.php?f=2&t=635>Процесс получения диплома стоматолога: реально ли это сделать быстро?</a>