1. 程式人生 > 其它 >高通晶片GPU是否有類似於HSR功能

高通晶片GPU是否有類似於HSR功能

1)高通晶片GPU是否有類似於HSR的功能
​2)UGUI上的RT動圖會不會導致UI更新
3)UI經常迭代外觀,如何儘量少改程式碼
4)開發過程中該使用AssetBundle包模式,還是模擬模式?


這是第279篇UWA技術知識分享的推送。今天我們繼續為大家精選了若干和開發、優化相關的問題,建議閱讀時間10分鐘,認真讀完必有收穫。

UWA 問答社群:answer.uwa4d.com
UWA QQ群2:793972859(原群已滿員)

GPU

Q:高通晶片GPU是否有類似於HSR功能?望解答,謝謝。

A:參考如下:
測試案例1:小米9
點選按鈕新增全屏不透明的Quad,使用Standard Shader,離相機越遠,RenderQueue越小。

結果:

從FPS上看,到100層,幀率也都是60幀。說明小米9上確實有類似於HSR的功能,就是不透明物件不需要手動調整渲染佇列從而讓靠近相機的先渲染。

測試案例2:小米5X
點選按鈕新增全屏不透明的Quad,使用Standard Shader,離相機越遠,RenderQueue越小。

結果:從FPS上看,1層的時候43幀,2層就掉到25幀,3層掉到17幀。說明小米5X上沒有類似於HSR的功能。

測試案例3:小米5X
點選按鈕新增全屏不透明的Quad,使用Standard Shader,離相機越遠,不調整RenderQueue。

結果:從FPS上看,1層的時候43幀,5層的時候依舊43幀。不調整RenderQueue後,因為Unity的不透明是按照由近到遠繪製的,由於Early-Z的關係,導致被遮擋的物體不會進行Fragment Shader計算了,所以幀率比較穩定。

總結:
對於像地形這樣的大面積且Shader複雜度通常比較高的物件,還是需要調整渲染佇列到比較靠後的,因為對於低端機型,並沒有HSR這樣的功能。所以可以通過調整地形的渲染佇列靠後,從而讓Early-Z對於大地形生效,從而減少地形的渲染畫素。

對於高階機型,如小米9,因為其GPU硬體支援HSR功能,所以對於不透明物體來說,渲染佇列沒有關係。

總體來說,還是建議調整大地形的渲染佇列,對低端機型有好處。

感謝小苗子@UWA問答社群提供了回答


UGUI

Q:在UGUI元件上使用RenderTexture動圖會不會導致UI更新?

A:對於這個問題進行了簡單的實驗:實驗設定了一個相機拍攝不停在運動兩個物體,然後得到的RenderTexture賦給UGUI上的100個RawImage。在真機上執行,此時UI更新耗時幾乎為0。

理論上,這些RawImage的頂點屬性沒有發生變化,確實不會導致UI更新。

感謝Faust@UWA問答社群提供了回答


UGUI

Q:問題:之前的專案是用Lua做UI開發的。一個ToLua,一個XLua。策劃或美術經常要調已經做好的UI的外觀,一般需要變佈局或節點層級。最早,程式碼裡訪問某個節點都是用GameObject.Find找到節點,層級關係一變,就需要程式配合改,很麻煩。怎麼做到調整UI,不用改程式碼呢?

思路:想到一個命名規範方案:程式拿到UI的Prefab綁上UI關聯Lua的指令碼後,把所有程式需要訪問的節點,改名成rd_xxxxx,指令碼控制下的子節點rd_開頭的不能重複,如果有列表的子節點,上面再掛一個輔助的指令碼(比如UItemHelper)。

Lua第一次載入時遍歷所有節點,快取所有rd_xxx的節點關係到腳本里(Lua裡),這樣,Lua程式碼裡直接用self.rd_xxxxx就能得到物件,也不怕Prefab層級調整,也不用多次呼叫GameObject.Find。

規定其他人員調整UI Prefab時,不能改掉rd_xxxxx,如果需要改則要程式配合。

參考程式碼:ToLua專案的一個參考修改:

關聯Lua指令碼物件的LuaXXX.cs裡(比如叫LuaMono.cs)初始化,增加:

……
        if (m_params.Count == 0)
        {
            //lua關聯物件自動收集
            SetRDObjectRef(luaClass, this.gameObject, true);
        }
        else
        {
            //節點整理期間的相容(複用UI也走這裡,避免反覆蒐集)
            foreach (ParamItem pi in m_params)
            {
                luaClass[pi.name] = pi.value;
            }
        }
