實施vertex compression所遇到的各種問題和解決辦法

千里馬肝發表於2014-01-07

關於頂點壓縮,好處是可以減少頻寬,一定程度提高載入速度,可以提高約5-10%的fps,特別是mobile上,簡單描述就是:

壓縮之前(32位元組)

position float3 12
normal float3 12
texcoord0 float2 8

壓縮之後(16位元組)

position short4 8
normal ubyte4 4
texcoord0 short2 4

 

壓縮的方法,其實就是在bounding box內分65536份,用"-32767.5"到"32767.5"描述。

參考文章:“Vertex Decompression using Vertex Shaders Part 2” by Dean Calve @ShaderX Programming

示例程式碼如下:

    // 計算position的範圍
    oiram::vec3 posCenter(    (mMesh.boundingBox.pmax.x + mMesh.boundingBox.pmin.x) * 0.5f, 
                            (mMesh.boundingBox.pmax.y + mMesh.boundingBox.pmin.y) * 0.5f, 
                            (mMesh.boundingBox.pmax.z + mMesh.boundingBox.pmin.z) * 0.5f),

                posExtent(    (mMesh.boundingBox.pmax.x - mMesh.boundingBox.pmin.x) * 0.5f,
                            (mMesh.boundingBox.pmax.y - mMesh.boundingBox.pmin.y) * 0.5f,
                            (mMesh.boundingBox.pmax.z - mMesh.boundingBox.pmin.z) * 0.5f);
                if (vertexDeclaration & oiram::Ves_Position)
                {
                    oiram::vec4 norm((vertex.vec3Position.x - posCenter.x) / posExtent.x,
                                     (vertex.vec3Position.y - posCenter.y) / posExtent.y,
                                     (vertex.vec3Position.z - posCenter.z) / posExtent.z,
                                     1.0f);
                    vertex.short4Position = oiram::short4(norm);
                }
    inline short packF32ToS16(float f)
    {
        return static_cast<short>(f * 32767.5f);;
    }

    struct short4
    {
        short s[4];

        short4() {}
        short4(const oiram::vec4& v)
        {
            s[0] = packF32ToS16(v.x);
            s[1] = packF32ToS16(v.y);
            s[2] = packF32ToS16(v.z);
            s[3] = packF32ToS16(v.w);
        }
    };

這樣,position的xyz就從float壓縮到short中了。接下來,要在vs中進行解壓,那麼同樣需要傳入center和extent:

float4 position = float4(iPosition.xyz / 32767.5 * positionExtent + positionCenter, 1);

當然,這裡可以直接將positionExtent除以32767.5之後再傳入,減少一次不必要的除法操作,這屬於自行研發優化的範疇之內,不累述。

接下來,同理可以將uv也進行壓縮,因為uv是2個值的緣故,所以center和extent可以合併在一起,只佔用一個float4即可,同上理不累述。

float4 texcoord0 = float4(iTexCoord0.xyzw * uvExtentCenter.xyxy + uvExtentCenter.zwzw);

 

需要注意的是,因為必須依賴vs進行解壓,而且center和extent的值必須正確。有意思的是,如果美術曾經將一個展分過uv的大模型,摘取其中某一塊,然後merge到一個新的模型中,那麼就容易出現混亂的uv值,比如u = 1.234567e+28, v = 1.234567e-44#DEN之類的。如果開啟3dsmax裡的UV map觀察,整個face的3個vertex都在uv上的同一個"點"上。無奈的是,獲取這些資料的函式都正確返回了,而且uv數值也是正常的float,無法通過isNAN神碼的來判斷是否有效。唯一能想到的辦法就是,檢查uv的絕對值,如果小於0.00001,或者大於10000,就將其重置為0。

還有一個在頂點壓縮之前不容易察覺的情況,因為某種原因,部分faces的material為空,即沒有附上材質。這一些頂點之前可能安全地藏在模型的體內,肉眼無法察覺。但現在經過壓縮之後,如果vs沒有照顧到它們,將其正確得解壓的話,因為是以short形式記錄的,於是你會發現場景中出現一些奇異的巨形的模型,附著奇怪的貼圖。

既然選擇了programmable pipeline代替Fixed Function,那麼意味著,你的shader在解壓資料之餘,渲染效果必須保持與之前FF的一致。可以想象的是,這並不是一件簡單的事情,比如一個模型中,一部分submesh是用diffuse map渲染,另一部分submesh只用到了diffuse color。那麼好吧,你的shader可要準備好了才行。

 

結論:一篇paper,一個演算法,一種優化,往往看上去很簡單很美好。當你往具體專案中加入時,通常不會像demo中執行得那麼順利,尤其是遇上大量的資料,甚至是各種奇葩資料,從而出現各種詭異現象的時候,那時候估計你就會跟我一樣,很難笑得出來了。

 

 

相關文章