1. 程式人生 > >Unity -----一些可能存在的錯誤

Unity -----一些可能存在的錯誤

5.x 烘培 coo post 有一種 目前 元素 mod cati

關於Unity中的資源管理,你可能遇到這些問題

技術分享張鑫 8 個月前

原文鏈接:關於Unity中的資源管理,你可能遇到這些問題 - Blog

在優化Unity項目時,對資源的管理可謂是個系統紛繁的大工程。鑒於Unity獨特又絕(cao)妙(dan)資源打包的AssetBundle管理機制,不同資源的屬性適合於不同的存儲和加載方式。此外,要處理好成百上千個資源之間的相互依賴關系也非易事。誰說良好的資源管理不是個藝術呢?:)

——————————————————

關鍵字

AssetBundle
資源制作 紋理\網格\材質\Shader\音頻\動畫
Lightmap

一、AssetBundle 相關

Q1:Unity中的SerializedFile是怎麽產生的?請問用Unload(false)可以清除嗎?因為讀取了Bundle裏面的內容後已經賦值給其他物體了。而且我把圖片都打成了Bundle,然後讀取出來,圖片的大小應該是超過了這個SerializedFile的大小的?

技術分享

SerializedFile是AssetBundle加載時產生的序列化信息,一般為LoadFromCacheOrDownload、LoadFromFile和New WWW加載本地AssetBundle文件所致。如果AssetBundle中的資源已經加載,且後續沒有依賴該AssetBundle的資源進行加載,那麽可以通過Unload(false)將其刪除。SerializedFile中記錄的是AssetBundle的序列化信息,而不是其包含資源的內容,因此,其大小要小於或遠小於資源的實際內存。

Q2:我們現在采取了2種資源管理方式, 我想了解下內存占用問題。 方式A:AssetBundle加載好以後在內存中保留, 如果需要創建對象就通過Instantiate來創建。方式B:AssetBundle加載好以後立刻通過Instantiate實例化一個對象, 然後Unload(false)這個AssetBundle,如果需要,創建對象通過clone來完成。對於方式A,在Profiler中發現 WebStream中有相關AssetBundle文件存在, 我們認為這份內存是多余的, 所以就采取了方式B, 方式B又面臨資源卸載不幹凈的情況。想確認一個問題, 如果采取方式A: 一個 xxx.assetBundle原始文件大小是 1MB, 解壓到Webstream內存中是2MB, 最終實際內存占用是2MB 還是3MB 呢?

如果采取方式A:最終實際內存占用是2MB,而不是3MB,WebStream中已經包含原始AssetBundle的數據。對於沒有依賴關系的AssetBundle文件,我們推薦方式B的形式對AssetBundle進行卸載,這樣可以免除不必要的內存占用,對於這種方式加載出來的資源,可以通過Resources.UnloadAsset和Resources.UnloadUnusedAssets來進行卸載,如果無法卸載,則該資源一定被緩存了,研發團隊可通過檢測自己的緩存池/Constainer來進行檢測。同時,如果是Unity 5.3以後版本,可嘗試通過Memory Profiler來進一步查看。

Q3:打包時候AssetBundle的md5總變化(被打包的東西沒變),請問能怎麽解決?有說法是加上DeterministicAssetBundle就可以,但是我嘗試後發現md5依然變化。

該方法確實並不受用。對於Unity 4.x版本的AssetBundle文件,其md5值在某些情況下確實會前後不一致(哪怕是完全一樣的內容進行打包)。對於該系列版本,僅能建議開發團隊建立配置文件來對AssetBundle進行管理。
而對於Unity 5.x版本,則可以在打包時開啟 AppendHashToAssetBundleName 選項,這樣Unity引擎會在每個AssetBundle文件後生成一個唯一的HashID(顯示地放在文件名後),開發團隊可以通過該ID來判斷對應的AssetBundle文件是否發生改變。

Q4:如果采用依賴打包的話,比如NGUI,圖集A作為被依賴包,界面1、2、3作為獨立包,分別依賴打包。當圖集A重新打包的時候,是不是界面1、2、3也都要重新打包?目前我們是界面打包時清空全部的圖集信息再打包,在客戶端加載後再動態賦值回來。這樣可以實現單獨更新圖集,但是代價就是加載的性能。請問有什麽更好的解決方案呢?

“當圖集A重新打包的時候,是不是界面1、2、3也都要重新打包”,這是不需要的,Unity 4.x的依賴打包的限制在於,在重新打一個包時需要將它依賴的包都重新打一次,但不需要重打依賴它的包。

Q5:請問內置的shader怎麽打包?我用到了內置材質球,不只是Shader,這時候在Profiler中看到加載的結果中會出現多份,如下圖所示
技術分享

通常有兩種方式對內置的Shader進行打包:

  1. 將其添加到Graphics Settings中的Always Included Shaders 中,此時添加後的內置Shader就不會被打入AssetBundle包中;
  2. 在Unity - Get Unity - Download Archive下載內置的 Shader,將其導入項目,並替換成非內置的材質球,從而可以直接通過腳本來控制其打包的方式。

