half-life2的圖形系統研究心得(轉)

post0發表於2007-08-12
half-life2的圖形系統研究心得(轉)[@more@]

  小T一邊作自己的3D引擎,一邊研究half-life2的圖形引擎,留下點心得。

  

  整體上half-life2給小T的感覺一是龐大,二就是難讀,com方式組織引擎,小T也不敢妄加評價,只是小T覺得整個引擎非常的難讀。小T只有藉助偵錯程式才勉強對half-life2的圖形引擎有個大致的瞭解,至於怎麼編譯half-life2,怎麼執行,以及怎樣除錯,小T就不詳細的描述了,到網上隨便一搜尋就能找到一大堆,這裡只是簡單的看看他的圖形系統。

  

  當前的3D環境,想要提高渲染速度,一個最基本的方法就是要減少硬體的渲染狀態的切換,今天小T就是講hl2的渲染狀態管理部分的。在hl2裡面,渲染狀態主要被放在了一個叫ShadowState_t的結構裡面,這個結構對應了大多數的硬體渲染狀態,渲染系統維護多個ShadowState_t結構,在必要的時候把ShadowState_t的內容真正的設定到硬體上面,從而減少硬體的狀態切換。當然實際上卻沒有這麼簡單,上面只是個簡單的介紹,下面開始詳細的解釋hl2怎麼進行硬體狀態管理。

  

  hl2裡面渲染狀態管理的一個比較重要的類就是CTransitionTable,從名字上面看,這個類描述的是一種轉換,他其實就是描述了,渲染過程中硬體渲染狀態的轉換流程,hl2把硬體狀態的管理也放在了這個裡面,舉個例子來說可能比較清楚。

  

  比如:整個場景裡面有2個物品,第一個物品使用一組渲染狀態,第二個物品使用另外一組渲染狀態,我們先渲染第一個物品,設定成第一個物品的渲染狀態,接著渲染第二個物品,設定第二個物品的渲染狀態,這個就是正常的操作方式,如果第一個物品和第二個物品的渲染狀態有相同的,那麼有些設定renderstate的函式就不用呼叫,我們可以把渲染狀態看作是一種狀態機,從第一種狀態轉換到第二種狀態,我們要作一些事情,如果我們能建立一張行動表,表裡面記錄狀態轉換的時候要執行的動作,那麼我們就可以簡化這種狀態管理模型,這個就是CTransitionTable類完成的工作。

  

  對於場景裡面的每個materila(順便說一句,hl2也是按照material的方式組織渲染動作的),都對應了一種渲染狀態,這些渲染狀態都被CTransitionTable類記錄下來,每一個渲染狀態對應一個唯一的id,那麼material只用記錄他對應的渲染狀態id就能完成自己的渲染了。比如我們要渲染一個物品,我們告訴渲染系統我們使用的渲染狀態id,渲染系統自動完成渲染狀態的設定,自動完成渲染狀態切換的最小化任務,然後我們呼叫一個drawprimitive就ok。實際上的操作也沒有這麼簡單,我們再看看實際一點的東西。

  

  hl2裡面的渲染狀態操作都被封裝到了ShaderRenderState_t類裡面

  struct ShaderRenderState_t

  {

  RenderPassList_t m_Snapshots[4];//代表了一種渲染方式,緩衝4種渲染方式0=預設,1=帶color資料,2=帶alpha資料,3=color+alpha

  int m_Flags;???????????// 標誌,

  VertexFormat_t m_VertexFormat;??// 頂點格式

  int m_VertexUsage;

  };

  

  struct RenderPassList_t

  {

  int m_nPassCount; ????????// 渲染pass的數目

  StateSnapshot_t m_Snapshot[MAX_RENDER_PASSES]; // 實際是一個short形式,就是渲染狀態id

  };

  

  解釋下RenderPass這個東西,一個material在hl2裡面渲染方式分成了4種,每種都對應了一個RenderPassList_t的類,而ShaderRenderState_t把這4種渲染方式都快取了,嗯,舉個例子,比如我現在想要按照預設的方式渲染一種material,我傳遞一個ShaderRenderState_t的結構給渲染系統,渲染系統根據我要求的渲染方式索引到正確的RenderPassList_t元素,使用裡面記錄的StateSnapshot_t陣列裡面的某個正確的id來獲取到正確的渲染狀態。

  

  我們已經知道了渲染系統的客戶端怎樣要求渲染系統完成自己的渲染狀態設定了,接下來看看渲染系統本身怎樣完成這個工作,當渲染系統得到一個渲染狀態id以後,就應該要獲取到與之對應的渲染狀態,並且正確的設定好這個狀態,換句話說也就是要完成當前狀態到新狀態的切換,首先看看渲染系統怎麼去找到正確的渲染狀態,這就落在了IShaderAPI和CTransitionTable身上了,小T使用的是DX9的ShaderApi,所以這個任務是由CShaderAPIDX8這個類來完成了,在CShaderAPIDX8類裡面有一個CTransitionTable類的資料成員m_TransitionTable,每當要獲取到一個渲染狀態id以後,ShaderApi就告訴m_TransitionTable要使用新的渲染狀態了,並且傳遞新狀態的id,到這裡先停一下,一直小T都使用渲染狀態這幾個字在描述,那究竟hl2使用什麼樣子的資料結構來表示實際的渲染狀態呢?ShadowState_t對應的是硬體狀態,而還有一個東西對應了vs,ps的狀態,他也是要在狀態轉換的時候進行設定的,渲染狀態id對應的其實是SnapshotShaderState_t 結構:

  

  struct SnapshotShaderState_t

  ??{

  ????ShadowShaderState_t m_ShaderState;

  ????ShadowStateId_t m_ShadowStateId;

  ??};

  

  ??struct ShadowShaderState_t

  ?? {

  ????// The vertex + pixel shader group to use...

  ????VertexShader_t m_VertexShader;

  ????PixelShader_t m_PixelShader;

  ????// The static vertex + pixel shader indices

  ????int m_nStaticVshIndex;

  ????int m_nStaticPshIndex;

  ??};

  

  ShaderApi獲得了新的渲染狀態id以後,和當前的渲染狀態id比較,如果不相同,則獲取到ShadowStateId,如果也不相同,那就要設定新的ShadowState了,這個操作下面講解。然後,ShaderApi儲存當前的渲染狀態id,儲存ShadowStateId,同時設定ShadowShaderState,這些都設定完成了,硬體的渲染狀態也就設定完成了,然後的任務就是呼叫DrawPrimitive等等函式完成繪製的時候了,這個繪製是由各個vs,ps完成的,關於這個部分,小T留到下次講解。

  

  現在我們來看看shaderapi,怎麼設定新的ShadowState,基本的方式就是獲得兩個狀態之間的轉換表,執行表裡面規定的動作,而實際上也是這樣進行的,那現在的重點就落在了轉換表上面。

  

  CTransitionTable儲存了當前渲染過程中所有可能的ShadowState(關於這些是怎麼儲存起來的,這些資訊是怎麼獲取到了,下面講解),然後再這些狀態中間拉了一張網,每兩個狀態之間總有一個弧,同時儲存了兩個狀態進行切換的時候要執行的操作,CTransitionTable的主要資料成員如下:

  

  CUtlVector< ShadowState_t > m_ShadowStateList ; // 全部的ShadowState_t的vector,ShadowState的id作為這個vector的下標

  CUtlVector< CUtlVector< TransitionList_t > > m_TransitionTable; // 狀態轉換表,TransitionList_t就代表了狀態轉換的操作.

  

  ?struct TransitionList_t

  ??{

  ????unsigned short m_FirstOperation;

  ????unsigned char m_NumOperations;

  ????unsigned char m_nOpCountInStateBlock;

  ????IDirect3DStateBlock9 *m_pStateBlock;

  ??};

  

  ??struct TransitionOp_t

  ??{

  ????ApplyStateFunc_t m_Op;

  ????int m_Argument;

  ??};

  

  TransitionOp_t也放到了一個vector裡面,而TransitionList_t的第一個和第二個成員能在這個vector裡面定址,從而定位到實際的操作,而TransitionOp_t定義的就是實際的操作,第一個成員是一個函式指標,執行操作就是呼叫那個函式指標,並且傳遞兩個引數,一個是新的ShadowState_t,一個就是結構裡面的另外一個成員。

  

  總結下,CTransitionTable儲存了全部的ShadowState_t,儲存全部的TransitionOp,都是使用下表作為索引訪問,再全部的ShadowState_t之間建立TransitionList,當要進行狀態切換的時候執行狀態之間定義的TransitionOp就完成了狀態的切換了.那ShaderApi是怎麼建立ShadowState_t表格和TransitionOP表格的呢?

  

  ShadowState_t的表格是在material建立的時候完成建立的,建立了一個新的ShadowState_t了以後就會向轉換表裡面加入新的節點,並且設定好轉換操作,而這動作卻是藉助Draw動作完成了。

  

  下面結合原始碼看看上面這些功能的具體實現。

  

  先看看draw的流程,最常用操作就是建立一個DynamicMesh,然後使用一個MeshBuilder修改剛剛建立的DynamicMesh,然後呼叫mesh的Draw函式,我們就從這裡開始。

  

  void CDynamicMeshDX8::Draw( int firstIndex, int numIndices )

  CBaseMeshDX8::DrawMesh(); // 呼叫自己類的函式,實際上是從父類繼承來的

  ShaderAPI()->DrawMesh( this ); // 呼叫CShaderAPIDX8的函式

  m_pMaterial->DrawMesh( m_pRenderMesh ); // 轉呼叫CMaterial的DrawMesh函式

  ShaderSystem()->DrawElements( m_pShader, m_pShaderParams, &m_ShaderRenderState );//呼叫CShaderSystem函式

  // 計算當前這次繪製操作的方式:普通?color?alpha?alpha+color?

  int mod = pShader->ComputeModulationFlags( params );

  g_pShaderAPI->SetDefaultState(); // 呼叫CShaderAPIDX8的函式

  ShaderUtil()->SetDefaultState(); // 實際上回到了CMaterialSystem:SetDefaultState()

  // 一系列的CShaderAPIDX8的函式,這些函式只是比較紀錄,並不真正的修改硬體的狀態

  // 準備渲染,紀錄當前的渲染操作,接下來的CurrentStateSnapshot()函式返回的就是當前渲染的狀態id

  PrepForShaderDraw( pShader, params, pRenderState, mod );

  // 呼叫CShaderAPIDX8的函式,紀錄當前要渲染的

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/8225414/viewspace-951699/,如需轉載,請註明出處,否則將追究法律責任。

相關文章