透明效果

  • 实时渲染中要实现透明效果,通常在渲染模型时控制他的透明通道。
  • Unity中有两种方式实现透明效果:一种是透明度测试这种方法无法得到真正的半透明效果;另一种是透明度混合
  • 之前没有强调过渲染顺序的问题,因为深度缓冲的存在。在实时渲染中,深度缓冲是用于解决可见性的,他可以决定那些部分物体会被渲染在前面,而哪些部分会被其他物体遮挡。它的基本思想是:根据深度缓冲中的值来判断该片元距离摄像机的距离,当渲染一个片元时,需要把它的深度值和已经存在于深度缓冲中的值进行比较(如果开启了深度测试),如果它的值距离摄像机更远,那么说明这个物体不该被渲染到屏幕上(有物体遮挡了它);否则这个片元应该覆盖此时颜色缓冲中的像素值,并且把它的深度值更新到深度缓存中(如果开启了深度写入)。

透明度测试

只要一个片元的透明度不满足条件(通常是小于某个阈值),那么它对应的片元就会被舍弃。被舍弃的片元就不会进行任何处理,也不会对颜色缓冲造成任何影响;否则,就会按照不透明物体来处理它,即进行深度测试、深度写入等。透明度测试是不用关闭深度写入的,它和其他不透明物体最大的不同就是它会根据透明度来舍弃一些片元。虽然简单,但是产生的效果也很极端,要么完全透明,要么完全不透明。

透明度混合

使用当前片元的透明度作为混合因子,与已经储存在颜色缓冲区中的颜色值进行混合,得到新的颜色。但是透明度混合需要关闭深度写入,这使得我们需要非常小心物体的渲染顺序。需要注意的是,透明度混合只关闭了深度写入,并没有关闭深度测试。这意味着,当使用透明度混合渲染一个片元时,还是会和深度缓冲区的值进行比较,如果他的深度值距离相机更远,那么就不会进行混合操作。也就是对于透明度混合来说,深度缓冲是只读的。

为什么渲染顺序很重要

渲染透明物体如果不关闭深度写入,那么在一个透明物体后面的物体便不会被渲染,即相当于被透明物体遮挡了,这并不符合逻辑。但是关闭了深度写入,这就破坏了深度缓冲的工作机制,即使这是必要的。

  • 我们先来看最简单的情况,假设场景中存在两个物体A和B,A是半透明物体,B是不透明物体,B处于A后方。
  • 第一种情况是先渲染B再渲染A。不透明物体开启了深度测试和写入,此时深度缓冲去没有数据,B写入深度和颜色缓冲区。随后我们渲染A,透明物体会进行深度测试,因此法线A距离摄像机更近,因此,我们会使用A的透明度与颜色缓冲区中的颜色进行混合,得到正确的半透明效果。
  • 第二种情况,我们先渲染A,再渲染B。渲染A时,深度缓冲区中没有任何数据,因此A直接写入颜色缓冲区。随后我们渲染B,渲染时B进行深度测试,深度缓冲区中依然没有数据,B直接写入并覆盖A在颜色缓冲区的数据,导致了实际效果仿佛是B遮挡了A,然而这时错误的。
    基于这种情况,渲染引擎一般都会先对物体进行排序,再渲染。常用方法是:
  • 先渲染所有不透明物体,并且开启它们的深度测试和写入。
  • 把半透明物体按照它们距离摄像机的远近进行排序,然后按照从后往前的顺序渲染这些半透明物体,并开启它们的深度测试,但关闭深度写入。
  • 但这样仍然存在一些问题,我们的深度值记录的是一个像素的深度,并不是一个物体的深度,如果几个物体循环重叠,这样就会出现问题。

Unity Shader 的渲染顺序

  • Unity 为了解决渲染顺序的问题提供了渲染队列这一解决方案。我们可以使用SubShader的Queue标签来决定我们的模型将归于哪个渲染队列。Unity在内部 使用一系列整数索引来表示每个渲染队列,且索引号越小表示越早被渲染。
  • Unity 中提前定义了5个渲染队列,且索引号越小表示越早被渲染。在Unity5中,Unity提前定义了5个渲染队列,在每个队列中间我们也可以使用其他队列。

名称

队列索引号

描述

Background

1000

这个渲染队列会在任何其他渲染队列之前被渲染,我们通常使用该队列来渲染那些需要绘制在背景上的物体。

Geometry

2000

默认的渲染队列,大多数物体都使用这个队列。不透明物体使用这个队列

