Работаем с Stencil в Unity3D ShaderLab

Приветы. Встала вот такая задача: сделать затемнение всего экрана, но в каком-то участке сделать как бы «дырку».

Как такое можно сделать?

1 Вариант — «Латать» экран
Заделываем экран кусками — тут думаю понятно) У нас есть одна текстура круга, а остальную часть мы заделываем кусками. Реализовывать дополнительно ничего не нужно, достаточно сверстать. НО! Как только понадобиться несколько «дыр», верстка усложнится. А если нужно динамически подсвечивать часть экрана?

2 Вариант — Шейдеры
С шейдерами можно сделать очень многое. Но нам нужно лишь небольшая их часть — отсечение пикселей. Что я бы хотел видеть?
1. Какая-то текстура на весь экран, у которой выбираем цвет и прозрачность
2. Вторая текстура, которая уже задает форму «дыры», ее можно перемещать, вращать и тд, отключать/включать при надобности.
3. Все это должно работать в рамках NGUI и UITexture

Начнем!

Вначале рекомендую прочесть про Stencil:
Стенсил буфер (Stencil Buffer — буфер шаблона или буфер трафарета) — это дополнительный буфер, соответствующий размеру выводимого кадра, то есть каждому пикселю изображения на экране соответствует свое значение в стенсил буфере. Каждый раз когда точка рисуется на экран, то кроме тестов, вроде сравнения с глубиной в Z-буфере, она проходит еще и стенсил тест. То есть, например, можно сказать — точка рисуется, только если в стенсиле значение больше единицы. С другой стороны, можно сказать, как изменить значение стенсила после того как пиксель в этом месте отрисуется.
1. www.gamedev.ru/terms/StencilBuffer
2. docs.unity3d.com/Manual/SL-Stencil.html

За основу я брал код по второй ссылке.

Вот так выглядит наш код шейдера для UITexture, которая является дырой
Shader "Unlit/Transparent Mask" {
	Properties
	{
		_MainTex ("Base (RGB), Alpha (A)", 2D) = "black" {}
	}
	
    SubShader {
        		Tags
		{
			"Queue" = "Transparent"
			"IgnoreProjector" = "True"
			"RenderType" = "Transparent"
		}
        Pass {
            Stencil {
                Ref 2
                Comp always
                Pass replace
                Fail keep
                ZFail decrWrap
            }
            
	        Cull Off
			Lighting Off
			ZWrite Off
			Fog { Mode Off }
			Offset -1, -1
			Blend SrcAlpha OneMinusSrcAlpha
			
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
             #include "UnityCG.cginc"
            	sampler2D _MainTex;
			float4 _MainTex_ST;
            
            struct appdata {
                float4 vertex : POSITION;
				float2 texcoord : TEXCOORD0;
				fixed4 color : COLOR;
            };
            struct v2f {
                float4 vertex : SV_POSITION;
				half2 texcoord : TEXCOORD0;
				fixed4 color : COLOR;
            };
            
            v2f o;
            
            v2f vert(appdata v)
            {
                 o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
				o.texcoord = v.texcoord;
				o.color = v.color;
                return o;
            }
            
            fixed4 frag(v2f IN) : SV_Target
            {
            	fixed4 color = tex2D(_MainTex, IN.texcoord);
            	color.rgb *= IN.color.rgb;
            	
            	if(color.a > 0.1){
            		color.a = 0;
            	}else{
            		color.a = IN.color.a;
            	}
            	
                return color;
            }
            ENDCG
        }
    } 
}


Что нужно учесть?
1. Добавили текстуру
2. Изменили очеред и тип отрисовки — Transparent
3. Добавили Блендинг, чтобы была видна альфа и правильно смешивалась
4. Изменили фрагментный шейдер. Вообще это не совсем нужная вещь, но я сделал так, что прозрачная часть текстуры заливается цветом и альфой из цвета в UITexture, а непрозрачная часть становиться прозрачной. 0.1 — это порог отличия прозрачного от непрозрачного. Костыль короч)
5. Параметры Stencil говорят, что рисуем все пиксели, и ставим в буфер значение 2

Теперь магия — шейдер для полноэкранной UITexture
Shader "Unlit/Transparent Shadow" {
	Properties
	{
		//_MainTex ("Base (RGB), Alpha (A)", 2D) = "black" {}
	}
	
    SubShader {
        		Tags
		{
			"Queue" = "Transparent"
			"IgnoreProjector" = "True"
			"RenderType" = "Transparent"
		}
		
        Pass {
            Stencil {
                Ref 2
                Comp NotEqual
                Pass keep 
                Fail keep 
                ZFail keep
            }
            
	        Cull Off
			Lighting Off
			ZWrite Off
			Fog { Mode Off }
			Offset -1, -1
			Blend SrcAlpha OneMinusSrcAlpha
			
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
           // 	sampler2D _MainTex;
			//float4 _MainTex_ST;
            
            struct appdata {
                float4 vertex : POSITION;
				float2 texcoord : TEXCOORD0;
				fixed4 color : COLOR;
            };
            struct v2f {
                float4 vertex : SV_POSITION;
				half2 texcoord : TEXCOORD0;
				fixed4 color : COLOR;
            };
            
            v2f o;
            
            v2f vert(appdata v)
            {
                o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
				//o.texcoord = v.texcoord;
				o.color = v.color;
				
				return o;
            }
            
            fixed4 frag(v2f IN) : COLOR
            {
                return IN.color;
            }
            ENDCG
        }
    } 
}


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

1. Самое главное отличие — Stencil параметры. Теперь мы рисуем только те пиксели, которые не равны значению 2, т е все, кроме той области, в которой отрисовывается предыдущий меш. Круто?)
2. Цвет берется из параметра цвета в UITexture

Вот и все) Пример работы ниже. Единственное, если две маски накладываются друг на друга, то получается не оч красиво(пересечение текстур дает большую плотность и область затемняется). Фиксить думаю нужно Blend параметры. Если кто-нибудь захочет и поделиться — буду рад и благодарен!

0 комментариев

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