unity shader:初探透明度

透明度实现方式:在unity中实现透明度效果有两种方案,一种是透明度测试,一种是透明度混合。

透明度测试实现方式:既是片元的透明度和透明度阈值进行比较,小于这个透明度阈值的片元会直接被舍弃掉,不会做任何处理,相当于该片元透明。而大于这个透明度阈值的则会按照不透明的方式进行处理。实现代码如下:

Shader "Custom/AlphaTest" {
    Properties {
        _Color("Main Tint", Color) = (1, 1, 1, 1)   // 主纹理颜色
        _MainTex("MainTex", 2D) = "white"{} // 主纹理
        _Cutoff("Alpha Cutoff", Range(0, 1)) = 0.5  // 透明度阈值
    }

    SubShader {
        // 所有Pass中渲染队列使用透明度测试队列,渲染类型选择透明度测试类型,并且渲染过程中不受投影器的影响
        Tags { "Queue" = "AlphaTest" "IgnoreProjector" = "True" "RenderType" = "TransparentCutout" }

        Pass {
            Tags { "LightModel" = "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 : SV_POSITION;
                    float3 world_normal:TEXCOORD0;
                    float2 uv : TEXCOORD1;
                    float3 world_pos : TEXCOORD2;
                };

                v2f vert(a2v i) {
                    v2f o;
                    o.pos = mul(UNITY_MATRIX_MVP, i.vertex);

                    o.world_normal = UnityObjectToWorldNormal(i.normal);

                    o.uv = TRANSFORM_TEX(i.texcoord, _MainTex); // 等价于:i.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw

                    o.world_pos = mul(unity_ObjectToWorld, i.vertex);

                    return o;
                }

                fixed4 frag(v2f i) : SV_Target {
                    // 获取归一化的世界空间法线方向
                    float3 world_normal = normalize(i.world_normal);

                    // 获取归一化的世界空间光照方向
                    float3 world_light_dir = normalize(UnityWorldSpaceLightDir(i.world_pos));

                    // 对主纹理进行采样
                    fixed4 tex_color = tex2D(_MainTex, i.uv);

                    // 进行透明度测试,小于透明度阈值的片元将会被舍弃,等价于 if ((tex_color.a - _Cutoff)) < 0.0) discard;
                    clip(tex_color.a - _Cutoff);

                    // 获取漫反射系数
                    float albedo = tex_color.rgb * _Color.rgb;

                    // 获取漫反射大小
                    float diffuse = _LightColor0 * albedo * saturate(dot(world_normal, world_light_dir));

                    // 获取环境光
                    float3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;

                    // 获取最终混合的颜色
                    return fixed4(ambient + diffuse, 1.0);
                }

            ENDCG
        }
    }

    FallBack "Transparent/Cutout/VertexLit" // 默认的透明度测试shader
}

注意点:
1.要想有透明度测试功能必须设置渲染队列标签为透明度测试队列模式,渲染类型为透明度测试类型,投影影响也必须关闭,也就是Tags { “Queue” = “AlphaTest” “IgnoreProjector” = “True” “RenderType” = “TransparentCutout” }
2.透明度测试实现透明处理的核心就是使用clip函数,当片元透明度小于阈值时,该片元就会被discard(舍弃)掉,从而表现为透明,否则就会按照不透明的方式进行处理。
3.透明度测试处理纹理的边界处往往由于像素精度的原因而存在不平滑,有锯齿的现象。

透明度混合实现方式:既是片元的透明度作为混合因子,片元的深度和深度缓冲区中的深度做比较,小于深度缓冲区中的深度时,片元的透明度不会与颜色缓冲区中的颜色进行混合,否则就会进行混合,最终片元的颜色会替换到颜色缓冲区中的颜色。实现代码如下:

