Home > 圖像編程 > 跨平台的圖像渲染引擎

跨平台的圖像渲染引擎

今天轉一個話題,暫時不想 Script 的部份。本來想寫一些圖像的代碼,但敲了幾個鍵又停下來思考,猶豫不決,便希望寫這一篇 blog 整理一下思緒。希望大家能多給意見。

沒有一個圖像渲染引擎可以適合所有平台、所有應用。我暫時設定的目標是

  1. Windows Direct3D 9
  2. Windows Direct3D 10
  3. Windows OpenGL
  4. Linux
  5. OSX
  6. M記3代(實際是2代? ^_^)
  7. S記3代
  8. S記手提
  9. N記W機

當然,我不是要立即把它們全部實現,而是在開始做的時候考慮到它們。當中,電腦的平台上只考慮支援有 Shader 的硬件 (其實不同的 GPU 可以視為不同的平台)。而一些遊戲機是只有 Fixed Pipeline的。

跨平台方案

如下圖所示,一個圖像渲染的引擎大概是介乎應用程式和 Graphics API之間。

renderengine1.png

有一些平台,例如一些遊戲機,可以讓應用程式跨過 Graphics API 和 OS/Driver,直接操作硬件。這種方式和 DOS 年代的電腦遊戲做法一樣。

要實現跨平台的應用程式 (遊戲),我想到有4個方案

  1. 使用跨平台的 Graphics API (如 OpenGL、或 Java 的甚麼幾個數字標準…)
  2. 每一個平台編寫一個把平台Graphics API 抽像化的 API
  3. 每一個平台編寫一個 Render Engine
  4. 每一個平台編寫一個 Application

由於我的目標比較廣,基本上第一個方案不作考慮。

自行抽像化的 Graphics API

這個方案是把不同的 Graphics API (例如 Direct3D 9 和 OpenGL) 抽像化到一個自定的介面,使 Render Engine 變成平台無關。我曾經實現過這個方案,還試驗過兩種編程方式。

Abstraction Factory Pattern

第一次是跟據 GoF 的 Abstraction Factory Pattern 來實現:

renderengine2.png

優點是,第一,使用者不需要包含平台相關的 header 檔;第二,Binding 是動態的,即是可以在執行期選擇使用那 “Driver” 也可以 (例如 3D Studio Max 可以選擇 Direct3D 和 OpenGL 的 Driver);第三,加入新的 “Driver” 不會影響原有的代碼,這方面的擴充很容易。

可是,因為要經過 virtual function 來產生這種抽像化,效能較低。

另外,要找出一個共通的介面並不容易,有可能為了另一個 Graphics API 而放棄一些功能,換句話說,就是建立一個交集 (intersection set)。這點是這類方案的通病,抽像化就是找共通部份,無法共通 (例如自己模擬出相同功能)的部份只能放棄。如果不想放棄的話,也可以建立一個聯集 (union set),把一些 Driver 裡不能實現的功能設為空函數。但是使用這個介面的程式就要自行選擇要呼叫的函式。

Conditional Compilation (Macro)

由於效能的問題,隔了兩年之後我們嘗試了第二種方法做 Graphics API 的抽像化,就是使用 Macro 在編譯期來選擇用那一個 API:

#if GRAPHICS_API == DIRECT3D9
    #include "d3d9.h"
#elif GRAPHICS_API == OPENGL
    #include "GL/gl.h"
#endif    

class Device
{
public:
    // 共同的介面
    void Clear(/*...*/);
    // ...
    
    // 個別 Graphics API 的介面
#if GRAPHICS_API == DIRECT3D9
    LPDIRECT3D9 mDevice;
    // ... 
#elif GRAPHICS_API == OPENGL
    // ... 
#endif    
};

inline void Device::Clear(/*...*/)
{
#if GRAPHICS_API == DIRECT3D9
    mDevice->Clear(/*... */);
#elif GRAPHICS_API == OPENGL
    glClear(/*...*/);
#endif
}

透過 inline function,使用者無需透過多一重間接去呼叫平台相關的 API,但又保持相同的介面。