Q6:請問粒子特效的Shader是否不能使用依賴打包? 我們對Shader的模型和特效使用了依賴打包,運行的時候發現模型顯示是正常的,但是粒子特效使用的Shader就不能正常運行,特效顯示不正常。而在編輯器中,我們看到Material中的Shader是存在的。這時候如果重新手動給這個Material指定同樣的Shader,這個粒子特效就能正常顯示,請問這是什麽原因引起的?

部分 Shader 在打包到 Android 版本的 Assetbundle 之後,會因為平臺不兼容而無法正確顯示,這是因為打包後的 Shader 代碼只保留了目標平臺的預編譯代碼,不一定能夠在 Editor 下運行,所以這是正常現象。 但這並不會影響依賴打包,因為在真機上並不會出現類似的問題。

Q7:Resource的場景下有兩個場景Scene1.unity和Scene2.unity。我要對這些文件進行打包,生成了

Scene1.assetbundle 
Scene1.assetbundle.meta 
Scene2.assetbundle 
Scene2.assetbundle.meta

如果我有相同的資源,理論上它會在這兩個包裏各存一份,這樣就造成了包體過大。所以有沒有辦法把共享資源做成依賴項單獨打包,這樣的話每個場景就不會過大了。Unity 5.x的BuildAssetBundles打包機制是否和Unity 4.x不一樣?原來的打包機制已經被剔除了嗎?

用Unity 4.x 的 Push/Pop 是可以抽出相同的資源,並且據我們所知該方法在Unity 5.x 中也受用。根據Unity 5.x新的打包機制,只要把相同資源的 AssetBundle Name 設置好,打包時就會自動抽出來。

Q8:現在生成AssetBundle的時候每個文件會多生成一個Manifest文件,這個文件也需要一起隨著AssetBundle上傳嗎,在資源加載的時候具體怎麽用呢?

每個文件多生成的Manifest 文件是不需要上傳的,其作用就是供開發人員查看AssetBundle 中的依賴關系等信息。
但除了每個文件多生成的 Manifest 以外,根目錄下還會有一個與根目錄同名的AssetBundle 以及 Manifest 文件,通過運行時加載這個AssetBundle,可以得到一個 AssetBundleManifest 對象,然後就可以通過這個對象得到AssetBundle直接的依賴關系。
更多信息可以參考AssetBundleManifest

Q9:如果我有一個Prefab,它的Dependencies都在Resources文件夾中,那麽,當我在AssetBundle打包時,只打包這個Prefab(不指定BuildAssetBundleOptions.CompleteAssets和BuildAssetBundleOptionsCollectDependencies)的話,這個Prefab能夠正確實例化嗎?

這是不能正確實例化的,因為AssetBundle中的資源和Resource文件夾下資源是不會建立依賴關系的(腳本除外,因為開啟BuildAssetBundleOptionsCollectDependencies 時,腳本依然不會打包到AssetBundle中)。所以會出現Mesh、Material等的丟失。

Q10:我們通過AssetBundle預加載Shader後,並沒有卸載AssetBundle,但是發現後面加載的Object並沒有引用到正確的Shader,這可能是由於什麽原因呢?

很可能是項目中AssetBundle的依賴關系打包不正確。後續加載的AssetBundle都需要與Shader的AssetBundle文件進行依賴,這樣Unity引擎才會在加載後續AssetBundle時,將Shader進行關聯。
建議開發團隊通過UWA資源檢測來檢測下AssetBundle文件的依賴關系。主要查看兩處,一個是Shader是否被冗余打包;一個是其> 他的AssetBundle是否與Shader的AB進行正確的依賴。具體檢測效果如下:
技術分享
如下圖紅框所示,開發團隊可以直接查看Shader以及其他資源在AssetBundle包中的冗余情況。

技術分享

Q11:我遊戲裏重復的特效較多,有些只是圖案相同但改變了顏色參數,如果都打成獨立AssetBundle,則內存裏面會有多份Texture。關於這樣的打包一般有什麽推薦的方法呢?

如果是相同內容且僅是整體顏色不同的話,那麽建議項目中只保留一份初始紋理資源,並通過Shader在運行時改變其整體配色,從而達到不同的效果。但如果是局部配色不同,那麽可以在原始紋理的基礎上加一種或幾種Mask紋理,用來負責顏色的自適應調配,然後再通過Shader來達到不同的展示效果。

Q12:請問一下,我兩個預設都引用了第三個AssetBundle的貼圖,如果不希望這張貼圖存在兩份,一定要等這兩個預設都加載好了,才能卸載貼圖的AssetBundle嗎 ?

是的,但並不是因為這樣做會使“這張貼圖存在兩份”,而是因為如果先卸載貼圖的AssetBundle會導致後續加載兩個預設時會丟失依賴,即找不到貼圖。如果腳本中會對這個情況進行檢查並重新加載貼圖的AssetBundle,那麽此時才會造成“這張貼圖存在兩份”的問題。

Q13:UGUI的圖集操作中我們有這麽一個問題,加載完一張圖集後,使用這個方式獲取其中一張圖的信息:assetBundle.Load (subFile, typeof (Sprite)) as Sprite; 這樣會復制出一個新貼圖(圖集中的子圖),不知道有什麽辦法可以不用復制新的子圖,而是直接使用圖集資源 。
技術分享

