Unity3D UGUI優化:製作鏡像圖片(2)

前言

上一篇文章我們學習到了,如何去製作簡單的鏡像圖片。

Unity3D UGUI優化:製作鏡像圖片(1)

有朋友問到有沒有必要這麼省吃儉用呢,答案是有必要的。

因為Unity3D的Sprite Package打包的圖集尺寸只能是2的倍數,有時候,就因為一張圖片大了點,整個圖集的尺寸就從512x512變成了512x1024了。而這種對稱的圖片很多時候占圖集的比例是高達20%甚至更多,這種節省就變得很有必要了。特別是移動端這種對性能很敏感的平台。

接下來就是今天的主題內容了,鏡像Sliced圖片。Sliced其實就類似九宮縮放圖片(Sliced可以是切四份、六份、九份)。九宮縮放原理就是把一張圖片切成九份,四個角的圖片不會拉伸,其他分別可以進行水平和垂直拉伸。如果你對Sliced(或九宮縮放)不太了解,那麼建議你先搜索相關的基礎教程或者使用一下Unity3D的Sliced功能。

聲明

  • 本文不允許任何形式轉載。

準備

首先我們拿到今天使用的圖片。

  • 第一個是比較常見的圓角漸變按鈕,是美術給過來的原圖。
  • 第二個是你平常普通能做到的圖,由於中間存在水平重複,所以把中間部分砍了大部分,然後通過Sliced拉伸得到中間部分。
  • 第三個是我能做到的最小尺寸,由於水平對稱,所以在第二個的基礎上再對砍一半,然後我們就通過今天的教程水平鏡像出重複的地方,然後再通過Sliced拉伸出中間的部分。

還記得我們上一篇教程預留了寫入Sliced的代碼局域嗎?我們先使用DrawSimple方法填入,看能不能直接得出我們想要的效果。

//ModifyMesh()代碼內n//......nswitch (type)n{n case Image.Type.Simple:n DrawSimple(output, count);n break;n case Image.Type.Sliced:n DrawSimple(output, count);n break;n case Image.Type.Tiled:nn break;n case Image.Type.Filled:nn break;n}n//......n

效果很明顯是有問題的,圓角變形了。(這就是為什麼用圓角按鈕做素材的原因)。

結論是:我們無法直接使用DrawSimple的邏輯去做Sliced鏡像(廢話!不然幹嘛還要寫這篇文章)

那為什麼呢?還記得DrawSimple裡面的嗎?它是對頂點進行整體縮放的函數,可是Sliced裡面的圓角卻是不需要縮放的,那麼整體縮放就會導致上圖的變形了。

代碼

那麼,我們只好另外寫一個SlicedScale的函數進行縮放了。 (PS:本教程並沒有按照代碼的先後順序講解,所以建議拉到最下方的下載源代碼,然後對照著進行閱讀學習)

/// <summary>n/// Sliced縮放位移頂點(減半)n/// </summary>n/// <param name="rect"></param>n/// <param name="verts"></param>n/// <param name="count"></param>nprotected void SlicedScale(Rect rect, List<UIVertex> verts, int count)n{n Vector4 border = GetAdjustedBorders(rect);nn float halfWidth = rect.width * 0.5f;nn float halfHeight = rect.height * 0.5f;nn for (int i = 0; i < count; i++)n {n UIVertex vertex = verts[i];nn Vector3 position = vertex.position;nn if (m_MirrorType == MirrorType.Horizontal || m_MirrorType == MirrorType.Quarter)n {n if (halfWidth < border.x && position.x >= rect.center.x)n {n position.x = rect.center.x;n }n else if (position.x >= border.x)n {n position.x = (position.x + rect.x) * 0.5f;n }n }nn if (m_MirrorType == MirrorType.Vertical || m_MirrorType == MirrorType.Quarter)n {n if (halfHeight < border.y && position.y >= rect.center.y)n {n position.y = rect.center.y;n }n else if (position.y >= border.y)n {n position.y = (position.y + rect.y) * 0.5f;n }n }nn vertex.position = position;nn verts[i] = vertex;n }n}n

