using UnityEngine; using UnityEngine.Rendering; using UnityEngine.Rendering.RenderGraphModule; using UnityEngine.Rendering.RenderGraphModule.Util; using UnityEngine.Rendering.Universal; public class GlobalAfterimageRendererFeature : ScriptableRendererFeature { [Tooltip("When the custom afterimage pass should execute.")] public RenderPassEvent renderPassEvent = RenderPassEvent.AfterRenderingPostProcessing; private Material material; private GlobalAfterimagePass pass; public override void Create() { if (pass == null) pass = new GlobalAfterimagePass(); pass.renderPassEvent = renderPassEvent; EnsureMaterial(); } public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData) { if (!EnsureMaterial()) return; Camera camera = renderingData.cameraData.camera; if (camera == null) return; if (renderingData.cameraData.cameraType == CameraType.Preview || renderingData.cameraData.cameraType == CameraType.Reflection) return; if (!renderingData.cameraData.postProcessEnabled) return; GlobalAfterimageVolume settings = VolumeManager.instance.stack.GetComponent(); bool hasVolume = settings != null && settings.active; float runtimeOpacity = GlobalAfterimageRuntimeController.RuntimeOpacity; float baseOpacity = hasVolume ? settings.opacity.value : 0f; float finalOpacity = Mathf.Clamp01(Mathf.Max(baseOpacity, runtimeOpacity)); if (finalOpacity <= 0.0001f) return; if (camera.cameraType == CameraType.SceneView && hasVolume && !settings.affectSceneView.value) return; float runtimeScale = Mathf.Max(1f, GlobalAfterimageRuntimeController.RuntimeScale); float baseScale = hasVolume ? settings.scale.value : 1.05f; float finalScale = Mathf.Max(baseScale, runtimeScale); bool captureRequested = GlobalAfterimageRuntimeController.ConsumeCaptureRequest(); pass.Setup(material, finalOpacity, finalScale, captureRequested, renderingData.cameraData.cameraTargetDescriptor); renderer.EnqueuePass(pass); } protected override void Dispose(bool disposing) { if (pass != null) { pass.Dispose(); pass = null; } if (material != null) { CoreUtils.Destroy(material); material = null; } } private bool EnsureMaterial() { if (material != null) return true; Shader shader = Shader.Find("Hidden/Bansonic/GlobalAfterimage"); if (shader == null) return false; material = CoreUtils.CreateEngineMaterial(shader); return material != null; } private sealed class GlobalAfterimagePass : ScriptableRenderPass { private static readonly int OpacityId = Shader.PropertyToID("_AfterimageOpacity"); private static readonly int ScaleId = Shader.PropertyToID("_AfterimageScale"); private static readonly int SnapshotTexId = Shader.PropertyToID("_SnapshotTex"); private Material passMaterial; private float opacity; private float scale; private bool captureRequested; private RenderTextureDescriptor descriptor; private RTHandle compatibilityCopy; private RTHandle snapshotTexture; public void Setup(Material material, float passOpacity, float passScale, bool passCaptureRequested, RenderTextureDescriptor targetDescriptor) { passMaterial = material; opacity = passOpacity; scale = Mathf.Max(1f, passScale); captureRequested = passCaptureRequested; descriptor = targetDescriptor; descriptor.msaaSamples = 1; descriptor.depthBufferBits = 0; requiresIntermediateTexture = true; RenderingUtils.ReAllocateHandleIfNeeded(ref snapshotTexture, descriptor, FilterMode.Bilinear, TextureWrapMode.Clamp, name: "_GlobalAfterimageSnapshot"); } public void Dispose() { compatibilityCopy?.Release(); compatibilityCopy = null; snapshotTexture?.Release(); snapshotTexture = null; } public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData) { if (passMaterial == null || snapshotTexture == null) return; UniversalResourceData resourceData = frameData.Get(); if (resourceData.isActiveTargetBackBuffer) return; ApplyMaterialProperties(); TextureHandle source = resourceData.activeColorTexture; TextureHandle importedSnapshot = renderGraph.ImportTexture(snapshotTexture); if (captureRequested) { renderGraph.AddBlitPass(source, importedSnapshot, Vector2.one, Vector2.zero, passName: "Global Afterimage Capture"); } TextureDesc tempDesc = renderGraph.GetTextureDesc(source); tempDesc.name = "CameraColor-GlobalAfterimageTemp"; tempDesc.clearBuffer = false; TextureHandle tempTexture = renderGraph.CreateTexture(tempDesc); RenderGraphUtils.BlitMaterialParameters effectParameters = new(source, tempTexture, passMaterial, 0); renderGraph.AddBlitPass(effectParameters, passName: "Global Afterimage Effect"); renderGraph.AddBlitPass(tempTexture, resourceData.activeColorTexture, Vector2.one, Vector2.zero, passName: "Global Afterimage Restore"); } public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) { if (passMaterial == null || snapshotTexture == null) return; ApplyMaterialProperties(); RenderingUtils.ReAllocateHandleIfNeeded(ref compatibilityCopy, descriptor, FilterMode.Bilinear, TextureWrapMode.Clamp, name: "_GlobalAfterimageCompatibilityCopy"); CommandBuffer cmd = CommandBufferPool.Get("Global Afterimage Pass"); try { if (captureRequested) { Blitter.BlitCameraTexture(cmd, renderingData.cameraData.renderer.cameraColorTargetHandle, snapshotTexture); } Blitter.BlitCameraTexture(cmd, renderingData.cameraData.renderer.cameraColorTargetHandle, compatibilityCopy); Blitter.BlitCameraTexture(cmd, compatibilityCopy, renderingData.cameraData.renderer.cameraColorTargetHandle, passMaterial, 0); context.ExecuteCommandBuffer(cmd); } finally { cmd.Clear(); CommandBufferPool.Release(cmd); } } private void ApplyMaterialProperties() { passMaterial.SetFloat(OpacityId, opacity); passMaterial.SetFloat(ScaleId, scale); if (snapshotTexture != null) passMaterial.SetTexture(SnapshotTexId, snapshotTexture); } } }