zoukankan      html  css  js  c++  java
  • Unity Shader 之 透明效果

    本文引用 Unity Shader入门精要

    开启透明混合后,一个物体被渲染到屏幕上时,每个片元除了颜色值和深度值外,还有——透明度。透明度为1,则完全不透明,透明度为0,则完全不会显示。

    在Unity中我们有两种方式实现透明度效果

    • 透明度测试(Alpha Test):这种方式无法得到真正的半透明效果。只是0或1(完全透明和完全不透明)
    • 透明度混合(Alpha Blending):使用当前的透明度作为混合因子,与已经存储在颜色缓冲中的颜色值进行混合。

    那让我讨论第一个问题:渲染顺序

    渲染顺序

    为什么先说渲染顺序呢?

    在之前的两篇文章中,我并没有涉猎到渲染顺序。因为对于不透明的物体,渲染顺序的决定是由深度缓冲决定的。

    深度缓冲的基本思想为:根据深度缓冲中的值来判断该片元距离摄像机的距离,当渲染一个片元时,需要把深度值和已经存在于深度缓冲中的值进行比较(前提:开启深度测试),如果它的值距离摄像机更远,说明该片元不应被渲染。

    但是当我们使用透明度混合时,就得关闭深度写入(ZWrite)。

    为什么关闭深度写入呢?

    一个半透明的物体后面如果有物体的话,应该是可以被看到的,但是深度写入会把它剔除掉。

    所以对于渲染顺序就得我们自行控制了。

     

    上两个图分别是两种渲染顺序情况,

    图一:透明物体在前,不透明物体在后。

    • 情况一:如果先渲染不透明物体(开启深度写入和深度测试),将不透明物体颜色写入颜色缓存,深度写入深度缓冲,然后渲染透明物体(关闭深度写入,开启深度测试),将透明物体的颜色与颜色缓冲中的颜色混合,得到正确结果。
    • 情况二:先渲染透明物体(关闭深度写入,开启深度测试),透明物体颜色写入颜色缓冲,然后渲染不透明物体(开启深度写入和深度测试),深度缓存中没有内容,所以直接覆盖颜色缓冲。得到错误结果。

    图二:两个透明物体。

    • 情况一:先渲染后方的透明物体,颜色写入颜色缓冲,然后渲染前方透明物体,颜色和颜色缓冲中的颜色混合,得到正确结果。
    • 情况二:先渲染前方的透明物体,颜色写入颜色缓冲,然后渲染后方透明物体,颜色和颜色缓冲中的颜色混合,得到后方物体在前方物体前的画面,得到错误结果。

    基于这种情况Unity给我们一种解决方式(大多数引擎的解决方式):物体排序+分割网格。

    • 物体排序:1.先渲染所有不透明物体,并开启它们的深度测试和深度写入。2.把半透明物体按它们距离摄像机的远近进行排序,然后按照从后往前的顺序渲染半透明物体(开启深度测试,关闭深度写入)。
    • 分割网格:解决物体排序遗留问题:循环重叠(例:3个物体互相重叠),我们把网格分割,分别判断分开后的网格的顺序来进行渲染。

    Unity Shader 的渲染顺序

    Unity提供了渲染队列(render queue)解决渲染顺序的问题。用SubShader的Queue标签来设置我们的模型在哪个渲染队列。索引越小越先渲染

    Unity的5个渲染队列:

    • Background:索引:1000,这个队列是最先渲染的。
    • Geometry:索引:2000,默认渲染队列。不透明物体使用这个队列。
    • Alpha Test:索引:2450,需要透明度测试的物体使用该队列。
    • Transparent:索引:3000,按照从后往前的顺序渲染,使用透明度混合的物体都应该用该队列。
    • Overlay:索引:4000,该队列用于实现一些叠加效果,最后渲染。

    透明度测试

    只要一个片元的透明度不满足条件,那么这个片元就会被舍弃。用clip来进行透明度测试。

    立方体的贴图每个块都是不同的透明度分别是50%、60%、70%、80%,下面是我把Alpha Cutoff设为0.7时的效果。你会发现50%和60%透明度的贴图已经不见了。

    透明度测试Shader代码:

     1 Shader "My Shader/AlphaShader"
     2 {
     3     Properties
     4     {
     5         _Color ("Color", Color) = (1,1,1,1)
     6         _MainTex ("Texture", 2D) = "white" {}
     7         _Cutoff ("Alpha Cutoff", Range(0, 1)) = 0.5
     8     }
     9     SubShader
    10     {
    11         // 透明度测试队列为AlphaTest,所以Queue=AlphaTest
    12         // RenderType标签让Unity把这个Shader归入提前定义的组中,以指明该Shader是一个使用了透明度测试的Shader
    13         // IgonreProjector为True表明此Shader不受投影器(Projectors)影响
    14         Tags { "Queue"="AlphaTest" "IgnoreProjector"="True" "RenderType"="TransparentCutout" }
    15 
    16         Pass
    17         {
    18             Tags { "LightMode"="ForwardBase" }
    19 
    20             CGPROGRAM
    21             #pragma vertex vert
    22             #pragma fragment frag
    23             
    24             #include "UnityCG.cginc"
    25             #include "Lighting.cginc"
    26 
    27             struct a2v
    28             {
    29                 float4 vertex : POSITION;
    30                 float3 normal : NORMAL;
    31                 float4 texcoord : TEXCOORD0;
    32             };
    33 
    34             struct v2f
    35             {
    36                 float4 pos : SV_POSITION;
    37                 float2 uv : TEXCOORD0;
    38                 float3 worldNormal : TEXCOORD1;
    39                 float3 worldPos : TEXCOORD2;
    40             };
    41 
    42             sampler2D _MainTex;
    43             float4 _MainTex_ST;
    44             fixed4 _Color;
    45             // 用于决定调用clip函数时进行的透明度测试使用的判断条件
    46             fixed _Cutoff;
    47             
    48             v2f vert (a2v v)
    49             {
    50                 v2f o;
    51 
    52                 o.pos = UnityObjectToClipPos(v.vertex);
    53                 o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
    54                 o.worldNormal = UnityObjectToWorldNormal(v.normal);
    55                 o.worldPos = mul(unity_ObjectToWorld, v.vertex);
    56 
    57                 return o;
    58             }
    59             
    60             fixed4 frag (v2f i) : SV_Target
    61             {
    62                 fixed3 worldNormal = normalize(i.worldNormal);
    63                 fixed3 worldPos = normalize(i.worldPos);
    64                 fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(worldPos));
    65                 // 纹素值
    66                 fixed4 texColor = tex2D(_MainTex, i.uv);
    67                 // 原理
    68                 // if ((texColor.a - _Cutoff) < 0.0) { discard; }
    69                 // 如果结果小于0,将片元舍弃
    70                 clip(texColor.a - _Cutoff);
    71                 // 反射率
    72                 fixed3 albedo =  texColor.rgb * _Color.rgb;
    73                 // 环境光
    74                 fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo;
    75                 // 漫反射
    76                 fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));
    77                 return fixed4(ambient + diffuse, 1.0);
    78             }
    79             ENDCG
    80         }
    81     }
    82 }

    透明度混合

    那我们看看Unity给我们提供的混合命令——Blend。给出Blend的常用语义。

    • Blend Off:关闭混合
    • Blend SrcFactor DstFactor:开启混合,设置混合因子。源颜色(片元颜色)乘以SrcFactor,目标颜色(已经在颜色缓冲中的颜色)乘以DstFactor,然后把两者相加
    • Blend SrcFactor DstFactor,SrcFactorA DstFactorA:同上,不过把透明通道(a)与颜色通道(rgb)用不同的因子。
    • BlendOp BlendOperation:使用BlendOperation对其进行其他操作,非简单相加混合。

    混合公式:DstColorNew = SrcAlpha * SrcColor + (1 - SrcAlpha) * DstColorOld

    下面是最简单的调整透明通道值的效果,AlphaScale = 0.6

    Shader代码为:

     1 Shader "My Shader/AlphaShader"
     2 {
     3     Properties
     4     {
     5         _Color ("Color", Color) = (1,1,1,1)
     6         _MainTex ("Texture", 2D) = "white" {}
     7         _AlphaScale ("Alpha Scale", Range(0, 1)) = 1
     8     }
     9     SubShader
    10     {
    11         // 透明度混合队列为Transparent,所以Queue=Transparent
    12         // RenderType标签让Unity把这个Shader归入提前定义的组中,以指明该Shader是一个使用了透明度混合的Shader
    13         // IgonreProjector为True表明此Shader不受投影器(Projectors)影响
    14         Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" }
    15 
    16         Pass
    17         {
    18             Tags { "LightMode"="ForwardBase" }
    19             
    20             // 关闭深度写入
    21             ZWrite Off
    22             // 开启混合模式,并设置混合因子为SrcAlpha和OneMinusSrcAlpha
    23             Blend SrcAlpha OneMinusSrcAlpha
    24 
    25             CGPROGRAM
    26             #pragma vertex vert
    27             #pragma fragment frag
    28             
    29             #include "UnityCG.cginc"
    30             #include "Lighting.cginc"
    31 
    32             struct a2v
    33             {
    34                 float4 vertex : POSITION;
    35                 float3 normal : NORMAL;
    36                 float4 texcoord : TEXCOORD0;
    37             };
    38 
    39             struct v2f
    40             {
    41                 float4 pos : SV_POSITION;
    42                 float2 uv : TEXCOORD0;
    43                 float3 worldNormal : TEXCOORD1;
    44                 float3 worldPos : TEXCOORD2;
    45             };
    46 
    47             sampler2D _MainTex;
    48             float4 _MainTex_ST;
    49             fixed4 _Color;
    50             // 用于决定调用clip函数时进行的透明度测试使用的判断条件
    51             fixed _AlphaScale;
    52             
    53             v2f vert (a2v v)
    54             {
    55                 v2f o;
    56 
    57                 o.pos = UnityObjectToClipPos(v.vertex);
    58                 o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
    59                 o.worldNormal = UnityObjectToWorldNormal(v.normal);
    60                 o.worldPos = mul(unity_ObjectToWorld, v.vertex);
    61 
    62                 return o;
    63             }
    64             
    65             fixed4 frag (v2f i) : SV_Target
    66             {
    67                 fixed3 worldNormal = normalize(i.worldNormal);
    68                 fixed3 worldPos = normalize(i.worldPos);
    69                 fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(worldPos));
    70                 // 纹素值
    71                 fixed4 texColor = tex2D(_MainTex, i.uv);
    72                 // 反射率
    73                 fixed3 albedo =  texColor.rgb * _Color.rgb;
    74                 // 环境光
    75                 fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo;
    76                 // 漫反射
    77                 fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));
    78                 // 返回颜色,透明度部分乘以我们设定的值
    79                 return fixed4(ambient + diffuse, texColor.a * _AlphaScale);
    80             }
    81             ENDCG
    82         }
    83     }
    84 }

    然而这种实现方式是有问题的。就像前面说的一样,它并不能渲染正确的顺序。

    解决方式:用两个Pass来渲染模型

    • 第一个Pass:开启深度写入,但不输出颜色,目的仅仅为了填充深度缓冲。
    • 第二个Pass:正常的透明度混合,由于上一个Pass已经得到了逐像素的正确深度信息,该Pass就可以按照像素级别的深度排序进行透明渲染。
    • 缺点:多了一个Pass性能有所影响。

    代码如下:

     1 Shader "My Shader/AlphaShader"
     2 {
     3     Properties
     4     {
     5         _Color ("Color", Color) = (1,1,1,1)
     6         _MainTex ("Texture", 2D) = "white" {}
     7         _AlphaScale ("Alpha Scale", Range(0, 1)) = 1
     8     }
     9     SubShader
    10     {
    11         // 透明度混合队列为Transparent,所以Queue=Transparent
    12         // RenderType标签让Unity把这个Shader归入提前定义的组中,以指明该Shader是一个使用了透明度混合的Shader
    13         // IgonreProjector为True表明此Shader不受投影器(Projectors)影响
    14         Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" }
    15 
    16         Pass
    17         {
    18             // 开启深度写入
    19             ZWrite On
    20             // 设置颜色通道的写掩码,0为不写入任何颜色
    21             ColorMask 0
    22         }
    23 
    24         Pass
    25         {
    26             Tags { "LightMode"="ForwardBase" }
    27             
    28             // 关闭深度写入
    29             ZWrite Off
    30             // 开启混合模式,并设置混合因子为SrcAlpha和OneMinusSrcAlpha
    31             Blend SrcAlpha OneMinusSrcAlpha
    32 
    33             CGPROGRAM
    34             #pragma vertex vert
    35             #pragma fragment frag
    36             
    37             #include "UnityCG.cginc"
    38             #include "Lighting.cginc"
    39 
    40             struct a2v
    41             {
    42                 float4 vertex : POSITION;
    43                 float3 normal : NORMAL;
    44                 float4 texcoord : TEXCOORD0;
    45             };
    46 
    47             struct v2f
    48             {
    49                 float4 pos : SV_POSITION;
    50                 float2 uv : TEXCOORD0;
    51                 float3 worldNormal : TEXCOORD1;
    52                 float3 worldPos : TEXCOORD2;
    53             };
    54 
    55             sampler2D _MainTex;
    56             float4 _MainTex_ST;
    57             fixed4 _Color;
    58             // 用于决定调用clip函数时进行的透明度测试使用的判断条件
    59             fixed _AlphaScale;
    60             
    61             v2f vert (a2v v)
    62             {
    63                 v2f o;
    64 
    65                 o.pos = UnityObjectToClipPos(v.vertex);
    66                 o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
    67                 o.worldNormal = UnityObjectToWorldNormal(v.normal);
    68                 o.worldPos = mul(unity_ObjectToWorld, v.vertex);
    69 
    70                 return o;
    71             }
    72             
    73             fixed4 frag (v2f i) : SV_Target
    74             {
    75                 fixed3 worldNormal = normalize(i.worldNormal);
    76                 fixed3 worldPos = normalize(i.worldPos);
    77                 fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(worldPos));
    78                 // 纹素值
    79                 fixed4 texColor = tex2D(_MainTex, i.uv);
    80                 // 反射率
    81                 fixed3 albedo =  texColor.rgb * _Color.rgb;
    82                 // 环境光
    83                 fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo;
    84                 // 漫反射
    85                 fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));
    86                 // 返回颜色,透明度部分乘以我们设定的值
    87                 return fixed4(ambient + diffuse, texColor.a * _AlphaScale);
    88             }
    89             ENDCG
    90         }
    91     }
    92 }

    双面渲染的透明效果

    对于刚才的立方体,虽然是透明的,但是却看不到里面的构造,是不是感觉也不太对,如果想看到内部构造怎么办呢?

    Unity默认会剔除物体的背面(就是内部),那么我们可以用Cull指令来控制需要剔除哪个面的渲染图元。

    • Cull Back:背对着摄像机的渲染图元不会渲染,默认情况。
    • Cull Front:朝向摄像机的渲染图元不会渲染。
    • Cull Off:关闭剔除功能,所有的都会渲染。缺点:需要渲染的数目成倍增加,除非用于特殊效果,建议不开启。

    接下来我们看一下效果:

    这回也是用连个Pass来完成:第一个Pass渲染背面,第二个Pass渲染前面

    Shader 代码如下:

      1 Shader "My Shader/AlphaShader"
      2 {
      3     Properties
      4     {
      5         _Color ("Color", Color) = (1,1,1,1)
      6         _MainTex ("Texture", 2D) = "white" {}
      7         _AlphaScale ("Alpha Scale", Range(0, 1)) = 1
      8     }
      9     SubShader
     10     {
     11         // 透明度混合队列为Transparent,所以Queue=Transparent
     12         // RenderType标签让Unity把这个Shader归入提前定义的组中,以指明该Shader是一个使用了透明度混合的Shader
     13         // IgonreProjector为True表明此Shader不受投影器(Projectors)影响
     14         Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" }
     15 
     16         Pass
     17         {
     18             Tags { "LightMode"="ForwardBase" }
     19 
     20             // 只渲染背面
     21             Cull Front
     22             // 关闭深度写入
     23             ZWrite Off
     24             // 开启混合模式,并设置混合因子为SrcAlpha和OneMinusSrcAlpha
     25             Blend SrcAlpha OneMinusSrcAlpha
     26 
     27             CGPROGRAM
     28             #pragma vertex vert
     29             #pragma fragment frag
     30             
     31             #include "UnityCG.cginc"
     32             #include "Lighting.cginc"
     33 
     34             struct a2v
     35             {
     36                 float4 vertex : POSITION;
     37                 float3 normal : NORMAL;
     38                 float4 texcoord : TEXCOORD0;
     39             };
     40 
     41             struct v2f
     42             {
     43                 float4 pos : SV_POSITION;
     44                 float2 uv : TEXCOORD0;
     45                 float3 worldNormal : TEXCOORD1;
     46                 float3 worldPos : TEXCOORD2;
     47             };
     48 
     49             sampler2D _MainTex;
     50             float4 _MainTex_ST;
     51             fixed4 _Color;
     52             // 用于决定调用clip函数时进行的透明度测试使用的判断条件
     53             fixed _AlphaScale;
     54             
     55             v2f vert (a2v v)
     56             {
     57                 v2f o;
     58 
     59                 o.pos = UnityObjectToClipPos(v.vertex);
     60                 o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
     61                 o.worldNormal = UnityObjectToWorldNormal(v.normal);
     62                 o.worldPos = mul(unity_ObjectToWorld, v.vertex);
     63 
     64                 return o;
     65             }
     66             
     67             fixed4 frag (v2f i) : SV_Target
     68             {
     69                 fixed3 worldNormal = normalize(i.worldNormal);
     70                 fixed3 worldPos = normalize(i.worldPos);
     71                 fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(worldPos));
     72                 // 纹素值
     73                 fixed4 texColor = tex2D(_MainTex, i.uv);
     74                 // 反射率
     75                 fixed3 albedo =  texColor.rgb * _Color.rgb;
     76                 // 环境光
     77                 fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo;
     78                 // 漫反射
     79                 fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));
     80                 // 返回颜色,透明度部分乘以我们设定的值
     81                 return fixed4(ambient + diffuse, texColor.a * _AlphaScale);
     82             }
     83             ENDCG
     84         }
     85 
     86         Pass
     87         {
     88             Tags { "LightMode"="ForwardBase" }
     89             
     90             // 只渲染前面
     91             Cull Back 
     92             // 关闭深度写入
     93             ZWrite Off
     94             // 开启混合模式,并设置混合因子为SrcAlpha和OneMinusSrcAlpha
     95             Blend SrcAlpha OneMinusSrcAlpha
     96 
     97             CGPROGRAM
     98             #pragma vertex vert
     99             #pragma fragment frag
    100             
    101             #include "UnityCG.cginc"
    102             #include "Lighting.cginc"
    103 
    104             struct a2v
    105             {
    106                 float4 vertex : POSITION;
    107                 float3 normal : NORMAL;
    108                 float4 texcoord : TEXCOORD0;
    109             };
    110 
    111             struct v2f
    112             {
    113                 float4 pos : SV_POSITION;
    114                 float2 uv : TEXCOORD0;
    115                 float3 worldNormal : TEXCOORD1;
    116                 float3 worldPos : TEXCOORD2;
    117             };
    118 
    119             sampler2D _MainTex;
    120             float4 _MainTex_ST;
    121             fixed4 _Color;
    122             // 用于决定调用clip函数时进行的透明度测试使用的判断条件
    123             fixed _AlphaScale;
    124             
    125             v2f vert (a2v v)
    126             {
    127                 v2f o;
    128 
    129                 o.pos = UnityObjectToClipPos(v.vertex);
    130                 o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
    131                 o.worldNormal = UnityObjectToWorldNormal(v.normal);
    132                 o.worldPos = mul(unity_ObjectToWorld, v.vertex);
    133 
    134                 return o;
    135             }
    136             
    137             fixed4 frag (v2f i) : SV_Target
    138             {
    139                 fixed3 worldNormal = normalize(i.worldNormal);
    140                 fixed3 worldPos = normalize(i.worldPos);
    141                 fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(worldPos));
    142                 // 纹素值
    143                 fixed4 texColor = tex2D(_MainTex, i.uv);
    144                 // 反射率
    145                 fixed3 albedo =  texColor.rgb * _Color.rgb;
    146                 // 环境光
    147                 fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo;
    148                 // 漫反射
    149                 fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));
    150                 // 返回颜色,透明度部分乘以我们设定的值
    151                 return fixed4(ambient + diffuse, texColor.a * _AlphaScale);
    152             }
    153             ENDCG
    154         }
    155     }
    156 }
  • 相关阅读:
    项目经理-要材料
    java stream 处理
    listGiftBagShowInfo 这个方法 我搞了一晚上
    BigDecimal.ROUND_UP 如果 从 double 到 Decimal 有异常, 必须从double到String然后 Decimal 就可以了
    json串 转 list<class> 方法 List转JSONArray和JSONArray转List
    昨晚加班到3点多-身体都虚脱了
    MVN 点击compile总是失败
    IDEA使用笔记(八)——自动生成 serialVersionUID 的设置
    Maven 版本号规范
    java多线程学习
  • 原文地址:https://www.cnblogs.com/SHOR/p/8019163.html
Copyright © 2011-2022 走看看