好久没有写新的文章了,最近一直沉迷FF14(绝神兵好难啊(:з」∠))。最近踩了一个新的坑,怕过几天自己又忘记了所以要赶紧写下来提醒自己~。
我们在制作UI功能的时候,有时候会需要一些额外的效果,为了性能考虑,有的时候我们会使用shader进行UI效果的优化。P.S.这里贴一篇大佬的优化文章,这个我看了好久,里面的代码有点奇怪,但是思路真的很有用!!! Unity手游开发札记——使用Shader进行UGUI的优化
然后我们有的时候要使用一些额外的贴图进行采样,然后采样要使用到uv值,当我们使用单张的图片是没有问题的,但是一旦我们使用了图集,这个时候UI中获取到的uv值就不是标准的[0-1]范围的uv值了,而是基于图集中的图片位置计算出的uv值。这也是Unity的UI究竟为什么可以合批的原因。
但是我们要对其他贴图(这张贴图没有被打进图集,当然打进图集也可以,但是要做一些额外操作,比如把这张图片的uv值也存在顶点信息中)采样时需要一个标准的[0-1]范围的uv,这个时候我们就要借助C#端进行图集的映射了。
通过DataUtility类中的GetOuterUV函数,我们可以获取到图片在图集中的uv值,然后借助BaseMeshEffect中的ModifyMesh函数,我们可以拿到传给shader之前的顶点数据。然后我们就可以给shader传递更多我们自己定义的参数啦。
public override void ModifyMesh(VertexHelper vh)
{
if (!IsActive())
return;
if (img==null)
{
img = GetComponent<Image>();
}
if(img.sprite==null)
return;
outerUv = DataUtility.GetOuterUV(img.sprite);
UIVertex vert = new UIVertex();
for (int i = 0; i < vh.currentVertCount; i++)
{
vh.PopulateUIVertex(ref vert, i);
vert.uv1 = Caluv1(vert.uv0);
vh.SetUIVertex(vert, i);
}
}
private Vector2 Caluv1(Vector2 uv0)
{
uv0.x = uv0.x - outerUv.x > 0 ? 1 : 0;
uv0.y = uv0.y - outerUv.y > 0 ? 1 : 0;
return uv0;
}
这里我直接使用了顶点的uv0数据,因为uv数据理论上与我使用GetOuterUV函数获取到的uv值是相同的,所以我只计算了左下角坐标,直接把uv1的值设置到了[0-1]范围。
然后还要在shader中添加对应的uv1值接收,要在a2v结构中添加TEXCOORD1属性,当然v2f结构中也要有对应的存储结构(下面的代码是直接从UI-Defalut中copy来的,只有//add的两行是我加的QAQ):
struct appdata_t
{
float4 vertex : POSITION;
float4 color : COLOR;
float2 texcoord : TEXCOORD0;
float2 texcoord1 : TEXCOORD1;//add
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct v2f
{
float4 vertex : SV_POSITION;
fixed4 color : COLOR;
float2 texcoord : TEXCOORD0;
float4 worldPosition : TEXCOORD1;
float2 texcoord1 : TEXCOORD2;//add
UNITY_VERTEX_OUTPUT_STEREO
};
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.texcoord1 = v.texcoord1;
OUT.color = v.color * _Color;
return OUT;
}
当我以为这样就大功告成的时候,我发现自己高兴的实在是太早了,因为在测试的时候不管怎么样,我的texcoord1都像是没办法从C#端传入shader一样。而且当我直接输出uv1的值的时候,发现uv1的值还会因为物体的X坐标不同而变化。。。。经过两个多小时的百度,我终于找到了这篇文章Unity3D UGUI 源码学习 BaseMeshEffect。
原来一切的罪魁祸首竟然是canvas,因为canvans不允许我们使用除了uv0以外更多的顶点信息,所以我们填充的uv1信息也就完全没有作用了。
设置完canvans之后,我的uv1值终于可以顺利传入shader啦!不多说了,FF14启动!
补充:
因为我之前的理解是Image的范围就是顶点的绘制范围,那么对应的uv也应该是处于[0-1]的范围内。但是后来在测试时发现并不是所有图片的uv值都在[0-1]的范围内。
当我们使用的图片周围有一些空白像素时,Unity内部会帮我们做一个优化,直接舍弃掉多余的像素。
就拿上一张图来说,这张图的大小是256*256,但是图片的周围有很多无用的像素,没有填充任何信息,这个时候我们在ModifyMesh函数中获取到的顶点uv值就不是一个[0-1]范围内的值了,而是一个Unity优化过的uv值。
就在我好奇为什么uv不是[0-1]范围时还可以绘制出正常的图像时,我打开了线框模式...才发现原来在绘制顶点的时候根本就不是根据Image范围来绘制顶点的,而是像文字一样,只绘制有效像素范围:
图中白色范围是Image组件范围,黑色范围是实际顶点所绘制的线框,上面的图片周围的无用像素已经被unity优化掉了,如果这种图片我们还用上面的算法把uv映射到[0-1]的范围,那么在使用这套uv采样的过程中就会出现错误的结果!(但是uv0是正确的,我暂时做了一个开关可以判断是否使用uv1,如果后面有更好的解决方法也会在这里补充)