經過測試,這確實是 Unity 在 4.x 版本中的一個缺陷,理論上這張“新貼圖(圖集中的子圖)”是不需要的,並不應該加載。 因此,我們建議通過以下方法來繞過該問題:
在 assetBundle.Load (subFile, typeof (Sprite)) as Sprite; 之後,調用
Texture2D t = assetBundle.Load (subFile, typeof (Texture2D)) as Texture2D;
Resources.UnloadAsset(t);
從而卸載這部分多余的內存。

Q14:下圖一是剛進遊戲時獲取的信息,第二張是開關幾次同一個UI界面後獲取,對比兩圖我們發現有多份重復的Texture。請問這是為什麽?我們的加載方式是UI通過AssetBundle加載,加載後會釋放AssetBundle,然後再次加載 UI 就會造成紋理資源的冗余。
技術分享技術分享

剛進遊戲時獲取的圖中出現的“重復”資源可能並不是冗余,因為 Atlas的一個 Group 中可能包含多張一樣大小的Page(即紋理),而這幾個Page在內存中的名字是一樣的。
但是,如果同一UI界面多次開啟後,內存中出現了更多同樣的資源,則說明UI的管理方式存在一定問題。對於頻繁使用的UI,我們建議在加載之後通過緩沖池對其進行緩存,後續使用時,直接通過緩沖池獲取即可。而不要每次均通過AssetBundle進行加載,這種做法既會造成更大的CPU占用,同樣會很大幾率造成資源的冗余。
同時,如果多次開啟的是不同UI界面,並且造成內存中同種資源的增加,則很有可能是UI在AssetBundle打包時形成了冗余(這種情況在目前的UGUI系統中較為常見)。對此,如果開發團隊使用的是UGUI,那麽我們的建議如下:

  1. 對於使用Unity 5.x的新AssetBundle打包系統,則打包時盡可能將同種Atlas的UI界面打成一個AssetBundle文件,否則將很有可能出現資源冗余的情況;
  2. 對於使用Unity 4.x的老AssetBundle打包系統,則可以將一個含有Atlas的Prefab(或其他Object)先打包,其他UI元素對其進行依賴即可。

此外,開發團隊可以考慮用 來加載共享包,因為 new WWW 的方式會在內存中形成 WebStream 造成較多的內存開銷。關於該函數的具體優劣,開發者可以參考你應該知道的AssetBundle管理機制。

Q15:項目在發布時,Player Setting中勾選的這個選項(Optimize Mesh Data),對於已經打包並且放到了Streaming Assets中的AssetBundle文件有效果嗎?模型資源裏有個Optimize Mesh,是否能達到同樣的效果?

理論上 Optimize Mesh Data 是 Build Player 或者 Bundle 時才生效的,所以之前打好的 Bundle 就沒效果了。另外,兩個選項的效果是不同的,後者是調整面片排序的。

二、資源使用

紋理相關

Q1:關於貼圖類型設置請問有什麽好的建議呢?是否所有的貼圖都設置為Advanced比較好?這種情況下的貼圖內存是不是比較小?
技術分享

Unity默認情況下會將絕大多數紋理設置為Texture模式。一般來說,Texture模式對於絕大多數紋理資源也都是合適的。上面的例子中,Advanced模式下之所以內存占用較小,是因為關閉了Mipmap選項,所以其內存下降了。其本質跟Advanced模式無關。Advanced模式較之Texture模式和其他模式,可以更大自由地對紋理資源進行控制。因此,如果你想對紋理資源進行更加自定義地設置,可以選擇Advanced模式進行編輯。

Q2:同樣的包同一個圖集,ETC2格式,在紅米Note1上會比酷派的內存會大四倍,請問這是什麽原因造成的?如果不支持OpenGL 3.0,會造成這麽大的影響嗎?

ETC2 的格式理論上只在OpenGL ES 3.0 的設備上被支持,而在不被支持的設備上則會內部自動轉成 RGBA32/ARGB32的格式,這對於 RGBA Compressed ETC2 8bits 的紋理就是放大了 4 倍。因此,如果希望在 OpenGL ES 2.0 的設備上對透明材質進行壓縮,那麽可以嘗試使用分離 Alpha 通道的方式,用兩個 ETC1 來進行壓縮。

Q3:我現在動態加載StreamingAssets下的貼圖,代碼如下
技術分享我發現這種方式內存消耗很大,一張512 x 512的貼圖占用了2MB,看官方的解析是內存和顯存各需一份。想了解下動態加載貼圖有什麽推薦的方式嗎?

一般來說,我們比較建議通過AssetBundle來動態加載資源,而非通過bytes流來進行加載。如果你的項目正在使用這種方式來加載紋理,我們建議從策略上考慮將其更改。在我們目前來看,通過bytes流來生成資源,絕大部分原因是想對其進行加密,從而讓資源難於破解。但其實這種加密方式用處不大,因為據我們所知,現在有很多工具可以直接通過底層顯卡層來直接查看各種紋理、Mesh資源,比如Mali Graphics Debugger、Qualcomn Profiler等。因此,如果是從加密的角度來通過bytes流生成資源,那麽我們建議通過AssetBundle這種直接的方式進行加載。

Q4:iOS平臺需要對圖集做RGB和Alpha通道的分離嗎?我發現在同樣大小的圖片(正方形),RGB Compressed PVRTC 4bits和RGBA Compressed PVRTC 4bits兩種格式,占用內存是一樣的,如果把一張圖片分成兩張,那麽在iOS平臺是不是占用內存多一倍?有透明通道的,對於它的圖集怎麽處理會更好一點?

