LOADING

加载过慢请开启缓存 浏览器默认开启

UnityUI的本质

沿着 Unity Image 面板逐项拆解 UI:从贴图、顶点色与材质,到 Mask 的模板测试及不同 Image Type 的网格变化。

最近学习到了很多Unity自带的UI功能,发现之前shader做的某些东西并不需要shader来实现,UI本身就可以实现一部分。了解这些功能的本质有助于之后写shader实现更复杂的功能的时候对这些功能的适配。

首先是UI本身的本质:

其实无论是UI还是场景中的2D,它的本质就是一个3D的面片,一般是由两个三角形组成的矩形,只不过我们常规的3D模型是由我们自己指定模型,而UI和2D是unity帮我们创建的面片而已。

UnityUI的本质示意图 1

理解了它的话,我们就知道,其实UI并没有什么特殊的,3D能做的效果,处理对UI一样使用,想要拿到的各种数据也都可以拿到的。


剩下的内容,就沿着Image面板逐个讲起吧。

UnityUI的本质示意图 2

Source Image

内容很简单,就是输入贴图,对应shader中的_MainTex,想怎么用都行,不过Unity中将材质球的_MainTex面板锁定住了,只能通过Source Image来输入想要的贴图。

Color

这个Color对应的模型顶点色,所以属于模型本身的属性,所以不会打断合批,当然如果不需要的话,拿来装一些自定义的数据也是可以的。

Material

材质,如果不填入的话就会自动使用Unity的一个UI基础材质,如下

UnityUI的本质示意图 3

Raycast Target/Raycast Padding

与检测鼠标点击的射线检测有关,跟渲染关系不大,就不介绍了

Maskable

控制组件是否受到父级的Mask影响。 Mask本质是由模板测试来实现的,模板测试相关内容可以查阅ShaderLab 命令:模板 - Unity 手册。

模板测试

在渲染流水线中,除了屏幕中用于显示物体颜色的颜色缓冲,还有深度缓冲和模板缓冲。通常背景的模板缓冲值为0,在某个像素被渲染的时候,可以根据模板测试的设置,来将某个模板值写入模板缓冲区,后续物体的模板值会依据当前物体的模板测试设置来进行比较,例如UI的Mask功能,子物体被设置为,如果当前像素与模板缓冲的值相同,就绘制这个像素。

Mask功能的模板测试

通常物体在渲染时与背景相同,模板测试的值为0,这样整个画面就会保持一个模板值为0的状态

UnityUI的本质示意图 4

当Mask功能开启的时候,会经历以下流程:父物体将模板值写入模板缓冲→子物体依据模板测试,只在与父物体模板值相同的位置绘制→再绘制一次父物体,将其模板值归零,防止污染后续模板缓冲。所以我们可以看到明明只有两个物体,却调用了3次绘制:

UnityUI的本质示意图 5

下面将一次梳理下这三次绘制: 第一次绘制对象是父物体

UnityUI的本质示意图 6

我们依次分析其中重要的部分: - Stencil Ref 1 :模板测试的参考值是1 - Stencil Comp Always :通过条件是永远通过模板测试,也就是说父物体的像素永远不会因为模板测试而被取消绘制 - Stencil Pass Replace:当通过模板测试的时候,将参考值写入模板缓冲中,也就是之前说过的“1”,现在只要是这个金币所在的区域,所有模板值都为1了。 第二次绘制对象是子物体

UnityUI的本质示意图 7
  • Stencil Ref 1 :模板测试的参考值依旧是1
  • Stencil ReadMask 1 :当读取缓冲中的参考值时,只读取二进制的最后一位,也就是他只和上图括号中的最后一位比较
  • Stencil Comp Equal :只有当前像素与模板缓冲区里值相同时才会通过,前文提到,硬币的区域是“1”,背景是“0”,所以子物体只会在硬币的区域被绘制。
  • Stencil Pass Keep :当模板测试通过时保持模板缓冲区不变 第三次绘制还是父物体:
UnityUI的本质示意图 8

第三次的功能是清除覆盖的模板缓冲区,防止污染之后的模板测试,所以他使用的还是父物体绘制,我们可以看到最下面_MainTex的位置还是黄色的硬币,为了防止这次绘制覆盖子物体,它将ColorMask设置为0,也就是说无论这次各种测试结果怎样都不会讲像素绘制到屏幕上。 然后来看模板测试设置:

UnityUI的本质示意图 9

我们只需要看两个位置: - Stencil Comp Always :这次绘制依旧永远通过模板测试, - Stencil Pass Zero :当模板测试通过时将模板缓冲区归零,也就避免了模板测试污染。 正因为每次都会将模板值归零,所以无论使用多少个平行的父物体,都可以从1开始设置模板测试。

Maskable开关

知道了这些内容我们就可以去看看Maskable的开关到底做了什么,它做的事情非常简单:模板测试永远通过,模板缓冲值不变,就只有这两件事,我们直接看模板设置:

UnityUI的本质示意图 10
UnityUI的本质示意图 11

其实只要能看懂模板测试设置,这些问题都迎刃而解了。 对于自定义shader应该注意适配他的模板测试功能。

ImageType

它用来设置各种UI的渲染模式,通过它们其实也可以达到一些蛮丰富的UI功能了。

Simple

这个模式是最常规的UI模式,普通UI都使用这个模式。 Use Sprite Mesh会让UI的顶点以更贴合贴图本身的形式分布,而不是一个矩形。

UnityUI的本质示意图 12

Preserve Aspect可以让UI永远保持原先的比例

UnityUI的本质示意图 13
UnityUI的本质示意图 14

不过它是靠防止网格拉伸来实现的,所以这种模式不能带来额外的绘制面积。

Sliced

这个功能可以保证UI边角的一致性,从而适配任意大小,他的它质其实是按照拉伸SpriteEditor的设置拉伸整个模型:

正因如此,整个模型的UV比例也会随着模型拉伸而产生变化,也就不可能去再用这个UV来采样规则模型了。

Tlied

这个功能常常用于需要重复平铺的背景,当贴图的WrapMode设置为Repeat时,可以做到下面这样的效果:

UnityUI的本质示意图 15
UnityUI的本质示意图 16

其本质是对于模型拉伸时UV的处理方式:

UnityUI的本质示意图 17
UnityUI的本质示意图 18

对于超出的部分,普通的Simple是将这部分拉伸为0-1的值,对应到UV的贴图采样中,就是贴图的拉伸和放大,而Tlied模式则是将超出的部分作为2,3,4,5….来处理,对于Repeat来说每个数字区间的值就是0-1的值,所以相当于将很多个小矩形平铺成为了一个巨大的矩形。

Filled

Filled可以做到这样的效果:

UnityUI的本质示意图 19

实际上也是对模型本身动的手:

UnityUI的本质示意图 20