0x00 前言
最近和朋友聊天,談到了Mesh的內存優化問題,他發現開啟Model Importer面板上的Mesh Compression選項之后,內存並沒有什么變化。事實上,期望開啟Mesh Compression后Mesh所占用的內存降低,是對Mesh Compression的作用的誤解。
我相信很多同學看到Mesh Compression這個名字之后,也會有類似的誤解。因此這篇博客就來聊一聊在Unity中如何對Mesh進行優化,以達到節約內存的目的,並且為何開啟了Mesh Compression選項反而對內存沒有什么幫助。
0x01 Unity中的Mesh壓縮
在Unity中,主要有三種方式用來優化mesh數據的空間開銷。
即Player Setting中的:
Vertex Compression
Optimize Mesh Data
以及Model importer中的:
Mesh Compression
其中,Vertex Compression的實現是將頂點channel的數據格式format設置為16bit,因此可以節約運行時的內存使用(float->half)。
Optimize Mesh Data則主要用來剔除不需要的channel,即剔除額外的數據。因為與壓縮無關,本文先暫時不討論這個選項。
但是,Mesh Compression是使用壓縮算法,將mesh數據進行壓縮,結果是會減少占用硬盤的空間,但是在runtime的時候會被解壓為原始精度的數據。
和Runtime時內存關系較大的是Vertex Compression的實現。
而是否可以進行Vertex Compression,則和模型的導入設置以及是否可以進行dynamic batching(在build 階段,主要判斷mesh的頂點數是否符合條件)有關。
簡單可以歸納為以下幾點:
1. 是否適合進行dynamic batching
2. 在Model Importer中是否開啟了Read/Write Enabled
3. 在Model Importer中是否開啟了Mesh Compression(是的,很吃驚是吧)
其中第一點,就是該mesh是否適合進行dynamic batching。這里不僅和player setting中是否勾選了dynamic batching有關,還和mesh的頂點數是否超過300有關。當然,dynamic batching是否可行主要和頂點屬性的數量有關,但是為了簡單,build階段就按照常見的一個頂點帶3個頂點屬性,也就是300個頂點來做限制了。
所以如果開啟了dynamic batching,則300個頂點以下的mesh不會被執行頂點壓縮。
其次,Read/Write Enabled這個選項也會使Vertex Compression失效。所以一般情況下,為了節約內存最好不好勾選這個選項,除了無法進行頂點壓縮之外,它還會額外在內存中保留一份mesh數據。
再次,也是大家常常忽略的一點,即如果開啟了Mesh Compression,則會override掉Vertex Compression以及Optimize Mesh Data的設置 。Mesh Compression會將mesh在硬盤上的存儲空間進行壓縮,但是不會在runtime時節省內存。
0x02 測試
下面我們就通過幾個小例子,來直觀地查看一下各種設置下Mesh的內存占用情況。
Unity Version 2018.2.1
Windows Player
情況1:
默認情況,不開啟壓縮(MeshCompression和vertex compression),不開啟Read/Write Enabled
Model Importer:
Building_e:
MeshCompression Off
Read/Write Enabled Off
Rock_c:
MeshCompression Off
Read/Write Enabled Off
Player Setting:
Dynamic Batching Off
vertex compression:None
Optimize Mesh Data:Off
測試結果:
Building_e 0.5mb
Rock_c 3.3kb
題外話:如果開啟了Dynamic Batching,則Rock_c這個模型即便不開啟Read/Write Enabled,其的內存占用也會更多,會達到5.9kb,這是因為對於頂點數較少(300以下)的模型,在開啟動態合批的情況下我們會多保留一份它的mesh數據,即和Read/Write Enabled開啟的效果是一樣的。
情況2:
測試只開啟vertex compression的情況。
Model Importer:
Building_e:
MeshCompression Off
Read/Write Enabled Off
Rock_c:
MeshCompression Off
Read/Write Enabled Off
Player Setting:
Dynamic Batching Off
vertex compression:Everything
Optimize Mesh Data:Off
測試結果:
Building_e 379.8kb
Rock_c 2.6kb
此時壓縮生效。
情況3:
測試開啟vertex compression 和 dynamic batching 的情況。
Model Importer:
Building_e:
MeshCompression Off
Read/Write Enabled Off
Rock_c:
MeshCompression Off
Read/Write Enabled Off
Player Setting:
Dynamic Batching On
vertex compression:Everything
Optimize Mesh Data:Off
測試結果:
Building_e 379.8kb
Rock_c 5.9kb
可以看到,Rock_c的內存占用升高了。這是因為Rock_c的頂點數只有40+,並且index buffer的format是16bit,在開啟dynamic batching的情況下Build時被認為是符合動態合批條件的,因此在構建時不會執行vertex compression的操作。
因此在開啟動態合批時,對一些小模型(vertex count<300)Vertex Compression是無效的。並且,這樣的模型同樣會保留在cpu可訪問的內存中,以備動態合批的需要。
情況4:
測試開啟vertex compression 和 dynamic batching 以及Read/Write Enabled的情況。
Model Importer:
Building_e:
MeshCompression Off
Read/Write Enabled On
Rock_c:
MeshCompression Off
Read/Write Enabled On
Player Setting:
Dynamic Batching On
vertex compression:Everything
Optimize Mesh Data:Off
測試結果:
Building_e 1.0mb
Rock_c 5.9kb
可以看到,此時Building_e的內存不僅沒有被壓縮變小,反而由於開啟了Read/Write Enable而翻倍了。而Rock_c由於之前說過的原因,顯示的也是翻倍后的內存占用。
所以,為了保證vertex compression能夠正常的執行,減小runtime時mesh的內存占用,不要開啟 Read/Write Enable。
情況5:
測試開啟vertex compression 和 dynamic batching 以及Mesh Compression的情況。
我想最讓人迷惑的可能就是Mesh Compression這個選項。從字面理解,這個選項的開啟是對模型進行壓縮的意思。但是實際上開啟這個選項只會減小mesh在硬盤的存儲大小,在runtime時vertex使用format並沒有被改變,仍然是Float。因此也就無法實現Runtime時內存的優化。
Model Importer:
Building_e:
MeshCompression On
Read/Write Enabled Off
Rock_c:
MeshCompression On
Read/Write Enabled Off
Player Setting:
Dynamic Batching On
vertex compression:Everything
Optimize Mesh Data:Off
測試結果:
Building_e 0.5mb
Rock_c 5.9kb
讓人吃驚的來了,Vertex Compression失效了。開啟了Mesh Compression之后,內存回到了沒有壓縮時的數值。
0x03 結論
之所以勾選Mesh Compression后會有這個結果的原因,在上文已經描述了很多。
因此,一個小建議就是如果為了優化Mesh的內存開銷,不要開啟Mesh Compression,以避免Vertex Compression的失效。