嗚嗚喔學習筆記

搜尋此網誌

2021年3月5日 星期五

Unity TextMeshPro 查看筆記

簡介 : 


支援SDF

支援各種標籤,圖文混排,3D字體,陰影,光影..等等

支援多Font混排( Drawcall++ )


SDF(Signed Distance Field) 簡單來說就是 因為記得邊界距離資訊 , 在拉近放大文字時不會變得糊糊的。 ( 相比Bitmap )

優勢: 1. 減少字集貼圖大小 (順便解決BestFit占用圖集問題)

- BestFit 開啟時 Unity在計算最適合的字體大小過程中, 計算過程中產生的字體都會寫進Texture裡,很浪費空間。

2. Outline 頂點數較小 (4倍)

- Unity本身Outline會多畫4次在文字後面,頂點數也會多4倍。

TMF使用SDF算法則不會增加頂點數,計算直接在Fragment階段做了。

劣勢: 動態生成計算慢。 (極限約20字)


SDF 產圖算法簡介:

step 1. 透過ttf 產出大字體


step 2. 分辨字體邊緣內外 & 計算與邊緣距離 & 線性塞進 alpha 得到小圖

具體數據大概是長這樣子的 如下圖:


step 3. 

     Shader 原理很簡單 alpha超過0.5 -> 外界 , alpha 小魚0.5 -> 內界
    或是用內插會更圓滑一點


Shader 改成:

fixed4 frag(v2f IN) : SV_Target

{

if (color.a < 0.5)

color.a = 0.0;

else

color.a = 1.0;

}

or


color.a = smoothstep(_DistanceMark - _SmoothDelta, _DistanceMark + _SmoothDelta, distance);



//--------------------------------------------------------------


多國語言做法  :


支援 dyniamc SDFAA ( 主要是中文字太多需要用 )

SamplingPointSize : 越高越精細。佔用空間越大。

Multi Atlas Textures : Atlas不夠塞,會幫產新的。


一個 ".ttf " = 一個以上Atlas 不可合併Drawcall。

一個 ".ttf " 如果字不夠塞可產多個Atlas ( 1 Atlas = 1 Drawcall & 記憶體++)  

多個 ".ttf " 可以混用,設定先後順序即可。


坑: 原本Unity的 TextMesh & UIText 在沒有字集的情況下會抓,裝置內的Font來用。

( 比如阿語&韓文 )

目前可抓內建Font List來用,但要開發者自行篩選,還要Runtime產Font,不太可行。



建議:

1.一個Static字集放常用單字 + 一個 dyniamc SDFAA 字集動態生成當備用。

2.確保韓文跟阿語都能正常,要把字集都塞進 同張 .ttf 裡 & 所有語言要測試過沒缺字。 ( 因為沒有抓系統預設Font的功能 )


//--------------------------------------------------------------


效能測試:


Unity 2018.4.2f1 && TextMeshPro 1.5.0


sumsumg

預產圖集(2000單) (SDFAA)

耗時6187ms 

mono:256KB

totol:4097KB ( 3張 1024*1024 atlas )


預產圖集(2000單) (Smooth)

耗時6002ms 

mono:272KB

totol:2976KB ( 3張 1024*1024 atlas )



samsung Galaxy Tab 4 7.0 ( opengl 2.0)

20 (中文字)

200 (中文字)

2000 (中文字)