AlphaTest

2450

需要透明度测试的物体使用这个队列。在Unity5中它从Geometry队列中被单独分出来,这是因为在所有不透明物体渲染之后再渲染它会更加高效。

Transparent

3000

这个队列中的物体会在所有Geometry和AlphaTest物体渲染后,再从后往前的顺序进行渲染。任何使用了透明度混合(例如关闭了深度写入的Shader)的物体都应该使用该队列

Overlay

4000

该队列用于实现一些叠加效果。任何需要在最后渲染的物体都应该使用该队列。

透明度混合

  • 它会使用当前片元的透明度作为混合因子,与已经储存在颜色缓冲区中的颜色值进行混合,得到新的颜色。透明度混合需要关闭深度写入,我们要非常小心物体的渲染顺序。
  • 为了进行混合,我们需要使用Unity提供的混合命令——Blend。要实现半透明效果,就需要把当前自身的颜色和已经存在于颜色缓冲中的颜色进行混合,混合时使用的函数就是由该指令决定的。
ShaderLab的Blend命令

语义

描述

Blend Off

关闭混合

Blend SrcFactor DstFactor

开启混合,并设置混合因子。源颜色(该片元产生的颜色)会乘以SrcFactor,而目标颜色(已经存在于颜色缓存的颜色)会乘以DstFactor,然后把两者相加后再存入颜色缓冲中

Blend SrcFactor DstFactor,SrcFactorA DstFactorA

和上面几乎一样,只是使用不同的因子来混合透明通道

BlendOp BlendOperation

并非是把源颜色和目标颜色简单相加后混合,而是使用BlendOperation对它们进行其他操作

只有打开混合模式以后,设置片元的透明通道才有意义,Unity在我们使用Blend命令的时候就自动帮我们打开了。我们通常会把源颜色的混合因子SrcFactor设为SrcAlpha,而目标颜色的混合因子DstFactor设为OneMinusSrcAlpha。这意味着,经过混合后的新颜色是:

unity2d tilemap透明图 unity透明贴图_Blend

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

  • 之前我们解释了由于关闭深度写入带来的各种问题。当模型本身有复杂的遮挡关系或是包含了复杂的非凸网格的时候,就会有各种各样因为排序错误产生的错误的透明效果。由于关闭了深度写入我们无法对模型进行像素级别的深度排序。这时我们可以想办法重新利用深度写入,让模型可以像半透明物体一样进行淡入淡出。
  • 一种解决方法是使用两个Pass来渲染模型:第一个Pass开启深度写入,但不输出颜色,它的目的仅仅是为了把该模型的深度值写入深度缓冲中;第二个Pass进行正常的透明度混合,由于上一个Pass已经得到了逐像素的正确的深度信息,该Pass就可以按照像素级别的正确深度信息,但这种方法的缺点在于多使用一个Pass造成的性能影响。

ShaderLab 的混合命令

  • 首先我们看一下混合是怎样实现的。当片元着色器产生一个颜色时,可以选择与颜色缓存中的颜色进行混合。这样混合就和两个操作数有关:源颜色目标颜色有关。源颜色我们使用S表示,指的是片元着色器产生的颜色;目标颜色我们使用D表示,指的是从颜色缓冲中读取到的颜色值。对它们混合后得到的颜色值,我们用O表示它会重新写入到颜色缓冲中。需要注意的是,当我们谈及混合中的源颜色、目标颜色和输出颜色时,它们都包含了RGBA四个通道的颜色值,而并非仅仅是RGB通道。
  • 想要使用混合,我们必须开启它。在Unity中,当我们使用Blend(Blend Off命令除外),除了设置混合状态也会自动开启混合。在其他图形API中我们是需要手动开启的。例如在OpenGL中,我们需要使用glEnable(GL_BLEND)来开启混合。但在Unity中吗,它已经在背后为我们做了这些工作。

混合等式和参数

  • 混合是一个逐片元级的操作,而且它是不可编程的,但是确实高度可配置的。我们可以控制混合时使用的运算操作、混合因子等来影响混合。
  • 现在我们知道两个操作数:源颜色S和目标颜色D,想要得到输出颜色O就必须使用一个等式来计算。我们把这个等式称作混合等式。当进行混合时,我们需要使用两个混合等式:一个用于混合RGB通道,一个用于混合A通道。设置混合状态时,我们就相当于设置了混合等式中的操作因子。默认情况下,混合等式使用的操作符是加操作(也可以设置其他操作),我们只需要设置一下混合因子就可以了。由于需要两个等式,每个等式需要两个因子,所以我们总共需要4个因子。
