使用MaterialPropertyBlock來(lái)替換Material屬性操作

在unite2017國(guó)外技術(shù)專場(chǎng)中Arturo Nú?ez在他的shader性能與優(yōu)化專題中提到了一個(gè)這樣的優(yōu)化建議那就是使用材質(zhì)屬性塊,其原話是Use MaterialPropertyBlock
Is faster to set properties using a MaterialPropertyBlock rather than
material.SetFloat(); Material.SetColor();

官網(wǎng)文檔

因此我特意查找了下關(guān)于MaterialPropertyBlock的官方文檔,文檔是這樣說(shuō)的,材質(zhì)屬性塊被用于Graphics.DrawMesh 和 Renderer.SetPropertyBlock兩個(gè)API,當(dāng)我們想要繪制許多相同材質(zhì)但不同屬性的對(duì)象時(shí)可以使用它。例如你想改變每個(gè)繪制網(wǎng)格的顏色,但是它卻不會(huì)改變渲染器的狀態(tài)

我們來(lái)看看Renderer這個(gè)類,它包含了material,sharedMaterial這兩個(gè)屬性;GetPropertyBlock,SetPropertyBlock兩個(gè)函數(shù),其中兩個(gè)屬性是用來(lái)訪問(wèn)和改變材質(zhì)的,而兩個(gè)函數(shù)是用來(lái)設(shè)置和獲取材質(zhì)屬性塊的,我們知道,當(dāng)我們操作材質(zhì)共性時(shí),可以使用sharedMaterial屬性,改變這個(gè)屬性,那么所有使用此材質(zhì)的物件都將會(huì)改變,而我們需要改變單一材質(zhì)時(shí),需要使用material屬性,而在第一次使用material時(shí)其實(shí)是會(huì)生成一份材質(zhì)拷貝的,即material(Instance)

實(shí)驗(yàn)

首先聲明兩個(gè)數(shù)組,一個(gè)用來(lái)保存用于操作材質(zhì),另一個(gè)用來(lái)保存操作材質(zhì)屬性塊

GameObject[] listObj = null;
GameObject[] listProp = null;

再次聲明一個(gè)公共變量,用來(lái)控制數(shù)組長(zhǎng)度,以及一個(gè)材質(zhì)屬性塊

public int objCount = 100;
MaterialPropertyBlock prop = null;

然后在Start函數(shù)中做初始化工作,我們?cè)谄聊蛔髠?cè)空間生成objCount個(gè)球體Sphere用來(lái)處理材質(zhì)材質(zhì),在屏幕右側(cè)空間生成objCount個(gè)球體Sphere用來(lái)處理材質(zhì)屬性塊

void Start () {
        colorID = Shader.PropertyToID("_Color");
        prop = new MaterialPropertyBlock();
        var obj = Resources.Load("Perfabs/Sphere") as GameObject;
        listObj = new GameObject[objCount];
        listProp = new GameObject[objCount];
        for (int i = 0; i < objCount; ++i)
        {
            int x = Random.Range(-6,-2);
            int y = Random.Range(-4, 4);
            int z = Random.Range(-4, 4);
            GameObject o = Instantiate(obj);
            o.name = i.ToString();
            o.transform.localPosition = new Vector3(x,y,z);
            listObj[i] = o;
        }
        for (int i = 0; i < objCount; ++i)
        {
            int x = Random.Range(2, 6);
            int y = Random.Range(-4, 4);
            int z = Random.Range(-4, 4);
            GameObject o = Instantiate(obj);
            o.name = (objCount + i).ToString();
            o.transform.localPosition = new Vector3(x, y, z);
            listProp[i] = o;
        }
    }

然后我們?cè)赨pdate函數(shù)中響應(yīng)我們的操作,這里我使用按鍵上下健位來(lái)操作

