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完整语法格式如下:
stencil{
	Ref referenceValue
	ReadMask  readMask
	WriteMask writeMask
	Comp comparisonFunction
	Pass stencilOperation
	Fail stencilOperation
	ZFail stencilOperation
}
 
Ref
Ref referenceValue
Ref用来设定参考值referenceValue,这个值将用来与模板缓冲中的值进行比较。referenceValue是一个取值范围位0-255的整数。
 
ReadMask
ReadMask  readMask
ReadMask 从字面意思的理解就是读遮罩,readMask将和referenceValue以及stencilBufferValue进行按位与(&)操作,readMask取值范围也是0-255的整数,默认值为255,二进制位11111111,即读取的时候不对referenceValue和stencilBufferValue产生效果,读取的还是原始值。
 
WriteMask
WriteMask writeMask
WriteMask是当写入模板缓冲时进行掩码操作(按位与【&】),writeMask取值范围是0-255的整数,默认值也是255,即当修改stencilBufferValue值时,写入的仍然是原始值。
 
Comp
Comp comparisonFunction
Comp是定义参考值(referenceValue)与缓冲值(stencilBufferValue)比较的操作函数,默认值:always
 
Pass
Pass stencilOperation
Pass是定义当模板测试(和深度测试)通过时,则根据(stencilOperation值)对模板缓冲值(stencilBufferValue)进行处理,默认值:keep
 
Fail
Fail stencilOperation
Fail是定义当模板测试(和深度测试)失败时,则根据(stencilOperation值)对模板缓冲值(stencilBufferValue)进行处理,默认值:keep
 
ZFail
ZFail stencilOperation
ZFail是定义当模板测试通过而深度测试失败时,则根据(stencilOperation值)对模板缓冲值(stencilBufferValue)进行处理,默认值:keep
 
Comp,Pass,Fail 和ZFail将会应用给背面消隐的几何体(只渲染前面的几何体),除非Cull Front被指定,在这种情况下就是正面消隐的几何体(只渲染背面的几何体)。你也可以精确的指定双面的模板状态通过定义CompFront,PassFront,FailFront,ZFailFront(当模型为front-facing geometry使用)和ComBack,PassBack,FailBack,ZFailBack(当模型为back-facing geometry使用)
     _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

posted @ 2020-10-21 18:01  陌冉  阅读(1126)  评论(0编辑  收藏  举报