mono (預先產圖

0KB

0KB

2800KB

mono (產圖 & 塞字

0KB

4KB

3392KB

mono (直接設值

56KB

3016KB

卡死


samsung Galaxy Tab 4 7.0 ( opengl 2.0)

20 (中文字)

200 (中文字)

2000 (中文字)

預先產圖 ( ms )

21

31

157

產圖&塞字 ( ms )

34

114

4316

直接塞字 ( ms )

97

1707

卡死


Htc u11+ ( opengl 3.0)

20 (中文字)

200 (中文字)

2000 (中文字)

預先產圖 ( ms )

20

20

51

產圖&塞字 ( ms )

15

54

1239

直接塞字 ( ms )

49

662

卡死


建議:

常用的能產的先產,加入載入。

如要塞字也先產圖在塞字 會比直接塞字好。

直接塞字記憶體會爆炸..直接卡死。(要再看 Source Code查原因



坑: 貼圖字越多時,產新字就越久 ( 20字/每次 )

samsung Galaxy Tab 4 7.0 ( opengl 2.0)

實測在 (250字/每張圖) 以內可以維持30fps : 

建議設定 font size : 32 , atlas : 512*512


阿語注意:


要下載下面插件 ( For TMP 的阿語偛件 )

https://github.com/mnarimani/RTLTMPro


問題

1. /n 不支援

2. 連接處會有點斷掉


未來:

可以看SourceCode 也可以改。但不開源要自己維護。

//--------------------------------------------------------------




參考連結: 

https://zhuanlan.zhihu.com/p/26217154

https://medium.com/codememo/spark-ar-%E5%AF%A6%E4%BD%9C-sdf-shader-%E4%B8%8A-b0864938df3b

https://www.jianshu.com/p/c466e8b2c854

https://zhuanlan.zhihu.com/p/91689792

https://forum.unity.com/threads/can-i-change-textmeshpro-dynamic-font-atlas-size-in-runtime.674275/

Unity Anti-aliasing shader (SDF)


SDF Genarater

Signed Distance Fields February 18th, 2009

http://www.klayge.org/docs/klayge%E4%B8%AD%E7%9A%84%E5%AD%97%E4%BD%93%E7%B3%BB%E7%BB%9F/#ref_9


https://blog.csdn.net/seizeF/article/details/104657900

SDF演算法參考:

http://www.codersnotes.com/notes/signed-distance-fields/

https://prideout.net/blog/distance_fields/



Unity UIParticle 查看筆記

 UIParticle 查看筆記


GitHub位置 :

https://github.com/mob-sakai/ParticleEffectForUGUI


Default

RenderMode 不設定Overlay , 這做法只是讓UI變3D方式渲染
如果要穿插 UI 要當改Z position 這樣做不太現實 , 也會影響Batch


RenderTexture 

原理: 

1. 將Particle 設定指定的Layer 

2. 再把Camera 設定只看那個Layer

3. Camera 指定Target Texture 畫進去

4. 用RawImage 指定該Textrue 並顯示。


Convert particle to UIVertex

有錯誤 & 有各種奇怪GC 不裡他


Baking mesh(UIParticle)Unity 2018.2 Above

原理 : 

ParticleSystemRenderer.BakeMesh > 抓出Mesh

canvasRenderer.SetMesh (_mesh);

canvasRenderer.SetTexture (mainTexture);

PS : 不需額外Canvas,RenderTextrue,無GC

Canvas to sort

原理:

猜分多個Canvas 設立Order 

如果UI跟特效穿插 , Batch機制會被打亂


結論 : 

2018.2 以上使用 Baking mesh 做法

以下使用RenderTexture做法


2020年10月22日 星期四

Byte & Bool[] 互相轉換

因為 1 byte = 8 bit 所以可以用來轉換。
8 個 bool 相當於 4byte*4 = 16 byte 的大小 ( 1 bool => 4 byte )
如果存在 1 個 byte 中 就可以節省 15 byte , 還蠻省的~

        public static void Byte2Flags(bool[] flags, byte range)
{
if (flags == null || flags.Length < 8)
{
return;
}
for (int i = 0; i < 8; i++)
flags[i] = (range & (1 << i)) > 0;
}
public static byte Flags2Byte(bool[] flags)
{
byte range = 0;
if (flags == null || flags.Length < 8)
{
range = 0;
}
for (int i = 0; i < 8; i++)
{
if (flags[i])
{
range |= (byte)(1 << i);
}
}
return range;
}

2020年8月6日 星期四

LINQ&GC&Lamda ( Linq效能的坑 )

為什麼使用 Linq 有時候發現效能不是很好,特別會多出來一些GC量。

( 特別是再做手機遊戲的人,GC 造成的卡頓會特別明顯 )

原因 :

C# 在 new Object 之後,會經過GC回收,所以要避免一直new Object, 這樣會給GC比較大的壓力。

 

舉個栗子 : 簡單的查找


一個使用For 迴圈查找 

一個使用Linq 查找


Lamda 表達式是一個語法糖,經過編譯後 會轉換為下面程式碼:


這裡可以查看 : https://reurl.cc/kd4n4n


轉換前:

轉換後 :

可以明顯看到編譯後的程式碼 

使用Linq 方式他會創見一個 Class &  new Object, 所以他會被GC。(因該要避免)

而且因為在Lamda裡出現的變數都會 "都會變成新Class理的成員", 新Class的成員越多,也就等於GC量越大~ 所以也要小心使用~


結語:


Linq & Lamda 很好用很直覺,但 *不要在迴圈中使用* *不要在Update()中使用* 會產生很多GC !! 請堅持偶爾用用沒關西的原則使用,不要一直用一直爽~




2020年5月12日 星期二

C# Dependency Injection 依賴注入

程式碼常常改來改去的,但怎麼樣在需求改變的時候,只改動最少的東西捏??
這邊介紹一下 "Dependency Injection 依賴注入" 的技巧~~

舉個例子搭車好了
有一個人搭賓士車

public class Human
{
    Benz _c;
    public Human()
    {
        _c = new Benz();
        ...
    }
}

因為這裡 Human 直接參與了 new Benz() (賓士) 的創建過程,所以如果Benz創建過程有改的話 Human也要改,hen麻煩。

現實生活上也不會發生,反正人搭車,人又不是汽車製造商,今天汽車換零件。乾我屁事XD?
所以我們把車改成 "參數" 的方式丟進去就好了~

public class Human
{    
    Benz _c;
    public Human(Benz  b)
    {
       _c = b;
    }
}

ok改成這樣之後 怎麼做汽車跟我就無關咯,反正我只是要搭而已。

有天又想改搭Ferrari(法拉利)了,結果又要改Human。hen麻煩又要改?
不用我們把剛剛的參數 改成 ICar 好了,反正有車搭就行了。( 抽象層次往上提升 )

public interface ICar
{
}

public class Benz : ICar
{
}

public class Ferrai : ICar
{
}

public class Human
{
    ICar_c;
    public Human(ICar c)
    {
        _c = c;
    }
}

好啦改成這樣之後以後要什麼車都可以直接 "依賴注入" 進 human 裡面拉~
這裡就用到物件導向 "多形" 的好處了。
比如 
new Human( new Benz() );
new Human( new Ferrai() );

但其實也沒人規定一定要是汽車吧 , 可以是火車,公車,三輪車有的沒的,只要是交通工具(ITransportaion)就行了
所以也可以改成 ( 抽象程度再提升一階 )

public interface ITransportaion
{

}

public interface ICar : ITransportaion
{


public class Human
{
    ITransportaion _c;
    public Human(ITransportaion c)
    {
        _c = c;
    }
}

好啦改完之後,以後要換成火車也沒問題啦~
這就是一步一步解耦合
從搭個車還要管車子怎麼生產的,到想改搭飛機都行,反正不關我的事,只要能到目的地就好。


上面的例子,我們盡可能地讓各個Class的關聯性不要那麼高
所以最後我們抽象出 ITransportaion 讓 Human 只依賴 ITransportaion 這個介面達到更好的擴充性。

另外 Dependency Injection 對於 "抽象層次" 有很重要的關係,抽象層次越高,擴充性就越好。
如果單單只是用 Dependency Injection 其實意義並不大的感覺。

另外 Dependency Injection 也可以方便測試。
在測試 Human 類別時。
還要創建一台車,往往粉麻煩(取決於創建複雜度)(可能還有環境搭建之類的)

但我們抽離出 ITransportaion 之後做假的測試資料就很方便拉~

2020年5月5日 星期二

Unity Get Sprite Index of Texture , Unity 查找在Texture中的Sprite Index


using System; using System.Linq; using UnityEditor; using UnityEngine;


public static class SpriteUtility

{

public static byte GetSpriteIndex(Sprite sp)

{

if (sp)

{

string spriteSheet = AssetDatabase.GetAssetPath(sp.texture);

Sprite[] sprites = AssetDatabase.LoadAllAssetsAtPath(spriteSheet)

.OfType<Sprite>().ToArray();


int index = Array.FindIndex(sprites, s => s.name == sp.name);

return (byte)index;

}

else

{

return 0;

}

}

}