学习100个Unity Shader (14) ---透明效果

发布于:2024-04-29 ⋅ 阅读:(36) ⋅ 点赞:(0)

渲染队列

由”Queue“ 标签决定,索引号越小越早被渲染:

名称 队列索引号
Background 1000
Geometry 2000
AlphaTest 2450
Transparent 3000
Overlay 4000

透明度测试(Alpha Test)

某一片元的透明度小于某个阈值,即被舍弃,反之,按非透明物体处理,进行正常的深度测试和深度写入【不需要关闭深度写入】。

效果

在这里插入图片描述

Shader

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

Shader "Example/AlphaTest"
{
  Properties
  {
    _Color ("Main Tint", Color) = (1, 1, 1, 1 )
    _MainTex ("Main Tex", 2D) = "white" {} 
    _Cutoff ("Alpha Cutoff", Range(0, 1)) = 0.5
  }
  SubShader
  {
    //TransparentCutout: 指明使用了透明度测试
    //不受投影标签的影响
    Tags {"Queue" = "AlphaTest" "IgnoreProjector" = "True" "RenderType" = "TransparentCutout"}

    Pass
    {
      Tags{"LightMode" = "ForwardBase"}
      CGPROGRAM
      #pragma vertex vert
      #pragma fragment frag

      #include "UnityCG.cginc"

      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;
      };

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

      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);

        //透明度测试, 输入值小于0则丢弃该片元
        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"
}

UnityObjectToClipPos 将顶点从模型空间转到剪裁空间
UnityObjectToWorldNormal 将法线方向从模型空间变换到世界空间
unity_ObjectToWorld 当前模型矩阵
TRANSFORM_TEX UnityCG.cginc内置宏,变换贴图的平移和缩放的属性到UV:

#define TRANSFORM_TEX(tex,name) (tex.xy * name##_ST.xy + name##_ST.zw)

UnityWorldSpaceLightDir 光照模式在ForwardBase下,根据给定在模型空间下的顶点位置,计算朝向光源的世界空间方向。
tex2D 从纹理中获取像素的颜色信息,传入一个纹理和一个二维坐标(通常是纹理坐标),以获取该坐标处的像素颜色。

透明度混合(Alpha Blending)

可以得到真正的半透明效果,使用当前片元的透明度作为混合因子,与已经存储在颜色缓冲中的颜色值进行混合,得到新的颜色。需要关闭深度写入,但没有关闭深度测试。

效果

在这里插入图片描述

Shader

Shader "Example/AlphaBlend"
{
  Properties
  {
    _Color ("Main Tint", Color) = (1, 1, 1, 1)
    _MainTex ("Texture", 2D) = "white" {}
    _AlphaScale ("Alpha Scale", Range(0, 1)) = 1
  }
  SubShader
  {
    Tags {"Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent"}
    // Pass的先后顺序有影响
    //开启深度写入,不输出颜色
    Pass
    {
      ZWrite On
      ColorMask 0
    }
    //透明度混合
    Pass
    {
      Tags{"LightMode" = "ForwardBase"}
      // 关闭深度写入
      ZWrite Off
      // 设置Pass的混合模式,SrcAlpha: 片元着色器产生的颜色的混合因子
      // OneMinusSrcAlpha 已经存在于颜色缓冲中的颜色的混合因子
      Blend SrcAlpha OneMinusSrcAlpha
      CGPROGRAM
      #pragma vertex vert
      #pragma fragment frag

      #include "UnityCG.cginc"

      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;
      };

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

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

使用两个Pass来渲染模型:第一个Pass开启深度写入,但不输出颜色,第二个Pass进行正常的透明度混合。注意写Pass有先后顺序。

使用两个Pass原因是,由于关闭了深度写入,当模型本身有复杂的遮挡关系或是包含了复杂的非凹凸网络,就会因为排序问题产生错误的透明效果。

ColorMask 设置颜色通道的写掩码,ColorMask RGB | A | 0 | 其他任何RGBA的组合,当设置为0时,Pass不写入任何的颜色通道,不会输出颜色。

参考

《Unity Shader入门精要》冯乐乐
内置着色器变量
内置着色器函数