void Update () {
        if (Input.GetKeyDown(KeyCode.DownArrow))
        {
            Stopwatch sw = new Stopwatch();
            sw.Start();
            for (int i = 0; i < objCount; ++i)
            {
                float r = Random.Range(0, 1f);
                float g = Random.Range(0, 1f);
                float b = Random.Range(0, 1f);
                listObj[i].GetComponent<Renderer>().material.SetColor("_Color", new Color(r, g, b, 1));
            }
            sw.Stop();     
            UnityEngine.Debug.Log(string.Format("material total: {0:F4} ms", (float)sw.ElapsedTicks *1000 / Stopwatch.Frequency));
        }
        if (Input.GetKeyDown(KeyCode.UpArrow))
        {
            Stopwatch sw = new Stopwatch();
            sw.Start();
            for (int i = 0; i < objCount; ++i)
            {
                float r = Random.Range(0, 1f);
                float g = Random.Range(0, 1f);
                float b = Random.Range(0, 1f);
                listProp[i].GetComponent<Renderer>().GetPropertyBlock(prop);
                prop.SetColor(colorID, new Color(r, g, b, 1));
                listProp[i].GetComponent<Renderer>().SetPropertyBlock(prop);             
            }
            sw.Stop();
            UnityEngine.Debug.Log(string.Format("MaterialPropertyBlock total: {0:F4} ms", (float)sw.ElapsedTicks * 1000 / Stopwatch.Frequency));
        }
    }

結(jié)論

然后我們?cè)賮?lái)看一下對(duì)比數(shù)據(jù)


table.png

從結(jié)果對(duì)比來(lái)看,確實(shí)使用材質(zhì)屬性塊要快于使用材質(zhì),其消耗將近是操作材質(zhì)耗時(shí)的四分之一。同時(shí)不管是材質(zhì)還是材質(zhì)屬性塊,第一次操作比后面的操作耗時(shí)要大,尤其是材質(zhì),可見(jiàn)在第一次使用材質(zhì)改變屬性操作時(shí),其拷貝操作消耗還是非常大的。同時(shí)我也通過(guò)profiler的memory模塊,切換進(jìn)Detailed選項(xiàng),對(duì)其進(jìn)行采樣,可以發(fā)現(xiàn)在Sence Memory下面會(huì)有material的拷貝(材質(zhì)操作導(dǎo)致,而材質(zhì)屬性操作不會(huì))

游戲中處理

正如官方文檔介紹材質(zhì)屬性塊一樣,unity地型引擎正是使用材質(zhì)屬性塊來(lái)繪制樹(shù)的,所有的樹(shù)使用的是相同的材質(zhì),但是每棵樹(shù)有不同的顏色,縮放和風(fēng)因子。對(duì)于大場(chǎng)景大世界來(lái)說(shuō),我們肯定是采取動(dòng)態(tài)加載來(lái)處理地圖的,這個(gè)時(shí)候我們可以配合Gpu Instance來(lái)進(jìn)一步的提高性能,使用Gpu Instance一是可以省去實(shí)體對(duì)象本身的開(kāi)銷,二是能夠起到減少Drawcall的作用,同時(shí)還能減少動(dòng)態(tài)合批的cpu開(kāi)銷,靜態(tài)合批的內(nèi)存開(kāi)銷;可謂一舉多得,遺憾的只能在ES3.0以上的設(shè)備上使用。對(duì)于一些游戲中存在自定義皮膚顏色玩法的,材質(zhì)屬性塊的優(yōu)勢(shì)就可以發(fā)揮出來(lái)了,你想當(dāng)你100個(gè)不同玩家同屏?xí)r,如果使用材質(zhì)操作顏色屬性的話,那么首先就存在100份材質(zhì)拷貝的實(shí)例,其次,材質(zhì)操作屬性本身就比材質(zhì)屬性塊操作要慢那么點(diǎn),在性能優(yōu)化中一毫秒的優(yōu)化就是勝利,這了一毫秒那里一毫秒加起來(lái)就不得了了。

最后

這里給出下載地址
Arturo Nú?ez 的shader性能與優(yōu)化的工程下載地址
本次測(cè)試工程

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容