Mesh中 material 和 sharedMaterial 的區別及內部實現的推斷
material 和 sharedMaterial 的區別
建立一個Material, 顏色為紅色,
建立兩個Quad,掛上剛剛建立的材質。 效果如下圖:
將第一個Quad掛載如下指令碼, 執行:
render = GetComponent<Renderer>();
render.material.color = Color.white;
效果如下圖:
修改指令碼內容如下, 執行:
render = GetComponent<Renderer>();
render.sharedMaterial.color = Color.white;
Quad的顏色變了
效果如下圖:
如何來解釋上面的現象呢, 根據官方文件
sharedMaterial 是共用的 Material,稱為共享材質。修改共享材質會改變所用使用該材質的物體,並且編輯器中的材質設定也會改變。
material 是獨立的 Material,返回分配給渲染器的第一個材質。修改材質僅會改變該物體的材質。如果該材質被其他的渲染器使用,將克隆該材質並用於當前的渲染器。
推測 sharedMaterial 和 material 的實現
下面我們通過一些實現猜測 material 是如何實現的:
我們從 UnityEngine.Renderer 中可得知 sharedMaterial 和 material 是屬性(property),當給屬性賦值時會隱式的呼叫 set 方法,當獲取屬性值得時候會隱式的呼叫 get 方法。假設 sharedMaterial 的值變數假設為 _sharedMaterial, material 的值變數假設為 _material。
Material _material;
Material _sharedMaterial;
- 第一個測試:
Material origin_sharedMat = render.sharedMaterial;
Debug.Log(origin_sharedMat == render.sharedMaterial);
// True
呼叫了 sharedMaterial 的 get方法。 可以推斷出 sharedMaterial 的 get:
return _sharedMaterial;
- 第二個測試:
Material new_Mat = new Material (Shader.Find("Standard"));
render.sharedMaterial = new_Mat;
Debug.Log(new_Mat == render.sharedMaterial);
// True
呼叫了 sharedMaterial 的 set方法。 可以推斷出 sharedMaterial 的 set:
_sharedMaterial = value;
- 第三個測試:
Material origin_sharedMat = render.sharedMaterial;
Material origin_Mat = render.material;
Debug.Log(origin_sharedMat == render.sharedMaterial);
Debug.Log(origin_Mat == render.sharedMaterial);
Debug.Log(origin_Mat == render.material);
// Flase True True
在隱式呼叫 material 的 get 後, sharedMaterial 被修改。且 _material 和 _sharedMaterial 相等。
推斷 material 的 get:
if (_sharedMaterial ~= _material) {
_material = new Material (_sharedMaterial);//這一步由第四個測試共同推出
_sharedMaterial = _material;
}
return _material;
- 第四個測試:
Material new_Mat = new Material(Shader.Find("Standard"));
render.sharedMaterial = new_Mat;
Debug.Log(new_Mat == render.sharedMaterial);
Material origin_Mat = render.material;
Debug.Log(new_Mat == origin_Mat);
Debug.Log(origin_Mat == render.sharedMaterial);
Debug.Log(origin_Mat == render.material);
// True Flase True True
很明顯 _material的初始化並未通過 material 屬性,因為未呼叫 material 屬性的 get 方法前,_sharedMaterial 和 _material並不相等。
推斷 sharedMaterial 的 set:
_sharedMaterial = value;
- 第五個測試:
Material new_Mat = new Material(Shader.Find("Standard"));
render.material = new_Mat;
Debug.Log(new_Mat.name);
Debug.Log(render.sharedMaterial.name);
Debug.Log(render.material.name);
// Standard Standard Standard(Instance)
推測 material 的 set:
_sharedMaterial = value;
綜上推斷:
public Material material {
get {
if (_sharedMaterial ~= _material) {
_material = new Material (_sharedMaterial);
_sharedMaterial = _material;
}
return _material;
}
set {
_sharedMaterial = value;
}
}
public Material sharedMaterial {
get {
return _sharedMaterial;
}
set {
_sharedMaterial = value;
}
}
使用 material 時的記憶體洩漏問題
每一次引用 Renderer.material 的時候,都會生成一個新的 material 到記憶體中去,銷燬物體的時候需要我們手動去銷燬該material,否則會一直存在記憶體中。
官方文件說:
This function automatically instantiates the materials and makes them unique to this renderer. It is your responsibility to destroy the materials when the game object is being destroyed. Resources.UnloadUnusedAssets also destroys the materials but it is usually only called when loading a new level.
此方法自動例項化該材質並且使其成為該渲染器獨有的材質。當該遊戲物體被刪除時,你應該手動刪除該材質。當替換場景呼叫 Resources.UnloadUnusedAssets 也可以刪除該材質。
網上的解決方案如下:
編輯器下使用 material, 其他平臺使用 sharedMaterial
public static Material GetMaterial(Renderer render)
{
#if UNITY_EDITOR
return render.material;
#else
return render.sharedMaterial;
#endif
}
如果是主角這一類gameobject身上需要修改材質的屬性或者shader屬性比較多的時候,可以第一次使用material,這樣可以動態的生成一個material例項,然後再使用sharedmaterial,動態的修改這個新生成的material,而且不會建立新的material。
一般情況下,資源屬性的改變情況都是固定的,並非隨機出現。比如,假設GameObject受到攻擊時,其Material屬性改變隨攻擊型別的不同而有三種不同的引數設定。那麼,對於這種需求,我們建議你直接製作三種不同的Material,在Runtime情況下通過程式碼直接替換對應GameObject的Material,而非改變其Material的屬性。這樣,你會發現,成百上千的instance Material在記憶體中消失了,取而代之的,則是這三個不同的Material資源。
如有錯誤,歡迎指出。
email:dxmdxm1992#gmail.com