UnityShader入门精要笔记(7)——透明效果

透明效果

一、实现透明效果的两种方法

  • 透明度测试(无法得到真正的半透明效果):采用一种“霸道极端”的机制,只要一个片元的透明度不满足条件,就会被舍弃。即进行了深度测试,深度写入等(因此透明度测试不需要关闭深度写入)。因此透明度测试产生的效果,也很极端,要么看得到,要么看不到。
  • 透明度混合(可以得到真正的半透明效果):使用当前片元的透明度作为混合因子,与已经存储在颜色缓冲中的颜色值进行混合,得到新颜色。需要关闭深度写入,这使得我们要非常小心物体的渲染顺序。需要注意的是,透明度混合只关闭了深度写入,但没有关闭深度测试。这意味着,当使用透明度混合渲染一个片元时,还是会比较它的深度值与当前深度缓冲中的深度值,如果它的深度值距离摄像机更远,那么就不会再进行混合操作。
    这一点决定了,当一个不透明物体出现在一个透明物体前面,而我们先渲染不透明物体,它仍然可以正常的遮挡住透明物体。也就是说,对于透明度混合来说,深度缓冲是只读的。

二、引擎常用渲染方法步骤(一般都是先对物体进行排序,再渲染)

  • 先渲染所有不透明物体并开启它们的深度测试和深度写入。
  • 把半透明物体按它们距离摄像机的远近进行排序,然后按照从后往前的顺序渲染这些半透明物体,并开启它们的深度测试,但关闭深度写入。

需要注意的是,就算按照上面的方法进行渲染,还是会出现问题(比如,循环重叠的情况),因此Unity为了解决渲染顺序的问题,提供了渲染队列的方法。

三、UnityShader的渲染顺序

1、 渲染队列

使用SubShader的Queue标签来决定我们的模型将归于哪个渲染队列。Unity内部使用了一系列整数索引来表示每个渲染队列,索引号越小表示越早被渲染。

名称 队列索引号 描述
Background 1000 这个渲染队列会在任何其他队列之前被渲染,通常使用该队列来渲染那些需要绘制在背景的物体
Geometry 2000 默认的渲染队列,大多数物体都使用这个队列。不透明物体使用这个队列
AlphaTest 2450 需要透明度测试的物体使用这个队列。在Unity5种,它从Geometry队列中单独分出来,这是因为在所有不透明物体渲染之后再渲染它们会更加高效
Transparent 3000 这个队列中的物体在所有Geometry和AlphaTest物体渲染后,再按从后往前的顺序进行渲染。任何使用了透明度混合(例如关闭了深度写入的shader)的物体都应该使用该队列
Overlay 4000 该队列用于实现一些叠加效果。任何需要在最后渲染的物体都应该使用该队列

2、实现代码

  • 通过透明度测试实现透明效果
SubShader{
    Tags{ "Queue"="AlphaTest"}
    Pass{
        ···
    }
}
  • 通过透明度混合实现透明效果
SubShader{
    Tags{ "Queue"="Transparent"}
    Pass{
        ···
    }
}
  • ZWrite Off是关闭深度写入,一般都会写在Pass里面,当然也可以写在SubShader中(这就意味着Subshader下的所有Pass都会关闭深度写入)。

四、透明度测试

1、说明

会在片元着色器中使用clip函数进行透明度测试

2、代码

// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'

Shader "Unlit/AlphaTest"
{
    Properties{
        _Color("Main Tint",Color) = (1,1,1,1)
        _MainTex("Main Tex",2D) = "white"{}
        _Cutoff("Alpha Cutoff",Range(0,1)) = 0.5
    }
    SubShader{
        Tags{ "Queue"="AlphaTest"  "IgnoreProject"="True" "RenderType"="TransparentCutout" }
        Pass{
            Tags{"LightModel"="ForwardBase"}

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

            fixed4 _Color;
            sampler2D _MainTex;
            float4 _MainTex_ST;
            fixed _Cutoff;

            struct a2v{
                float4 vertex:POSITION;
                float3 normal:NORMAL;
                float4 texcoord:TEXCOORD0;
            };
            struct v2f{
                float4 pos:SV_POSITION;
                float3 worldNormal:TEXCOORD0;
                float3 worldPos:TEXCOORD1;
                float2 uv:TEXCOORD2;
            };
            v2f vert(a2v v){
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;
                o.uv = TRANSFORM_TEX(v.texcoord,_MainTex);
                return o;
            }
            fixed4 frag(v2f i):SV_TARGET{
                fixed3 worldNormal = normalize(i.worldNormal);
                fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
                fixed4 texColor = tex2D(_MainTex,i.uv);
                clip(texColor.a - _Cutoff);
                // Equal to
                // if(texColor.a - _Cutoff < 0.0){
                //      discard;   //忽略这个片元
                //}
                fixed3 albedo = texColor.rgb * _Color.rgb;
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
                fixed3 diffuse = _LightColor0.rgb * albedo * max(0,dot(worldNormal,worldLightDir));
                return fixed4(ambient+diffuse,1.0);
            }
            ENDCG
        }
    }
}

