close

最近在開發遊戲上一直碰到效能上的瓶頸。

目前的遊戲高畫質可以算是基本上的需求,傳統的640 x 480的解析度已經不能滿足玩家的眼睛。

1280 x 720以上的高清時代已經來臨。

面對這樣高清的解析度,Pixel Shader的負擔就越重,減少每個Pixel著色的次數將是首要目標。

要怎樣才能減少Pixel Over Draw的現象呢?

首先大家一定會先想到,將要Render的物件做排序,讓物件由前往後排序,在依著前到後的順序Render。

如此就可以依著順序把前面的物件做Z-Write,接下來再Redner後面的物件時會做Z-Test,當發現要Render上去的那一點的Pixel的深度值比較大時就不會再針對這個Pixel著色一次。

減少著色有什麼好處?

當然就是增加效能啦。

事實上一個Pixel Shader在執行時,並不是只有輸出這個Pixel的顏色,基本上會做以下的事情。

1. Sample Texture

2. Normalize Vector

3. Lighting

而這些事情會取決於貼圖的張數、光源數量還有頂點的資料量來影響計算的複雜度。

而往往要畫面好,通常都是要靠Pixel Shader。

(圖片出處於wiki http://upload.wikimedia.org/wikipedia/commons/8/84/Phong-shading-sample.jpg)

這張圖可以明顯看出Vertex Lighting與Pixel Lighting的效果差異。

Vertex Lighting即頂點打光,也就是光只影響頂點並計算好光強度與要輸出的顏色,每個頂點間做內插來顯示該Pixel的顏色,如此看起來很平不真實。

Pixel Lighting即像素打光,每個像素都有自己的打光計算,依據每個像素自己的法向量與頂點資訊來計算光的強度和輸出的顏色,看起來很光滑而且比較好看。

如果一個Pixel需要0.00001ms的時間來Render,每個都Over Draw 10次,

那麼1280 x 720的解析度將要花上92.16ms來Render一個Frame。

這太可怕了,這樣遊戲根本跑不動。

難到剛剛說的將物件排序後Render沒有辦法解決嗎?

老實說解決的是有限的

目前的遊戲都是大場景,物件的複雜度很高,而且更不用說美術會幫你想好你會需要怎樣的將物件分割,好讓你做排序。

在實做上幾乎是派不上用場。

但是身為一個遊戲開發者,不能因為如此就妥協,找出好的解法就是我們研發的工作。

想來想去,如何可以讓Over Draw的次數減少到1次,又不需要排序(排序竟然無用,排了也是浪費CPU時間)

竟然Pixel Shader是在Z-Test成功時才會做,那麼是不是只要把正確的Z值先寫到Z-Buffer裡

那麼只要是真正在Render物件時要Test的Z值與Z-Buffer裡的Z值相同才執行該Pixel Shader。

哇,這真是太美好了,這麼說來,Pixel Shader只會做一次ㄟ

到底要怎麼執行這種技術呢

首先寫一個簡單的Sahder

float4x4 WorldViewProj : WORLDVIEWPROJECTION;

float4 VSMain(float4 inPos: POSITION) : POSITION
{
    return mul(inPos, WorldViewProj);
}

float4 PSMain() : COLOR0
{
    float4 color = 0.0f;
    return color;
}

這個Shader只有一個目的,Vertex Shader計算vertex投影到screen space的轉換矩陣得到深度值並寫到硬體z-buffer,

Pixel Shader只做把畫面清成黑色的事情。

在Render時先將每個物件都Apply這個Shader然後執行Render,

等全部Render一次後,再將物件本來的Shader Apply回來,再Render一次。

如此在Render第二次時已經先將物件用最便宜的Shader Code來Render過一次,ZBuffer上也都已經有所有物件的深度資訊。也就是說這時候Render上去的物件只要是深度值一樣的才會執行Pixel Shader(當然你的Z Test的Funciton要選擇為equal)。

還有一些要注意的事項

在做第一次Render時要設定RenderState

    // Disable color writes
    m_pD3DDevice->SetRenderState(D3DRS_COLORWRITEENABLE, 0x00000000);
    // Ensure alpha off
    m_pD3DDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, false);
    m_pD3DDevice->SetRenderState(D3DRS_ALPHATESTENABLE, false);
    // Ensure z-enabled
    m_pD3DDevice->SetRenderState(D3DRS_ZENABLE, true);
    m_pD3DDevice->SetRenderState(D3DRS_ZWRITEENABLE, true);

Render完時要設定成

    // Enable color writes
    m_pD3DDevice->SetRenderState(D3DRS_COLORWRITEENABLE, 0x0000000F);

其他的Alpha Blending和Z Test等renderstate就看需求來設定

如此一來ZPass的工作就做完了

以目前執行的效率來看,92.16ms的效能可以提升到9.126ms(只做一次Pixel FillRate,ZPass那一次的Pixel FillRate太便宜了不於計算)

足足提升了10倍

如果再利用Portal或是Occlusion Culling等技術來減少Draw Call的次數,將可以在提升更多的效能

就先寫到這裡啦

以後有新的東西在跟大家分享囉

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 kgsprogrammer 的頭像
    kgsprogrammer

    太陽系後援會

    SnakeEater 發表在 痞客邦 留言(0) 人氣()