Unity Shader实现图片的区域遮罩,支持半透明,实现地图动态上色功能

发表于2019-01-16
评论0 1w浏览
做项目中的世界地图时,希望未开启的地块是线稿,新地块开启时,做一个上色处理。

想到的方案就是:上了色的彩图盖在线稿上,然后用mask 控制彩图的局部显隐。

参考了这个,可以半透明遮罩的shader:使用透明度实现Mask遮罩的Unity Shader

要控制不同区块显示或不显示,要怎么处理呢?? mask图其实只用到了 alpha,还有rgb24位可以用。

于是自己改了一下shader,对mask图有特殊要求,rgb颜色必须是 1 2 4 8 16 32 64 128 就可以用位操作组合最多控制24个图块了。

有一个难点在,shader怎么对rgb色值位操作,? 找了一下发现? (int)round(color.r*255.0) 转成整数可以。

代码如下:
Shader "ImageEffect/AlphaMask"
{
	Properties
	{
		[PerRendererData] _MainTex("Sprite Texture", 2D) = "white" {}
		_Mask("Base (RGB)", 2D) = "white" {}			//遮罩图
		_AreaMask("AreaMask", Color) = (1,0,0,0)		//区块显隐配置(可完全显示的),按 r g b位运算,最多24块区域
		_AlphaVal("AlphaVal", Range(0,5)) = 1.0			//控制动态显示的变化值,乘以 mask.color.a, 让其从强到弱慢慢显示出来
		_AnimAreaMask("AnimAreaMask", Color) = (1,0,0,0)	//当前要随AlphaVal变化而显示变化过程的区块配置,按 r g b位运算,最多24块区域
		_AlphaThreshold("AlphaThreshold",float) = 0.4 //mask的alpha乘以AlphaVal后 显隐阈值,不到则不显示
		_Color("Tint", Color) = (1,1,1,1)
		_StencilComp("Stencil Comparison", Float) = 8
		_Stencil("Stencil ID", Float) = 0
		_StencilOp("Stencil Operation", Float) = 0
		_StencilWriteMask("Stencil Write Mask", Float) = 255
		_StencilReadMask("Stencil Read Mask", Float) = 255
		_ColorMask("Color Mask", Float) = 15
		[Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip("Use Alpha Clip", Float) = 0
	}
	SubShader
	{
		Tags
		{
			"Queue" = "Transparent"
			"IgnoreProjector" = "True"
			"RenderType" = "Transparent"
			"PreviewType" = "Plane"
			"CanUseSpriteAtlas" = "True"
		}
		Stencil
		{
			Ref[_Stencil]
			Comp[_StencilComp]
			Pass[_StencilOp]
			ReadMask[_StencilReadMask]
			WriteMask[_StencilWriteMask]
		}
		Cull Off
		Lighting Off
		ZWrite Off
		ZTest[unity_GUIZTestMode]
		Blend SrcAlpha OneMinusSrcAlpha
		ColorMask[_ColorMask]
		Pass
		{
			CGPROGRAM
	#pragma vertex vert
	#pragma fragment frag
	#include "UnityCG.cginc"
	#include "UnityUI.cginc"
	#pragma multi_compile __ UNITY_UI_ALPHACLIP
			struct a2v
			{
				fixed2 uv : TEXCOORD0;
				half4 vertex : POSITION;
				float4 color    : COLOR;
			};
			struct v2f
			{
				fixed2 uv : TEXCOORD0;
				half4 vertex : SV_POSITION;
				float4 color    : COLOR;
			};
			sampler2D _MainTex;
			sampler2D _Mask;
			float _AlphaVal;
			fixed4 _Color;
			fixed4 _AreaMask;
			fixed4 _AnimAreaMask;
			float _AlphaThreshold;
			v2f vert(a2v i)
			{
				v2f o;
				o.vertex = mul(UNITY_MATRIX_MVP, i.vertex);
				o.uv = i.uv;
				o.color = i.color * _Color;
				return o;
			}
			fixed4 frag(v2f i) : COLOR
			{
				half4 color = tex2D(_MainTex, i.uv) * i.color;
				half4 mask = tex2D(_Mask, i.uv);
				//color.a *= mask.a;  //原半透明遮罩算法
				int maskr = round(mask.r * 255.0);
				int maskg = round(mask.g * 255.0);
				int maskb = round(mask.b * 255.0);
				int t = ((int)round(_AreaMask.r * 255.0) & maskr)
					| ((int)round(_AreaMask.g * 255.0) & maskg)
					| ((int)round(_AreaMask.b * 255.0) & maskb); //mask 和 配置的颜色块进行位运算,> 0表示符合开启条件
				int t2 = ((int)round(_AnimAreaMask.r * 255.0) & maskr)
					| ((int)round(_AnimAreaMask.g * 255.0) & maskg)
					| ((int)round(_AnimAreaMask.b * 255.0) & maskb); //mask 和 配置的颜色块进行位运算,> 0表示符合开启条件
				float ma = mask.a * _AlphaVal;
				color.a *= step(1, t)*mask.a*5 + step(1, t2) * ((ma - _AlphaThreshold) * 5 * step(_AlphaThreshold, ma));
				/* 上面的算式相当于下面的逻辑判断
				if(t > 0)
				{
					color.a *= mask.a*5;	//显示这个区域 (或者 mask.a==0时不显示)
				}
				else if(t2 > 0)				//逐渐显示的区域
				{
					if (ma > _AlphaThreshold)	//超过阈值才显示
					{
						color.a *= (ma - _AlphaThreshold) * 5;  //color.a *= ma 边缘会有明显轮廓,改进了一下算法,让边缘柔和些。5可以随便调整一下
					}
					else
					{
						color.a = 0;
					}
				}
				else
				{
					color.a = 0;
				} */
				return color;
			}
			ENDCG
		}
	}
}
不知道效率如何。

用到的测试mask图是:

编号和rgb对应关系:
0: r1 g0 b0
5: r32 g0 b0
7: r128 g0 b0
13: r0 g32 b0
20: r0 g0 b16

AreaMask 的颜色控制了 直接显示的色块 比如 填 (33,0,0),则 0、5块图就会显示出来。

AnimAreaMask 的颜色控制哪些图块需要 随 AlphaVal变化而动态显示出来。

用程序设置这两个颜色,然后调整 AlphaVal就能实现策划需要的功能了,至于具体效果嘛,还要美术把对应的图做好。

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引