五、透明度混合

1、unity提供的混合命令:Blend

语义 描述
Blend Off 关闭混合
Blend SrcFactor DstFactor 开启混合,并设置混合因子。源颜色(该片元产生的颜色)会乘以SrcFactor,而目标颜色(已经存在于颜色缓存的颜色)会乘以DstFactor,然后把两者相加后再存入颜色缓冲中
Blend SrcFactor DSTFactor,SrcFactorA DstFactorA 和上面几乎一样,只是使用不同的因子来混合透明通道
BlendOp BlendOperation 并非是把源颜色和目标颜色简单的相加后混合,而是使用BlendOpertion 对它们进行其他操作

注意

  • 只有开启了混合之后,设置片元的透明通道才有意义,所以使用Blend命令会自动打开混合模式
  • 一般会把源颜色的混合因子SrcFactor设为SrcAlpha,目标颜色的混合因子设为OneMinusSrcAlpha,所以混合后的新颜色为:
    DstColornew = SrcAlhap * SrcColor+(1-SrcAlpha) * DstColorold

2、代码(以下是错误的排序,因为关闭了深度写入)

Shader "Unlit/AlphaBlend"
{
   Properties{
         _Color("Main Tint",Color) = (1,1,1,1)
        _MainTex("Main Tex",2D) = "white"{}
        _AlphaScale("Alpha Scale",Range(0,1)) = 0.5
    }
    SubShader{
        Tags{ "Queue"="Transparent"  "IgnoreProject"="True" "RenderType"="Transparent" }
        Pass{
            Tags{"LightModel"="ForwardBase"}
            ZWrite Off
            Blend SrcAlpha OneMinusSrcAlpha
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "Lighting.cginc"
            #include "UnityCG.cginc"

            fixed4 _Color;
            sampler2D _MainTex;
            float4 _MainTex_ST;
            fixed _AlphaScale;

            struct a2v{
                float4 vertex:POSITION;
                float3 normal:NORMAL;
                float4 texcoord:TEXCOORD0;
            };
            struct v2f{
                float4 pos:SV_POSITION;
                float3 worldNormal:TEXCOORD0;
                float3 worldPos:TEXCOORD1;
                float2 uv:TEXCOORD2;
            };
            v2f vert(a2v v){
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;
                o.uv = TRANSFORM_TEX(v.texcoord,_MainTex);
                return o;
            }
            fixed4 frag(v2f i):SV_TARGET{
                fixed3 worldNormal = normalize(i.worldNormal);
                fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
                fixed4 texColor = tex2D(_MainTex,i.uv);
                fixed3 albedo = texColor.rgb * _Color.rgb;
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
                fixed3 diffuse = _LightColor0.rgb * albedo * max(0,dot(worldNormal,worldLightDir));
                return fixed4(ambient+diffuse,texColor.a * _AlphaScale);
            }
            ENDCG
        }
    }
}

六、开启深度写入的半透明效果

由于关闭深度写入而造成的错误排序的情况,有解决方案:

1、使用两个Pass来渲染模型

  • 原理:使用两个Pass来渲染模型:第一个Pass开启深度写入,但是不输出颜色,目的在于把该模型的深度值吸入深度缓冲中。第二个pass进行正常的透明度混合,由于上一个pass已经得到了逐像素的正确的深度信息,该Pass可以按照像素级别的深度排序效果进行透明渲染。
  • 缺点:性能下降
  • 代码(跟之前的混合度的代码一样,只不过多了一个Pass):
Shader ""
{
    Properties{
        ···
    }
    SubShader{
        ···
        Pass{
            ZWrite On
            //ColorMask用于设置颜色通道的写掩码,语义如下
            //ColorMask RGB | A | 0 | 其他任何R、G、B、A的组合
            //允许定义好的颜色通道显示出来
            ColorMask 0  
        }
        Pass{
            ···
        }
    }
    ···
}

七、ShaderLab的混合命令

