UnityShaderLab读书笔记 第7章(透明效果,透明混合、透明测试、模板测试)


第7章 透明效果

  • 不透明和半透明物体渲染顺序、混合效果、透明测试、模板测试

不透明物体渲染顺序

  • 按照正常思维,先绘制距离摄像机远的物体,然后再绘制离摄像机近的物体,绘制出来的结果并没有问题,但是如果从性能方面考虑,这个会产生重叠绘制(OverDraw)当重叠过多会造成巨大的性能开销,但是如果仅仅改变绘制的顺序并不能解决性能开销反而还会造成绘制出来物体显示的错误。所以引入了深度值(Depth)存储在屏幕空间顶点坐标的z中,这个深度值与物体距离屏幕远近距离成正比
  • 深度测试 绘制时片段如果Depth比深度缓冲中的值大说明这个片段被其他物体遮挡,不应该被绘制出来,应被丢弃。
  • 深度写入 将Depth更新至深度缓存中

透明物体渲染顺序

  • 假设半透明物体距离摄像机近,不透明物体距离摄像机远
  • Unity中深度测试在顶点着色后进行,图像叠加是在最后一步Blending阶段完成,并且只有在Blending阶段才知道挡住自己的是否是半透明物体
  • (“Queue” = “Transparent”)为半透明物体,在所有不透明物体渲染完成后绘制,但是这样并没有解决半透明物体遮挡住了不透明物体,因为开启了深度测试,当不透明物体绘制时,发现深度缓冲器的值比自己大,半透明物体依旧会将不透明物体的绘制丢弃
  • 那么有什么办法解决呢?
    – 1. 关闭不透明物体的深度写入 ZTest Off,但是如果关闭了深度测试,如果前方还有其他不透明物体会直接覆盖 ×
    – 2. 让不透明物体的片段不会被丢弃,深度测试总是通过的 ZTest Always,如果前方有不透明物体,根本不会被遮挡 ×
    – 3. 关闭透明物体的深度写入 ZWrite Off, 这样会有一个问题:如果场内有多个半透明物体,并且还是按照先绘制距离近的半透明物体的话,那么就会导致远的半透明物体在近的半透明物体上面,所以Unity中半透明渲染队列是按照先绘制远的半透明物体,再绘制近的半透明物体

混合透明效果

  • 当所有Shader执行完成后,需要将新渲染的的图像和已经存在的图像合并这时候需要用到混合指令(Blending),可以在SubShader和Pass中使用,SubShader中会影响到所有Pass
  • Blend Off 关闭混合处理,什么都不写就是这个
  • Blend SrcFactor DstFactor SrcFactor新渲染出来的混合系数,DstFactor目标图像的混合系数
    C o l o r r g b a = S o u r c e r g b a ∗ S r c F a c t o r + D e s t i n a t i o n r g b a ∗ D s t F a c t o r Color_{rgba}=Source_{rgba}*SrcFactor+Destination_{rgba}*DstFactor Colorrgba=SourcergbaSrcFactor+DestinationrgbaDstFactor
  • Blend SrcFactor DstFactor SrcFactorA DstFactorA和上面一样,但是对于alpha通道的混合系数用后面2个混合系数
    C o l o r r g b = S o u r c e r g b ∗ S r c F a c t o r + D e s t i n a t i o n r g b ∗ D s t F a c t o r Color_{rgb}=Source_{rgb}*SrcFactor+Destination_{rgb}*DstFactor Colorrgb=SourcergbSrcFactor+DestinationrgbDstFactor
    C o l o r a = S o u r c e a ∗ S r c F a c t o r + D e s t i n a t i o n a ∗ D s t F a c t o r Color_{a}=Source_{a}*SrcFactor+Destination_{a}*DstFactor Colora=SourceaSrcFactor+DestinationaDstFactor
  • BlendOp Op 使用其他操作进行混合,不再只是RGBA
  • 常用的混合指令、所有可以使用的混合系数、混合指令表 P_116
Shader "Unlit/BlendingTransparent"
{
    Properties
    {
        _MainTex("MainTexture", 2D) = "White"{}
        _MainColor("MainColor", Color) = (1,1,1,1)
    }
    SubShader
    {
        
        Tags{
            "Queue" = "Transparent"
            "RenderType" = "Transparent"
            "IgnoreProjector" = "True"
        }
        
        Pass
        {
            Tags{"LightMode" = "ForwardBase"}
            ZWrite Off
            Blend SrcAlpha OneMinusSrcAlpha // 普通透明叠加
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_fwdbase
            #include "UnityCG.cginc"
            #include "UnityLightingCommon.cginc"

            struct v2f
            {
                float4 pos:SV_POSITION;
                float4 worldPos:TEXCOORD0;
                float2 uv:TEXCOORD1;
                float3 worldNormal:TEXCOORD2;
            };

            float4 _MainColor;
            sampler2D _MainTex;
            fixed4 _MainTex_ST;

            v2f vert (appdata_base v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.worldPos = mul(unity_ObjectToWorld, v.vertex);
                // 纹理坐标变换,没有缩放平移直接填v.uv
                o.uv = TRANSFORM_TEX(v.texcoord, _MainTex); 
                float3 normal = UnityObjectToWorldNormal(v.normal);
                o.worldNormal = normalize(normal); 
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                float worldLight = UnityWorldSpaceLightDir(i.worldPos);
                worldLight = normalize(worldLight);

                fixed ndotl = dot(i.worldNormal, worldLight);

                fixed4 c = tex2D(_MainTex, i.uv);
                c.rgb *= _LightColor0 * _MainColor.rgb * saturate(ndotl);
                c.rgb += unity_AmbientSky;

                c.a *= _MainColor.a;

                return c;
            }
            ENDCG
        }
    }
    FallBack "Diffuse"
}

