前言
在实时渲染中要实现透明效果,通常会在渲染模型时控制它的透明通道。
Unity中通常使用两种方法来实现透明 :(1)透明度测试(AlphaTest)(2)透明度混合(AlphaBlend)。前者往往无法实现真正的半透明效果。

深度缓冲(Depth Buffer)

深度缓冲是用于解决可见性问题的,它可以决定物体的哪些部分渲染在前面,哪些部分被其他物体遮挡。其基本思想是:根据深度缓存中的值来判断该片元距离摄像机的距离,当渲染一个片元时,需要把它的深度值和已经存在深度缓存中的值进行比较(前提是开启了深度测试),如果它的值距离摄像机更远,那么说明它不应该被渲染到屏幕上(被挡住了);否则,这个片元应该覆盖掉此时颜色缓冲中的像素值,并把它的深度更新到深度缓冲中(前提是开启了深度写入,Unity中为ZWrite On)。

透明度混合时应关闭深度写入(ZWrite Off)

如果不关闭深度写入,一个半透明表面背后的表面本就是透过它被我们看到的,但由于深度测试时判断结果是该半透明表面)距离摄像机更近,导致后面的表面会被剔除掉,也就无法通过半透明面观察到后面的物体。

另外注意关闭深度写入后要考虑物体的渲染顺序。如下图,一个半透明物体A和一个不透明物体B,B在A后方,如果先渲染A再渲染B会出现A被B遮挡的情况,这是错误的。因此渲染顺序在关闭深度写入的情况下极为重要。

为了保证渲染顺序正确,渲染引擎一般会对物体进行排序,再渲染,常用的方法是

  • 1)先渲染所有不透明物体,并开启它们的深度测试和深度写入。
  • 2)把半透明物体按他们离摄像机的远近进行排序,然后按照从后往前的顺序渲染透明物体,并开启它们的深度测试,但关闭深度写入。

但这种方法无法解决物体重叠的情况,因此需要额外的解决方案,比如分割网格等。但是也可以试着让透明通道更柔和,是穿插重叠看起来不那么明显。

Unity的渲染顺序

Unity通过一组Queue标签来决定模型归于哪个渲染队列,队列由整数索引表示,索引号越小越先被渲染。

渲染队列 渲染队列描述 渲染队列值
Background 这个队列被最先渲染。它被用于skyboxes等。 1000
Geometry 这是默认的渲染队列。它被用于绝大多数对象。不透明几何体使用该队列。 2000
AlphaTest 通道检查的几何体使用该队列。它和Geometry队列不同,对于在所有立体物体绘制后渲染的通道检查的对象,它更有效。 2450
Transparent 该渲染队列在Geometry和AlphaTest队列后被渲染。任何通道混合的(也就是说,那些不写入深度缓存的Shaders)对象使用该队列,例如玻璃和粒子效果。 3000
Overlay 该渲染队列是为叠加效果服务的。任何最后被渲染的对象使用该队列,例如镜头光晕。 4000

透明度测试

只要有一个片元的透明度不满足条件(通常是小于某个阈值),那么它对应的片元便会被舍弃,不做任何处理。

在Unity中是用如下函数来进行透明度测试:

  1. clip(texColor.a - _Cutoff);

texColor.a为纹理的alpha值,_Cutoff为阈值,该函数等价于

  1. if(texColor.a - _Cutoff < 0)
  2. discard;