通常iOS下是不需要做通道分離的,因為 iOS 通用的 PVRTC 格式支持 Alpha 通道。但目前也有團隊反饋,在 iOS 上進行通道分離有助於減少失真,可以在一定程度上提高視覺效果,因此也可以嘗試做一個對比。
如果發現占用內存是一樣的,那麽原始圖片是RGB的。如果iOS上做通道分離,內存確實會增加一倍。UI的紋理在iOS下可以直接選擇默認的 Compress,在打Atlas時會自動處理成 PVRTC,開發團隊可以從Sprite packer窗口來看Atlas的壓縮格式做個確認。

Q5:在Unity 4.x的版本中,所有UI貼圖使用ETC2格式,即使目標設備不支持該格式,也會解壓成RGBA32使用。 而在目前使用的Unity 5.3.4版本中,iOS平臺無法設置ETC2格式。如果壓縮只能使用PVRTC格式,那麽PVRTC存在顯示效果比較差,並且圖片必須為正方形,但如果用RGBA32格式,貼圖占用的內存和存儲又都過大,請問目前版本的Unity在iOS平臺上應該如何設置UI貼圖的壓縮格式?

我們建議可以先嘗試其他的壓縮格式看是否可以達到類似的效果,如ASTC格式。ASTC 在 iOS 的高端機上是被支持的,因此理論上在 Editor 下不會強制把 ASTC 轉為 RGBA32,建議嘗試設置為 ASTC 後打包,從 Editor.log 中或者直接從包體大小上可以看出是否確實使用了 ASTC。

一般來說,如果 RGBA16 的效果可以接受的話,建議使用 RGBA16,雖然打包時相對大一些,但是內存中相比 RGBA32 能夠減半,但使用 ASTC 的話,雖然打包時比較小,但是在普通機型上會被處理成 RGBA32,導致過大的內存開銷。

Q6:請問Unity引擎中使用什麽貼圖壓縮格式,可以保證在占用內存相對較小的情況下True Color效果和原圖相當?同時在iOS和Android平臺上圖片的壓縮格式分別用什麽比較合適?有什麽需要註意的地方嗎?

目前來講,並不存在一個所有GPU平臺都支持硬件解壓的壓縮格式。 ETC1 和 PVRTC 分別是Android和iOS上我們最推薦的格式。 但對於透明紋理,ETC1不支持,而 PVRTC 則可能有較大失真,因此更推薦使用 RGBA 16。
一般來說建議直接使用 Unity 默認的壓縮格式(即選擇 Compressed 即可,不需要做特殊設置),Unity 會做如下處理:

  1. Android 上不帶Alpha通道的圖片采用 ETC1,帶Alpha通道的圖片采用True Color中的RGB16,TrueColor中的 RGBA16 會>比 RGBA32 更節省空間,但圖像的顯示質量會差一些;
  2. iOS 上使用 PVRTC,但PVRTC格式要求紋理的長寬相等,且都是2的冪次(即POT,在ImportSettings中可以將NPOT的紋理自動轉換成POT)。

另外,針對Android 上的帶Alpha通道的圖片,還有一種常見的做法,即把Alpha通道獨立出來作為另一張紋理,從而將 RGB 部分和 Alpha 部分分別采用 ETC1來壓縮,但渲染時就需要自定義的 Shader來處理。
同時,我們不建議直接使用 RGBA32 格式的紋理,因為它占用了很大的內存。一般建議使用 RGBA16 和 ETC 格式的紋理來進行加載。 如果轉換到 RGBA16 格式時出現了類似“色階”的顏色問題,則建議盡可能避免大量的過渡色使用。

Q7:請問遊戲中特效使用的很多貼圖, 一般有什麽好的方式去管理嗎 ? 不合並圖集的話會有上千張小的透明貼圖, 合並圖集又會有占用內存過多的問題。

可以合並成Atlas,一般將盡可能同時出現頻率較高的Texture合成Atlas,這樣並不會造成內存過大。在這方面也可以參考我們前不久推薦的插件Mesh Baker。

Q8:iOS上方形POT圖片有時候會失真,請問這種情況如何避免?一張NPOT的圖變換成POT,是否有推薦的方法? 采用 ToLarger 的模式拉成POT是否會有損失呢?

在其他設置一致的情況下,這兩種方式無論在加載還是渲染方面其實並沒有實質上的差別。在我們接觸到的大多數案例中,紋理資源方面的問題除了尺寸外,紋理格式、Mipmap設置和Read&Write功能同樣是需要研發團隊時刻關註的。

Q9:紋理Atlas是建議合成一張2048(尺寸)的紋理還是四張1024的紋理?

在其他設置一致的情況下,這兩種方式無論在加載還是渲染方面其實並沒有實質上的差別。在我們接觸到的大多數案例中,紋理資源方面的問題除了尺寸外,紋理格式、Mipmap設置和Read&Write功能同樣是需要研發團隊時刻關註的。

Q10:NGUI的圖集在內存裏存了多份,求問怎麽清理?
技術分享

