【Unity Shader学习笔记】Unity透明效果-透明度测试与透明度混合

1、基本概念

1.1、基本概念

在实时渲染中实现透明效果,需要在渲染模型时控制它的透明通道(Alpha Channel)。

透明度为1代表该像素是完全不透明的;0代表完全透明。

Unity中可以使用两种方式得到透明效果:透明度测试(alpha test,无法得到真正的透明效果)、透明度混合(alpha blending)。

对于不透明物体,深度缓冲(depth buffer)就能正确判断物体的遮挡关系。

  • 透明度测试:一个片元的透明度达不到某个标准,那么就会被舍弃;否则就按不透明物体进行处理。因此,透明度测试不需要关闭深度写入。
Unity透明效果-透明度测试
  • 透明度混合:使用当前片元作为混合因子,与已经存储在颜色缓冲区中的颜色进行混合。透明度混合需要关闭深度写入(即离镜头更近的片元不会覆盖原有的同一位置的片元)。因此我们需要小心物体的渲染顺序。但是仍然可以进行深度测试。
img

1.2、渲染顺序

为了保证半透明物体后面的物体能够被我们看到,渲染时我们必须关闭深度写入。
因此,渲染顺序会很大程度上影响显示效果。

理想状况下,我们需要先正常渲染所有不透明物体,再由远及近渲染透明物体。
但是现实情况中,可能涉及到不同透明物体交错排列的情况,因此这种方法会遇到问题。

尽管这种方法存在问题,但是实现简单,因此大多数游戏引擎都使用了这样的方法。

为了减少bug,我们可以让模型尽可能是凸面体,将复杂的模型拆分成多个独立排序的子模型。

2、Unity Shader的渲染顺序

Unity 使用渲染队列 (render queue) 解决渲染顺序问题。

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

Unity5 中, Unity 提前定义了5个渲染队列。

下面是五个渲染队列及其描述:

透明度测试代码中应该包含如下代码:

SubShader{
	Tags {"Queue" = "AlphaTest"}
	Pass{
		...
	}
}

透明度混合则是这样的:

SubShader{
	Tags {"Queue" = "Transparent"}
	Pass{
		Zwrite Off
		...
	}
}

也可以在SubShader打开Zwrite Off,这样一个SubShader中的全部Pass都会关闭深度写入。

3、透明度测试

上面已经介绍了透明度测试:一个片元的透明度达不到某个标准,那么就会被舍弃;否则就按不透明物体进行处理。

使用Cg中的Clip函数:给定的参数的任何一个分量是负数,就会舍弃当前像素的输出颜色。等同于下面的代码:

void clip(float4 x){
	if (any(x < 0)){
		discard;
	}
}

在SubShader中,我们定义Tags:

Tags{"Queue" = "AlphaTest" "IgnoreProjector"="True" "RenderType" = "TransparentCutout"}
  • 将队列Queue设置为AlphaTest。
  • 将RenderType标签指定为TransparentCutout,让Unity将此Shader归入提前定义的组中。该标签通常用于着色器替换功能。
  • 将IgnoreProjector设置为True,意味着这个Shdaer不会受到投影器(Projectors)的影响。

代码为:

Shader "Unity Shader Book/Chapter 8/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" "IgnoreProjector"="True" "RenderType" = "TransparentCutout"}

        Pass{
            Tags { "LightMode"="ForwardBase" }

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "Lighting.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 : 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);

                //Alpha Test
                clip(texColor.a - _Cutoff);

                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
        }
    }
    FallBack "Transparent/Cutout/VertexLit"
}

4、透明度混合

使用Blend命令控制混合。

Shader "Unity Shader Book/Chapter 8/AlphaBlend"
{
    Properties
    {
        _Color ("Main Tint", Color) = (1, 1, 1, 1)
        _MainTex ("Main Tex", 2D) = "white" {}
        _AlphaScale ("Alpha Scale", Range(0, 1)) = 1  
    }
    SubShader
    {
        Tags{"Queue" = "Transparent" "IgnoreProjector"="True" "RenderType" = "Transparent"}

        Pass{
            Tags {"LightMode"="ForwardBase"}

            ZWrite Off
            Blend SrcAlpha OneMinusSrcAlpha

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "Lighting.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));

                //设置返回值的透明通道:即纹理像素的透明通道与材质参数_AlphaScale的乘积。
                return fixed4(ambient + diffuse, texColor.a * _AlphaScale);
            }
            ENDCG
        }
    }
    FallBack "Transparent/VertexLit"
}

产生的效果如下所示:

需要注意的是,由于我们关闭了深度写入,因此在显示重叠物体的时候,前后关系显示会出现问题。

Screenshot_2022-07-06-14-43-23-172_com.jideos.jnotes

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

可以使用两个Pass来渲染模型,第一个Pass开启深度写入,但是不输出颜色;第二个Pass使用前一个Pass写入的深度缓冲中的值进行透明度混合。

使用这种方法,可以实现正常的遮挡关系,但是在半透明物体后面的半透明物体会被完全覆盖,不能显示。

在新创建的Pass中,我们使用新的渲染命令——ColorMask。ShaderLab中,ColorMask用于设置颜色通道的写掩码(write mask)。

ColorMask RGB | A | 0 | 其他任何R、G、B、A的组合

当ColorMask设置为0时,意味着该Pass不写入任何颜色通道。

6、Shdaer Lab 的混合命令

这是使用加法混合的混合公式(S为源颜色,D为目标颜色):

image-20220706150853625

下面是混合因子可能的值:

6.1、其他混合操作

使用BlendOP BlendOperation命令(混合操作命令),使用其他运算模式。

混合操作命令通常与混合因子命令一同工作。

6.2、常见混合

//正常混合,即透明度混合
Blend SrcAlpha OneMinusSrcAlpha
//柔和相加(Soft Additive)
Blend OneMinusDstColor One
//正片叠底(Multiply),即相乘
Blend DstColor SrcColor
//两倍相乘(2X Multiply)
Blend DstColor SrcColor
//变暗(Darken)
BlendOp Min
Blend One One
//变亮(Lighten)
BlendOp Max
Blend One One
//滤色(Screen)
Blend OneMinusDstColor One
//等同于
Blend One MinusSrcColor
//线性减淡(Linear Dodge)
Blend One One

7、双面渲染的透明效果

可以使用Cull指令选择需要剔除那个面的渲染图元。

Cull Back | Front | Off

  • Back:背对摄像机的图元不会被渲染(默认)
  • Front:朝向摄像机的图元不会被渲染
  • Off:关闭剔除功能,所有图元都会被渲染

7.1、透明度测试的双面渲染

直接在Pass的Tags后面添加

Cull Off

7.2、透明度混合的双面渲染

复制透明度混合的Pass,形成两个Pass。

第一个Pass只渲染背面,第二个Pass只渲染正面,这样就能保证背面总是在正面渲染之前被渲染。

本篇文章为读《Unity Shader 入门精要》时写的笔记

posted @ 2022-07-10 22:00  IDEA_W  阅读(1893)  评论(0编辑  收藏  举报