完整的代码 

  1. Shader "Unity Shader Book/Chapter 8/AlphaTest"
  2. {
  3. Properties
  4. {
  5. _Color("Color Tint",Color) = (1,1,1,1)
  6. _MainTex ("Texture", 2D) = "white" {}
  7. _Cutoff("Alpha Cutoff",Range(0,1)) = 0.5
  8. }
  9. SubShader
  10. {
  11. Tags { "RenderType"="TransparentCutout" "Queue"="AlphaTest" "IgnoreProjector"="True" "LightMode"="ForwardBase"}
  12. LOD 100
  13.  
  14. Pass
  15. {
  16. CGPROGRAM
  17. #pragma vertex vert
  18. #pragma fragment frag
  19.  
  20. #include "UnityCG.cginc"
  21. #include "Lighting.cginc"
  22. struct appdata
  23. {
  24. float4 vertex : POSITION;
  25. float3 normal:NORMAL;
  26. float4 texcoord : TEXCOORD0;
  27. };
  28.  
  29. struct v2f
  30. {
  31. float4 pos : SV_POSITION;
  32. float3 worldNormal:TEXCOORD0;
  33. float3 worldPos:TEXCOORD1;
  34. float2 uv : TEXCOORD2;
  35. };
  36.  
  37. fixed4 _Color;
  38. fixed _Cutoff;
  39. sampler2D _MainTex;
  40. float4 _MainTex_ST;
  41.  
  42. v2f vert (appdata v)
  43. {
  44. v2f o;
  45. o.pos = UnityObjectToClipPos(v.vertex);
  46. o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
  47. o.worldNormal = UnityObjectToWorldNormal(v.normal);
  48. o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;
  49. return o;
  50. }
  51.  
  52. fixed4 frag (v2f i) : SV_Target
  53. {
  54. fixed3 worldNormal = normalize(i.worldNormal);
  55. fixed3 worldLightDir = UnityWorldSpaceLightDir(i.worldPos);
  56.  
  57. fixed4 texColor = tex2D(_MainTex,i.uv);
  58.  
  59. //Alpha Test
  60. clip(texColor.a - _Cutoff);
  61. //equal to
  62. //if(texColor.a - _Cutoff < 0) discard;
  63. fixed3 albedo = texColor.rgb * _Color.rgb;
  64. fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
  65. fixed3 diffuse = _LightColor0.rgb * albedo * max(0,dot(worldNormal,worldLightDir));
  66. return fixed4(diffuse + ambient,1.0);
  67. }
  68. ENDCG
  69. }
  70. }
  71. }

效果如下,可以看到只是单纯的颜色剔除,并不算正常的透明效果。

为了得到正确的透明效果,可以使用透明度混合。

透明度混合

透明度混合即将透明物体的源颜色与其表面后方的目标颜色混合,其基本公式为:

DstColor-new(混合后的颜色) = SrcAlpha * SrcColor + (1- SrcAlpha) * DstColor。(SrcAlpha为混合因子)

该公式可以在Unity的ShaderLab语义中表示为

  1. Blend SrcAlpha OneMinusSrcAlpha

更多语义可以参考官方文档

完整代码实现

  1. // Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
  2.  
  3. Shader "Unity/Chapter 8/AlphaBlend"
  4. {
  5. Properties
  6. {
  7. _Color("Color Tint",Color) = (1,1,1,1)
  8. _MainTex ("Texture", 2D) = "white" {}
  9. _AlphaScale("Alpha Scale",Range(0,1)) = 0.5
  10. }
  11. SubShader
  12. {
  13. Tags { "RenderType"="Transparent" "IgnoreProjector"="True" "Queue"="Transparent" }
  14. LOD 100
  15. Blend SrcAlpha OneMinusSrcAlpha
  16. ZWrite On
  17. Pass
  18. {
  19. CGPROGRAM
  20. #pragma vertex vert
  21. #pragma fragment frag
  22.  
  23. #include "UnityCG.cginc"
  24. #include "Lighting.cginc"
  25. struct appdata
  26. {
  27. float3 normal:NORMAL;
  28. float4 vertex : POSITION;
  29. float4 texcoord : TEXCOORD0;
  30. };
  31.  
  32. struct v2f
  33. {
  34. float2 uv : TEXCOORD2;
  35. float3 worldNormal : TEXCOORD0;
  36. float3 worldPos : TEXCOORD1;
  37. float4 pos : SV_POSITION;
  38. };
  39.  
  40. fixed4 _Color;
  41. fixed _AlphaScale;
  42. sampler2D _MainTex;
  43. float4 _MainTex_ST;
  44.  
  45. v2f vert (appdata v)
  46. {
  47. v2f o;
  48. o.worldNormal = UnityObjectToWorldNormal(v.normal);
  49. o.pos = UnityObjectToClipPos(v.vertex);
  50. o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
  51. o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;
  52. return o;
  53. }
  54.  
  55. fixed4 frag (v2f i) : SV_Target
  56. {
  57. fixed3 worldNormal = normalize(i.worldNormal);
  58. fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
  59. fixed4 texColor = tex2D(_MainTex,i.uv);
  60. fixed3 albedo = texColor.rgb * _Color.rgb;
  61. fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
  62. fixed3 diffuse = _LightColor0.rgb * albedo * max(0,dot(worldNormal,worldLightDir));
  63. return fixed4(ambient + diffuse,texColor.a * _AlphaScale);
  64. }
  65. ENDCG
  66. }
  67. }
  68. Fallback "Transparent/VertexLit"
  69. }

