#if KAMGAM_RENDER_PIPELINE_URP using UnityEngine; using UnityEngine.Rendering; #if UNITY_6000_0_OR_NEWER using UnityEngine.Rendering.RenderGraphModule; #endif using UnityEngine.Rendering.Universal; namespace Kamgam.UIToolkitBlurredBackground { public class BlurredBackgroundPassURP : ScriptableRenderPass { public System.Action OnPostRender; public bool Active = false; protected int _iterations; public int Iterations { get => _iterations; set { if (_iterations != value) { _iterations = value; } } } protected float _offset = 1.5f; public float Offset { get => _offset; set { _offset = value; setOffset(value); } } protected Color _additiveColor = new Color(0f, 0f, 0f, 0f); public Color AdditiveColor { get => _additiveColor; set { _additiveColor = value; setAdditiveColor(_material, value); } } void setAdditiveColor(Material material, Color color) { if (material == null) return; material.SetColor("_AdditiveColor", color); } protected Vector2Int _resolution = new Vector2Int(512, 512); /// /// The texture resolution of the blurred image. Default is 512 x 512. Please use 2^n values like 256, 512, 1024, 2048. Reducing this will increase performance but decrease quality. Every frame your rendered image will be copied, resized and then blurred [BlurStrength] times. /// public Vector2Int Resolution { get => _resolution; set { _resolution = value; updateRenderTextureResolutions(); } } void updateRenderTextureResolutions() { if (_renderTargetBlurredA != null) { _renderTargetBlurredA.Release(); _renderTargetBlurredA.width = _resolution.x; _renderTargetBlurredA.height = _resolution.y; _renderTargetBlurredA.Create(); } if (_renderTargetBlurredB != null) { _renderTargetBlurredB.Release(); _renderTargetBlurredB.width = _resolution.x; _renderTargetBlurredB.height = _resolution.y; _renderTargetBlurredB.Create(); } } public const string ShaderName = "Kamgam/UI Toolkit/URP/Blur Shader"; protected ShaderQuality _quality = ShaderQuality.Medium; public ShaderQuality Quality { get => _quality; set { _quality = value; _material = null; } } [System.NonSerialized] protected Material _material; public Material Material { get { if (_material == null) { // Create material with shader var shader = Shader.Find(ShaderName); if (shader != null) { _material = new Material(shader); _material.color = Color.white; switch (_quality) { case ShaderQuality.Low: _material.SetKeyword(new LocalKeyword(shader, "_SAMPLES_LOW"), true); _material.SetKeyword(new LocalKeyword(shader, "_SAMPLES_MEDIUM"), false); _material.SetKeyword(new LocalKeyword(shader, "_SAMPLES_HIGH"), false); break; case ShaderQuality.Medium: _material.SetKeyword(new LocalKeyword(shader, "_SAMPLES_LOW"), false); _material.SetKeyword(new LocalKeyword(shader, "_SAMPLES_MEDIUM"), true); _material.SetKeyword(new LocalKeyword(shader, "_SAMPLES_HIGH"), false); break; case ShaderQuality.High: _material.SetKeyword(new LocalKeyword(shader, "_SAMPLES_LOW"), false); _material.SetKeyword(new LocalKeyword(shader, "_SAMPLES_MEDIUM"), false); _material.SetKeyword(new LocalKeyword(shader, "_SAMPLES_HIGH"), true); break; default: break; } setOffset(_offset); setAdditiveColor(_material,AdditiveColor); } } return _material; } set { _material = value; } } void setOffset(float value) { if (_material != null) _material.SetVector("_BlurOffset", new Vector4(value, value, 0f, 0f)); } [System.NonSerialized] protected RenderTexture _renderTargetBlurredA; public RenderTexture RenderTargetBlurredA { get { if (_renderTargetBlurredA == null) { _renderTargetBlurredA = createRenderTexture(); if (_renderTargetHandleA != null) { _renderTargetHandleA.Release(); _renderTargetHandleA = null; } } return _renderTargetBlurredA; } } [System.NonSerialized] protected RenderTexture _renderTargetBlurredB; public RenderTexture RenderTargetBlurredB { get { if (_renderTargetBlurredB == null) { _renderTargetBlurredB = createRenderTexture(); if (_renderTargetHandleB != null) { _renderTargetHandleB.Release(); _renderTargetHandleB = null; } } return _renderTargetBlurredB; } } [System.NonSerialized] protected RTHandle _renderTargetHandleA; public RTHandle RenderTargetHandleA { get { if (_renderTargetHandleA == null) _renderTargetHandleA = RTHandles.Alloc(RenderTargetBlurredA); return _renderTargetHandleA; } } [System.NonSerialized] protected RTHandle _renderTargetHandleB; public RTHandle RenderTargetHandleB { get { if (_renderTargetHandleB == null) _renderTargetHandleB = RTHandles.Alloc(RenderTargetBlurredB); return _renderTargetHandleB; } } RenderTexture createRenderTexture() { var texture = new RenderTexture(Resolution.x, Resolution.y, 0); texture.filterMode = FilterMode.Bilinear; return texture; } public void ClearRenderTargets() { if (_renderTargetHandleA != null) { _renderTargetHandleA.Release(); _renderTargetHandleA = null; } if (_renderTargetBlurredA != null) { _renderTargetBlurredA.Release(); _renderTargetBlurredA = null; } if (_renderTargetHandleB != null) { _renderTargetHandleB.Release(); _renderTargetHandleB = null; } if (_renderTargetBlurredB != null) { _renderTargetBlurredB.Release(); _renderTargetBlurredB = null; } } public Texture GetBlurredTexture() { return RenderTargetBlurredA; } // Actual Render Pass stuff starts here: // ------------------------------------------------------------- #region PASS_RENDER_NON_GRAPH_PATH // Turns out profiling scopes should NOT be mixed with CommandBuffers, see: // https://forum.unity.com/threads/how-to-use-profilingscope-correctly.1366812/#post-8621289 // ProfilingSampler _profilingSampler = new ProfilingSampler("UGUI Blurred Background Pass"); #if KAMGAM_RENDER_PIPELINE_URP_13 RTHandle _cameraColorTarget; #endif #if UNITY_6000_0_OR_NEWER [System.Obsolete] #endif public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData) { ConfigureInput(ScriptableRenderPassInput.Color); #if KAMGAM_RENDER_PIPELINE_URP_13 _cameraColorTarget = renderingData.cameraData.renderer.cameraColorTargetHandle; #endif } #if UNITY_6000_0_OR_NEWER [System.Obsolete] #endif public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) { if (!Active || _iterations == 0 || Offset <= 0f) return; // Do not render while switching play modes. #if UNITY_EDITOR if (EditorPlayState.State != EditorPlayState.PlayState.Playing && EditorPlayState.State != EditorPlayState.PlayState.Editing) return; #endif // Skip rendering in scene view or preview. Why? Because rendering in these // makes the scene view flicker if not in play mode. // See: https://forum.unity.com/threads/urp-custom-pass-blit-flickering-in-scene-view.1461932/ #if UNITY_EDITOR if ( renderingData.cameraData.cameraType == CameraType.SceneView || renderingData.cameraData.cameraType == CameraType.Preview) return; #endif #if !KAMGAM_RENDER_PIPELINE_URP_13 var source = renderingData.cameraData.renderer.cameraColorTarget; #else var source = renderingData.cameraData.renderer.cameraColorTargetHandle; // Check if source is null, if yes then try to fetch it from the set target. Otherwise abort. if (renderingData.cameraData.cameraType != CameraType.Game || source == null) { source = _cameraColorTarget; if (source == null) { #if UNITY_EDITOR // TODO: Investigate: This is happening in URP 14 though it has no effect (everything works). // Logger.LogWarning("Camera color target source is null. Will skip blur rendering. Please investigate this issue."); #endif return; } } #endif CommandBuffer cmd = CommandBufferPool.Get(name: "UGUI Blurred Background Pass"); cmd.Clear(); // Notice: Do not use cmd.Blit() in SPRs, see: // https://forum.unity.com/threads/how-to-blit-in-urp-documentation-unity-blog-post-on-every-blit-function.1211508/#post-7735527 // Blit Implementation can be found here: // https://github.com/Unity-Technologies/Graphics/blob/b57fcac51bb88e1e589b01e32fd610c991f16de9/Packages/com.unity.render-pipelines.core/Runtime/Utilities/Blitter.cs#L221 // First pass scales down the image Blit(cmd, source, RenderTargetHandleA); // 2 pass blur A > B, B > A for (int i = 0; i < Iterations; i++) { // Blur horizontal (pass 0) Blit(cmd, RenderTargetHandleA, RenderTargetHandleB, Material, 0); // Blur vertical (pass 1) Blit(cmd, RenderTargetHandleB, RenderTargetHandleA, Material, 1); } context.ExecuteCommandBuffer(cmd); cmd.Clear(); CommandBufferPool.Release(cmd); OnPostRender?.Invoke(); } public override void OnCameraCleanup(CommandBuffer cmd) { base.OnCameraCleanup(cmd); } #endregion #if UNITY_6000_0_OR_NEWER #region PASS_RENDER_GRAPH_PATH // The custom copy color pass data that will be passed at render graph execution to the lambda we set with "SetRenderFunc" during render graph setup private class CopyPassData { public TextureHandle inputTexture; } // The custom main pass data that will be passed at render graph execution to the lambda we set with "SetRenderFunc" during render graph setup private class BlurPassData { public Material material; public TextureHandle inputTexture; public int pass; } RenderTargetInfo getRenderTargetInfo(RenderTexture texture) { RenderTargetInfo info = new RenderTargetInfo(); info.format = texture.descriptor.graphicsFormat; info.width = texture.width; info.height = texture.height; info.volumeDepth = texture.volumeDepth; info.bindMS = texture.bindTextureMS; return info; } // Here you can implement the rendering logic for the render graph path public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData) { // This works var infoA = getRenderTargetInfo(RenderTargetBlurredA); var targetA = renderGraph.ImportTexture(RenderTargetHandleA, infoA); var infoB = getRenderTargetInfo(RenderTargetBlurredB); var targetB = renderGraph.ImportTexture(RenderTargetHandleB, infoB); // This does not. Wth?!? // see: https://forum.unity.com/threads/introduction-of-render-graph-in-the-universal-render-pipeline-urp.1500833/page-7#post-9822162 //var targetA = renderGraph.ImportTexture(RenderTargetHandleA, getRenderTargetInfo(RenderTargetBlurredA)); //var targetB = renderGraph.ImportTexture(RenderTargetHandleB, getRenderTargetInfo(RenderTargetBlurredB)); UniversalResourceData resourcesData = frameData.Get(); // Color buffer copy pass // * This pass makes a temporary copy of the active color target for sampling // * This is needed as GPU graphics pipelines don't allow to sample the texture bound as the active color target // * This copy can be avoided if you won't need to sample the color target or will only need to render/blend on top of it using (var builder = renderGraph.AddRasterRenderPass("UITKBlurredBackground_CopyColor", out var passData, profilingSampler)) { passData.inputTexture = resourcesData.activeColorTexture; builder.UseTexture(resourcesData.activeColorTexture, AccessFlags.Read); builder.SetRenderAttachment(targetA, 0, AccessFlags.WriteAll); builder.SetRenderFunc((CopyPassData data, RasterGraphContext context) => ExecuteCopyColorPass(data, context)); } // Blur horizontal pass using (var builder = renderGraph.AddRasterRenderPass("UITKBlurredBackground_BlurHori", out var passData, profilingSampler)) { passData.material = Material; passData.inputTexture = targetA; passData.pass = 0; builder.UseTexture(targetA, AccessFlags.Read); builder.SetRenderAttachment(targetB, 0, AccessFlags.WriteAll); builder.SetRenderFunc((BlurPassData data, RasterGraphContext context) => ExecuteBlurPass(data, context)); } // Blur vertical pass using (var builder = renderGraph.AddRasterRenderPass("UITKBlurredBackground_BlurVerti", out var passData, profilingSampler)) { passData.material = Material; passData.inputTexture = targetB; passData.pass = 1; builder.UseTexture(targetB, AccessFlags.Read); builder.SetRenderAttachment(targetA, 0, AccessFlags.WriteAll); builder.SetRenderFunc((BlurPassData data, RasterGraphContext context) => ExecuteBlurPass(data, context)); } OnPostRender?.Invoke(); } private static void ExecuteCopyColorPass(CopyPassData data, RasterGraphContext context) { Blitter.BlitTexture(context.cmd, data.inputTexture, new Vector4(1, 1, 0, 0), 0.0f, bilinear: true); } private static void ExecuteBlurPass(BlurPassData data, RasterGraphContext context) { Blitter.BlitTexture(context.cmd, data.inputTexture, new Vector4(1, 1, 0, 0), data.material, data.pass); } #endregion #endif } } #endif