高通晶片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
更多精彩問題等你回答~
封面圖來源於網路
今天的分享就到這裡。當然,生有涯而知無涯。在漫漫的開發週期中,您看到的這些問題也許都只是冰山一角,我們早已在UWA問答網站上準備了更多的技術話題等你一起來探索和分享。歡迎熱愛進步的你加入,也許你的方法恰能解別人的燃眉之急;而他山之“石”,也能攻你之“玉”。
官網:www.uwa4d.com
官方技術部落格:blog.uwa4d.com
官方問答社群:answer.uwa4d.com
UWA學堂:edu.uwa4d.com
官方技術QQ群:793972859(原群已滿員)