该代码得到的效果如下

嗯,这才是正常的透明效果

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

上述的透明混合并没有开启深度写入(ZWrite On),因此如果物体有重叠,那么会产生错误的效果,如下

这时候需要在上文的透明度混合代码中的Pass代码块之前,插入一个新的Pass代码块

  1. Pass
  2. {
  3. ZWrite On
  4. ColorMask 0 //用于设置颜色通道的写掩码,0表示不写入任何通道
  5. }

这样就会得到正确的效果

双面渲染的透明效果

仔细观察上文的透明效果可以发现,我们并不能透过半透明物体观察它们的内部情况,这是不合乎常理的,因此我们需要进行双面渲染。

在Unity中,我们可以通过Cull指令来控制剔除哪一面的渲染图元

  1. Cull Back //剔除背面,只渲染前面,引擎的默认设置
  2. Cull Front //剔除前面,仅渲染背面
  3. Cull Off //关闭剔除

透明度测试的双面渲染

仅需要在Tags标签后插入一行

  1. Cull Off

效果对比

透明度混合的双面渲染

将Pass代码块复制一份,一个Pass负责渲染前面,一个Pass负责渲染后面就行,格式如下

  1. Shader "........."{
  2. propeties
  3. {
  4. /*.....*/
  5. }
  6. SubShader{
  7. Pass
  8. {
  9. Tags{.......}
  10. Cull Front //剔除前面
  11. /*其余代码保持不变
  12. ....
  13. */
  14. }
  15.  
  16. Pass
  17. {
  18. Tags{.......}
  19. Cull Back //剔除后面
  20. /*其余代码保持不变
  21. ....
  22. */
  23. }
  24.  
  25. }
  26. }

实现的效果如下

Unity3D学习(八):《Unity Shader入门精要》——透明效果的更多相关文章

  1. Unity Shader入门精要学习笔记 - 第4章 学习 Shader 所需的数学基础

    摘录自 冯乐乐的<Unity Shader入门精要> 笛卡尔坐标系 1)二维笛卡尔坐标系 在游戏制作中,我们使用的数学绝大部分都是计算位置.距离.角度等变量.而这些计算大部分都是在笛卡尔坐 ...

  2. Unity Shader入门精要之 screen post-processing effect

    本篇记录了学习Unity Shader入门精要的屏幕后处理的一些知识点. OnRenderImage(RenderTexture src, RenderTexture dest) 以上函数是Unity ...

  3. Unity Shader入门精要学习笔记 - 第15章 使用噪声

    转载自 冯乐乐的 <Unity Shader 入门精要> 消融效果 消融效果常见于游戏中的角色死亡.地图烧毁等效果.这这些效果中,消融往往从不同的区域开始,并向看似随机的方向扩张,最后整个 ...

  4. Unity Shader入门精要学习笔记 - 第14章非真实感渲染

    转载自 冯乐乐的 <Unity Shader 入门精要> 尽管游戏渲染一般都是以照相写实主义作为主要目标,但也有许多游戏使用了非真实感渲染(NPR)的方法来渲染游戏画面.非真实感渲染的一个 ...

  5. Unity Shader入门精要学习笔记 - 第11章 让画面动起来

    转自 冯乐乐的 <Unity Shader入门精要> Unity Shader 中的内置变量 动画效果往往都是把时间添加到一些变量的计算中,以便在时间变化时画面也可以随之变化.Unity ...

  6. Unity Shader入门精要学习笔记 - 第10章 高级纹理

    转载自 冯乐乐的 <Unity Shader入门精要> 立方体纹理 在图形学中,立方体纹理是环境映射的一种实现方法.环境映射可以模拟物体周围的环境,而使用了环境映射的物体可以看起来像镀了层 ...

  7. Unity Shader入门精要学习笔记 - 第9章 更复杂的光照

    转载自 冯乐乐的<Unity Shader入门精要> Unity 的渲染路径 在Unity里,渲染路径决定了光照是如何应该到Unity Shader 中的.因此,如果要和光源打交道,我们需 ...

  8. Unity Shader入门精要学习笔记 - 第8章 透明效果

    转载自 冯乐乐的 <Unity Shader入门精要> 透明是游戏中经常要使用的一种效果.在实时渲染中要实现透明效果,通常会在渲染模型时控制它的透明通道.当开启透明混合后,当一个物体被渲染 ...

  9. Unity Shader入门精要读书笔记(一)序章

    本系列的博文是笔者读<Unity Shader入门精要>的读书笔记,这本书的章节框架是: 第一章:着手准备. 第二章:GPU流水线. 第三章:Shader基本语法. 第四章:Shader数 ...

  10. 【我的书】《Unity Shader入门精要》出版上市

    重要的事 先说重要的事,就是我的书籍<Unity Shader入门精要>在经过无数次跳票后,终于出版上市了(泪目-)! 购买传送门: 亚马逊 当当 京东 截止到我写这篇文章的时候,京东是没 ...