……
 
    /// <summary>
    /// 把節點下所有節點查一下,如果名字是"rd_"開頭的則是程式需要
    /// </summary>
    /// <param name="luaClass"></param>
    /// <param name="go"></param>
    void SetRDObjectRef(LuaTable luaClass,GameObject go,bool is_root = false)
    {
        if(go)
        {
            if(MyExtensions.StringStartsWith(go.name,"rd_"))
            {
#if UNITY_EDITOR
                //檢查是否有重複
                if(luaClass[go.name] != null && !luaClass[go.name].Equals(null))
                {
                    GameFramework.Log.Error("{0} LuaClass already have GO key:{1} {2}", LuaScript, go.name, luaClass[go.name]);
                }
#endif
                luaClass[go.name] = go;
                ParamItem item = new ParamItem();
                item.name = go.name;
                item.value = go;
                m_params.Add(item);
            }
 
            if(is_root || (go.GetComponent<LuaMono>() == null && go.GetComponent<UItemHelper>() == null))
            {
                //遍歷所有子節點
                for(int i=0;i<go.transform.childCount;++i)
                {
                    SetRDObjectRef(luaClass, go.transform.GetChild(i).gameObject);
                }
            }
        }
    }

  

UItemHelper.cs

using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine.UI;
 
public class UItemHelper : MonoBehaviour
{
#if UNITY_EDITOR
    public class ParamItem
    {
        public string name;
        public UnityEngine.Object value;
    }
#endif
 
    Dictionary<string, GameObject> m_nodes = new Dictionary<string, GameObject>();
    bool init = false;
#if UNITY_EDITOR
    [SerializeField]
    protected List<ParamItem> m_params = new List<ParamItem>();
#endif
    void Awake()
    {
        Init();
    }
 
    public void Init()
    {
        if(!init)
        {
            m_nodes.Clear();
            SetRDObjectRef(this.gameObject, true);
            init = true;
        }
    }
 
    /// <summary>
    /// 把節點下所有節點查一下,如果名字是"rd_"開頭的則是程式需要
    /// </summary>
    /// <param name="luaClass"></param>
    /// <param name="go"></param>
    void SetRDObjectRef(GameObject go, bool is_root = false)
    {
        if (go)
        {
            if (MyExtensions.StringStartsWith(go.name, "rd_"))
            {
#if UNITY_EDITOR
                //檢查是否有重複
                if (m_nodes.ContainsKey(go.name))
                {
                    GameFramework.Log.Error("sub item node {0} already have GO key:{1} {2}", this.name, go.name, m_nodes[go.name]);
                }
                else
#endif
                {
                    m_nodes.Add(go.name, go);
#if UNITY_EDITOR
                    ParamItem item = new ParamItem();
                    item.name = go.name;
                    item.value = go;
                    m_params.Add(item);
#endif
                }
            }
 
            if (is_root || (go.GetComponent<LuaMono>() == null && go.GetComponent<UItemHelper>() == null))
            {
                int childCount = go.transform.childCount;
                Transform t = go.transform;
                //遍歷所有子節點
                for (int i = 0; i < childCount; ++i)
                {
                    SetRDObjectRef(t.GetChild(i).gameObject);
                }
            }
        }
    }
 
    public GameObject GetNode(string name)
    {
        if(m_nodes.ContainsKey(name))
        {
            return m_nodes[name];
        }
        return null;
    }
 
    public Image GetNodeImage(string name)
    {
        if (m_nodes.ContainsKey(name))
        {
            GameObject go = m_nodes[name];
            if (go != null)
            {
                Image img = go.GetComponent<Image>();
                return img;
            }
        }
        return null;
    }
 
    public Text GetNodeText(string name)
    {
        if (m_nodes.ContainsKey(name))
        {
            GameObject go = m_nodes[name];
            if (go != null)
            {
                Text txt = go.GetComponent<Text>();
                return txt;
            }
        }
        return null;
    }
 
    public Button GetNodeButton(string name)
    {
        if (m_nodes.ContainsKey(name))
        {
            GameObject go = m_nodes[name];
            if (go != null)
            {
                Button btn = go.GetComponent<Button>();
                return btn;
            }
        }
        return null;
    }
 
    // 後期新增的節點
    public void AddNode(GameObject go)
    {
#if UNITY_EDITOR
        //檢查是否有重複
        if (m_nodes.ContainsKey(go.name))
        {
            GameFramework.Log.Error("sub item node {0} already have GO key:{1} {2}", this.name, go.name, m_nodes[go.name]);
        }
        else
#endif
        {
            m_nodes.Add(go.name, go);
#if UNITY_EDITOR
            ParamItem item = new ParamItem();
            item.name = go.name;
            item.value = go;
            m_params.Add(item);
#endif
        }
    }
 
