Context:
I’m developing a 2.5D game that combines Sprites and MeshRenderers.
i want to copy the screen to a texture, then use that texture on a transparent object for effects like a heat wave effect.
While Unity’s built-in Opaque Texture feature can copy the screen after the DrawOpaqueObjects
pass, I need to copy the screen after transparent objects are rendered.
Current Implementation:
To achieve this, I’ve created a custom RendererFeature that performs two main tasks after the DrawTransparentObjects
pass:
- 1- Copies the screen to a global texture named
_SceneColorAfterTransparent
- 2- Re-renders all transparent objects, including those in a special “RenderAfterTransparent” layer
Here’s the code:
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
using UnityEngine.Rendering.RenderGraphModule;
using System.Collections.Generic;
public class CopyColorAfterTransparentFeature : ScriptableRendererFeature
{
[System.Serializable]
private class Settings
{
[SerializeField] internal RenderPassEvent passEvent = RenderPassEvent.AfterRenderingTransparents;
[Header("CopyColor Settings")]
[SerializeField] internal Downsampling downsampling;
[Header("Rendering Settings")]
[SerializeField] internal string[] shaderTagStrings = new string[]
{"UniversalForward", "UniversalForwardOnly", "SRPDefaultUnlit"};
}
[SerializeField] private Settings settings;
private CopyColorPass copyColorPass;
private RenderPass renderPass;
public const string propertyName = "_SceneColorAfterTransparent";
private static readonly int TextureID = Shader.PropertyToID(propertyName);
public override void Create()
{
copyColorPass = new CopyColorPass(settings.downsampling)
{ renderPassEvent = settings.passEvent };
List<ShaderTagId> shaderTagIds = new();
for (int i = 0; i < settings.shaderTagStrings.Length; i++)
shaderTagIds.Add(new ShaderTagId(settings.shaderTagStrings[i]));
renderPass = new RenderPass(shaderTagIds, -1)
{ renderPassEvent = settings.passEvent + 2 };
}
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
renderer.EnqueuePass(copyColorPass);
renderer.EnqueuePass(renderPass);
}
private class CopyColorPass : ScriptableRenderPass
{
private readonly Downsampling downsampling;
internal CopyColorPass(Downsampling downsampling) => this.downsampling = downsampling;
private class PassData
{
internal TextureHandle copySourceTexture;
internal TextureHandle destinationTexture;
}
public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
{
using (var builder = renderGraph.AddRasterRenderPass<PassData>
("CopyColor_AfterTransparent", out var passData))
{
var resourceData = frameData.Get<UniversalResourceData>();
passData.copySourceTexture = resourceData.activeColorTexture;
int factor = downsampling switch
{
Downsampling._2xBilinear => 2,
Downsampling._4xBox or Downsampling._4xBilinear => 4,
_ => 1
};
var cameraData = frameData.Get<UniversalCameraData>();
RenderTextureDescriptor desc = cameraData.cameraTargetDescriptor;
desc.msaaSamples = 1;
desc.depthBufferBits = 0;
desc.width /= factor;
desc.height /= factor;
FilterMode filterMode = downsampling == Downsampling.None ? FilterMode.Point : FilterMode.Bilinear;
passData.destinationTexture = UniversalRenderer.CreateRenderGraphTexture(
renderGraph, desc, "CopyTexture", false, filterMode);
builder.UseTexture(passData.copySourceTexture);
builder.SetRenderAttachment(passData.destinationTexture, 0);
builder.SetGlobalTextureAfterPass(passData.destinationTexture, TextureID);
builder.AllowPassCulling(false);
builder.SetRenderFunc((PassData data, RasterGraphContext context) =>
{
Blitter.BlitTexture(context.cmd, data.copySourceTexture,
new Vector4(1, 1, 0, 0), 0, false);
});
}
}
}
private class RenderPass : ScriptableRenderPass
{
private readonly List<ShaderTagId> shaderTagIdList;
private readonly LayerMask layerMask;
internal RenderPass(List<ShaderTagId> shaderTagIds, LayerMask layerMask)
{
shaderTagIdList = shaderTagIds;
this.layerMask = layerMask;
}
internal class PassData { internal RendererListHandle rendererList; }
public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameContext)
{
using (var builder = renderGraph.AddRasterRenderPass<PassData>("Re-RenderTransparentes", out var passData))
{
var renderingData = frameContext.Get<UniversalRenderingData>();
var cameraData = frameContext.Get<UniversalCameraData>();
var lightData = frameContext.Get<UniversalLightData>();
var drawSettings = RenderingUtils.CreateDrawingSettings(
shaderTagIdList, renderingData, cameraData, lightData, SortingCriteria.CommonTransparent);
var filterSettings = new FilteringSettings(RenderQueueRange.transparent, layerMask);
var rendererListParams = new RendererListParams(renderingData.cullResults, drawSettings, filterSettings);
passData.rendererList = renderGraph.CreateRendererList(rendererListParams);
builder.UseRendererList(passData.rendererList);
var resourceData = frameContext.Get<UniversalResourceData>();
builder.SetRenderAttachment(resourceData.activeColorTexture, 0);
builder.SetRenderAttachmentDepth(resourceData.activeDepthTexture, AccessFlags.Write);
builder.SetRenderFunc((PassData data, RasterGraphContext context) =>
context.cmd.DrawRendererList(data.rendererList));
}
}
}
}
Setup:
- 1- I’ve created a dedicated layer for objects that will use the
_SceneColorAfterTransparent
texture - 2- This layer is excluded from the normal URP rendering and is instead rendered by the custom renderer feature
- 3- I’m using a simple distortion shader that uses the
_SceneColorAfterTransparent
texture
Current Results:
The implementation works as intended in most cases:
The main issue occurs when an object’s center point is closer to the camera - it gets rendered first. This appears to be related to the rendering order of transparent objects.
Final note:
i’m not using another camera with a RenderTexture for performance reasons.
Resources that helped me (I’m very new to Unity’s rendering pipeline): Unity Documentation | This Repo (it has more problems + uses the old API)
Can anyone help me understand what’s causing this rendering order issue and how to fix it?