遊戲運行中,UI Mesh出現多份不同內存的情況,是正常的,因為隨著UI widget使用的增加或減少,創建的UI Mesh是會隨著變化的。同時,如果不同UIPanel中存在相同Atlas的Widgets,則也會出現上圖中的情況。因此,建議大家遇到這種情況時,查看單幀中NGUI UI Mesh重名的是否有多份重名資源。如果存在,則說明相同Atlas中的資源被多個不同的UIPanel所使用,這種情況是需要盡可能避免的。
技術分享

Q11:我在UWA報告中看到大量的n/a資源,其格式的高度和寬度等信息都無法獲取,並且存在大量重復,請問我該如何定位這些文件?能否優化解決呢?

"圖中n/a資源是我們在項目運行時檢測到的無名稱資源,一般來說,該類型資源並非Asset文件夾中的美術資源,而是項目通過腳本動態生成的資源,比如new Mesh、new Texture等操作,即會生成這種類型的無名稱資源。
對此,我們建議研發團隊詳細檢測邏輯代碼/第三方插件中諸如New Mesh/Textue等此類操作,同時,在腳本動態生成該類資源後,為其賦上一個名字,這樣即可在後續測試中看到對應名稱的資源使用情況。"

網格相關

Q1:如果一個模型對應Skinned Mesh Renderer實例,那其所占的內存會隨著角色增加而增長麽 ?

簡單地從一個角色Prefab實例化(Instantiate)出多個實例時,Mesh並不會出現多份(這個行為與其他資源是一致的,包括Texture,AnimationClip,Material等等)。如果在內存中發現多份,可以考慮從項目中AssetBundle的加載方式入手,因為即使是同一個AssetBundle中的同一個角色Prefab,如果被反復進行“加載-實例化-卸載”操作,依然是會導致Mesh出現多份的(當然其他的資源也是一樣)。

Q2:我有一個特效依賴了兩個FBX。我把這兩個FBX的這個勾選去掉,Editor運行正常。否則就會宕機,請問這是什麽原因?

技術分享

將FBX上的Read/Write Enabled關閉後,內存中便不再保存其Mesh的副本(只存在顯存中),因此其屬性就不可再被訪問和修改。而粒子系統通常需要動態地修改其粒子的頂點屬性。因此,理論上來說,供粒子系統使用的Mesh是需要開啟Read/Write Enabled的,而在Editor下Mesh和Texture都是強制開啟的,所以在真機上就會出現問題。

Q3:MeshBaker 烘焙的Mesh可以保存到Prefab中,但是不能像FBX一樣,設置Model導入設置中的Generate Lightmap UVs 等信息,請問有沒有大招可以處理此情況?

首先從理論上說,一個 Mesh 只能有一個 LightmapIndex 和 LightmapOffset 屬性,這就決定了 Mesh 的合並必須在 Lightmap 的烘焙之前。
而在做Mesh合並時,需要註意到UV2對於一個Mesh而言,其所有三角面的UV區域都必須是互不重疊的,所以不能簡單直接地合並。
考慮到合並前每一個Mesh的UV2區域都應該是在[0,1]x[0,1]的區間中,在合並時可以給每一個UV2區域做一個縮小和平移,從而可以把每個區間在互不重疊的情況下,放到同一個[0,1]x[0,1]的區間中。
在UV2也正確拼合後,重新進行Lightmap的烘焙即可得到正確的效果。

動畫片段相關

Q1:我想要在Editor下批量地對Animator Controller文件中每一個的State裏的Animation Clip進行替換,但好像沒有看到 Unity引擎有提供類似的API,只提供了能在Runtime時進行替換的方法,類似下圖:

技術分享
我現在想要達到的目地是在Editor下寫一個批量替換Animation Clip的插件,請問下Unity引擎是否有提供這樣的接口呢?

在Unity 5.x 中已有一套完整且穩定的 API 可以訪問、修改和創建 Animator 中的任何元素。其命名空間是在 UnityEditor.Animations 下,以下Blog 中有一個簡單的例子是通過腳本創建一個 AnimatorController 以及其中的各種屬性和參數。Shiny new animation features in Unity 5.0 – Unity Blog
另外,如果需要修改Animation Clip,可以直接修改一個 AnimationState 的 Motion 屬性。 Animation Clip可以繼承自 Motion。

Q2:如果我的Animator是直接引用了FBX裏的動畫文件,而不是復制了FBX的動畫文件出來再引用,那麽打包的時候會把FBX都打包嗎?

這種情況下並不會把 FBX 打入 AssetBundle 中。將動畫文件Copy出來通常是為了直接對動畫文件打包,作為依賴包。

Q3:我們的動畫是放在FBX文件裏的

技術分享這樣做導致打包的時候把一些無用的文件也打進AssetBundle包裏了,實際上我們只想使用最後這個動畫文件。但在編輯器裏選中FBX文件裏的動畫文件時卻沒有AssetLabels這個窗口,設置不了AssetBundle Name。

技術分享
是不是只能用代碼設置?還是意味著不能設置AssetBundle Name,只能把動畫文件提取出來,類似下圖這樣單獨的一個文件?

技術分享

在Unity 5.x 的打包機制下確實無法手動為 FBX 下的 Mesh 或 AnimationClip 單獨資源設置 AssetBundle Name。因此,如果需要將這部分資源抽出來作為依賴包,目前確實就需要先通過腳本將這部分資源提取出來了。

音頻相關