UnityShaderLab读书笔记 第7章(透明效果,透明混合、透明测试、模板测试)_第1张图片

半透明物体双面渲染

  • 单纯的加上Cull Off会导致反面叠加到正面上了,因为关闭了深度写入,没有深度信息导致正反面绘制顺序错误,为了不让方面叠加在正面上,可以先绘制反面再去绘制正面,这样就需要2个Pass分别绘制反面和正面
    代码都是一样的,只需要在反面Pass加上Cull Front,正面Pass加上Cull Back
    UnityShaderLab读书笔记 第7章(透明效果,透明混合、透明测试、模板测试)_第2张图片

透明测试效果

  • clip()可以丢弃数值小于0的像素clip(c.a - _AlphaTest),效果如下,可以通过添加AlphaToMask On来开启抗锯齿
    UnityShaderLab读书笔记 第7章(透明效果,透明混合、透明测试、模板测试)_第3张图片

模板测试(Stencil Test)

  • 除了透明混合、透明测试还可以通过模板测试达到透明效果
  • 模板缓存中,每个像素都有8位整数值(0~255)的模板值,在执行DrawCall时,会对模板值进行比较,不符合要求的像素将会被丢弃,从而达到透明效果
  • 模板测试流程,给物体A指定参考值m,让A直接通过模板测试,并将m写入模板缓存中,给物体B指定参考值n,将n和模板缓存中的m进行比较有2种情况(通过或未通过模板测试),在模板测试结束之后,可以对模板缓存中的模板值进行操作(替换或保留)
  • 语法
 Stencil
 {       
     Ref referenceValue 上面说的参考值(0~255)
     ReadMask readMask  读模板,例如11111111代表所有位都可以读取
     WriteMask writeMask 写模板,当为0时,表示没有数值会被写入缓存,255表示所有位都允许写入
     Comp comparisonFunction 参考值与缓存中的值进行比较函数,默认为always
     Pass stencilOperation 如果模板测试和深度测试都通过,缓存中的模板值怎么处理,默认keep
     Fail stencilOperation 如果模板测试未通过,缓存中的模板值怎么处理,默认keep
     ZFail stencilOperation 如果模板测试通过,但是深度测试未通过,缓存中的模板值怎么处理,默认keep
 }

比较的方法Comp:
渲染像素的参照值-符号-模板缓存中的值
Greater          >                 通过
GEqual           >=                通过
Less             <                 通过
LEqual           <=                通过
Equal            =                 通过
NotEqual         !=                通过
Always                           总是通过
Never                            总是不通过

模板操作
Keep	 继续保持模板缓存中的值
Zero	 把0写入
Replace  替代
IncrSat  递增,如果等于255将停止
DecrSat  递减,如果为0将停止
Invert   将模板值按位取反
IncrWrap 递增,如果等于255变为0
DecrWrap 递减,如果为0变为255

下面是模板测试代码

Shader "Hidden/StencilTestA"
{
    SubShader
    {
        Tags { "Queue"="Geometry-1" }

        Pass
        {
            // 总是通过模板测试、A渲染的结果是模板缓存中全是1
            Stencil
            {
                Ref 1
                Comp Always
                Pass Replace
            }

            // 物体A用于打洞,不需要任何渲染
            ColorMask 0
            // A不会将深度值写入深度缓存中
            ZWrite Off

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            float4 vert (in float4 vertex:POSITION):SV_POSITION
            {
                float4 pos = UnityObjectToClipPos(vertex);
                return pos;
            }

            void frag (out fixed4 color:SV_Target)
            {
                color = fixed4(0,0,0,0);
            }
            ENDCG
        }
    }
}

Shader "Hidden/StencilTestB"
{
    Properties
    {
        _MainColor("MainColor", Color) = (1,1,1,1)
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        // A的是Geometry-1所以,先绘制A再绘制B
        Tags { "Queue"="Geometry" }

        Pass
        {
            // 
            Stencil
            {
                Ref 1
                Comp NotEqual
                Pass Keep
            }

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            #include "UnityLightingCommon.cginc"

            struct v2f
            {
                float4 pos:SV_POSITION;
                float4 worldPos:TEXCOORD0;
                float3 worldNormal:TEXCOORD1;
                float2 uv : TEXCOORD2;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            fixed4 _MainColor;

            v2f vert (appdata_base v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.worldPos = mul(unity_ObjectToWorld, v.vertex);

                float3 normal = UnityObjectToWorldNormal(v.normal);
                o.worldNormal = normalize(normal);

                o.uv = TRANSFORM_TEX(v.texcoord,_MainTex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                float3 worldLight = UnityWorldSpaceLightDir(i.worldPos.xyz);
                worldLight = normalize(worldLight);
                fixed ndotl = saturate(dot(i.worldNormal, worldLight));
                fixed4 color = tex2D(_MainTex, i.uv);
                color.rgb *= _MainColor * ndotl * _LightColor0.rgb;
                color.rgb += unity_AmbientSky;
                return color;
            }
            ENDCG
        }
    }
}

UnityShaderLab读书笔记 第7章(透明效果,透明混合、透明测试、模板测试)_第4张图片

下面走进科学: A物体像素区域写入模板缓存为1,因为比较函数为NotEqual,所以重合部位B的像素被丢弃,没有重合的部位被渲染出来,从而达到B被A挖孔效果,如果将NotEqual换成Equal,可以发现B只有和A重合的像素被绘制出来


你可能感兴趣的:(ShaderLab,unity)