URP Camera Motion Blur 青春版

URP Motion Blur 青春版

刚刚…好像有什么东西过去了。

阅读注意

  • 本文的URP版本为10.8.1

当前版本URP的运动模糊是比较清晰简单的,其大体思路与GPU Gems3中提到的方法大致相同。首先记录前一帧的观察矩阵和投影矩阵,然后计算出相同位置前一帧和当前帧的位置差,得出速度方向,最后沿着速度方向上多次采样进行模糊。

顶点着色器

我们直接来康康顶点部分:

VaryingsCMB VertCMB(Attributes input)
{
    
    
    VaryingsCMB output;
    UNITY_SETUP_INSTANCE_ID(input);
    UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output);

 #if _USE_DRAW_PROCEDURAL
    GetProceduralQuad(input.vertexID, output.positionCS, output.uv.xy);
 #else
    output.positionCS = TransformObjectToHClip(input.positionOS.xyz);
    output.uv.xy = input.uv;
 #endif
    float4 projPos = output.positionCS * 0.5;
    projPos.xy = projPos.xy + projPos.w;
    output.uv.zw = projPos.xy;

    return output;
}

这段除了基本的坐标转换,就是计算NDC坐标小连招了(_USE_DRAW_PROCEDURAL的内容用于处理全屏面片)。

float4 projPos = output.positionCS * 0.5;

其中,positionCS的w分量为 w w w,x、y范围为 [ − w , w ] [-w,w] [w,w]。经过计算后,projPos的x、y范围为 [ − 0.5 w , 0.5 w ] [-0.5w,0.5w] [0.5w,0.5w],w的值为 0.5 w 0.5w 0.5w

projPos.xy = projPos.xy + projPos.w;

projPos.xy范围映射为 [ 0 , w ] [0,w] [0,w]。之后只需要进行齐次除法和重新映射到 [ − 1 , 1 ] [-1,1] [1,1]就可以当NDC坐标用了。 因为后面会用这个坐标会乘一个投影逆矩阵unity_CameraInvProjection,来还原观察空间中的坐标,其中本身就包含平台信息,所以上面操作中的y分量没有必要乘以_ProjectionParams.x来消除平台差异。

片元着色器

half4 Frag(VaryingsCMB input) : SV_Target
{
    
    
	return DoMotionBlur(input, 2);// 2为迭代次数
}

URP的运动模糊根据迭代次数的不同,低中高三种品质,其迭代次数分别为2、3和4。

half4 DoMotionBlur(VaryingsCMB input, int iterations)
{
    
    
    UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input);

    float2 uv = UnityStereoTransformScreenSpaceTex(input.uv.xy);// 你可以当作直接返回UV
    float2 velocity = GetCameraVelocity(float4(uv, input.uv.zw)) * _Intensity;
    float randomVal = InterleavedGradientNoise(uv * _SourceSize.xy, 0);
    float invSampleCount = rcp(iterations * 2.0);

    half3 color = 0.0;

    UNITY_UNROLL
    for (int i = 0; i < iterations; i++)
    {
    
    
        color += GatherSample(i, velocity, invSampleCount, uv, randomVal, -1.0);
        color += GatherSample(i, velocity, invSampleCount, uv, randomVal,  1.0);
    }

    return half4(color * invSampleCount, 1.0);
}

我们先来看看速度是怎么计算的

float2 GetCameraVelocity(float4 uv)
{
    
    
    float depth = SAMPLE_TEXTURE2D_X(_CameraDepthTexture, sampler_PointClamp, uv.xy).r;

    #if UNITY_REVERSED_Z
    depth = 1.0 - depth;
    #endif

    depth = 2.0 * depth - 1.0;

    float3 viewPos = ComputeViewSpacePosition(uv.zw, depth, unity_CameraInvProjection);
    float4 worldPos = float4(mul(unity_CameraToWorld, float4(viewPos, 1.0)).xyz, 1.0);
    float4 prevPos = worldPos;

    float4 prevClipPos = mul(_PrevViewProjM, prevPos);
    float4 curClipPos = mul(_ViewProjM, worldPos);

    float2 prevPosCS = prevClipPos.xy / prevClipPos.w;
    float2 curPosCS = curClipPos.xy / curClipPos.w;

    return ClampVelocity(prevPosCS - curPosCS, _Clamp);
}
  • _PrevViewProjM_ViewProjM分别为前一帧和当前的VP矩阵