    // 刪除後期新增的節點
    public void RemoveNode(GameObject go)
    {
#if UNITY_EDITOR
        if (m_nodes.ContainsKey(go.name))
#endif
        {
            m_nodes.Remove(go.name);
#if UNITY_EDITOR
            ParamItem item = new ParamItem();
            item.name = go.name;
            item.value = go;
            m_params.Remove(item);
#endif
        }
    }
 
    public Dictionary<string, GameObject> GetAllRdGameObject()
    {
        return m_nodes;
    }
}

  

其他:當然各個專案情況不一樣,需要各自實現。不知大家專案裡遇到類似問題,用的是什麼方案?

比如有的方案不希望一開始初始化,可以在建立關聯Lua時,設定元表,重寫_index,當self.rd_XXX這樣訪問時,取到索引的字首是rd_,則去判定快取,沒有則去找對應節點關聯。

A1:基於路徑的UI控制元件查詢還是比較脆弱,直接使用Unity序列化引用更好,可以隨意修改結構改名字不用擔心丟失。

這裡有一個不錯的方案:UIControlBinding

感謝張迪@UWA問答社群提供了回答

A2:別用GameObject.Find方法,編輯器序列化直接繫結到Lua。和使用原生CS繫結一樣。

感謝1 9 7 3-311135@UWA問答社群提供了回答

A3:把UI控制元件直接序列化到C#指令碼上,執行時不要用Find和GetComponent,UI面板的Prefab例項化後,直接把這些物件塞到一個Lua Table裡傳給Lua。

感謝唐崇@UWA問答社群提供了回答


Asset

Q:使用AssetBundle包,能夠較大程度保證資源的正確性,但一點資源的修改就要打包,比較浪費時間,特別是美術、策劃同學對它比較抵制;使用模擬模式,大概只是開發效率高一些,效果錯誤、迴圈引用等問題要延後到打包後才能發現(當然有很多其他方式可以提早發現,但屬於附加手段)。

最近專案中發現,即使預載入了資源,Play過程中還是會出現非常嚴重的卡頓(1s+),這麼長時間的卡幀導致表現效果直接不對了:比如,1s從起點移動到終點的過程,直接變成閃現到終點,成了Bug。

卡頓原因查下來是:AssetDatabase.Load出來的Prefab並不會將其依賴的Texture一起載入進記憶體,Prefab第一次渲染的時候Texture才會載入。我們這一個很複雜的(未優化)技能特效,用了40MB+的Texture,把UploadTexture卡了。

以這樣一個特殊的問題為契機,準備重新考慮下要實行的方案,希望大家能多給些建議,可以是二者的對比選擇,也可以是其他更好的做法或實現。

A:把Unity專案放在高速SSD中應該能緩解不少。

之前見到一個方案是在預載入時,建立一個玩家看不到的相機,把技能特效的Animator、ParticleSystem和音訊元件等都執行一下,同時呼叫Camere.Render,強制把各類資源都WarmUp好,這樣進入遊戲後基本沒有IO操作,應該能大大緩解題主描述的問題。

針對一些特效是被Timeline的軌道管理的,然後Timeline本身是一個獨立模組的,可以根據技能配置找到特效(技能配置關聯Timeline,Timeline關聯特效),然後對特效預渲染就可以了,Timeline只是邏輯流程控制器。當然如果Timeline本身卡頓,還是要預執行一遍更好。

感謝張迪@UWA問答社群提供了回答

20211213
更多精彩問題等你回答~

  1. Unity增量打包AssetBundle沒變化的資源也會被重新打包
  2. 在模型有UV2的情況下開啟Generate Lightmap UVs
  3. 如何實現AAB包的增量更新

封面圖來源於網路


今天的分享就到這裡。當然,生有涯而知無涯。在漫漫的開發週期中,您看到的這些問題也許都只是冰山一角,我們早已在UWA問答網站上準備了更多的技術話題等你一起來探索和分享。歡迎熱愛進步的你加入,也許你的方法恰能解別人的燃眉之急;而他山之“石”,也能攻你之“玉”。

官網:www.uwa4d.com
官方技術部落格:blog.uwa4d.com
官方問答社群:answer.uwa4d.com
UWA學堂:edu.uwa4d.com
官方技術QQ群:793972859(原群已滿員)