前言
在实时渲染中要实现透明效果,通常会在渲染模型时控制它的透明通道。
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中是用如下函数来进行透明度测试:

clip(texColor.a - _Cutoff);

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

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

完整的代码 

Shader "Unity Shader Book/Chapter 8/AlphaTest"
{
Properties
{
_Color("Color Tint",Color) = (1,1,1,1)
_MainTex ("Texture", 2D) = "white" {}
_Cutoff("Alpha Cutoff",Range(0,1)) = 0.5
}
SubShader
{
Tags { "RenderType"="TransparentCutout" "Queue"="AlphaTest" "IgnoreProjector"="True" "LightMode"="ForwardBase"}
LOD 100 Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag #include "UnityCG.cginc"
#include "Lighting.cginc"
struct appdata
{
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;
fixed _Cutoff;
sampler2D _MainTex;
float4 _MainTex_ST; v2f vert (appdata v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;
return o;
} fixed4 frag (v2f i) : SV_Target
{
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = UnityWorldSpaceLightDir(i.worldPos); fixed4 texColor = tex2D(_MainTex,i.uv); //Alpha Test
clip(texColor.a - _Cutoff);
//equal to
//if(texColor.a - _Cutoff < 0) discard;
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(diffuse + ambient,1.0);
}
ENDCG
}
}
}

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

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

透明度混合

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

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

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

Blend SrcAlpha OneMinusSrcAlpha

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

完整代码实现

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

Shader "Unity/Chapter 8/AlphaBlend"
{
Properties
{
_Color("Color Tint",Color) = (1,1,1,1)
_MainTex ("Texture", 2D) = "white" {}
_AlphaScale("Alpha Scale",Range(0,1)) = 0.5
}
SubShader
{
Tags { "RenderType"="Transparent" "IgnoreProjector"="True" "Queue"="Transparent" }
LOD 100
Blend SrcAlpha OneMinusSrcAlpha
ZWrite On
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag #include "UnityCG.cginc"
#include "Lighting.cginc"
struct appdata
{
float3 normal:NORMAL;
float4 vertex : POSITION;
float4 texcoord : TEXCOORD0;
}; struct v2f
{
float2 uv : TEXCOORD2;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
float4 pos : SV_POSITION;
}; fixed4 _Color;
fixed _AlphaScale;
sampler2D _MainTex;
float4 _MainTex_ST; v2f vert (appdata v)
{
v2f o;
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;
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"
}

该代码得到的效果如下

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

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

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

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

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

这样就会得到正确的效果

双面渲染的透明效果

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

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

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

透明度测试的双面渲染

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

Cull Off

效果对比

透明度混合的双面渲染

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

Shader "........."{
propeties
{
/*.....*/
}
SubShader{
Pass
{
Tags{.......}
Cull Front //剔除前面
/*其余代码保持不变
....
*/
} Pass
{
Tags{.......}
Cull Back //剔除后面
/*其余代码保持不变
....
*/
} }
}

实现的效果如下

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. How To Transact Move Order Using INV_PICK_WAVE_PICK_CONFIRM_PUB.Pick_Confirm API

    In this Document Goal   Solution   Sample Code:   Steps:   FAQ   References APPLIES TO: Oracle Inven ...

  2. 算法面试题-leetcode学习之旅(二)

    题目: Given a non-negative integer num, repeatedly add all its digits until the result has only one di ...

  3. 【Qt编程】基于Qt的词典开发系列<二>--本地词典的设计

    我设计的词典不仅可以实现在线查单词,而且一个重大特色就是具有丰富的本地词典库:我默认加入了八个类型的词典,如下所示: 由于是本人是通信专业,因此加入了华为通信词典.电子工程词典,又由于我喜爱编程,也加 ...

  4. 《java入门第一季》之面向对象(static关键字内存图解)

  5. linux下gtk+一个将字符串大写化的小示例

    首先用glade画图形界面: 并且设置gtk元素名称(ID)以及设置事件回调函数. 下面写代码: #include <gtk/gtk.h> #include <string.h> ...

  6. java基础多线程之共享数据

    java基础巩固笔记5-多线程之共享数据 线程范围内共享数据 ThreadLocal类 多线程访问共享数据 几种方式 本文主要总结线程共享数据的相关知识,主要包括两方面:一是某个线程内如何共享数据,保 ...

  7. 和菜鸟一起学linux之dlna的学习记录

    关于DLNA框架 1.Networking & Connectivity 为了解决物理设备连通问题, 主要依赖于Ethernet,802.11,Ipv4协议栈,Ipv6协议栈. TCP/IP协 ...

  8. The 1st tip of DB Query Analyzer

     The 1st tip of DB Query Analyzer               Ma Genfeng   (Guangdong Unitoll Services incorporate ...

  9. Nginx使用图片处理模块

    Nginx可以编写很多额外的模块,这里我们需要按照能够通过URL响应返回缩放且含图片水印功能的模块. 1.安装一些使用过程中会用到的工具 yum install libgd2-devel yum in ...

  10. 学习ASP.NET Core Razor 编程系列八——并发处理

    学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 学习ASP.NET Core Razor 编程系列二——添加一个实体 学习ASP.NET ...