游戲引擎是如何調度渲染和邏輯代碼的?
前排跑題警告,想借著這個題交流一下最近搗鼓的一套渲染器的接口流程,而不是單純局限于Unity的時鐘處理。下面提到的這套接口流程還沒有經過任何驗證,所以如果有大佬能指出缺陷,將會是十分珍貴的。
我的目標就是使用原生的DirectX 12開發獨立渲染模塊,而這個模塊要做到的是可以打包成動態庫給其他游戲引擎使用,因此要做到本身只做純渲染的工作,輸入和輸出都相對干凈,同時還要盡可能繞開C++標準中沒有的部分,防止踩坑,此為前提。
首先,我將整體框架解耦成:窗口,渲染器兩個部分,窗口可以是最簡單的Winmain + WNDCLASS,也可以是Unity或者Unreal那種豐富到極致的花里胡哨的GUI,而我認為這些窗口無論怎么千奇百怪也離不開Backbuffer, Swapchain等等,因此,我把窗口部分向渲染部分傳遞的數據整理下來:
有些操作是不同窗口不同做法的,這就意味著這些操作的事件需要經過窗口層傳遞到渲染層,這就多開一個單獨存儲函數指針的類型:
因為調用的主動權一直是在窗口層,而渲染層只有被調用權,因此窗口層將會持有渲染層的“引用”,這個引用將會拆解成一個void*指向類的地址,和一個存儲所有渲染層應有的邏輯的函數指針列表:
這里Initialize也就是包括了整個渲染器的構造,并且返回指針,Dispose自然就是析構函數,其他兩個則是窗口大小改變和每幀的繪制函數,最后,渲染層的動態庫將會提供一個生成這個IRendererBase的裸函數,拼上最后一塊磚:
這樣如果要解耦合渲染器部分,比如打包DLL,基本只需要將GetRendererFunction函數標記為DLL導出格式,窗口層的設計就比較隨意,只要保證讓渲染器在窗口初始化之后再構造,在窗口銷毀之前被銷毀,并且每幀調用一下,就可以了。
在最原始的windows app里(忽略Debug部分)調用過程就幾行,其中D3DApp是窗口:
Run內部的邏輯比較簡單粗暴,就是while循環調用Draw,外加一些更新時間等操作。因為渲染器是一個多線程并行的多幀異步渲染模式,因此Draw函數邏輯很簡單并不會有什么開銷,唯一的阻塞就是需要等待兩幀之前的GPU的渲染結果,當然這也是沒有辦法的事情。
通過這套系統,把可能的邏輯和渲染層的溝通都涵蓋住了,如果邏輯需要更新,則通過DataPack傳遞進去,渲染器因為只負責把結果輸出到屏幕,所以可以不設計任何數據上和邏輯上的回調。