using UnityEngine; using UnityEngine.Rendering; using UnityEngine.Rendering.Universal; using UnityEngine.Experimental.Rendering; public class BloomRenderFeature : ScriptableRendererFeature { [System.Serializable] public class BloomSettings { public RenderPassEvent passEvent = RenderPassEvent.AfterRenderingPostProcessing; public Shader blurShader; public Shader compositeShader; [Range(0, 4)] public int downsample = 1; [Range(0.0f, 2.0f)] public float threshold = 1.0f; [Range(0.0f, 10.0f)] public float radius = 1.0f; [Range(0.0f, 10.0f)] public float intensity = 1.0f; // x = w2, y = w1, z = w0 (symmetric 5-tap kernel) public Vector3 weights = new Vector3(0.2f, 0.4f, 0.8f); } BloomPass bloomPass; Material blurMat; Material compositeMat; public BloomSettings settings = new BloomSettings(); // ──────────────────────────────────────────────────────────────── // CREATE // ──────────────────────────────────────────────────────────────── public override void Create() { if (settings.blurShader) blurMat = CoreUtils.CreateEngineMaterial(settings.blurShader); if (settings.compositeShader) compositeMat = CoreUtils.CreateEngineMaterial(settings.compositeShader); bloomPass = new BloomPass(settings); bloomPass.renderPassEvent = settings.passEvent; } // ──────────────────────────────────────────────────────────────── // ONLY enqueue pass here (NO camera target access!) // ──────────────────────────────────────────────────────────────── public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData) { if (blurMat == null || compositeMat == null) return; // Pass only materials bloomPass.Setup(blurMat, compositeMat); renderer.EnqueuePass(bloomPass); } // ====================================================================== // BLOOM PASS // ====================================================================== class BloomPass : ScriptableRenderPass { BloomSettings settings; Material blurMat; Material compositeMat; RTHandle rt1; RTHandle rt2; // final camera target (RenderTargetIdentifier) RenderTargetIdentifier source; string tag = "BloomPass"; public BloomPass(BloomSettings settings) { this.settings = settings; } public void Setup(Material blurMat, Material compositeMat) { this.blurMat = blurMat; this.compositeMat = compositeMat; } // ──────────────────────────────────────────────────────────────── // VALID place to access cameraColorTargetHandle // ──────────────────────────────────────────────────────────────── public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData) { // SAFE: this is the correct place to access cameraColorTargetHandle source = renderingData.cameraData.renderer.cameraColorTargetHandle; float scale = 1f / (1 << settings.downsample); Vector2 scaleFactor = new Vector2(scale, scale); rt1 = RTHandles.Alloc( scaleFactor, colorFormat: GraphicsFormat.R16G16B16A16_SFloat, filterMode: FilterMode.Bilinear, name: "_BloomRT1" ); rt2 = RTHandles.Alloc( scaleFactor, colorFormat: GraphicsFormat.R16G16B16A16_SFloat, filterMode: FilterMode.Bilinear, name: "_BloomRT2" ); } // ──────────────────────────────────────────────────────────────── // EXECUTE BLOOM EFFECT // ──────────────────────────────────────────────────────────────── public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) { if (blurMat == null || compositeMat == null) return; CommandBuffer cmd = CommandBufferPool.Get(tag); // Pass parameters blurMat.SetFloat("_Threshold", settings.threshold); blurMat.SetFloat("_Radius", settings.radius); blurMat.SetFloat("_Intensity", settings.intensity); blurMat.SetVector("_Weights", new Vector4(settings.weights.x, settings.weights.y, settings.weights.z, 0)); // 1. Extract bright areas cmd.Blit(source, rt1, blurMat, 0); // 2. Blur horizontal blurMat.SetVector("_Direction", new Vector4(1, 0, 0, 0)); cmd.Blit(rt1, rt2, blurMat, 1); // 3. Blur vertical blurMat.SetVector("_Direction", new Vector4(0, 1, 0, 0)); cmd.Blit(rt2, rt1, blurMat, 1); // 4. Composite bloom back compositeMat.SetFloat("_Intensity", settings.intensity); cmd.Blit(rt1, source, compositeMat, 0); context.ExecuteCommandBuffer(cmd); CommandBufferPool.Release(cmd); } // ──────────────────────────────────────────────────────────────── // CLEANUP (Release RTHandles) // ──────────────────────────────────────────────────────────────── public override void OnCameraCleanup(CommandBuffer cmd) { if (rt1 != null) RTHandles.Release(rt1); if (rt2 != null) RTHandles.Release(rt2); } } }