1、混合的实现方式

  • 当片元着色器产生颜色(源颜色,用S表示)时,可以选择与颜色缓存中的颜色(目标颜色,用D表示)进行混合,再输出颜色(用O表示)。
  • 需要注意,混合颜色,包含了RGBA四个通道的值,而并不仅仅是RGB通道。

2、ShaderLab中设置混合因子的命令

设置混合状态时,相当于设置混合等式中的操作和因子

命令 描述
Blend SrcFactor DstFactor 开启混合,并设置混合因子。源颜色(该片元产生的颜色)会乘以SrcFactor,而目标颜色(已经存在于颜色缓存的颜色)会乘以DstFactor,然后把两者相加后再存入颜色缓冲中
Blend SrcFactor DstFactor,SrcFactorA DstFactorA 和上面几乎一样,只是使用不用的因子来混合透明通道
  • 混合等式:需要用到两个混合等式,一个用于混合RGB通道,一个用于混合A通道
    Orgb = SrcFactor * Srgb + DstFactor * Drgb
    Oa = SrcFactor * Sa + DstFactorA * Da

3、ShaderLab中的混合因子

参数 描述
One 因子为1
Zero 因子为0
SrcColor 因子为源颜色值。当用于混合RGB的混合等式时,使用SrcColor的RGB分量作为混合因子;当用于混合A的混合等式时,使用SrcColor的A分量作为混合因子
SrcAlpha 因子为源颜色的透明度值A(A通道)
DstColor 因子为源颜色值。当用于混合RGB通道的混合等式时,使用DstColor的RGB分量作为混合因子;当用于混合A通道时,使用DstColor的A分量作为混合因子。
DstAlpha 因子为目标颜色的透明度值(A通道)
OneMinusSrcColor 因子为(1-源颜色)。当用于混合RGB的混合等式时,使用结果的RGB分量作为混合因子;当用于混合A的混合等式时,使用结果的A分量作为混合因子
OneMinusSrcAlpha 因子为(1-源颜色的透明度值)
OneMinusDstColor 因子为(1-目标颜色)。当用于混合RGB的混合等式时,使用结果的RGB分量作为混合因子;当用于混合A的混合等式时,使用结果的A分量作为混合因子
OneMinusDstAlpha 因子为(1-目标颜色的透明度值)

4、命令

当我们希望使用不同的参数混合A通道时,可以利用Blend SrcFactor DstFactor,SrcFactorA DstFactorA
案例:(输出颜色的透明度值就是源颜色的透明度值)

Blend SrcAlpha OneMinusSrcAlpha,One Zero

5、混合操作

混合操作命令

操作 描述
Add 将混合后的源颜色和目的颜色相加。默认的混合操作。使用的混合等式是:
Orbg = SrcFactor * Srgb + DstFactor * Drgb
Oa = SrcFactorA * Sa + DstFactorA * Da
Sub 用混合后的源颜色减去混合后的目的颜色,使用的混合等式是:
Orbg = SrcFactor * Srgb - DstFactor * Drgb
Oa = SrcFactorA * Sa - DstFactorA * Da
RevSub 用混合后的目的颜色减去混合后的源颜色,使用的混合等式是:
Orbg =DstFactor * Drgb - SrcFactor * Srgb
Oa =DstFactorA * Da - SrcFactorA * Sa
Min 使用源颜色和目的颜色中较小的值,是逐分量比较的。使用的混合等式是:
Orgba = (min(Sr , Dr) , min(Sg , Dg) , min(Sb , Db) , min(Sa , Da))
Max 使用源颜色和目的颜色中较大的值,是逐分量比较的。使用的混合等式是:
Orgba = (max(Sr , Dr) , max(Sg , Dg) , max(Sb , Db) , max(Sa , Da))

注意的是,使用Min或Max混合操作时,混合因子实际上是不起作用的,他们仅会判断原始的源颜色和目的颜色之间的比较结果

6、常见混合操作

Blend SrcAlpha OneMinusSrcAlpha  //正常(normal),即透明度混合

Blend OneMinusDstColor One  //柔和相加(Soft Additive)

Blend DstColor Zero //正片叠底(Multiply) , 即相乘

//变暗(Darken)
BlendOp Min 
Blend One One 

//变亮(Lighten)
BlendOp Max
Blend One One 

Blend OneMinusDstColor One  //滤色(Screen)
Blend One OneMinusSrcColor  //滤色(Screen)

Blend One One //线性减淡(Linear Dodge)

7、双面渲染

Cull Back | Front | Off  //设置剔除哪个面的渲染图元

你可能感兴趣的:(Unity,Shader,unity,shader)