我们都知道在Unity中渲染一个3D的物体, 需要提供3D物体的模型, 纹理, 材质甚至Shader. 但我们在制作UI时, 并不是必须提供这些. 那UI是另起炉灶, 有一套新机制吗? 答案是否定的. 下面我们就从UGUI源码层面来看看Image的渲染.
初遇Image组件
我们在Hierarchy窗口创建一个Image控件, 可以在Inspector窗口看到它由 RectTransform, Canvans Renderer, Image 几个Component组成.
我们可以给 Image 组件指定 Source Image, Color, Material 等属性.
我们可以看到 Image 组件的源码, 位于 Runtime/UI/Core/Image.cs, Image 渲染相关的类继承关系如下:
Mesh的生成
我们在一个测试Scene中加入一个Image, 并给 Image 类的 OnPopulateMesh 函数添加断点, 运行调试后, 可以看到如下调用堆栈:

从上图我们也可以看到, 如果 activeSprite 为空的情况下(一般可以理解为在Inspector窗口不设置Source Image), 则会直接调用
父类 Graphic 的 OnPopulateMesh 函数, 我们来看下其实现.
|
|
我们知道, UI元素一般是按四边形来渲染, 即需要两个三角形. 在上面代码中, 可以看到变量 v 存储了四边形的左下角和右上角的坐标.
然后以顺时针顺序把左下角, 左上角, 右上角, 右下角, 当作四个顶点坐标传给 VertexHelper, 这四点的顶点索引(Index), 依次为0, 1, 2, 3.
分别以(0, 1, 2)和(2, 3, 0) 组成两个三角形, 构成了UI元素的Mesh.
我们也看到通过给 VertexHelper 的 AddVert 函数, 也设置了对应顶点的颜色及纹理坐标.
在Scene窗口的 Wireframe 模式下, 可以看到上述两个三角形组成的Mesh.

打开Frame Debug可以看到绘制参数, 和顶点信息, 4个顶点, 6个顶点索引.
Image有多种类型, 这些类型生成Mesh的方式不同, 但都是通过 OnPopulateMesh 函数来提供的.
UI元素一定是按四边形来渲染吗? 答案是否定的. 当 Image 组件的 Image Type 为 Simple 并且勾选了 Use Sprite Mesh 时,
当前使用的Sprite包含透明的区域, 此时其 Sprite Mesh 可能不是四边形, 就不以四边形来渲染.
Image 的部分参数变化, 可能也会引起Mesh的变化.

材质的选择
我们从 Inspector 窗口可以看到 Image 组件可以设置 Material 属性. 实际上该属性由 Graphic 类提供.
材质相关的方法和属性及类继承关系如下图所示:
给 Image 的 material 属性的 getter 方法添加断点后, 调试运行, 可以看到如下调用堆栈:
我们也从 material 的 getter 方法中看到, 在Inspector窗口设置的材质有最高优先级, 其次是如果给 sprite 设置了 associatedAlphaSplitTexture,
则会使用 defaultETC1GraphicMaterial, 最后才会使用 defaultMaterial.
追踪 defaultMaterial 的定义, 会发现它实际上是由 Canvas.GetDefaultCanvasMaterial() 提供,
但该方法实现在 Unity 的C++代码中, 并不开源. 根据网上资料所说, 该方法会优先调用 SPR 中提供的UI材质, 再调用内置的UI默认Shader生成材质.
我们也注意到, 在 Graphic.materialForRendering 属性中, 可以对材质进行修改.
这样我们就获得了渲染UI时用到的材质.
Shader在哪里?
材质使用的Shader在哪里呢?
我们可以在Unity网站下载到对应版本的内置Shader以作为参考. UI部分Shader位于 ./DefaultResourcesExtra/UI 目录.
纹理的提供
可以在 Inspector 窗口给 Image 组件设置 Source Image, 为 Sprite 类型. Sprite 类位于 UnityEngine 命名空间下, 并非 UGUI 专用.
我们给 Image 的 mainTexture 属性的 getter 方法增加断点, 调试后可以看到如下堆栈:
我们看到首先如果有 activeSprite , 则会使用 activeSprite.texture. 再接着会尝试使用 material 中的纹理, 都不存在的情况下, 会使用默认的白色纹理.
activeSprite 会优先返回 m_OverrideSprite, 其次返回 sprite (即 m_Sprite, Inspector中设置的 Sprite).
纹理相关的方法和属性及类继承关系如下图所示:
我们上面分析Mesh的生成时, 提到 VertexHelper 需要纹理坐标. 我们可以再回头看下, Image 不同类型的 Mesh
是如何匹配其纹理坐标的.
例如, 我们在 GenerateSimpleSprite 可以看到:
|
|
渲染一下
我们从上面调试时的堆栈可以看到, 这些调用都是由 Graphic.Rebuild 触发, 代码如下:
|
|
我们查看 UpdateGeometry 和 UpdateMaterial 的实现, 就可以看到生成的Mesh以及被选择的纹理, 材质等, 分别设置给了
CanvasRenderer, 最终由 CanvasRenderer 来负责 Image (也包括所有 Graphic 的子类) 的渲染.