探寻 Unity Camera 属性之 Clear Flags

Camera 作为开发者最熟悉的组件之一,有很多常用属性,比如 Clear FlagsProjectionDepth 等。接下来主要就来谈谈简单易用而又不寻常的 Clear Flags

Unity 文档中对 Clear Flags 的描述也很简单:

Determines which parts of the screen will be cleared. This is handy when using multiple Cameras to draw different game elements.

设置这个标志位会对屏幕(缓存帧)指定区域进行清除,通常在多个 Camera 绘渲染不同物体的时候用到。字面意思很简单,下面来解析一下设置不同的标志位的情况。

Skybox & Solid Color

将这两个标志位值放在一起,因为它们很相似。

当标志位被设置为这两个值中的某一个时,那么 Camera 在渲染每一帧前,逐像素过程(Shader 部分知识,读者可自行查阅)中会对每个像素进行深度值与颜色值的 Clear,然后在将需要渲染的颜色值与深度值(如有需要)写入。这两个标志位值的差异就是进行写入时前者是用的天空盒子中的颜色值,后者 Solid Color 需要配合 Camera 下另一个属性 Background 设置的颜色值进行写入。

关于 Background 的说明如下:

The color applied to the remaining screen after all elements in view have been drawn and there is no skybox.

Camera 进行渲染物体时的流程大致是: 逐像素过程中,由于像素的深度值被 Clear 了,根据使用的 Shader 中的设定规则如果 ZTest 通过,把要渲染的颜色值写入,深度值是否写入看使用的 Shader 中的 ZWrite 是否开启。由于 SkyboxSolid Color 是最先被渲染的(默认 Queue=Background),所以默认它们就作为了背景。

unity_camera_clear_flags_screen_record_1.gif

上图为当 Clear Flags 设置为 Skybox 时的效果,其中三个物体都使用了内置 Standard.shader,开启了 ZWrite、ZTest,所以渲染每一帧都是清除了之前的所有信息渲染出了新的图像。

Depth Only

作为 Clear Flags 中最常用的设置值,Depth Only 就是仅清除缓存帧中的深度信息。

清除深度信息后会发生什么?渲染过的像素如果没有需要渲染的物体,则会保持原来的颜色值;否则会渲染新的物体颜色值,不论这个点之前是什么(因为深度值被 Clear),所有在这个像素上发生的 ZTest 默认规则下都会通过,ZWrite 也会按照设定的规则生效写入对应的深度值。

如下图所示:

unity_camera_clear_flags_screen_record_2.gif

之前说 Depth Only 是最常使用的设定值,官方文档中介绍的:

This is handy when using multiple Cameras to draw different game elements.

意义就在此,当我们需要使用多个 Camera 渲染不同的物体时,当在编辑器 Hierarchy 中为后面 Camera 的 Clear Flags 设定为该值时,该 Camera 渲染时由于不会清除之前 Camera 已渲染的图像(假定后面的 Camera 的 Depth 值大于前面的 Camera,后执行渲染),并且由于清除了深度值,所以不会因为后面摄像机渲染的物体的深度值大于之前物体的深度值而不太能通过 ZTest,所以图像会覆盖在已有图像上方。

注意: 上面提到的 Camera 的 Depth 值是 Camera 下的一个属性。Camera 的 Depth 值设定越大,越会渲染在上方。

The camera’s position in the draw order. Cameras with a larger value will be drawn on top of cameras with a smaller value.

unity_camera_clear_flags_screen_record_3.gif

如上图,红蓝黄三个物体由一个 Camera 渲染,绿色物体由第二个 Camera 渲染(第二个 Camera 的 Depth 值大于第一个且第二个的 Clear Flags 设置为了 Depth Only),可以看出第一个 Camera 渲染的图像保留在屏幕上,当改变绿色物体的 Z 坐标时,无论是否大于或小于红蓝黄物体的 Z 坐标都能覆盖在最上面。

Don't Clear

设定为这个值不会清除任何信息。

所以之前的颜色值会一直存在于屏幕上,直至新的颜色值替换掉缓存的颜色值。同理深度值也不会被清除,直至某个通过了 ZTest 并且 ZWrite 为 On 的像素将深度值更新。

大致流程: 逐像素 ZTest,通过则写入新的颜色值,并根据是否开启 ZWrite 来确定是否更新深度值,否则舍弃。

演示如下图:

unity_camera_clear_flags_screen_record_5.gif

对于使用 ZWrite Off 的 Shader 的物体,在 Don't Clear 下(注意: 在设置为该值的同时关闭了 Camrea 的 HDR 和抗锯齿),由于深度值未写入,所以就算该物体当前帧的深度值比较小而渲染在了前方,但是当下一帧如果深度值变大之后,比其深度值小的物体渲染时就算没有清除之前的颜色缓存,但依旧能通过 ZTest,从而更新像素点的颜色值和深度值。这和开启 ZWrite 刚好相反。

如下图所示,紫色片物体是一个 Sprite,使用了 Sprite-Default.shader,该 Shader 默认关闭了 ZWrite,所以当它 Z 坐标变大(深度值变大)之后,比其深度值小的物体依旧能在前面显示出来。

unity_camera_clear_flags_screen_record_4.gif

OpenGL 和 DirectX 中也有 Clear?

答案是肯定的!

在 OpenGL 关于 Clear 对应了几个方法,分别是 glClearColorglClearDepthglClear

  • glClearColor 方法用于指定颜色缓冲区的值,在颜色缓冲区清空时使用:
1
void glClearColor(GLclampf red,GLclampf green,Glclampf blue,GLclampf alpha);
  • glClearDepth 方法指定清除深度缓存时使用的值,范围在[0,1]之间:
1
void glClearDepth(GLclampd depth);
  • glClear 方法将缓存清除为预先设置的值:
1
void glClear(GLbitfield mask);

其中参数对应需要清除的缓存区(有 GL_COLOR_BUFFER_BIT 颜色缓冲区、GL_DEPTH_BUFFER_BIT 深度缓冲区、GL_STENCIL_BUFFER_BIT 模板缓冲区和 GL_ACCUM_BUFFER_BIT 累计缓冲区),这个方法参数的几种组合就正好对应了 Unity Camera 中的 Clear Flags 几个标志位。

在 Direct3D 中也有这样的方法,如下:

1
2
3
4
5
6
7
8
HRESULT Clear(
DWORD Count,
const D3DRECT *pRects,
DWORD Flags,
D3DCOLOR Color,
float Z,
DWORD Stencil
);

其中 Flags 参数也是对应着 Unity Clear Flags 标志位,包含 D3DCLEAR_STENCILD3DCLEAR_TARGETD3DCLEAR_ZBUFFER,详见 IDirect3DDevice9::Clear method