源码 / Ch27/MotionBlurPreviewFeature.cs
Ch27/MotionBlurPreviewFeature.cs
using UnityEngine; using UnityEngine.Rendering; using UnityEngine.Rendering.Universal; namespace Cookbook.Ch27 { /// 27.17 节配图用:在编辑模式下复现 URP 的相机运动模糊。 /// URP 14 的 PostProcessPass 里有一行 `useMotionBlur = useMotionBlur && Application.isPlaying;`, /// 不在 Play 模式时 Motion Blur 一律关闭。这个 Feature 直接调用 URP 自带的 /// Hidden/Universal Render Pipeline/CameraMotionBlur.shader,参数含义与 MotionBlur Volume 完全一致, /// 只是“上一帧的 VP 矩阵”由外部指定。顺带也演示了这个 Shader 需要哪些输入。 public class MotionBlurPreviewFeature : ScriptableRendererFeature { [Tooltip("只对这台相机生效(为空则对所有 Game 相机生效)")] public Camera targetCamera; [Tooltip("上一帧的 GPU 视图投影矩阵:GL.GetGPUProjectionMatrix(P, true) * V")] public Matrix4x4 previousViewProj = Matrix4x4.identity; [Range(0f, 1f)] public float intensity = 0.5f; [Range(0f, 0.2f)] public float clamp = 0.05f; [Tooltip("0 = Low(2×2 次采样) 1 = Medium(3×2) 2 = High(4×2),对应 Shader 的三个 Pass")] [Range(0, 2)] public int quality = 2; Material m_Material; BlurPass m_Pass; public override void Create() { m_Pass ??= new BlurPass(); m_Pass.renderPassEvent = RenderPassEvent.BeforeRenderingPostProcessing; // 与 URP 一样:在色调映射之前,作用于 HDR 颜色 } public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData) { var cam = renderingData.cameraData.camera; if (cam.cameraType != CameraType.Game || (targetCamera != null && cam != targetCamera) || intensity <= 0f) return; if (m_Material == null) { var shader = Shader.Find("Hidden/Universal Render Pipeline/CameraMotionBlur"); if (shader == null) return; m_Material = CoreUtils.CreateEngineMaterial(shader); } m_Pass.Setup(this, m_Material); renderer.EnqueuePass(m_Pass); } public override void SetupRenderPasses(ScriptableRenderer renderer, in RenderingData renderingData) { m_Pass.source = renderer.cameraColorTargetHandle; // URP 14:只能在这里(而不是 AddRenderPasses)取颜色目标 } protected override void Dispose(bool disposing) { m_Pass?.Dispose(); CoreUtils.Destroy(m_Material); } class BlurPass : ScriptableRenderPass { static readonly int s_ViewProjM = Shader.PropertyToID("_ViewProjM"); static readonly int s_PrevViewProjM = Shader.PropertyToID("_PrevViewProjM"); static readonly int s_Intensity = Shader.PropertyToID("_Intensity"); static readonly int s_Clamp = Shader.PropertyToID("_Clamp"); static readonly int s_SourceSize = Shader.PropertyToID("_SourceSize"); readonly ProfilingSampler m_Sampler = new ProfilingSampler("MotionBlurPreview"); public RTHandle source; RTHandle m_Temp; MotionBlurPreviewFeature m_Owner; Material m_Material; public void Setup(MotionBlurPreviewFeature owner, Material material) { m_Owner = owner; m_Material = material; ConfigureInput(ScriptableRenderPassInput.Depth); // 速度由深度重建世界坐标后算出 } public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData) { var desc = renderingData.cameraData.cameraTargetDescriptor; desc.depthBufferBits = 0; desc.msaaSamples = 1; RenderingUtils.ReAllocateIfNeeded(ref m_Temp, desc, FilterMode.Bilinear, TextureWrapMode.Clamp, name: "_MotionBlurPreviewTemp"); } public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) { ref var cameraData = ref renderingData.cameraData; var vp = GL.GetGPUProjectionMatrix(cameraData.GetProjectionMatrix(), true) * cameraData.GetViewMatrix(); m_Material.SetMatrix(s_ViewProjM, vp); m_Material.SetMatrix(s_PrevViewProjM, m_Owner.previousViewProj); m_Material.SetFloat(s_Intensity, m_Owner.intensity); m_Material.SetFloat(s_Clamp, m_Owner.clamp); var cmd = CommandBufferPool.Get(); using (new ProfilingScope(cmd, m_Sampler)) { var d = cameraData.cameraTargetDescriptor; cmd.SetGlobalVector(s_SourceSize, new Vector4(d.width, d.height, 1f / d.width, 1f / d.height)); Blitter.BlitCameraTexture(cmd, source, m_Temp, m_Material, m_Owner.quality); Blitter.BlitCameraTexture(cmd, m_Temp, source); } context.ExecuteCommandBuffer(cmd); CommandBufferPool.Release(cmd); } public void Dispose() => m_Temp?.Release(); } } }