Q1:請問音頻中的 Quality 什麽意思?一般設置為多少合適?我拖進去一首歌曲,試了一下 在0 和 100 的情況下區別不大,但是生成的音頻文件大小差別很大。

技術分享

Quality 表示在壓縮音頻時的失真程度(實際上可以認為是壓縮算法的一個參數),該值越大,壓縮後的文件越大,但音質保留的越好。而對於其失真的程度是視音頻數據以及內部的壓縮算法而定的,確實會有區別不大的情況。該值的設置原則就是,在音質失真可接受的情況下,越小越好。

材質相關

Q1:我們發現材質實例數量特別多,想問下這個對性能的影響如何,有沒有什麽建議?

Material的內存占用一般很小,所以大量的Material資源對於內存的壓力其實很小的。但是,它本身對於場景的切換時間是有影響的,即資源冗余得越多,切換場景時,UnloadUnusedAssets的開銷越大,進而增加了場景的切換時間,同時也會影響DrawCall的拼合。所以,建議研發團隊盡可能定位資源冗余的原因,並對其進行修復和完善。這一點,我們在UWA性能測評報告中的“分析和建議”中都有詳細說明。

技術分享

Q2:我在UWA上進行了性能檢測,在資源內存這裏看到詳情如下。請問這個數量峰值大於1是不是就是有問題的?但一個Material是很可能被實例化多份的。實例化後對象釋放掉,然後清理掉原始的Material,按道理就不算冗余吧?畢竟大部分都是材質在操作,對貼圖資源應該沒有影響。而且,如果確實有那麽多角色同屏怎麽辦呢?

技術分享

是的,如果數量峰值>1,則說明存在資源冗余的風險。如果是Material實例化,那麽後者是有個(instance)後綴的,比較好識別。如果是new Material(Shader)出來的,那麽是沒有instance後綴的。但無論是哪種方式,如果數量峰值較大,都應引起大家註意。一般都是可以通過其他方法來盡可能避免冗余的。

如果變化不是隨機的,且Material參數變化情況比較少,那麽可以根據參數的變化情況只做幾種Material,然後通過腳本動態賦值。如果是隨機或者動畫,那就沒什麽辦法了,要麽改需求,要麽就接受這麽多。

Shader相關

Q1:我用內建的Shader渲染場景,深度圖裏有內容。而用自己的Shader,取到的深度圖什麽都沒有,都是1,什麽原因導致的呢?我已經打開ZWrite了。

Unity的_CameraDepthTexture 的生成,會根據Rendering Path的選擇和設備的不同使用不同的方法實現,一種是直接從depth buffer獲取,另一種是添加額外的Pass來渲染到紋理。目前在移動設備上主要是通過後者實現,而後者的實現借助了Shader Replacement的機制(批量替換為簡單的Shader,並將深度渲染到紋理中)。
因此,在使用自定義的Shader時,就需要正確地設置RenderType的Tag(所有內置Shader都是設置好的),從而使得Shader Replacement正確地執行。具體可見文檔:Rendering with Replaced Shaders

Q2:我們的遊戲使用 Spine 插件,因為要用到裁切動畫,所以修改了Shader,但在使用的時候出現異常: Shader wants normals, but the mesh Skeleton Mesh doesn‘t have them,可能是什麽原因?

開發團隊需要註意:Surface Shader在生成代碼中默認會處理normal(即 Spine/Skeleton 實際上是需要 normal 的),而對應的Mesh並沒有包含normal,所以在預覽窗口裏渲染的時候會檢查 Mesh 是否包含 normal 信息,沒有的話會報這個錯誤。
開發團隊可以嘗試編寫一個Vertex & Fragment Shader 從而避免處理normal,也可以嘗試創建帶normal的Mesh,來避免該問題。

Q3:以前端遊時代,材質根據Pass不同、光照環境不同可以離線預編譯成ShaderCache,運行時並不需要拼材質再實時編譯,只要加載二進制代碼就好了。那Unity有沒有做這件事呢?我們是根據平臺和環境預編譯的Shader。

對於支持 Binary Shader 加載的設備,在首次編譯某個 Shader 的時候是會生成其對應的 Binary Shader Cache ,生成的 Binary 文件位於和 Application.persistantPath 並列的Cache 目錄下。

Q4:相同效果前提下,就性能而言,Shader 是用 V&F 還是Surface好?

Surface生成的V&F比較龐雜,分支較多,如果不註意 #pragma surface 參數的選擇,容易出現不必要的開銷。舉例來說,如果直接用 Unity 5.x 中默認創建的 Surface Shader (默認參數為 #pragma surface surf Standard fullforwardshadows),那麽 Shader 是會做 Physically based Standard Lighting 的,而這在移動端開銷非常大,且並非必要。

Q5:某個Shader裏設置了Culling Off,會影響到後面所有Shader的渲染狀態麽(假設後面不再設置Culling)?
技術分享

一次 Draw Call 提交所相關的 Render State 是不會影響到下一次的渲染狀態的。如果不在 Shader 中顯示指定 Cull 模式,則會使用默認的 Cull Back。

Q6:我們將Shader放到了Resource的目錄下,也已加到Editor的GraphicSetting裏,也試過在加載一個空的Prefab時綁定對應的Shader。 但該Shader在Editor裏無法正常顯示,看運行時指向是有的,重新指一下就能顯示了, 然而打包以後在手機上顯示正常。