SlicedScaleSimpleScale有相似的地方,也就是遍歷頂點,進行操作。而不同的地方就是:

  • 在Image繪製寬度(高度)小於等於2倍圓角寬度(高度)的情況下,整體進行縮放
  • 在Image繪製寬度(高度)大於2倍圓角寬度(高度)的情況下,圓角部分不進行縮放,其他部分進行縮放

因此我們先使用GetAdjustedBorders(複製於Image.GetAdjustedBorders())獲得Sprite的切片區域。

上圖這種情況下,Image繪製寬度是少於等於2倍圓角寬度的,所以,位於中心偏右的頂點全部都會縮放到中心點位置

if (halfWidth < border.x && position.x >= rect.center.x)n{n position.x = rect.center.x;n}n

上圖這種情況下,Image繪製寬度是大於等於2倍圓角寬度的,所以,圓角部分的三角面不會移動,位於圓角以外的頂點全部縮放一半。

else if (position.x >= border.x)n{n position.x = (position.x + rect.x) * 0.f;n}n

接著,我們像之前一樣調用MirrorVerts鏡像頂點就可以了。

/// <summary>n/// 繪製Sliced版n/// </summary>n/// <param name="output"></param>n/// <param name="count"></param>nprotected void DrawSliced(List<UIVertex> output, int count)n{n //sprite的border為零的情況下,遵從Image的設定,按照Simple模式繪製。n if (!(graphic as Image).hasBorder)n {n DrawSimple(output, count);nn return;n }nn Rect rect = graphic.GetPixelAdjustedRect();nn SlicedScale(rect, output, count);n n //TODO:nn switch (m_MirrorType)n {n case MirrorType.Horizontal:n ExtendCapacity(output, count);n MirrorVerts(rect, output, count, true);n break;n case MirrorType.Vertical:n ExtendCapacity(output, count);n MirrorVerts(rect, output, count, false);n break;n case MirrorType.Quarter:n ExtendCapacity(output, count * 3);n MirrorVerts(rect, output, count, true);n MirrorVerts(rect, output, count * 2, false);n break;n }n}n

好了,我們看一下效果,恩,不錯,是我們想要的效果。 然而,用久了,你就會發現,明明我就顯示幾個面,怎麼三角面數量會爆增了!

是不是Mirror有問題?=。=筆者表示這鍋我不背!

三角面增多是因為Image的Sliced會繪製很多看不見的面,也就是頂點會重合的面,而這些面是沒用的,當鏡像時,就會成倍的增加,而且有可能會導致U3D合併頂點時報錯(超過Mesh最大頂點數65000)。

所以我們在鏡像頂點時,要先把這些沒用的三角面剔除。就是在上面DrawSlicedTODO注釋那一行。

/// <summary>n/// 清理掉不能成三角面的頂點n/// </summary>n/// <param name="verts"></param>n/// <param name="count"></param>n/// <returns></returns>nprotected int SliceExcludeVerts(List<UIVertex> verts, int count)n{n int realCount = count;nn int i = 0;nn while (i < realCount)n {n UIVertex v1 = verts[i];n UIVertex v2 = verts[i + 1];n UIVertex v3 = verts[i + 2];nn if (v1.position == v2.position || v2.position == v3.position || v3.position == v1.position)n {n verts[i] = verts[realCount - 3];n verts[i + 1] = verts[realCount - 2];n verts[i + 2] = verts[realCount - 1];nn realCount -= 3;n continue;n }nn i += 3;n }nn if (realCount < count)n {n verts.RemoveRange(realCount, count - realCount);n }nn return realCount;n}n

由於頂點數組裡面是按順序的,三個頂點為一個面。所以我們遍歷頂點,判斷三個頂點是否有重合,然後把無用的三角面挪到數組最後,在遍歷完之後,把結尾的無用三角面刪掉。

好了,現在三角面的數量就和界面上顯示的一樣了。

這個方法同樣可以使用到Image的Sliced里。

好了,Sliced的鏡像類就完成了。下一章將是Tiled的鏡像。

源代碼

Mirror.cs

MirrorEditor.cs

如果你喜愛我的文章,記得點個贊哦

推薦閱讀:

基於物理的渲染—基於Haar小波基的實時全局光照明
大萌喵的著色器特效第二發---相交高亮(掃描效果)
優化筆記:C# 從 List<T> 移除空元素
聊聊那些不常見的Shader
基於物理的景深效果

TAG:Unity游戏引擎 |