關於頂點壓縮,好處是可以減少頻寬,一定程度提高載入速度,可以提高約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中執行得那麼順利,尤其是遇上大量的資料,甚至是各種奇葩資料,從而出現各種詭異現象的時候,那時候估計你就會跟我一樣,很難笑得出來了。