FGUIshader浅析——ImageShader
Shader "FairyGUI/Image" { Properties { _MainTex ("Base (RGB), Alpha (A)", 2D) = "black" {} _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 _BlendSrcFactor ("Blend SrcFactor", Float) = 5 _BlendDstFactor ("Blend DstFactor", Float) = 10 } SubShader { LOD 100 Tags { "Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent" } Stencil { Ref [_Stencil] Comp [_StencilComp] Pass [_StencilOp] ReadMask [_StencilReadMask] WriteMask [_StencilWriteMask] } Cull Off Lighting Off ZWrite Off Fog { Mode Off } Blend [_BlendSrcFactor] [_BlendDstFactor], One One ColorMask [_ColorMask] Pass { CGPROGRAM #pragma multi_compile NOT_COMBINED COMBINED #pragma multi_compile NOT_GRAYED GRAYED COLOR_FILTER #pragma multi_compile NOT_CLIPPED CLIPPED SOFT_CLIPPED ALPHA_MASK #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata_t { float4 vertex : POSITION; fixed4 color : COLOR; float4 texcoord : TEXCOORD0; }; struct v2f { float4 vertex : SV_POSITION; fixed4 color : COLOR; float4 texcoord : TEXCOORD0; #ifdef CLIPPED float2 clipPos : TEXCOORD1; #endif #ifdef SOFT_CLIPPED float2 clipPos : TEXCOORD1; #endif }; sampler2D _MainTex; #ifdef COMBINED sampler2D _AlphaTex; #endif #ifdef CLIPPED float4 _ClipBox = float4(-2, -2, 0, 0); #endif #ifdef SOFT_CLIPPED float4 _ClipBox = float4(-2, -2, 0, 0); float4 _ClipSoftness = float4(0, 0, 0, 0); #endif #ifdef COLOR_FILTER float4x4 _ColorMatrix; float4 _ColorOffset; float _ColorOption = 0; #endif v2f vert (appdata_t v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.texcoord = v.texcoord; #if !defined(UNITY_COLORSPACE_GAMMA) && (UNITY_VERSION >= 550) o.color.rgb = GammaToLinearSpace(v.color.rgb); o.color.a = v.color.a; #else o.color = v.color; #endif #ifdef CLIPPED o.clipPos = mul(unity_ObjectToWorld, v.vertex).xy * _ClipBox.zw + _ClipBox.xy; #endif #ifdef SOFT_CLIPPED o.clipPos = mul(unity_ObjectToWorld, v.vertex).xy * _ClipBox.zw + _ClipBox.xy; #endif return o; } fixed4 frag (v2f i) : SV_Target { fixed4 col = tex2D(_MainTex, i.texcoord.xy / i.texcoord.w) * i.color; #ifdef COMBINED col.a *= tex2D(_AlphaTex, i.texcoord.xy / i.texcoord.w).g; #endif #ifdef GRAYED fixed grey = dot(col.rgb, fixed3(0.299, 0.587, 0.114)); col.rgb = fixed3(grey, grey, grey); #endif #ifdef SOFT_CLIPPED float2 factor = float2(0,0); if(i.clipPos.x<0) factor.x = (1.0-abs(i.clipPos.x)) * _ClipSoftness.x; else factor.x = (1.0-i.clipPos.x) * _ClipSoftness.z; if(i.clipPos.y<0) factor.y = (1.0-abs(i.clipPos.y)) * _ClipSoftness.w; else factor.y = (1.0-i.clipPos.y) * _ClipSoftness.y; col.a *= clamp(min(factor.x, factor.y), 0.0, 1.0); #endif #ifdef CLIPPED float2 factor = abs(i.clipPos); col.a *= step(max(factor.x, factor.y), 1); #endif #ifdef COLOR_FILTER if (_ColorOption == 0) { fixed4 col2 = col; col2.r = dot(col, _ColorMatrix[0]) + _ColorOffset.x; col2.g = dot(col, _ColorMatrix[1]) + _ColorOffset.y; col2.b = dot(col, _ColorMatrix[2]) + _ColorOffset.z; col2.a = dot(col, _ColorMatrix[3]) + _ColorOffset.w; col = col2; } else //premultiply alpha col.rgb *= col.a; #endif #ifdef ALPHA_MASK clip(col.a - 0.001); #endif return col; } ENDCG } } }
上面是FGUI版本FairyGUI-unity-4.1.0的ImageShader内容:
先从Properties开始吧,_MainTex存储的就是一张基本的纹理贴图,通常是一张XXXX_atlas0,FGUI通过编辑器自动打包发布的图集,但是这里可以说的是某些特定的大图也可以类似UGUI的手法单独加载散图进来使用,因为FGUI默认打包成图集,如果多张背景大图的情况下就会冗余很多空间,要想充分利用内存可以用散图替代,详细的话给自己挖个坑,我后面找机会再来谈一谈这块的实现细节和注意的坑。
_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
接下来这部分那就是UGUI的"UI/Default"同款:
Shader "UI/Default" { Properties { [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {} _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 }
功能也一样,就是对Stencil值根据功能需求进行修改,做到不同的效果,基本语法如下:
stencil{ Ref referenceValue ReadMask readMask WriteMask writeMask Comp comparisonFunction Pass stencilOperation Fail stencilOperation ZFail stencilOperation }
Ref referenceValue
ReadMask readMask
WriteMask writeMask
Comp comparisonFunction
Pass stencilOperation
Fail stencilOperation
ZFail stencilOperation
_ColorMask ("Color Mask", Float) = 15 _BlendSrcFactor ("Blend SrcFactor", Float) = 5 _BlendDstFactor ("Blend DstFactor", Float) = 10
这两个参数也很简单:
Blend [_BlendSrcFactor] [_BlendDstFactor], One One
ColorMask [_ColorMask]
一个是设置外部可以设置混合的方式,如透明度混合,一个是设置颜色遮罩。而ColorMask可以让我们制定渲染结果的输出通道,而不是通常情况下的RGBA这4个通道全部写入。3
Tags { "Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent" }
对于Tags的话和UGUI也是大差不差,走的是不透明渲染队列。
接下来是FGUI和UGUI不一样的地方,我们看Vert
v2f vert (appdata_t v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.texcoord = v.texcoord;
#if !defined(UNITY_COLORSPACE_GAMMA) && (UNITY_VERSION >= 550)
o.color.rgb = GammaToLinearSpace(v.color.rgb);
o.color.a = v.color.a;
#else
o.color = v.color;
#endif
#ifdef CLIPPED
o.clipPos = mul(unity_ObjectToWorld, v.vertex).xy * _ClipBox.zw + _ClipBox.xy;
#endif
#ifdef SOFT_CLIPPED
o.clipPos = mul(unity_ObjectToWorld, v.vertex).xy * _ClipBox.zw + _ClipBox.xy;
#endif
return o;
}
先是最常规的计算顶点位置,记录uv.之后进行了gamma校正。细说下裁切的计算,这里的
mul(unity_ObjectToWorld, v.vertex).xy * _ClipBox.zw + _ClipBox.xy;
是将点坐标转换到Panel的局部坐标系下,并执行一个“归一化”操作,操作上就是首先执行一个位移操作,然后依据Panel的大小执行一个缩放。这块是类似NGUI的Imager处理,
_ClipBox代表的是Panel的(裁剪)区域大小,其中xy分量代表区域中心,zw分量代表区域大小的一半(注意,是区域大小的一半!)
对于裁剪,我们的目标其实很明确,就是判断某个点是否在Panel的区域中,如果不在则将其直接“裁剪掉”,在则保留,相关的方法有不少,比较直接的一种就是直接比较点坐标与Panel的Corner坐标,不过这里我们使用一种更为通用的方法,就是将点坐标转换到Panel的局部坐标系下,并执行一个“归一化”操作,操作上就是首先执行一个位移操作,然后依据Panel的大小执行一个缩放,即:
假设顶点坐标为 vx, vy, Panel的中心位置为px, py, Panel的区域大小为pw, ph,并设pw’ = 0.5 * pw, ph’ = 0.5 * ph, 则相关操作可表示为:
vx’ = (vx - px) / pw’
vy’ = (vy - py) / ph’
考虑之前提到的ClipRange,其如果使用上述分量来表示的话,即为:
ClipRange.x = -px / pw’
ClipRange.y = -py / ph’
ClipRange.z = 1 / pw’
ClipRange.w = 1 / ph’
综上,则有:
vx’ = vx * ClipRange.z + ClipRange.x
vy’ = vy * ClipRange.w + ClipRange.y
即等于:o.clipPos = mul(unity_ObjectToWorld, v.vertex).xy * _ClipBox.zw + _ClipBox.xy;
fixed4 frag (v2f i) : SV_Target { fixed4 col = tex2D(_MainTex, i.texcoord.xy / i.texcoord.w) * i.color; #ifdef COMBINED col.a *= tex2D(_AlphaTex, i.texcoord.xy / i.texcoord.w).g; #endif #ifdef GRAYED fixed grey = dot(col.rgb, fixed3(0.299, 0.587, 0.114)); col.rgb = fixed3(grey, grey, grey); #endif //参考ngui https://cloud.tencent.com/developer/article/1175658 #ifdef SOFT_CLIPPED float2 factor = float2(0,0); if(i.clipPos.x<0) factor.x = (1.0-abs(i.clipPos.x)) * _ClipSoftness.x; else factor.x = (1.0-i.clipPos.x) * _ClipSoftness.z; if(i.clipPos.y<0) factor.y = (1.0-abs(i.clipPos.y)) * _ClipSoftness.w; else factor.y = (1.0-i.clipPos.y) * _ClipSoftness.y; col.a *= clamp(min(factor.x, factor.y), 0.0, 1.0); #endif #ifdef CLIPPED float2 factor = abs(i.clipPos); col.a *= step(max(factor.x, factor.y), 1); #endif #ifdef COLOR_FILTER if (_ColorOption == 0) { fixed4 col2 = col; col2.r = dot(col, _ColorMatrix[0]) + _ColorOffset.x; col2.g = dot(col, _ColorMatrix[1]) + _ColorOffset.y; col2.b = dot(col, _ColorMatrix[2]) + _ColorOffset.z; col2.a = dot(col, _ColorMatrix[3]) + _ColorOffset.w; col = col2; } else //premultiply alpha col.rgb *= col.a; #endif #ifdef ALPHA_MASK clip(col.a - 0.001); #endif return col; }
再来看frag部分:
fixed4 frag (v2f i) : SV_Target { //贴图采样 fixed4 col = tex2D(_MainTex, i.texcoord.xy / i.texcoord.w) * i.color; #ifdef COMBINED //采样透明贴图的G通道进行透明度混合 col.a *= tex2D(_AlphaTex, i.texcoord.xy / i.texcoord.w).g; #endif #ifdef GRAYED //默认的置灰颜色,如果要改置灰颜色改这里,把当前颜色和置灰色进行点成混合成置灰后的颜色 fixed grey = dot(col.rgb, fixed3(0.299, 0.587, 0.114)); col.rgb = fixed3(grey, grey, grey); #endif #ifdef SOFT_CLIPPED //总体思路就是计算出当前距离边界的位置,然后取最小值再结合裁切包围盒的参数调整透明度来完成柔性裁剪的效果、 float2 factor = float2(0,0); if(i.clipPos.x<0) factor.x = (1.0-abs(i.clipPos.x)) * _ClipSoftness.x; else factor.x = (1.0-i.clipPos.x) * _ClipSoftness.z; if(i.clipPos.y<0) factor.y = (1.0-abs(i.clipPos.y)) * _ClipSoftness.w; else factor.y = (1.0-i.clipPos.y) * _ClipSoftness.y; col.a *= clamp(min(factor.x, factor.y), 0.0, 1.0); #endif #ifdef CLIPPED //根据factor来判断当前frag显隐 float2 factor = abs(i.clipPos); col.a *= step(max(factor.x, factor.y), 1); #endif #ifdef COLOR_FILTER //颜色滤镜 if (_ColorOption == 0) { fixed4 col2 = col; col2.r = dot(col, _ColorMatrix[0]) + _ColorOffset.x; col2.g = dot(col, _ColorMatrix[1]) + _ColorOffset.y; col2.b = dot(col, _ColorMatrix[2]) + _ColorOffset.z; col2.a = dot(col, _ColorMatrix[3]) + _ColorOffset.w; col = col2; } else //premultiply alpha col.rgb *= col.a; #endif #ifdef ALPHA_MASK //将透明度<0.001的面片都舍弃 clip(col.a - 0.001); #endif return col; }
一个个打字累了,就直接上注释了,值得一提的是ColorFilter颜色滤镜的处理,这块其实结合FGUI官方示例Example 23 - Filter会很清楚的看到,他实际是通过修改颜色
属性实现的,和设置颜色为灰阶颜色一样的效果。例如设置颜色
为0xCCCCCC,和设置亮度为0xCC是相同的效果。如果有特殊的需求可以加个参数对图片效果进行后处理。
今天的Imageshader就到这了,这里原理看清楚以后其实其他的比如Textshdar你会发现,哎哟大部分居然一个样。所以后面那几个我可能会挑着差异讲,然后再谈谈FGUI的DrawCall优化浅析,说说FGUI的动态合批是如何实现的。下次再见~
参考文档:https://cloud.tencent.com/developer/article/1175658