TBR架構下EnvironmentMap的優化

來自專欄 Unity Graphics59 人贊了文章

前言

Environment Map在遊戲開發中最常見的是CubeMap。但是根據實際測試CubeMap在TBR架構上性能並不高。

原因是因為CubeMap在採樣到單個面的邊緣時,需要分別採樣鄰近面來做雙線性插值。也就是A面採樣2次,B面採樣2次。然後再插值,一共4次採樣。如果是用在IBL中則需要三線性插值,一共8次採樣。對於TBR架構來說帶寬壓力會更大。

經緯圖

所以在實際開發中我們採樣了經緯圖作為代替。經緯圖實測效率在PC端沒有Cubemap高,但在驍龍835上效率比Cubemap高。

經緯圖示例

經緯圖的採樣代碼如下:

mediump vec2 ShpericalSampleing(mediump vec3 v){ mediump vec2 uv = vec2(atan(v.x, v.z), -asin(v.y)); return uv * vec2(1.0 / (2.0 * PI), 1.0 / (1.0 * PI)) + vec2(0.5);}

不難看出,在上述代碼中由於使用了atan和asin實際指令數量會非常高。

AdvanceSphereMap

經緯圖帶來了帶寬上的優勢,然而運算上的性能卻讓人無法忽視。所以有沒有一種映射方式能在指令和帶寬上都有優勢呢? 於是我在玩弄HDRShop的過程中發現了一種被稱為Mirrored Ball的映射方式。

為了搞清楚它的映射方式,我做了如下圖的測試:

可以看出,這種被稱為Mirrored Ball的映射方式還是保留了整個環境圖各個方向的信息,但是在Z-的方向上細節還原是個比較大的挑戰。 經過多方搜索,終於找到了這個所謂MirroredBall的採樣演算法[1],以下把這種映射稱為AdvanceSphere。

採樣演算法如下,可以看到當向量 V 接近與 (0, 0, -1)時,由於精度問題,開方的結果會不精確。

mediump vec2 AdvanceSphereEnvSampleing(mediump vec3 v){ mediump float p = sqrt(8.0 * v.z + 1.0); return (v.xy / p) + 0.5;}

在大部分方向上的採樣結果基本和Cubemap沒有差距。但是在Z-這個方向上的採樣失真就有點嚴重了,這裡就不上圖了。

性能比較

在指令數量上,AdvanceSphereMap有明顯的優勢。

實際測試中,AdvanceSphereMap比經緯圖快1ms,測試平台為驍龍835,保證2次渲染的畫面相同。遊戲畫面就不貼了,這裡直接上測試結果。

AdvanceSphereMap

經緯圖

從以上測試中可以看出,AdvenceSphereMap還是很有前途的。

改進

但是由於AdvanceMap在Z-方向上的缺陷,於是有了以下2個版本的改進:

改進1

mediump vec2 AdvanceSphereEnvSampleing(mediump vec3 v){ mediump float ang = max(dot(v, vec3(0.0, 0.0, -1.0)), 0.0); mediump float hackValue = mix(1.0, 1.008, ang); mediump float p = sqrt(8.0 * v.z + hackValue); return (v.xy / p) + 0.5;}

可以看出,這種方式無法保證採樣的正確性,但是如果不追求正確性也是一個可以接受的結果。

改進2

mediump vec2 AdvanceSphereEnvSampleing(mediump vec3 v){ mediump vec3 p = vec3(v.x, v.y, v.z + 1.0); mediump float m = 2.0 * sqrt(dot(p, p)); return v.xy / m + 0.5;}

這種方式在演算法上保證了採樣的正確性,但也只是緩解的開方精度問題,而且這種映射布局的缺陷也無法完全避免。

改進3

雖然以上2種方式都有一定的缺陷,但是用在IBL中已經足以達到以假亂真的效果了。接下來的改進純屬障眼法,在絕大部分的遊戲中,反射貼圖都極少反射Y-的方向,也就是地下。那麼自然就想到通過修改演算法來把缺陷的Z-方向轉移到Y-方向。由於之前的貼圖都是通過HDRShop來實現的,所以要修改方向只能自行寫工具實現這個轉換。

修改後的生成演算法如下:

vec3 AdvanceSphereToDirection(vec2 uv){ uv = uv - 0.5; vec3 p = vec3(0.0); vec3 ray = vec3(0.0); float t = 2.0 * uv.y; float s = 2.0 * uv.x; p.x = s; p.y = t; p.z = sqrt(1.0 - dot(p.xy, p.xy)); ray.x = p.x * p.z * 2.0; ray.y = p.y * p.z * 2.0; ray.z = p.z * p.z * 2.0 - 1.0; return vec3(ray.x, ray.z, ray.y);}

相應的採樣演算法修改為:

mediump vec2 AdvanceSphereEnvSampleing(mediump vec3 v){ v.z = -v.z; mediump vec3 p = vec3(v.x, v.z, v.y + 1.0); mediump float m = 2.0 * sqrt(dot(p, p)); return v.xz / m + 0.5;}

最終結果如下:

引用

[1]

EnvironmentMapping?

fenix.tecnico.ulisboa.pt


推薦閱讀:
相关文章