Unity的UI究竟为什么可以合批

简介: 1.UI/Default代码研究首先,我想到的是,既然是对图集纹理进行采样,而且又不能统一更改材质的纹理UV值,我们通常写的shader都是直接根据模型UV值对主纹理进行采样,那会不会是shader中对MainTexture进行了什么神奇的处理,让图片采样只根据指定的UV值进行采样呢?我去官网下载...

1.UI/Default代码研究
首先,我想到的是,既然是对图集纹理进行采样,而且又不能统一更改材质的纹理UV值,我们通常写的shader都是直接根据模型UV值对主纹理进行采样,那会不会是shader中对MainTexture进行了什么神奇的处理,让图片采样只根据指定的UV值进行采样呢?
我去官网下载了shader代码,找到了UI/Default的具体实现:

fixed4 _Color;
fixed4 _TextureSampleAdd;
float4 _ClipRect;

v2f vert(appdata_t v)
{
v2f OUT;
UNITY_SETUP_INSTANCE_ID(v);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT);
OUT.worldPosition = v.vertex;
OUT.vertex = UnityObjectToClipPos(OUT.worldPosition);

OUT.texcoord = v.texcoord;

OUT.color = v.color * _Color;
return OUT;
}

sampler2D _MainTex;

fixed4 frag(v2f IN) : SV_Target
{
half4 color = (tex2D(_MainTex, IN.texcoord) + _TextureSampleAdd) * IN.color;

ifdef UNITY_UI_CLIP_RECT

color.a *= UnityGet2DClipping(IN.worldPosition.xy, _ClipRect);

endif

ifdef UNITY_UI_ALPHACLIP

clip (color.a - 0.001);

endif

return color;
}
看了上面的代码,我们可以基本确定,没有在shader中做什么特别神奇的MainTexture处理。但是我们还是可以发现一些不同的地方,这里上面的变量_Color,_TextureSampleAdd,_ClipRect并没有暴露在面板上,可以看出来这三个变量是通过某些脚本传递给shader的。
我们知道,伴随着Defalut材质的一般使用的是Image组件、Text组件。这两个组件会绘制顶点与三角形,然后使用指定的材质进行渲染。所以会不会是Image组件或Text组件中使用了什么算法,计算过图片UV值,并把上面三个变量填充好传给shader的呢?

2.Image组件代码研究
因为unity的ui代码已经开源了,所以我们很幸运的可以看到Image的源码是怎么实现的,因为Image组件代码很多,所以这里就只贴出比较主要的绘制顶点的函数:

///
/// Update the UI renderer mesh.
///
protected override void OnPopulateMesh(VertexHelper toFill)
{
if (activeSprite == null)
{
base.OnPopulateMesh(toFill);
return;
}

switch (type)
{
case Type.Simple:
if (!useSpriteMesh)
GenerateSimpleSprite(toFill, m_PreserveAspect);
else
GenerateSprite(toFill, m_PreserveAspect);
break;
case Type.Sliced:
GenerateSlicedSprite(toFill);
break;
case Type.Tiled:
GenerateTiledSprite(toFill);
break;
case Type.Filled:
GenerateFilledSprite(toFill, m_PreserveAspect);
break;
}
}

我们可以看到,这个函数是用来刷新UI渲染的,unity对图片的四种类型分别进行了处理,这里我们就只看一下最简单的Simple模式的代码:

///
/// Generate vertices for a simple Image.
///
void GenerateSimpleSprite(VertexHelper vh, bool lPreserveAspect)
{
Vector4 v = GetDrawingDimensions(lPreserveAspect);
var uv = (activeSprite != null) ? Sprites.DataUtility.GetOuterUV(activeSprite) : Vector4.zero;

var color32 = color;
vh.Clear();
vh.AddVert(new Vector3(v.x, v.y), color32, new Vector2(uv.x, uv.y));
vh.AddVert(new Vector3(v.x, v.w), color32, new Vector2(uv.x, uv.w));
vh.AddVert(new Vector3(v.z, v.w), color32, new Vector2(uv.z, uv.w));
vh.AddVert(new Vector3(v.z, v.y), color32, new Vector2(uv.z, uv.y));

vh.AddTriangle(0, 1, 2);
vh.AddTriangle(2, 3, 0);
}