這確實是Unity已知的一個問題,Android 和 iOS 的部分Shader在打包後,在Editor 下無法正常顯示。 主要原因是在打包時,只會把對應平臺的Shader預編譯代碼(如 gles )打入包中,因此在 Editor 下會執行失敗(通常 Editor 是 d3d 驅動)。 因此,目前只能嘗試在Editor下重新指定Shader來繞過這個問題。

Q7:我在shader裏這麽寫的代碼

o.texcoord1 = vec2(mod(v.texcoord.x,1.0),mod(v.texcoord.y,1.0));  

但是報錯 undeclared identifier ‘mod‘ at line 106 (on d3d11),請問是什麽原因導致的呢?

報錯表明mod在d3d11的Shader中是未定義的,如果開發團隊無需在d3d11的平臺上使用該 Shader,則可以添加以下預編譯指令:
#pragma exclude_renderers d3d11
如果Shader依舊無法正常顯示,那可能是因為在Editor中使用的是 DX11(可以從標題欄中看出)。 可以嘗試修改DX9的參數 :Build Settings -> 點擊 PC, Mac ... -> Player Settings(不需要點擊 Switch Platform) -> 去掉 Auto Graphics API for Windows 的勾選,只保留 Direct3D9。同時,開發團隊也可以直接使用 fmod 替換 mod,理論上 fmod 在各個平臺都是支持的。

字體相關

Q1:我們現在為了美觀,需要同時使用2套字體。但是每增加一套字體,就會內存增加50MB左右。請問你們在優化其他Unity遊戲時,怎麽處理類似情況的?

如果美術字不多的話,建議使用單獨的字體貼圖來進行實現,從而來達到特定美術字顯示的效果;如果美術字較多的話,那麽僅能建議使用另外一個套字體來進行實現。一般來說,字體的內存量應該也是完全可以控制在10MB以下的。
開發者可以直接在Unity Profiler中查看字體的內存占用,也可以通過UWA網站上測評報告中的具體資源信息頁面查看對應的字體資源使用情況。

技術分享

Q2:我做的預設用到了這個字體
技術分享我將這個預設打成AssetBundle包,通過Profiler分析時發現占了兩份內存。

技術分享下面這個SIMHEI應該是那個TTF的,為什麽會占兩份內存呢?

通常TTF文件會包含一個字體的多個字型,如可能包含正常字型、加粗字型、斜體字型等。而在Unity中會將其分為不同的Font資源,且他們之間會相互依賴。所以,如果項目中確實需要加粗字型的話,內存裏出現兩個Font是正常的,但如果實際上不需要加粗,那麽可以嘗試尋找一個不包含加粗字型的字體文件來替換該TTF文件。

粒子系統

Q1:我在UWA上提交了資源檢測,資源打的是依賴包,報告顯示Default—Particle這個資源存在大量冗余,這個是正常的嗎?

技術分享

Default—Particle 這個是粒子系統的默認資源。如果使用的是默認的粒子系統,沒有對Material進行修改,且每個粒子系統都是打一個AssetBundle文件的話,那麽該冗余問題是正常的。對此,我們建議開發團隊把下圖中默認的Material換成自己的資源,不使用Built-in資源,這樣通過依賴關系打包,就不會出現該資源冗余的問題了。

技術分享

Lightmap

Q1:Unity 5.x 能把指定集合的Mesh烘焙到一張Lightmap裏嗎?

Unity 5.x 是沒有這樣的功能項的。據我們的判斷,這樣的需求通常是為了動態加載,因此可以嘗試將Mesh分組到不同的場景分別烘焙。而在加載時可通過腳本動態地合並Lightmap,同時將物件的Lightmap Index進行正確的偏移即可。

Q2:Lightmap在Baked GI的等待時間比較長(Realtime GI已關閉),想請教有沒有什麽建議的參數或是方式,可以縮短等待的時間?

目前就我們的了解,在Unity 5.x比較影響烘焙時間的主要是大面積的面片導致Light Transport 過程過久(Enlighten 的機制所限)。可以嘗試拆分面積較大的面片,來提高烘焙的速度(通常,拆分大面積的面片對渲染性能也會有所提升)。
主要原因可參考如下的帖子: Light transport problem with large objects

Q3:如下圖所示,第一張顯示的是沒有烘培Lightmap的場景效果,裏面有一盞點光源;第二張顯示的是烘培Lightmap後的場景效果。請問為什麽同一盞點光源照亮的效果差別那麽大?
技術分享技術分享

簡單來說,這就是實時的直接光照和全局光照的差別。
在渲染時前者只能處理光源和單個物件之間的直接光照,而後者在烘焙時是通過光線跟蹤或者輻射度等復雜算法,計算出所有物體各個表面之間相互反射的光照信息,這也是烘焙Lightmap需要較久的時間的原因 。可以發現在全局光照下,即使是物體的背面也會因為其它表面的反射而被照亮,這在直接光線下就無法實現這樣的效果。

Q4:當關閉預渲染GI時會出現IndirectResolution,這個參數有什麽用,為什麽調大了以後會大大增加渲染時間,但是烘培出來沒有啥效果。

