using UnityEngine; using UnityEngine.Rendering; using UnityEngine.Rendering.Universal; using UnityEngine.Rendering.RenderGraphModule; using UnityEngine.Rendering.RenderGraphModule.Util; public class UIBlurDualKawaseFeature : ScriptableRendererFeature { [System.Serializable] public class Settings { public RenderPassEvent passEvent = RenderPassEvent.AfterRenderingTransparents; [Range(0.25f, 1f)] public float downscale = 0.5f; // 0.5(中高端) / 0.25(低端更稳) [Range(1, 6)] public int blurPasses = 4; // 3~4 移动端常用 [Range(0.5f, 4f)] public float offset = 1.0f; // 糊的“扩散感” public bool updateEveryFrame = false; // 推荐 false,只在需要时刷新 public string refreshFlagName = "_UIBlurRefresh"; // UI 触发用 public string globalTextureName = "_UIBlurTexture"; public Shader blurShader; // Hidden/URP/DualKawaseBlur } public Settings settings = new Settings(); class Pass : ScriptableRenderPass { readonly Settings s; readonly Material mat; // 持久 RT:让 UI 能跨帧拿到(需要 ImportTexture):contentReference[oaicite:2]{index=2} RTHandle persistentOutput; static readonly int SourceTexId = Shader.PropertyToID("_SourceTex"); static readonly int OffsetId = Shader.PropertyToID("_Offset"); public Pass(Settings settings, Material material) { s = settings; mat = material; } public RTHandle OutputRT => persistentOutput; public void EnsureOutput(ref RenderTextureDescriptor desc, int w, int h) { desc.width = w; desc.height = h; desc.depthBufferBits = 0; desc.msaaSamples = 1; desc.useMipMap = false; desc.autoGenerateMips = false; RenderingUtils.ReAllocateIfNeeded( ref persistentOutput, desc, FilterMode.Bilinear, TextureWrapMode.Clamp, name: s.globalTextureName ); } public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData) { // 资源/相机数据(官方示例也是这么取 activeColorTexture):contentReference[oaicite:3]{index=3} var resourceData = frameData.Get(); var cameraData = frameData.Get(); // 避免从 backbuffer blit(官方示例也这么做):contentReference[oaicite:4]{index=4} if (resourceData.isActiveTargetBackBuffer) return; // 刷新策略:不每帧更新时,只有收到 UI 请求才做一轮 if (!s.updateEveryFrame) { if (Shader.GetGlobalInt(s.refreshFlagName) == 0) return; Shader.SetGlobalInt(s.refreshFlagName, 0); } // 基础描述符(跟相机一致) var desc = cameraData.cameraTargetDescriptor; desc.depthBufferBits = 0; desc.msaaSamples = 1; int baseW = Mathf.Max(16, Mathf.RoundToInt(desc.width * s.downscale)); int baseH = Mathf.Max(16, Mathf.RoundToInt(desc.height * s.downscale)); EnsureOutput(ref desc, baseW, baseH); // 把持久 RTHandle 导入 RenderGraph(跨帧可用):contentReference[oaicite:5]{index=5} TextureHandle outTex = renderGraph.ImportTexture(persistentOutput); // 生成金字塔:0层(base), 1层(/2), 2层(/4)... int n = Mathf.Clamp(s.blurPasses, 1, 8); TextureHandle[] pyr = new TextureHandle[n]; for (int i = 0; i < n; i++) { int w = Mathf.Max(8, baseW >> i); int h = Mathf.Max(8, baseH >> i); var d = desc; d.width = w; d.height = h; pyr[i] = UniversalRenderer.CreateRenderGraphTexture(renderGraph, d, $"_UIBlurPyr{i}", false); } mat.SetFloat(OffsetId, s.offset); // source:相机颜色 TextureHandle src = resourceData.activeColorTexture; // Downsample chain (pass 0) { var p = new RenderGraphUtils.BlitMaterialParameters(src, pyr[0], mat, 0); renderGraph.AddBlitPass(p, "UIBlur DualKawase Down 0"); } for (int i = 1; i < n; i++) { var p = new RenderGraphUtils.BlitMaterialParameters(pyr[i - 1], pyr[i], mat, 0); renderGraph.AddBlitPass(p, $"UIBlur DualKawase Down {i}"); } // Upsample chain (pass 1) for (int i = n - 1; i > 0; i--) { var p = new RenderGraphUtils.BlitMaterialParameters(pyr[i], pyr[i - 1], mat, 1); renderGraph.AddBlitPass(p, $"UIBlur DualKawase Up {i}"); } // 最终写到持久输出 RT { var p = new RenderGraphUtils.BlitMaterialParameters(pyr[0], outTex, mat, 1); renderGraph.AddBlitPass(p, "UIBlur DualKawase Final"); } } public void Cleanup() { persistentOutput?.Release(); persistentOutput = null; } } Material _mat; Pass _pass; public override void Create() { if (settings.blurShader == null) settings.blurShader = Shader.Find("Hidden/URP/DualKawaseBlur"); if (settings.blurShader != null) _mat = CoreUtils.CreateEngineMaterial(settings.blurShader); _pass = new Pass(settings, _mat) { renderPassEvent = settings.passEvent }; } public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData) { if (_mat == null) return; if (renderingData.cameraData.cameraType != CameraType.Game) return; // 这里提前把全局纹理指向持久 RT(即使本帧不刷新,也能用旧结果) if (_pass.OutputRT != null) Shader.SetGlobalTexture(settings.globalTextureName, _pass.OutputRT); renderer.EnqueuePass(_pass); } protected override void Dispose(bool disposing) { _pass?.Cleanup(); CoreUtils.Destroy(_mat); } }