/// Image's dimensions used for drawing. X = left, Y = bottom, Z = right, W = top.
private Vector4 GetDrawingDimensions(bool shouldPreserveAspect)
{
var padding = activeSprite == null ? Vector4.zero : Sprites.DataUtility.GetPadding(activeSprite);
var size = activeSprite == null ? Vector2.zero : new Vector2(activeSprite.rect.width, activeSprite.rect.height);

Rect r = GetPixelAdjustedRect();
// Debug.Log(string.Format("r:{2}, size:{0}, padding:{1}", size, padding, r));

int spriteW = Mathf.RoundToInt(size.x);
int spriteH = Mathf.RoundToInt(size.y);

var v = new Vector4(
padding.x / spriteW,
padding.y / spriteH,
(spriteW - padding.z) / spriteW,
(spriteH - padding.w) / spriteH);

if (shouldPreserveAspect && size.sqrMagnitude > 0.0f)
{
PreserveSpriteAspectRatio(ref r, size);
}

v = new Vector4(
r.x + r.width * v.x,
r.y + r.height * v.y,
r.x + r.width * v.z,
r.y + r.height * v.w
);

return v;
}

public void AddVert(Vector3 position, Color32 color, Vector2 uv0);
就是在这里了,首先拿到绘制的尺寸v,也就是四个顶点的位置,然后根据activeSprite拿到纹理的UV值。我们可以看到AddVert函数中,第三个值是绘制的顶点中填充的uv0也就是这个得到的UV值,而shader中也会根据这个uv值对MainTexture进行采样。

3.小实验
我们已经知道计算顶点与UV值的操作是在image中进行的,其实unity有一个组件可以自己控制采样的uv值,就是RawImage组件,相比Image组件,RawImage组件更为精简,因为没有处理Image中的四种图片样式。
其实Image组件中帮我们做的操作其实就相当于(是相当于,其实计算比这复杂的多)在RawImage中设置了不同的UV偏移值。这样就可以做到,每个组件使用的UV值不同,而不是改变统一使用材质上的UV值。

修改RawImage中的UV值

总结
我们最开始的想法是修改材质中的UV值,但是这样是不行的,因为改变了材质UV值后所有物体都会跟着改变。Unity使用了一个巧妙的办法,也就是在建模(绘制顶点/三角形)的时候,就把得到的图集中纹理的UV采样值填充到mesh的UV中。所以材质使用的都是同一个材质,也都是对MainTexture进行采样,只不过每个图片的mesh中存储的UV值都是不同的。

更多unity2018的功能介绍请到paws3d爪爪学院查找。

相关文章
|
XML 编解码 编译器
Unity跨平台UI解决方案:可能是最全的FairyGUI系列教程-第八天
众所周知,人生是一个漫长的流程,不断克服困难,不断反思前进的过程。在这个过程中会产生很多对于人生的质疑和思考,于是我决定将自己的思考,经验和故事全部分享出来,以此寻找共鸣!!!
1489 0
|
7月前
|
图形学
unity判断鼠标在不在UI上
unity判断鼠标在不在UI上
|
4月前
|
编解码 前端开发 人机交互
【Unity 3D】UI系统中UGUI各个组件的详细讲解(附源码 超详细)
【Unity 3D】UI系统中UGUI各个组件的详细讲解(附源码 超详细)
77 0
|
4月前
|
编解码 前端开发 图形学
Unity 用脚本操作常用UI控件(上)
Unity 用脚本操作常用UI控件(上)
|
前端开发 图形学
Unity中影响UI排序的组件
Unity中影响UI排序的组件
|
移动开发 前端开发 小程序
|
图形学
Unity 编辑器开发实战【Custom Editor】- 为UI视图制作动画编辑器
Unity 编辑器开发实战【Custom Editor】- 为UI视图制作动画编辑器
199 1
Unity 编辑器开发实战【Custom Editor】- 为UI视图制作动画编辑器
|
前端开发 图形学
Unity & 蓝湖 关于UI工作流优化的思考(二)
Unity & 蓝湖 关于UI工作流优化的思考(二)
256 0
Unity & 蓝湖 关于UI工作流优化的思考(二)
|
JSON 前端开发 程序员
Unity & 蓝湖 关于UI工作流优化的思考
Unity & 蓝湖 关于UI工作流优化的思考
335 1
Unity & 蓝湖 关于UI工作流优化的思考
|
数据可视化 图形学 流计算
Unity 基础 之 代码动态监听UI交互组件汇总
通过介绍组件面板和代码示例,演示代码监听UI交互组件。
217 0
Unity 基础 之 代码动态监听UI交互组件汇总