Question
How can I apply the negative color effect to all cameras (including the UI camera) using Record Render Graph?
Current Issue
- When the UI camera is disabled, the effect applies correctly to the entire world.
- When the UI camera is enabled, only the UI is inverted, while the world remains unchanged.
- In Scene View, both the UI and the world are inverted, but in Game View, only the UI is affected.
- The overlay UI camera in the stack seems to take priority, causing the effect to apply only to the UI.
However, I can’t figure out how to fix this issue in the code.
Goal
I want to ensure that the negative color effect is applied to the entire world even when the UI camera is enabled.
Hierarchy & Code
Here is my hierarchy setup,
- Main Camera (Cinemachine Brain, stacking an overlay UI Camera)
- UI Camera (Render Type: Overlay, Culling Mask: UI)
- Cinemachine Camera
- Cube (Base Map Color: White)
- Canvas (Screen Space - Camera)
- Text (TMP)
Enable UI Camera :
Disable UI Camera:
and the code is below:
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.RenderGraphModule;
using UnityEngine.Rendering.RenderGraphModule.Util;
using UnityEngine.Rendering.Universal;
public class NegativeRenderPass : ScriptableRenderPass
{
private const string ShaderPath = "Hidden/Sample/Negative";
private Material _material;
private Material Material
{
get
{
if (_material == null)
{
_material = CoreUtils.CreateEngineMaterial(ShaderPath);
}
return _material;
}
}
public void Cleanup()
{
CoreUtils.Destroy(_material);
}
private class PassData
{
public Material Material;
public TextureHandle SourceTexture;
}
public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
{
var resourceData = frameData.Get<UniversalResourceData>();
var sourceTextureHandle = resourceData.activeColorTexture;
var negativeDescriptor = renderGraph.GetTextureDesc(sourceTextureHandle);
negativeDescriptor.name = "NegativeTexture";
negativeDescriptor.clearBuffer = false;
negativeDescriptor.msaaSamples = MSAASamples.None;
negativeDescriptor.depthBufferBits = 0;
var negativeTextureHandle = renderGraph.CreateTexture(negativeDescriptor);
using (var builder = renderGraph.AddRasterRenderPass<PassData>("NegativeRenderPass", out var passData))
{
passData.Material = Material;
passData.SourceTexture = sourceTextureHandle;
builder.SetRenderAttachment(negativeTextureHandle, 0, AccessFlags.Write);
builder.UseTexture(sourceTextureHandle, AccessFlags.Read);
builder.SetRenderFunc(static (PassData data, RasterGraphContext context) =>
{
var cmd = context.cmd;
var material = data.Material;
var source = data.SourceTexture;
Blitter.BlitTexture(cmd, source, Vector2.one, material, 0);
});
}
renderGraph.AddBlitPass(negativeTextureHandle, sourceTextureHandle, Vector2.one, Vector2.zero, passName: "BlitNegativeTextureToCameraColor");
}
}
public class NegativeRendererFeature : ScriptableRendererFeature
{
private NegativeRenderPass _pass;
public override void Create()
{
_pass = new NegativeRenderPass
{
renderPassEvent = RenderPassEvent.BeforeRenderingPostProcessing
};
}
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
renderer.EnqueuePass(_pass);
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
_pass.Cleanup();
}
}
}
Shader "Hidden/Sample/Negative"
{
SubShader
{
Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" }
ZTest Off ZWrite Off Cull Off
Pass
{
Name "Negative"
HLSLPROGRAM
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.core/Runtime/Utilities/Blit.hlsl"
#pragma vertex Vert
#pragma fragment frag
half4 frag(Varyings input) : SV_Target0
{
float2 uv = input.texcoord.xy;
half4 color = SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_LinearClamp, uv);
half4 negative = half4(1 - color.rgb, color.a);
return negative;
}
ENDHLSL
}
}
}
If the overlay is excluded in AddRenderPasses,
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
if (renderingData.cameraData.renderType == CameraRenderType.Overlay)
{
return;
}
renderer.EnqueuePass(_pass);
}
the result is as follows:
Any help would be greatly appreciated. Thank you!


