UnityShader透明效果

透明效果

在实时渲染中要实现透明效果,通常会在渲染模型时控制它的透明通道。

当开启透明混合后,当一个物体被渲染到屏幕上时,每个片元除了颜色和深度值之外,它还有另一个属性——透明度。

当透明度为1时,表示该像素是完全不透明的,而当其为0时,则表示该像素完全不会显示。

实现:使用透明度测试,这种方法其实无法得到真正的半透明效果

另一种方法是透明度混合

对于不透明(Opaque)物体,不考虑他们的渲染顺序也能得到正确的排序效果,这是由于强大的深度缓冲(depth buffer,z-buffer)的存在。

在实时渲染中,深度缓冲是用于解决可见性(visibility)问题的,它可以决定哪个物体的哪些部分会被渲染在前面,而哪些部分会被其他物体遮挡。

基本思想:根据深度缓冲中的值来判断该片元距离摄像机的距离,当渲染一个片元时,需要把它的深度值和已经存在与深度缓冲中的值进行比较(开启了深度测试),如果它的值距离摄像机更远,那么说明这个片元不应该被渲染到屏幕上(有物体挡住了它)。否则,这个片元应该覆盖掉此时颜色缓冲中的像素值,并把它的深度值更新到深度缓冲中(开启了深度写入)。

使用透明度混合时,关闭了深度写入(ZWrite)


透明度测试

只要一个片元的透明度不满足条件(通常是小于某个阈值),那么它对应的片元就会被舍弃。被舍弃的片元将不会再进行任何处理,也不会对颜色缓冲区产生任何影响;否则就会按照普通的不透明物体的处理方式来处理它,即进行深度测试、深度写入等。

透明度混合

得到真正的半透明效果。它会使用片元的透明度作为混合因子,与已经存储在颜色缓冲区中的颜色值进行混合,得到新的颜色。但是,透明度混合需要关闭深度写入,这使得我们需要非常小心物体的渲染顺序。

UnityShader中的渲染顺序

Unity提供了渲染队列这一解决方案来解决渲染顺序的问题。

可以使用SubShader的Queue标签来决定我们的模型将归于哪个渲染队列。

Background 1000

Geometry 2000

AlphaTest 2450

Transparent 3000

Overlay 4000

ZWrite Off用于关闭深度写入。

透明度测试

工作原理:

只要一个片元的透明度不满足条件,那么它对应的片元就会被舍弃。被舍弃的片元将不会再进行任何处理,也不会对颜色缓冲残产生任何影响;否则,就会按照普通不透明物体的处理方式来处理它。

使用clip函数进行深度测试。

Properties
  {
       _Color("Color",Color)=(1,1,1,1)
       _MainTex ("Texture", 2D) = "white" {}
       _Cutoff("Alpha Cutoff",Range(0,1))=0.5
  }
   SubShader
  {
       Tags{"Queue"="AlphaTest" "IgnoreProjector"="True" "RenderType"="TransparentCoutout"}
       Pass
      {
           Tags{"LightMode"="ForwardBase"}
           CGPROGRAM
           #pragma vertex vert
           #pragma fragment frag
           
           #include "Lighting.cginc"
           #include "UnityCG.cginc"

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

           struct v2f
          {
               float4 pos : SV_POSITION;
               float2 uv : TEXCOORD0;
               fixed3 worldNormal :TEXCOORD1;
               fixed3 worldPos :TEXCOORD2;
          };

           v2f vert (appdata_base v)
          {
               v2f o;
               o.pos = UnityObjectToClipPos(v.vertex);
               o.uv = TRANSFORM_TEX(v.texcoord ,_MainTex);
               o.worldNormal = UnityObjectToWorldNormal(v.normal);
               o.worldPos = UnityObjectToWorldDir(v.vertex);
               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);
               // 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 * saturate(dot(worldNormal,worldLightDir));
               return fixed4(ambient+diffuse,1.0);
          }
           ENDCG
      }
  }
   Fallback "Transparent/Cutoff/VertexLit"

RenderType标签可以让Unity把这个Shader归入到提前定义的组中,以指明该Shader是一个使用了透明度测试的Shader。

RenderType标签通常被用于着色器替换功能。

IgnoreProjector设置为True,意味着该Shader不会受到投影器的影响。

Discard指令显示剔除该片元

使用内置的Transparent/Cutoff/VertexLit来作为回调Shader,这不仅能够保证在我们编写的SubShader无法在当前显卡上工作时可以有适合的代替Shader,还可以保证使用透明度测试的物体可以正确地向其他物体投射阴影。

 