簡單來說,該值主要控制的是GI的烘焙密度,數值越大,表示每個單位距離內的texel越多,即烘焙得越精致,自然烘焙的時間也越長。該值並不需要越大越好,場景越小,建議該值越低。該值為1時,對於多數場景,其烘焙效果已經足夠了,升高該值,其效果也不會有明顯提升。
開發者也可以參考Unity官方的說明文檔:
Unity - Manual: Lighting Window

Q5:Lightmap丟失。用Unity5.1.2的AssetBundle做熱更新,資源導出的時候分析了所有的依賴項單文件導出。比如在導出場景的時候場景的烘焙出來的LightmapSnapshot.asset文件導出不了,導致運行的時候場景的Lightmap丟失了。

LightmapSnapshot.asset 本身是Editor下使用的,並不能單獨被打包進AssetBundle。 運行時加載Lightmap,一種方法是把場景(.unity 文件)打成AssetBundle加載,Lightmap 信息會打入場景AssetBundle(因為Lightmap信息和場景綁定)。另一種是通過Lightmapsettings.Lightmaps方法來運行時設置。需要註意的是,同時還需要重設Prefab的Lightmap信息(Lightmapindex和Lightmapscaleoffset),因為Lightmap信息在Unity 5.x下不會保存在Prefab 上。
另外存在一種可能,Unity 5.x中加入了Shader Stripping功能,在打包時,默認情況下會根據當前場景的Lightmap及Fog設置對資源中的Shader進行代碼剝離。這意味著,如果在一個空場景下進行打包,則Bundle中的Shader會失去對Lightmap和Fog的支持,從而出現運行時Lightmap和Fog丟失的情況。 而通過將Edit->Project Settings->Gaphics下Shader Stripping中的modes改為Manual,並勾選相應的Mode即可避免這一問題。

Q6:Lightmap在PC上顯示正常,但是轉到Android平臺上存在色差,顏色普遍偏暗。

一般來講,有兩種情況可能會導致色偏和亮度差異。

  1. Unity烘焙的Lightmap是32bit的HDR圖,而移動設備通常不支持HDR圖(32bit per channel),會按照LDR圖(8bit per channel)的形式進行處理,因此會出現色偏問題。因此我們建議:
    1)在移動平臺下使用Mobile/Diffuse材質,可載入Standard Assets(Mobile) package獲得。如果要獲得更合適的效果,需要自行修改Lightmap的DecodeLightmap函數,該函數可在Unity\Editor\Data\CGIncludes\UnityCG.cginc文件中找到。需要說明的是,這種方法也不能達到與PC端完全一致的效果。
    2)如果需要PC和移動平臺的顯示效果一致,可以用圖像編輯軟體修改Lightmap為LDR格式,例如PNG(8bit per channel)。
    3)為了避免類似問題,請不要使用過於強烈的Light進行烘焙,因為Light的強度(Intensity)越高,色偏問題會越嚴重。若有陰影丟失時,可以嘗試檢查一下模型的Lightmapindex、Lightmapscaleoffset、UV2等影響Lightmap采樣的一些參數。

  2. 另一種可能是存在過曝現象,可以嘗試將playersettings -> use direct3d 11關閉,看問題是否解決。

Q7:在同一場景裏烘培的Lightmap,我用了2張10241024的光照圖,大小是5.3MB;別人用了3張10241024的圖,大小是4.3MB。請問是什麽影響這個光照圖的大小,在哪裏調?

首先,請確認下Lightmap的類型,Single類型只生成一張,而Dual和Directional會生成兩張。 其次,請確認下當前的發布平臺,Android下的Lightmap會比Standalone更小。因為不同平臺采用的壓縮格式不同。此外,Lightmapping中的Lock Atlas,Resolution,Padding等選項也會影響最後烘焙光照圖的大小。

Q8:在遊戲中,有些Mesh在編輯時候是接收Lightmap的,出於某些原因我們合並了相同的Mesh(材質也相同)。但是發現原先的Lightmap不再影響合並後的Mesh,請問怎麽才能實現讓合並後的Mesh也接收原先的Lightmap?

如果Lightmap不止一個的話,手動合並Mesh是會出現問題的,因為合並的Mesh烘焙信息很可能出現在不同的Lightmap中,但合並之後的Mesh在渲染時只能使用一個Lightmap,這樣uv2讀取到Lightmap信息就會出現問題,進而出現這種現象。其實,對於材質相同的Static物體並不需要手動對其Mesh進行combine,因為Unity的Static Batching會自行完成。而如果由於某種特定需求一定要將Mesh進行合並的話,那麽也要將其所需要的Lightmap也一並合並,同時改變相應的uv2。不僅如此,Shader中Lightmap也需要進行相應修改,這是比較復雜的,所以我們並不建議這樣的做法,因為可能會花掉開發團隊大量的開發時間。

Q9:當關閉預渲染GI時會出現IndirectResolution,這個參數有什麽用,為什麽調大了以後會大大增加渲染時間,但是烘培出來卻沒有什麽效果。

該值主要控制的是GI的烘焙密度,數值越大,表示每個單位距離內的Texel越多,即烘焙得越精致,自然烘焙的時間也越長。
該值並非越大越好,場景越小建議該值越低。該值為1時,對於多數場景,其烘焙效果已經足夠了,升高該值,其效果也不會有明顯提升。

Unity -----一些可能存在的錯誤