來源:https://blog.csdn.net/coffeecato/article/details/78536488
coffeecato寫在前面:
本文確實不錯,作者用以說明自動生成網格的示圖非常具有代表性,從drawcall的生成過程分析性能開銷的重點,引出了overdraw和達到GPU像素填充率限制的原因,從中也可以看出作者對這個主題的理解頗有深度。查看作者的個人自述,居然是個2012年畢業的小伙子,后生可畏啊!翻譯本文對自己也是個考驗。
英文水平捉急,如果錯誤請多多指正。
原文:UNITY SPRITES: SPRITERENDERER VS. CANVASRENDERER (UI IMAGE)
翻譯已征得原作者同意:
當在一個公司項目工作時,我被問到關於sprites(SpriteRenderer)和UI image(CanvasRenderer)的區別。我沒找到多少相關的信息,所以我決定在公司准備一場介紹來幫助大家把兩者的區別搞清楚。本文中你將會看到一個比當時的介紹更完整的版本。運行環境是Unity5.3.4f1.
Sprites本質上是半透明texture,其中texture是在導入時被設置導入為sprites的。它們不是直接被應用於meshes,而是會應用於長方形/多邊形(最后,它們始終是meshes,因為沒有那么大的區別)。Sprites就是被渲染到2d/3d場景或者其他界面中的圖片.
1.用法
在Unity中使用sprites很簡單。只需將目標圖片移動到assets文件夾下然后點擊打開inspector settings.將texture type改為sprite(2D and UI),如下圖:
現在你該決定將圖片當成sprite使用還是作為UI Image.但是如果考慮到渲染,你可能並不確定你想要使用哪種方式。我們將會在下一節描述兩種方式間的區別;現在我們大概描述一下如何在Unity中創建它們。
如果你想使用SpriteRenderer,將sprite從Project窗口移動到Hierarchy窗口或者Scene窗口。成功創建后的窗口應該像這樣:
如果你想創建UI Image,在Hierarchy中右鍵然后create new UI–>Image.這個控件需要canvas,如果沒有canvas會自動創建一個。最后,你將看到:
2.對比:SpriteRenderer vs CanvasRenderer
在Hierarchy窗口,你可以把sprites隨便放在哪。然而,UI Images不得不放在canvas下面。你可以通過transform移動其他obejcts那樣移動sprites,但是images使用RectTransform來在界面系統中移動。
使用默認材質時,Sprites是在”Queue” = “Transparent” “RenderType” = “Geometry”(原文: transparent geometry queue)模式下渲染的。UI Images也是在這種模式下渲染的,除非你使用了Overlay 模式渲染(coffeecato補充:Canvas的Render Mode),這種情況下它將會通過Canvas.RenderOverlay渲染。你可能會猜到,這樣在移動設備上的開銷很大。我們稍后會討論到。
sprites和images的一個最重要的區別在於sprites支持網格的自動構造,而UI Image的網格始終由矩形構成。構造網格的原因將會在下一節講到;我們將會看到它的重要性及它對性能的重大影響。
最后,兩種方式都可以通過使用sprite atlases來減少draw calls.
下面的例子將會幫助看到二者之間的區別:
上圖中可以看到,UI Image創造了一個緊密的矩形來包裹sprite,而SpriteRenderer創造了一個能更好匹配將要渲染的sprite的網格。看看另一個例子:
同樣的情況出現這個例子中。但是網格這次看起來更復雜了,為什么呢?Unity嘗試去為sprites構造最佳的網格來避免引入太多的多邊形。可能有人會說這樣的權衡到底是利是弊。
如果我們導入一張擁有孤島(coffeecato補充:原文是islands)的png,一張圖片包含被透明區域分隔開的圖形會發生什么情況?
上圖中我們看到的情況很有意思,SpriteRenderer創造了兩個子網格,一個對應一個孤島;然后UI Image只是通過擴展矩形來覆蓋整個圖片。
3.性能
你可能會猜到,上面例子顯示的不同處理方式可能會導致性能的差異。當渲染很多對象時,差別會更明顯(比如地形中的草,或者粒子特效)。下面我們來分析一下其中的原因。
當渲染texture時,設置好頂點,索引,uv坐標,紋理數據和shader參數,然后向GPU發送數據,這個過程就是著名的draw call.隨后,在圖片最終顯示之前,一些亂七八糟的事情在GPU發生。一個簡單的渲染管線通常包括:
1. CPU 發送一個draw命令到GPU
2. GPU獲取到繪制所需的所有信息
3. 幾何圖形通過頂點着色器和光柵化被轉化為像素
4. 每個像素通過片元着色器被轉化然后被寫入到幀緩存一次或數次
5. 當一幀結束時,圖形會顯示在你的顯示器上
回到主題,SpriteRenderer和UI Image之間的區別是什么?顯而易見,sprites的開銷更大,因為它的幾何數據更復雜。但是如果我告訴你頂點操作通常比片元操作開銷小的多呢?尤其對於移動設備和半透明對象。
在很多引擎包括Unity中,半透明材質是由后向前渲染的。那意味着,最遠的物體(從camera出發)最先渲染,這樣alpha混合操作才能像預期那樣工作。對於不透明材質,渲染正好反過來這樣便於我們剔除不可見物體。
像素着色器會被渲染sprite中的每一個像素都執行,因此,當存在較大的圖形時(相對屏幕尺寸),片元着色器將會在很多像素上執行。問題在於,當透明物體在視錐體內時沒有很有效的方法將它們剔除,因此你將會渲染所有的半透明物體即使其中的大多數最終都不可見。所以你會發現同一個像素會渲染多次,在幀緩存中也會重寫多次。這個問題通常被稱為overdraw.同樣地,由於這種現象帶來了內存帶寬的浪費,會很快達到GPU 像素填充率的限制,這種情況是移動設備應當極力避免的。這就是問題的關鍵。
如果你確實理解了上面一段,你將會弄明白SpriteRenderer和CanvasRenderer是多么的不同。前者通過構造網格清除了不必要的透明像素(因此,避免了執行開銷巨大的片元着色器,從而避免了overdraw),然而UI Image創建了一個簡單的網格很可能會引起很多overdraw。你需要在復雜的幾何圖形和更多的片操作之間做一個權衡。
你應該會想到使用sprite atlases,因為spritest通常數量很大同時尺寸很小。這會導致繪制sprites有很多drawcall.同樣地,對於較大的圖形,圖形壓縮也是不錯的方法。
你可以通過Atlas Packer很方便的創建sprite atlas.同時,有時自動構造的網格性能上並不好你也無法控制它,因此你可能會使用一些性能更好的插件比如ShoeBox 或者 TexturePacker.
4.結論
當下次再遇到sprites時,不妨考慮下面的建議:
1. 如果sprites的數量不多,想用什么用什么。如果擁有上百個sprites,重新讀讀本文。
2. 使用profiler和frame debugger來搞清楚發生的狀況。
3. 避免使用透明,盡量使用不透明的物體替代透明物體。
4. 避免在屏幕上渲染尺寸較大的sprites,這會引起更多的overdraw。你可以通過在Scene View中選擇rendering mode為Overdraw來查看overdraw的情況。這對於粒子特效很關鍵。
5. 選擇更復雜的幾何體而不是更多的像素,尤其對於移動設備。可以通過選擇Scene View中的Shading Mode為shaded wireframe來查看。
6. 如果需要對界面進行較多的位置操作(比如content fitter, vertical groups等)選擇UI Images.
7. 減少渲染區域的分辨率來查看性能有沒有實質的提升,通過這種方法來判斷是否達到了像素填充率的限制。