ShaderLab中设置混合因子的命令

命令

描述

Blend SrcFactor DstFactor

开启混合,并设置混合因子。源颜色(该片元产生的颜色)会乘以SrcFactor,而目标颜色(已经存在于颜色缓存的颜色)会乘以DstFactor,然后把两者相加后再存入颜色缓冲中

Blend SrcFactor DstFactor,SrcFactorA DstFactorA

和上面一样,只是使用不同的因子来混合透明通道

  • 第一个命令只提供两个因子,这意味着将使用同样的混合因子来混合RGB通道和A通道。
  • 混合公式
ShaderLab中的混合因子

参数

描述

One

因子为1

Zero

因子为0

SrcColor

因子为源颜色值。当用于RGB的混合等式时,使用SrcColor中的RGB分量作为混合因子;当用于混合A的混合公式时,使用SrcColor的A分量作为混合因子

SrcAlpha

因子为源颜色的透明度值(A通道)

DstColor

因子为目标颜色值。当用于RGB的混合等式时,使用DstColor中的RGB分量作为混合因子;当用于混合A的混合公式时,使用DstColor的A分量作为混合因子

DstAlpha

因子为目标颜色的透明度值(A通道)

OneMinusSrcColor

因子为(1-源颜色值)。当用于混合RGB的混合等式时,使用结果的RGB分量作为混合因子;当用于混合A的混合等式时,使用结果的A分量作为混合因子。

OneMinusSrcAlpha

因子为(1-源颜色透明度)

OneMinusDstColor

因子为(1-目标颜色)。当用于混合RGB的混合等式时,使用结果的RGB分量作为混合因子;当用于混合A的混合等式时,使用结果的A分量作为混合因子。

OneMinusDstAlpha

因子为(1-目标颜色透明度)

混合操作
  • 我们可以使用ShaderLab的BlendOp BlendOperation 命令,即混合操作命令来设置混合操作的操作符。

操作

描述

Add

将混合后的源颜色和目的颜色相加。默认的混合操作。

Sub

用混合后的源颜色减去混合后目的颜色。

RevSub

用混合后的目的颜色减去混合后的源颜色。

Min

使用源颜色和目的颜色中较小的值,是逐分量比较的。

Max

使用源颜色和目的颜色中较大的值,是逐分量比较的。

  • 混合操作命令通常是与混合因子命令一起工作的。但需要注意的是,当使用Min或Max混合操作时,混合因子是不起任何作用的。

常见的混合类型

  • 通过混合操作和混合因子命令的组合,我们可以得到一些类似Photoshop混合模式中的混合效果:
//正常(Normal),即透明度混合
Blend SrcAlpha OneMinusSrcAlpha

//柔和相加(Soft Additive)
Blend OneMinusDstColor One

//正片叠底(Multiply),即相乘
Blend DstColor Zero

//两倍相乘(2x Multiply)
Blend DstColor SrcColor

//变暗(Darken)
BlendOp Min
Blend One One

//变亮(Lighten)
BlendOp Max
Blend One One

//滤色(Screen)
Blend OneMinusDstColor One
//等同于
Blend One OneMinusSrcColor

//线性减淡
Blend One One

双面渲染的透明效果

  • 在现实生活中,如果一个物体是透明的,意味着我们不仅可以透过它看到其他物体的样子,也可以看到它内部的结构。之前的透明度混合或者是透明度测试中,我们都无法观察到正方体内部及其背面的形状,导致物体看起来就好像只有半个。这是因为默认情况下渲染引擎剔除了物体背面(相对于摄像机方向)的渲染图元。

透明度测试的双面渲染

  • 在Pass的渲染设置中添加Cull指令来关闭剔除即可(Cull Off)

透明度混合的双面渲染

  • 透明度混合的双面渲染要更复杂一些这是因为透明度混合需要关闭深度写入。对于透明度测试来说,由于我们没有关闭深度写入,因此可以利用深度缓冲逐像素的粒度进行深度排序,从而保证渲染的正确性。我们如果直接关闭剔除功能,我们无法保证同一个物体的正面和背面图元的渲染顺序,就可能的到错误的半透明效果。
  • 为此,我们把双面渲染的工作分成两个Pass——第一个Pass只渲染背面,第二个Pass只渲染正面,由于Unity会顺序执行各个Pass,因此我们可以保证背面总是在正面之前被渲染,从而可以保证正确的渲染关系。