我们采样了深度贴图_CameraDepthTexture,并把它映射到了 [ − 1 , 1 ] [-1,1] [1,1]。并根据这个深度和NDC信息还原观察空间的坐标。

float3 viewPos = ComputeViewSpacePosition(uv.zw, depth, unity_CameraInvProjection);

值得注意的是,uv.zw没有进行齐次除法(映射到 [ − 1 , 1 ] [-1,1] [1,1]的部分在函数内部进行),在这里直接进行使用。这是因为后处理运动模糊是通过Bilt命令进行的,它会重置观察、投影矩阵,所以齐次除法的 w w w实际上是1,也就没必要除了。

得到观察空间坐标,继续还原到世界空间坐标。然后,通过前后两帧的VP矩阵,计算裁剪空间的坐标,并进行齐次除法。prevPosCS - curPosCS即为相机的运动方向。

float2 ClampVelocity(float2 velocity, float maxVelocity)
{
    
    
    float len = length(velocity);
    return (len > 0.0) ? min(len, maxVelocity) * (velocity * rcp(len)) : 0.0;
}

ClampVelocity函数限制了一下速度的大小,并交由_Clamp进行调节。

回到DoMotionBlur函数:

 float randomVal = InterleavedGradientNoise(uv * _SourceSize.xy, 0);

这里通过一个交叉梯度噪音函数生成了一个随机值。

//From  Next Generation Post Processing in Call of Duty: Advanced Warfare [Jimenez 2014]
// http://advances.realtimerendering.com/s2014/index.html
float InterleavedGradientNoise(float2 pixCoord, int frameCount)
{
    
    
    const float3 magic = float3(0.06711056f, 0.00583715f, 52.9829189f);
    float2 frameMagicScale = float2(2.083f, 4.867f);
    pixCoord += frameCount * frameMagicScale;
    return frac(magic.z * frac(dot(pixCoord, magic.xy)));
}

这个算法源于Next Generation Post Processing in Call of Duty。我们可以用工具将这个噪声图像生成出来(输入uv * _SourceSize.xy):
在这里插入图片描述

噪声拥有丰富的值范围,有着交错的梯度,这能让我们后续的模糊采样效果更加平滑。

后续的采样部分:

float invSampleCount = rcp(iterations * 2.0);

half3 color = 0.0;

UNITY_UNROLL
for (int i = 0; i < iterations; i++)
{
    
    
    color += GatherSample(i, velocity, invSampleCount, uv, randomVal, -1.0);
    color += GatherSample(i, velocity, invSampleCount, uv, randomVal,  1.0);
}

return half4(color * invSampleCount, 1.0);

其中GatherSample部分:

float3 GatherSample(float sampleNumber, float2 velocity, float invSampleCount, float2 centerUV, float randomVal, float velocitySign)
{
    
    
    // offsetLength 范围为[sampleNumber, sampleNumber + 1]
    float  offsetLength = (sampleNumber + 0.5) + (velocitySign * (randomVal - 0.5));
    float2 sampleUV = centerUV + (offsetLength * invSampleCount) * velocity * velocitySign;
    return SAMPLE_TEXTURE2D_X(_SourceTex, sampler_PointClamp, sampleUV).xyz;
}

我们沿着速度方向的两边进行随机采样,制造运动模糊。以2次迭代为例(红点为当前位置):
在这里插入图片描述

白点是invSampleCount * velocity的倍数。

运动模糊效果如下:

在这里插入图片描述


水平有限,如有错误,请多包涵 (〃‘▽’〃)

猜你喜欢

转载自blog.csdn.net/zigzagbomb/article/details/125349048