1. 程式人生 > >【翻譯】Motion Blur for mobile devices in Unity

【翻譯】Motion Blur for mobile devices in Unity

采樣 nim 模糊效果 步驟 深度 put ++ 足夠 函數

原文鏈接:https://tech.spaceapegames.com/2018/09/06/motion-blur-for-mobile-devices-in-unity/

什麽是運動模糊?-What is Motion Blur?

維基百科將運動模糊定義為:

運動模糊是在照片或序列幀中移動物體的明顯拖尾,例如電影動畫由於快速移動或長時間曝光,在記錄單次曝光期間記錄的圖像發生變化時,會出現這種情況

當我們使用相機拍攝圖像時,快門打開,傳感器捕獲圖像,然後快門再次關閉。快門打開的時間越長,傳感器捕獲的光線就越多。但是,將快門打開更長時間同時意味著拍攝的圖像可能會改變。

想象一下,我們正嘗試拍攝一輛沿著賽道飛馳的汽車圖像。

如果快門保持打開一秒,汽車會飛射出相機,並且整個圖像將變得模糊。現在,如果我們將快門打開很短的時間,比如說1/500秒,那麽我們可以拍攝到完全沒有模糊的圖像。

運動模糊是令快門長時間打開的一個副作用。在遊戲中,可能需要模擬這種效果。它可以為我們的場景增添速度感和動感。根據遊戲的類型,這可以幫助遊戲提高真實感級別。可能從運動模糊效果中受益的遊戲類型包括賽車遊戲,第一人稱射擊遊戲和第三人稱射擊遊戲等。

管線概述-Pipeline Overview

我們希望為我們的一款賽車遊戲開發運動模糊效果。目前有大量不同的實現方式。

幀模糊-Frame Blur

模擬運動模糊的最簡單方法是獲取前一幀的渲染目標(render tagert),並在該幀與當前幀的渲染目標之間進行插值。

當可編程著色器首次出現時,就是這樣做的。它非常簡單易用,並且不需要對現有渲染管線進行任何更改。但是它並不真實,並且你無法對場景中的不同對象進行不同程度的模糊。

位置重建-Postion Reconstruction

幀模糊的進一步是位置重建。在這個方法中,我們正常的渲染場景。然後,我們對渲染目標中的每個像素采樣深度緩沖區,並重建屏幕空間位置。使用先前幀的變換矩陣,然後我們計算該像素在先前幀的屏幕空間位置。之後我們可以計算屏幕空間的方向和距離,並模糊此像素。這個方法假定場景中的所有對象都是靜態的。它認為幀緩沖區中像素的世界空間位置不會改變。因此,它非常適合模擬來自攝像機的運動,但如果您想要對場景中的動態對象模擬更細粒度的運動,那麽它並不理想。

速度緩沖-Velocity Buffer

如果你確實需要處理動態對象,那麽這就是適合你的解決方案。它也是三者中性能最昂貴的。這裏我們需要對場景中的每個對象渲染兩次,一次輸出常規的場景渲染目標,第二次創建一個速度緩沖區(通常是R16G16的渲染目標)。你也可以通過綁定多個渲染目標(MRT)來規避第二次繪制調用。

當我們創建速度緩沖區時,我們從模型空間通過當前和先前的世界-視圖-投影矩陣來變換我們渲染的每個對象。這樣做我們也能夠考慮到世界空間的變化。然後我們計算屏幕空間的變化並將此向量存儲在速度緩沖區中。

實現-Implementation

要求-Requiremnets

我們決定實現位置重建方法。

  • 幀模糊不是一種選擇 - 這種方法太舊了,並不能提供足夠的真實感。
  • 我們遊戲中的攝像機跟隨著不斷移動的玩家車輛,所以即使我們無法模擬世界空間變換,我們仍應該獲得令人信服的效果。
  • 我們不希望產生填充速度緩沖區的額外繪制調用成本。
  • 我們不希望產生填充速度緩沖區的額外帶寬開銷。
  • 我們不想消耗存儲速度緩沖區所需的額外內存。

代碼-Code

我們初始像往常一樣渲染我們的場景。作為後處理步驟,我們在著色器中讀取場景中每個像素的深度:

float depthBufferSample = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture,uv).r;

然後我們將從深度重建屏幕空間位置:

float4 projPos;
projPos.xy = uv.xy * 2.0  -  1.0;
projPos.z = depthBufferSample;
projPos.w = 1.0f;

在C#中,我們將轉換矩陣傳遞到我們的著色器中。此矩陣將轉換當前屏幕空間位置,如下所示:

  1. 相機空間-Camera Space
  2. 世界空間-World Space
  3. 先前幀的相機空間-Previous frames Camera Space
  4. 先前幀的屏幕空間-Previous frames Screen Space

這一切都是通過簡單的乘法完成的:

float4 previous = mul(_CurrentToPreviousViewProjectionMatrix,projPos);
previous / = previous.w;

要計算此轉換矩陣,我們在C#中執行以下操作

private Matrix4x4 previousViewProjection;

private void OnRenderImage(RenderTexture src, RenderTexture dest)
{
    var viewProj = cam.projectionMatrix * cam.worldToCameraMatrix;
    var invViewProj = viewProj.inverse;
    var currentToPreviousViewProjectionMatrix = previousViewProjection * invViewProj;

    motionBlurMaterial.SetMatrix("_CurrentToPreviousViewProjectionMatrix", currentToPreviousViewProjectionMatrix);

    ...

    previousViewProjection = viewProj;
}

我們現在可以計算兩個屏幕空間向量之間的方向和距離。然後,我們使用距離作為縮放值,並沿著方向向量對渲染目標進行采樣。

float2 blurVec = (previous.xy - projPos.xy) * 0.5f;
float2 v = blurVec / NumberOfSamples;

half4 colour;
for(int i = 0; i < NumberOfSamples; ++i)
{
    float2 uv = input.uv + (v * i);
    colour += tex2D(_MainTex, uv);
}

colour /= NumberOfSamples

控制運動模糊-Controlling the Motion Blur

一旦我們得到了屏幕的內容,我們很快就會發現太過於模糊。我們希望場景的大部分都模糊不清,但藝術家們希望車輛和驅動器是清晰的。為了實現這一目標,同時盡可能少的影響管線,我們決定使用alpha通道來屏蔽我們不想模糊的場景區域。然後,我們將此蒙版乘以模糊向量,以便有效地生成模糊向量[0,0]。

half4 colour = tex2D(_MainTex, input.uv);
float mask = colour.a;

for(int i = 1; i < NumberOfSamples; ++i)
{
    float2 uv = input.uv + (v * mask * i);
    colour += tex2D(_MainTex, uv);
}

除此之外,我們還發現遠距離的物體不應該像前景中的物體那樣模糊。為了實現這一點,我們簡單地通過線性眼睛(視圖空間)深度縮放模糊向量,從深度緩沖區(LinearEyeDepth)的計算是Unity的cginc頭內的輔助函數。

float d = LinearEyeDepth(depthBufferSample);
float depthScale = 1 - saturate(d / _DepthScale

結論-Conclusion

立即可用,Unity為你生成速度緩沖區來支持運動模糊,但是對於我們的要求,這是過度的。我們始終需要記住,我們是一個移動工作室,所以我們需要在每一步都考慮到性能。我們實現的方法有其權衡,我們必須添加基於距離的縮放以防止距離中的對象模糊太多。然而,由於我們的相機不斷移動,它給我們帶來了令人信服的效果。如果您有任何問題或反饋,請隨時在Twitter上留言或在下面發表評論。

【翻譯】Motion Blur for mobile devices in Unity