zoukankan      html  css  js  c++  java
  • 【翻译】Motion Blur for mobile devices in Unity

    原文链接: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上留言或在下面发表评论。

  • 相关阅读:
    Python常用转换函数
    Python随机数
    sublime text的pylinter插件设置pylint_rc后提示错误
    使用Pydoc生成文档
    字符编码笔记:ASCII,Unicode和UTF-8
    Windows编程MessageBox函数
    魔方阵算法及C语言实现
    iOS通讯录整合,兼容iOS789写法,附demo
    谈谈iOS app的线上性能监测
    ReactiveCocoa代码实践之-更多思考
  • 原文地址:https://www.cnblogs.com/jaffhan/p/9740350.html
Copyright © 2011-2022 走看看