透明度混合

原理:

这种方法可以得到真正的半透明效果。他会使用当前片元的透明度作为混合因子,与已经存储在颜色缓冲中的颜色值进行混合,得到新的颜色。但是,透明度混合需要关闭深度写入,这使得要非常小心物体的渲染顺序。

Unity的混合命令——Blend。想要实现半透明效果就需要把当前自身的颜色和已经存在与颜色缓冲中的颜色值进行混合,混合时使用的函数就是由该指令决定的。

使用Blend SrcFactor DstFactor来进行混合。需要注意的是,这个命令在设置混合因子的同时也开启了混合模式。只有开启混合之后,设置片元的透明通道才有意义,而Unity在我们使用Blend命令的时候就自动帮我们打开了。

Properties
  {
       _Color("Color",Color)=(1,1,1,1)
       _MainTex ("Texture", 2D) = "white" {}
       _AlphaScale("Alpha Scale",Range(0,1))=1
  }
   SubShader
  {
       Tags {"Queue"="Transparent" "IgnoreProject"="True" "RenderType"="Transparent" }
       Pass
      {
           Tags{"LigheMode"="ForwardBase"}
           ZWrite off
           Blend SrcAlpha OneMinusSrcAlpha
           CGPROGRAM
           #pragma vertex vert
           #pragma fragment frag

           #include "UnityCG.cginc"
           #include "Lighting.cginc"

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

           struct v2f
          {
               float4 pos : SV_POSITION;
               float2 uv : TEXCOORD0;
               fixed3 worldNormal :TEXCOORD1;
               fixed3 worldPos :TEXCOORD2;
          };

           v2f vert (appdata_base v)
          {
               v2f o;
               o.pos = UnityObjectToClipPos(v.vertex);
               o.uv = TRANSFORM_TEX(v.texcoord ,_MainTex);
               o.worldNormal = UnityObjectToWorldNormal(v.normal);
               o.worldPos = UnityObjectToWorldDir(v.vertex);
               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 * saturate(dot(worldNormal,worldLightDir));
               return fixed4(ambient+diffuse,texColor.a * _AlphaScale);
          }
           ENDCG
      }
  }

透明度混合使用的渲染队列是Transparent的队列。RenterType标签可以让Unity把这个Shader归入到提前定义的组(Transparent组)中,用来指明该Shader是一个使用了透明度混合的Shader。

Zwrite Off 关闭深度写入

Blend SrcAlpha OneMinusSrcAlpha开启并设置该Pass的混合模式。

设置了该片元着色器返回值中的透明通道,它是纹理像素的透明通道和材质参数_AlphaScale的乘积。

关闭深度写入的问题:

当模型本身有复杂的遮挡关系或者是包含了复杂的非凸网格的时候,就会有各种各样因为排序错误而产生的错误的透明效果。

这都是因为关闭深度写入造成的,因为这样我们就无法对模型进行像素级别的深度排序。

我们可以重新利用深度写入,让模型可以像半透明物体一样进行导入淡出。

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

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

第一个Pass开启深度写入,但不输出颜色,它的目的仅仅是为了把该模型的深度值写入深度缓冲中;第二个Pass进行正常的透明度混合,由于上一个Pass已经得到了逐像素的正确的深度信息,该Pass就可以按照像素级别的深度排序结果进行透明渲染。

缺点:多使用一个Pass会对性能造成影响。

pass{
           ZWrite On
           ColorMask 0
      }

这个新Pass的目的仅仅是为了把模型的深度信息写入深度缓冲中,从而剔除模型中被自身遮挡的片元。

渲染命令ColorMask :用于设置颜色通道的写掩码。

ColorMask RGB |A|0|

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

双面透明的渲染效果

透明度测试

Tags{"LightMode"="ForwardBase"}
           Cull off

透明度混合

把双面渲染的工作分成两个Pass­——第一个Pass只渲染背面,第二个Pass只渲染正面,由于Unity会顺序执行SubShader中的各个Pass因此我们可以保证背面总是在正面被渲染之前渲染,从而保证正确的深度渲染关系。

只需要将AlphaBlend的代码改动一下即可

(1) 复制原Pass的代码得到另一个Pass

(2) 在两个Pass中分别使用Cull指令突出不同朝向的渲染图元

先Front 再Back

 
posted @   Apollonia  阅读(797)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
点击右上角即可分享
微信分享提示