Shader "Custom/AlphaBlend" {
    Properties {
        _Color("Main Tint", Color) = (1, 1, 1, 1)   // 主纹理颜色
        _MainTex("MainTex", 2D) = "white"{} // 主纹理
        _AlphaScale("Alpha Scale", Range(0, 1)) = 0.5   // 透明度系数
    }

    SubShader {
        // 所有Pass中渲染队列使用透明度混合队列,渲染类型选择透明度混合类型,并且渲染过程中不受投影器的影响
        Tags { "Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent" }

        Pass {
            Tags { "LightModel" = "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 world_normal:TEXCOORD0;
                    float2 uv : TEXCOORD1;
                    float3 world_pos : TEXCOORD2;
                };

                v2f vert(a2v i) {
                    v2f o;
                    o.pos = mul(UNITY_MATRIX_MVP, i.vertex);

                    o.world_normal = UnityObjectToWorldNormal(i.normal);

                    o.uv = TRANSFORM_TEX(i.texcoord, _MainTex); // 等价于:i.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw

                    o.world_pos = mul(unity_ObjectToWorld, i.vertex);

                    return o;
                }

                fixed4 frag(v2f i) : SV_Target {
                    // 获取归一化的世界空间法线方向
                    float3 world_normal = normalize(i.world_normal);

                    // 获取归一化的世界空间光照方向
                    float3 world_light_dir = normalize(UnityWorldSpaceLightDir(i.world_pos));

                    // 对主纹理进行采样
                    fixed4 tex_color = tex2D(_MainTex, i.uv);

                    // 获取漫反射系数
                    float albedo = tex_color.rgb * _Color.rgb;

                    // 获取漫反射大小
                    float diffuse = _LightColor0 * albedo * saturate(dot(world_normal, world_light_dir));

                    // 获取环境光
                    float3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;

                    // 获取最终混合的颜色
                    return fixed4(ambient + diffuse, tex_color.a * _AlphaScale);
                }

            ENDCG
        }
    }

    FallBack "Transparent/VertexLit"    // 默认的透明度混合shader
}

注意点:
1.必须使用Tags渲染标签来设置渲染队列为透明度混合模式,渲染类型为透明度混合类型,投影影响功能关闭。也就是Tags { “Queue” = “Transparent” “IgnoreProjector” = “True” “RenderType” = “Transparent” }。
2.必须使用Blend渲染状态来设置片元颜色乘积因子以及像素缓冲区中颜色的乘积因子,然后按照BlendOp指定的混合操作方式(默认是加方式)进行混合得到最终的像素值。也就是Blend SrcAlpha OneMinusSrcAlpha。
3.为了避免透明度混合时出现由于深度写入开启,深度值发生变化而造成透明后面的物体由于距离摄像机远而被裁剪掉,通常需要关闭深度写入的。也就是ZWrite Off。

渲染物体顺序:一般而言渲染顺序遵循以下原则:
1.先渲染不透明物体,并开启深度测试和深度写入。
2.将透明物体按照距离摄像机远近距离进行排序,然后从后往前进行渲染物体,并开启深度测试,关闭深度写入。
3.对于多个透明物体重叠时,往往会产生循环重叠的透明效果,此时可以采用分割物体的方式进行重新正确排序来解决。
4.对于自身透明物体网格中不同的顶点存在不同深度时,往往会产生错误的透明效果,此时可以采用分割网格或者多启用一个pass进行顶点自身的深度写入,另外一个pass进行颜色混合输出来解决。

混合因子:unity中混合公式有两个,一个是针对rgb,一个是针对a,并且混合对象有片元颜色和像素缓冲区颜色,所以混合因子有四个,对应的计算公式如下:
这里写图片描述
1.对应的命令设置为:Blend SrcFactor_rgb DstFactor_rgb SrcFactor_a DstFactor_a,并且不设置SrcFactor_a和DstFactor_a时,此时使用SrcFactor_rgb和DstFactor_rgb的值来代替SrcFactor_a和DstFactor_a。
2.常见的混合因子如下:
这里写图片描述
此处截图中DstColor为目标颜色,而不是源颜色。

混合操作:使用BlendOp指令来进行指定,默认为Add混合操作,常见的混合操作如下:
这里写图片描述
这里写图片描述
1.结合混合因子以及混合操作,常见的混合效果如下:
透明度混合:Blend SrcAlpha OneMinusSrcAlpha
柔和相加:Blend OneMinusDstColor One
正片叠底,既是相乘:Blend DstColor Zero
两倍相乘:Blend DstColor SrcColor
变暗:BlendOp Min Blend One One
变亮:Blend Max Blend One One
滤色:Blend OneMinusDstColor One 等价与 Blend One OneMinusSrcColor
线性减淡:Blend One One

图元剔除:就是unity将某一个图元基于摄像机观察方向进行裁减,使用Cull渲染命令进行操作,默认剔除物体的背面,当然也可以剔除前面甚至关掉剔除功能,对应的命令如下:Cull Back/Front/Off。而当关闭剔除时,针对透明度测试而言,由于已经开启深度测试和深度写入,所以前后面都是有序的,渲染时不会存在问题,但是针对透明度混合而言,由于开启深度测试,关闭了深度写入,此时前后两面对应的顺序就是未知的,此时可以使用两个Pass渲染通道,一个在渲染时剔除掉正面,一个在渲染时剔除掉背面,从而实现先绘制背面,再绘制正面从而变的有序,得到正确的渲染效果。

猜你喜欢

转载自blog.csdn.net/zjz520yy/article/details/78087194