缺點是,代碼變得難看。並且只可以在編譯時缺定使用那個 “Driver” (理論上是可以透過選擇 DLL 來做到選擇 “Driver”,但我現時仍未清楚 DLL call 的實現方式,不知道當中是否有 Run-time overheads,請知道的人指教)。

抽像化 Render Engine

我們看到,從 Graphics API 層面去抽像化有各種缺點。一個直接的想法就是把抽像的層面向上推,編寫每個平台不同的 Render Engine。

要做到能優化每個平台提供的功能,這個方案似乎是唯一辦法。事實上在我上一個項目裡,除了在 “Driver” 層面加入平台相關的 Macro 外,在較上層也有平台相關的代碼。

這個方案的目的就是希望透過抽像化,使應用程式的代碼變成平台無關。那麼,問題就變更為,應該如何設計一個平台無關的介面。

跟據我的經驗,這些介面最重要的是如何表達要渲染的物件。這些物件可以用 Scene-graph,或是簡單的一條 Renderable object list去表達。按我的概念,從應用程式來說,Render Engine 最基本要使用以下的物件:

  • Geometry
  • Material
  • Light
  • Camera
  • Animation

理論上,再簡單些的話,Material 和 Light 可以省略 (例如寫一個像《無限回廊》的遊戲)。

以上談及的些物件是儲存有平台相關的內容 (例如有 Shader parameter 和 render states 的 material),但應用程式可以把這些物件當成基本單位使用。例如每個平台載入自己格式的 geometry、material。

應用程式

最後,我必須考虑到,每個平台除了在檔案格式及內容會有分別,還存在有平台的差異,因為:

  1. 每個平台的遊戲邏輯會有分別 (這是最大的原因,亦不可試圖消除這差異)
  2. 每個平台的顯示比及其他畫面性質的差異 (還好我沒有考慮兩螢幕的手提遊戲機)
  3. 每個平台的輸入模式差異而影響渲染內容

分析

我現時考慮會放棄以前用 Graphics API 抽像的方式去做這次的試驗。但應該如何分割 Render Engine 的平台共通和差異部份是接下來的課題。

在最近的經歷裡,我深感 Graphics API 並非跨平台的主要問題。反而是硬件是否相似。比如現時的兩部較高階遊戲機,GPU 是 ATI 和 NVidia 的特製產品,由於基於同一代的 Shader Model,硬件的功能 (不計算效能) 是和同一代的 PC 顯示卡差不多。雖然 API 不同,但 API 最終都是執行那些硬件的功能,因此實際上的差異只是來自 API 包裝的方法,以及額外在軟件上提供的功能 (例如資源管理、Profiling 等)。對於一些較低階的硬件,例如 N記家用街和各手提遊戲機,功能上反而差異較大,可能 Render Engine 的大部份也需要平台相關 。

結語

理想地,這個世界只有一種遊戲專用的硬件、一個 OS、一套 Graphics API,那麼這篇文章就不存在了。但概然你在看這句話,就是因為我們有不同的平台,各自有不同的性能,也互相在競爭著,而不給予程序員過舒適的日子。

編寫跨平台的遊戲以一個業餘的項目來說似乎沒有甚麼用,可是這卻是現實世界裡必須跨過的難關。所以我希望再思考這個問題,多做一些實驗。

Comments:2

Edwin 08-03-06 (四) 2:08

DLL 在被載入之後,會影射入程序的相同記憶體空間,所以Call一個DLL 函數 和一個普通函數理論上是一樣的,但當然,可能有memory paging,也可能較難給編譯器優化。

Edwin 08-03-06 (四) 2:11

另,Milo兄,我最近寫了兩個開源的項目,有空希望你給點意見:

amop : http://code.google.com/p/amop/
ysql : http://code.google.com/p/ysql/

Comment Form
Remember personal info

Trackbacks:0

Trackback URL for this entry
http://miloyip.seezone.net/wp-trackback.php?p=28
Listed below are links to weblogs that reference
跨平台的圖像渲染引擎 from Milo的遊戲開發

Home > 圖像編程 > 跨平台的圖像渲染引擎

Search
Feeds
Meta

Return to page top