移動端雲渲染的實現

2021-09-01 17:25:00 字數 3491 閱讀 6043

先展示最終效果:

類似渲染雲這種自然現象的時候,必須首先了解雜訊這個概念。這個雜訊指的是描述自然界規律的一些隨機函式。例如大名鼎鼎的柏林雜訊。perlin雜訊被大量用於雲朵、火焰和地形等自然環境的模擬,而worley雜訊被提出用於模擬一些多孔結構,例如紙張、木紋等。不過其實wroley雜訊也可以用在雲上面。

對於雜訊這裡不想深入討論,感興趣的可以去看**或者馮女神的部落格

另外,我們使用的雜訊其實是這個雜訊:

這是iq大神弄的乙個雜訊,效率和表現都算很好。

float noise(in float3 x)

float4 map(in float3 p, in float t)

這些還不夠,我們還要分析下基本的演算法,看下地平線早期的雲實現:

主要思想還是根據raymarching得到雲的外形,然後加上光照。

接下來先一步一步實現整個過程,但因為手機上要考慮效能,所以會對完整的雲進行大幅度閹割。

中間紅色的部分就是我們攝像機的fov,那麼可以這麼計算出水平的fov的tan

//算出垂直fov的一半的弧度

float halffov_vert_rad = camera.main.fieldofview * mathf.deg2rad / 2.0f;

//根據tan可以算出當距離為1的時候攝像機的寬度,進行atan就可以得到水平的弧度,依然是一半

float halffov_horiz_rad = mathf.atan(mathf.tan(halffov_vert_rad) * camera.main.aspect);

//同樣,在shader裡也是進行如此的計算

void computecamera(in float2 screenpos, out float3 ro, out float3 rd)

float4 raymarch(in float3 ro, in float3 rd, in float zbuf)

return saturate(sum);}

實現以上所有的部分你可以得到乙個普通的固定視角看上去還不錯的雲。類似於這樣:

然而,如果你移動你攝像機的位置,你會發現這個雲會出現各種異常。於是我們要一步一步解決問題。

首先是計算量的浪費問題,我們建立的雲層模型是水平的,有頂部和底部組成,攝像機出發的射線,其實是經過頂部和底部的部分才有效果,那麼其實我們可以直接從攝像機的頂部交點或者底部交點開始運算。如果位於攝像機裡面,那麼就是從攝像機位置開始運算。

float t = 0;

if (rd.y < 0)

else

這樣子就可以解決第乙個問題。

由於正常情況下雲層一般較高,移動攝像機位置引起的變化量容易過大,導致一旦開始移動,取樣貼圖的座標也迅速移動,導致雲層瞬間變化。然而我們知道 ,攝像機的那點移動對於雲來說不算什麼,於是我增加了乙個縮放係數,因為雲層基本在幾千公尺左右,我就把這個係數定位1000,然後根據和雲層的距離再做乙個基本的非線性關係。

float times = 1000;

if (ro.y > _cloudtop)

else

pos = ro / times + rd * (t - abs(_cloudoffset / rd.y)) + offset;

可以看到,我都會根據頂部還是底部做不同的分支,這其實是我偷懶,可以用直線和線段求教簡化這段**,但現在就先這樣,能實現功能優先。最後還有乙個_cloudoffset和offset,我們後面再講解。

接下來是邊緣過度問題,雲層的邊緣超過頂部和底部會直接不計算,那麼會導致乙個難看的切邊,於是我要根據頂部和底部增加乙個透明過度。另外,如果雲層很厚,我們將整個噪點分布在全部雲層,會導致雲層和稀薄,於是需要乙個迴圈處理,我通過乙個簡單的線性週期函式來實現這個東西。

float offy = p.y - _cloudbottom;

float topoff = _cloudtop - p.y;

topoff = clamp(topoff, 0, _cloudedge) / _cloudedge;

offy = clamp(offy, 0, _cloudedge) / _cloudedge;

float offy1 = _cloudpadding * abs(frac(offy * _cloudlayers) - 0.5) - 1.5;

float den = clamp((_cloudcut - offy1 + _smooth * f) * topoff * offy, 0, 1);

return den

到這裡,理論上應該可以了,但實際操作中發現離雲很遠的時候,雲的噪點很厲害。想象一下在現實中,我們很遠看雲的時候,雲的那些細節會逐漸變成較大塊的純色,邊緣還是有細節。而我們根據位置進行那麼遠的取樣,必然會導致都是噪點。我思來想去,覺得如果一致能維持乙個較近的渲染效果,根據距離做一丟丟的變化,那就好了。於是我增加了_cloudoffset這個變數,會根據攝像機的位置和雲的位置動態變化,保證雲的渲染效果一直以較近距離觀察為主。這樣雖然在人靠近雲的感受不太自然,但整體的感覺還可以接受。

最後,是光照。

這部分我我沒有太多想法,而是直接使用了iq大佬的基本思想,雲的邊緣會變數。通過往光線方向取樣,得到新的位置的雲的密度,如果密度變小,說明越靠近雲的邊緣,就更亮。

具體實現的時候依然根據雲的上層和下層做了區別,因為如果乙個方向變數,那麼另乙個方向反而會變暗,所以需要反向一下y軸。

void addsum(float den, float3 pos, float t, inout float4 sum, float3 suncolor, float3 ro, float3 rd)

float dif = clamp((den - map(pos , ro, rd,  t, 0.3 * _worldspacelightpos0.xyz)) / a, 0.0, 1.0);

float3 lin = float3(0.65, 0.7, 0.75) * 1.4 + _lightcolor0 * dif;

float4 col = float4(lerp(float3(1, 0.95, 0.8), float3(0.25, 0.3, 0.35), den), den);

col.xyz *= lin;

col.a *= 0.4;

col.rgb *= col.a;

sum = sum + col * (1 - sum.a);

}

}

至此,我們大概實現了這樣的雲:

最後說下這個雲的問題。

這個雲還遠遠沒有到可使用的級別,第一,作為後處理特效,最早也只能在非透明物體渲染完成後,也就是雲老是疊在非透明物體上。更合理的應該還是用commandbuffer去做,可以控制渲染的時機。或者用乙個麵片去替代後處理,但是麵片就無法降取樣了,效能無法達到手機上的標準。所以還是需要用commandbuffer改造這個雲。

當然也可以自己設定深度快取,這樣子就可以實現雲和物體的交融,但是效能當然就更耗了。

即便沒有上述這些,這個手機場景demo在vivo x6手機上也只能跑30幀。那個穿越雲層的大咖只有10幀。所以,還是需要更好一點的手機才能使用這種方式製作的雲。

總而言之,還需要繼續優化。

UE4實時渲染 移動端渲染費率

當材質數量大於16時對幀速率影響較大。2,drawcall數量 想要提高渲染效率,減少drawcall的數量是最直接有效的方法 具體減少drawcall的方式有 1,cull distance volume 剔除距離 2,precompute visibility volume 計算可視性 3,me...

移動端實現專案

method post enctype multipart form data type file class pic id img name pic1 type button class btn id tp 選擇 class sub type submit id sub type button d...

vuejs 移動端 實現div拖拽移動

本文講述,在使用vue的移動端實現類似於iphone的懸浮窗的效果。touchstart 當在螢幕上按下手指時觸發 touchmove 當在螢幕上移動手指時觸發 touchend 當在螢幕上抬起手指時觸發 mousedown mousemove mouseup對應的是pc端的事件 touchcanc...