随机推荐

  1. 安卓Tv开发(二)移动智能电视之焦点控制(按键事件)

    原文:http://blog.csdn.net/sk719887916/article/details/44781475 skay 前言:移动智能设备的发展,推动了安卓另一个领域,包括智能电视和智能家 ...

  2. Android应用---基于NDK的samples例程hello-jni学习NDK开发

    Android应用---基于NDK的samples例程hello-jni学习NDK开发 NDK下载地址:http://developer.android.com/tools/sdk/ndk/index ...

  3. rabbitMQ之AMQP协议

    1.什么是AMQP协议 即高级消息队列协议,规范客户端与消息中间件服务器之间的通信,并能相互操作. 2.AMQP协议的作用 降低应用程序之间的耦合度,这样不同应用之间的集成的难度将变得更小,并开发出更 ...

  4. 股票K线图

    代码链接地址:点击打开链接

  5. Linux文件系统构成(第二版)

    Linux文件系统构成 /boot目录: 内核文件.系统自举程序文件保存位置,存放了系统当前的内核[一般128M即可] 如:引导文件grub的配置文件等 /etc目录: 系统常用的配置文件,所以备份系 ...

  6. iOS中NSBundle的介绍

    bundle是一个目录,其中包含了程序会使用到的资源.这些资源包含了如图像,声音,编译好的代码,nib文件(用户也会把bundle称为plug-in).对应bundle,cocoa提供了类NSBund ...

  7. Altium Designer设计PCB板之“精神”

    通过一小段时间的练习,感觉先领悟设计PCB板的“精神”更加重要.在这里,我指的“精神”是指PCB板中涉及的元器件原理图及其封装设计.当然,设计PCB板还有其他方面重要的精神需要掌握.本文所提到的“精神 ...

  8. Android ROM开发(二)——ROM架构以及Updater-Script脚本分析,常见的Status错误解决办法

    Android ROM开发(二)--ROM架构以及Updater-Script脚本分析,常见的Status错误解决办法 怪自己二了,写好的不小心弄没了,现在只好重新写一些了,上篇简单的配置了一下环境, ...

  9. Android Data Binding高级用法-Observable、动态生成Binding Class(三)

    设置View的id 虽然说Data Binding这种分层模式使得我们对数据的传递简单明了,一般情况下我们可以不设置View的id,不使用findViewById即可对View进行数据上一系列的操作, ...

  10. 导入android SlidingMenu 应用

    SlidingMenu is a helpful Android library for developers. It creates a side navigation like the Faceb ...