I have a texture that I want to apply different render pass “filters” on to change the image. Many of the URP render pass examples assume render textures based on cameras, but I don’t necessarily need to render any cameras, I just need to change the texture. What would be the best way to go about that? Thanks in advance!
This is what I’ve got so far, but I don’t see any change in the target texture. The _targetMaterial
shader has a _TargetTex
texture property, and the _renderPassMaterial
has a _BlitTexture
texture property.
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
namespace Visuals
{
public class EnqueueRenderPasses : MonoBehaviour
{
private CustomRenderPass customRenderPass;
[SerializeField]
private Renderer _rendererWithTargetTexture;
private Material _targetMaterial;
private Material _renderPassMaterial;
[SerializeField]
private Shader _renderPassShader;
private void OnEnable()
{
_targetMaterial = _rendererWithTargetTexture.material;
_renderPassMaterial = CoreUtils.CreateEngineMaterial(_renderPassShader);
customRenderPass = new CustomRenderPass(_targetMaterial, _renderPassMaterial);
RenderPipelineManager.beginCameraRendering += OnBeginCamera;
}
private void OnDisable()
{
RenderPipelineManager.beginCameraRendering -= OnBeginCamera;
customRenderPass.Dispose();
}
private void OnBeginCamera(ScriptableRenderContext context, Camera cam)
{
cam.GetUniversalAdditionalCameraData().scriptableRenderer.EnqueuePass(customRenderPass);
}
}
}
using UnityEditor;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
namespace Visuals
{
public class CustomRenderPass : ScriptableRenderPass
{
private Material _targetMaterial;
private Material _renderPassMaterial;
private const string _targetTexturePropertyName = "_TargetTex";
private int _targetTextureID = Shader.PropertyToID(_targetTexturePropertyName);
private RenderTextureDescriptor _textureDescriptor;
private RTHandle _targetRTHandle;
private RTHandle _renderPassRTHandle;
public CustomRenderPass(Material targetMaterial, Material renderPassMaterial)
{
_targetMaterial = targetMaterial;
_renderPassMaterial = renderPassMaterial;
_textureDescriptor = new RenderTextureDescriptor(_targetMaterial.GetTexture(_targetTextureID).width,
_targetMaterial.GetTexture(_targetTextureID).height,
RenderTextureFormat.Default, 0);
}
public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
{
_targetRTHandle = RTHandles.Alloc(_targetMaterial.GetTexture(_targetTextureID));
RenderingUtils.ReAllocateIfNeeded(ref _renderPassRTHandle, _textureDescriptor);
}
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
CommandBuffer cmd = CommandBufferPool.Get();
Blitter.BlitTexture(cmd, _targetRTHandle, _renderPassRTHandle, _renderPassMaterial, 0);
cmd.SetGlobalTexture(_targetTextureID, _renderPassRTHandle.nameID);
context.ExecuteCommandBuffer(cmd);
cmd.Clear();
CommandBufferPool.Release(cmd);
}
public void Dispose()
{
#if UNITY_EDITOR
if (EditorApplication.isPlaying)
{
Object.Destroy(_renderPassMaterial);
}
else
{
Object.DestroyImmediate(_renderPassMaterial);
}
#else
Object.Destroy(_renderPassMaterial);
#endif
_renderPassRTHandle?.Release();
_targetRTHandle?.Release();
}
}
}
I had to uncheck Exposed
on the _BlitTexture
property in my shader for cmd.SetGlobalTexture
to work. My working code is a bit messy at the moment, but I’ll try and clean it up and post a sample.
I’m struggling to figure out how to set the source texture to equal the destination texture. When I call _sourceMaterial.SetTexture(_sourceTextureID, _destinationRTHandle);
the resulting texture is black.
I’m starting to wonder if I’m even on the right track? If I want to apply multiple different shader passes to the same texture, is this approach correct, or should I do something different? I think what I’m trying to do is multipass rendering, which I know can be tricky in URP. Should I be using something like CommandBuffer.DrawRenderer
instead? Could really use some help.
Here’s an image of a cat to illustrate what I’m going for:
EnqueueRenderPasses
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
namespace Visuals
{
public class EnqueueRenderPasses : MonoBehaviour
{
private CustomRenderPass customRenderPass;
[SerializeField]
private Renderer _sourceRenderer;
private Material _sourceMaterial;
private Material _destinationMaterial;
[SerializeField]
private Shader _destinationShader;
private void OnEnable()
{
_sourceMaterial = _sourceRenderer.material;
_destinationMaterial = CoreUtils.CreateEngineMaterial(_destinationShader);
customRenderPass = new CustomRenderPass(_sourceMaterial, _destinationMaterial);
RenderPipelineManager.beginCameraRendering += OnBeginCamera;
}
private void OnDisable()
{
RenderPipelineManager.beginCameraRendering -= OnBeginCamera;
customRenderPass?.Dispose();
}
private void OnBeginCamera(ScriptableRenderContext context, Camera cam)
{
cam.GetUniversalAdditionalCameraData().scriptableRenderer.EnqueuePass(customRenderPass);
}
}
}
CustomRenderPass
using UnityEditor;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
namespace Visuals
{
public class CustomRenderPass : ScriptableRenderPass
{
private RenderTextureDescriptor _textureDescriptor;
//Source
private Material _sourceMaterial;
private const string _sourceTexturePropertyName = "_MainTex";
private int _sourceTextureID = Shader.PropertyToID(_sourceTexturePropertyName);
private Texture _sourceTexture;
private RTHandle _sourceRTHandle;
private RenderTexture _sourceRT;
//Destination
private Material _destinationMaterial;
private RTHandle _destinationRTHandle;
public CustomRenderPass(Material sourceMaterial, Material destinationMaterial)
{
//Source
_sourceMaterial = sourceMaterial;
_sourceTexture = _sourceMaterial.GetTexture(_sourceTextureID);
_sourceRT = new RenderTexture(_sourceTexture.width, _sourceTexture.height, 0);
//Destination
_destinationMaterial = destinationMaterial;
_textureDescriptor = new RenderTextureDescriptor(_sourceMaterial.GetTexture(_sourceTextureID).width,
_sourceMaterial.GetTexture(_sourceTextureID).height,
RenderTextureFormat.Default, 0);
RTHandles.Initialize(_sourceTexture.width, _sourceTexture.height);
}
public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
{
//Source
RenderTexture.active = _sourceRT;
Graphics.Blit(_sourceTexture, _sourceRT);
_sourceRTHandle = RTHandles.Alloc(_sourceRT);
//Destination
RenderingUtils.ReAllocateIfNeeded(ref _destinationRTHandle, _textureDescriptor);
ConfigureTarget(_destinationRTHandle);
}
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
CommandBuffer cmd = CommandBufferPool.Get();
Blitter.BlitTexture(cmd, _sourceRTHandle, _destinationRTHandle, _destinationMaterial, 0);
//I get a black texture here :(
_sourceMaterial.SetTexture(_sourceTextureID, _destinationRTHandle);
context.ExecuteCommandBuffer(cmd);
cmd.Clear();
CommandBufferPool.Release(cmd);
}
public void Dispose()
{
#if UNITY_EDITOR
if (EditorApplication.isPlaying)
{
Object.Destroy(_destinationMaterial);
}
else
{
Object.DestroyImmediate(_destinationMaterial);
}
#else
Object.Destroy(_renderPassMaterial);
#endif
_destinationRTHandle?.Release();
_sourceRTHandle?.Release();
}
}
}
Have you tried cmd.SetGlobalTexture
? And can you see your Blit destination texture’s result in Frame Debugger? And maybe you should set texture for your source material after execute commandbuffer.
By the way, if you need to copy the source texture to the destination texture, just use Blitter.BlitCameraTexture(cmd, src, dst)
which will blit texture using Unity’s default copy pass.But if using Blitter.BlitTexture(cmd, src, dst, mat, passindex)
, you have to write your own blit shader. So do you have your own blit shader? and can you share with us?
Further more, I’m not sure it’s right or not, Blitter.BlitTexture
will not set render target for you, and you may need to use cmd.SetRenderTarget
to set it by yourself.
Thanks for the reply.
Here is some updated code with the following edits:
- Changed
_sourceTexturePropertyName = "_MainTex"
to be_sourceTexturePropertyName = "_SourceTex"
so I could try outcmd.SetGlobalTexture(_sourceTextureID, _destinationRTHandle)
with a more unique name instead of_MainTex
. - Replaced
_sourceMaterial.SetTexture
withcmd.SetGlobalTexture
Unfortunately, running cmd.SetGlobalTexture
or _sourceMaterial.SetTexture
before or after context.ExecuteCommandBuffer(cmd)
resulted in the same black texture.
I’ve also included some screenshots of the shaders and frame debugger. Note: the destination shader has a _BlitTexture
unexposed property.
CustomRenderPass
using UnityEditor;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
namespace Visuals
{
public class CustomRenderPass : ScriptableRenderPass
{
private RenderTextureDescriptor _textureDescriptor;
//Source
private Material _sourceMaterial;
private const string _sourceTexturePropertyName = "_SourceTex";
private int _sourceTextureID = Shader.PropertyToID(_sourceTexturePropertyName);
private Texture _sourceTexture;
private RTHandle _sourceRTHandle;
private RenderTexture _sourceRT;
//Destination
private Material _destinationMaterial;
private RTHandle _destinationRTHandle;
public CustomRenderPass(Material sourceMaterial, Material destinationMaterial)
{
//Source
_sourceMaterial = sourceMaterial;
_sourceTexture = _sourceMaterial.GetTexture(_sourceTextureID);
_sourceRT = new RenderTexture(_sourceTexture.width, _sourceTexture.height, 0);
//Destination
_destinationMaterial = destinationMaterial;
_textureDescriptor = new RenderTextureDescriptor(_sourceMaterial.GetTexture(_sourceTextureID).width,
_sourceMaterial.GetTexture(_sourceTextureID).height,
RenderTextureFormat.Default, 0);
RTHandles.Initialize(_sourceTexture.width, _sourceTexture.height);
}
public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
{
//Source
RenderTexture.active = _sourceRT;
Graphics.Blit(_sourceTexture, _sourceRT);
_sourceRTHandle = RTHandles.Alloc(_sourceRT);
//Destination
RenderingUtils.ReAllocateIfNeeded(ref _destinationRTHandle, _textureDescriptor);
ConfigureTarget(_destinationRTHandle);
}
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
CommandBuffer cmd = CommandBufferPool.Get();
Blitter.BlitTexture(cmd, _sourceRTHandle, _destinationRTHandle, _destinationMaterial, 0);
//I still get a black texture here :(
cmd.SetGlobalTexture(_sourceTextureID, _destinationRTHandle);
context.ExecuteCommandBuffer(cmd);
cmd.Clear();
CommandBufferPool.Release(cmd);
}
public void Dispose()
{
#if UNITY_EDITOR
if (EditorApplication.isPlaying)
{
Object.Destroy(_destinationMaterial);
}
else
{
Object.DestroyImmediate(_destinationMaterial);
}
#else
Object.Destroy(_renderPassMaterial);
#endif
_destinationRTHandle?.Release();
_sourceRTHandle?.Release();
}
}
}
Runtime Result
Source Shader
Destination Shader
Frame Debugger: Blitter.BlitTexture SRP Batch
Frame Debugger: cmd.SetGlobalTexture SRP Batch
From looking at the source, it appears that it does set the render target:
/// <summary>
/// Blit a Texture with a specified material. The reference name "_BlitTexture" will be used to bind the input texture.
/// </summary>
/// <param name="cmd">Command Buffer used for rendering.</param>
/// <param name="source">Source render target.</param>
/// <param name="destination">Destination render target.</param>
/// <param name="material">Material to invoke when blitting.</param>
/// <param name="pass">Pass idx within the material to invoke.</param>
public static void BlitTexture(CommandBuffer cmd, RenderTargetIdentifier source, RenderTargetIdentifier destination, Material material, int pass)
{
// Unfortunately there is no function bind a RenderTargetIdentifier with a property block so we have to bind it globally.
cmd.SetGlobalTexture(BlitShaderIDs._BlitTexture, source);
cmd.SetRenderTarget(destination);
DrawTriangle(cmd, material, pass);
}
Can you show me your Destination ShaderGraph’s Graph Settings? Is it in FullScreen Mode now? It should looks like this pic:
And for Blit Texture ,you can use
URP Sample Buffer
node, and set Source Buffer
to Blit Source
, for example:By the way, if you don’t want to perform a full screen blit action, just send your source texture to a shader graph and apply some filters in it, there’s no need to create an additional render pass.
The destination shader graph is set to Unlit. When I set it to Fullscreen with a URP Sample Buffer set to BlitSource the _SourceTex
remains black. After modifying the code so I could see the _destinationRTHandle
Render Texture in the Inspector, it also appears black, so that seems to be the fundamental problem.
Regarding simply sending the texture through a shader instead of render passes, I can’t do that because I need runtime control to apply any number of render passes to the same texture, in various orders.
Maybe the issue is that I’m injecting my pass in a scene with RenderPipelineManager.beginCameraRendering
instead of a Renderer Feature?
Here is my updated code:
EnqueueRenderPasses
- Added
RenderTexture _sourceRT
andRenderTexture _destinationRT
to see the textures in the Inspector
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
namespace Visuals
{
public class EnqueueRenderPasses : MonoBehaviour
{
private CustomRenderPass customRenderPass;
//Source
[SerializeField]
private Renderer _sourceRenderer;
private Material _sourceMaterial;
[SerializeField]
private RenderTexture _sourceRT;
//Destination
private Material _destinationMaterial;
[SerializeField]
private Shader _destinationShader;
[SerializeField]
private RenderTexture _destinationRT;
private void OnEnable()
{
_sourceMaterial = _sourceRenderer.material;
_destinationMaterial = CoreUtils.CreateEngineMaterial(_destinationShader);
customRenderPass = new CustomRenderPass(_sourceMaterial, _destinationMaterial);
//Event listeners
RenderPipelineManager.beginCameraRendering += OnBeginCamera;
customRenderPass.OnSourceRTCreated += OnSourceRTCreated;
customRenderPass.OnDestinationRTCreated += OnDestinationRTCreated;
}
private void OnDisable()
{
RenderPipelineManager.beginCameraRendering -= OnBeginCamera;
customRenderPass?.Dispose();
}
private void OnBeginCamera(ScriptableRenderContext context, Camera cam)
{
cam.GetUniversalAdditionalCameraData().scriptableRenderer.EnqueuePass(customRenderPass);
}
private void OnSourceRTCreated(RenderTexture rt)
{
_sourceRT = rt;
}
private void OnDestinationRTCreated(RenderTexture rt)
{
_destinationRT = rt;
}
}
}
CustomRenderPass
- Added events for the render texture creation
- Added an
_OutputTex
unexposed property to a new Output shader to use with thecmd.SetGlobalTexture
call. Did this so I could expose the_SourceTex
property to set the source cat texture.
using UnityEditor;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
namespace Visuals
{
public class CustomRenderPass : ScriptableRenderPass
{
private RenderTextureDescriptor _textureDescriptor;
//Source
private Material _sourceMaterial;
private const string _sourceTexturePropertyName = "_SourceTex";
private int _sourceTextureID = Shader.PropertyToID(_sourceTexturePropertyName);
private Texture _sourceTexture;
private RTHandle _sourceRTHandle;
private RenderTexture _sourceRT;
public event System.Action<RenderTexture> OnSourceRTCreated;
//Destination
private Material _destinationMaterial;
private RTHandle _destinationRTHandle;
public event System.Action<RenderTexture> OnDestinationRTCreated;
//Output
private const string _outputTexturePropertyName = "_OutputTex";
private int _outputTextureID = Shader.PropertyToID(_outputTexturePropertyName);
public CustomRenderPass(Material sourceMaterial, Material destinationMaterial)
{
//Source
_sourceMaterial = sourceMaterial;
_sourceTexture = _sourceMaterial.GetTexture(_sourceTextureID);
_sourceRT = new RenderTexture(_sourceTexture.width, _sourceTexture.height, 0);
//Destination
_destinationMaterial = destinationMaterial;
_textureDescriptor = new RenderTextureDescriptor(_sourceMaterial.GetTexture(_sourceTextureID).width,
_sourceMaterial.GetTexture(_sourceTextureID).height,
RenderTextureFormat.Default, 0);
RTHandles.Initialize(_sourceTexture.width, _sourceTexture.height);
}
public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
{
//Source
RenderTexture.active = _sourceRT;
Graphics.Blit(_sourceTexture, _sourceRT);
_sourceRTHandle = RTHandles.Alloc(_sourceRT);
OnSourceRTCreated?.Invoke(_sourceRT);
//Destination
RenderingUtils.ReAllocateIfNeeded(ref _destinationRTHandle, _textureDescriptor);
ConfigureTarget(_destinationRTHandle);
OnDestinationRTCreated?.Invoke(_destinationRTHandle.rt);
}
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
CommandBuffer cmd = CommandBufferPool.Get();
Blitter.BlitTexture(cmd, _sourceRTHandle, _destinationRTHandle, _destinationMaterial, 0);
//I still get a black texture here :(
cmd.SetGlobalTexture(_outputTextureID, _destinationRTHandle);
context.ExecuteCommandBuffer(cmd);
cmd.Clear();
CommandBufferPool.Release(cmd);
}
public void Dispose()
{
#if UNITY_EDITOR
if (EditorApplication.isPlaying)
{
Object.Destroy(_destinationMaterial);
}
else
{
Object.DestroyImmediate(_destinationMaterial);
}
#else
Object.Destroy(_renderPassMaterial);
#endif
_destinationRTHandle?.Release();
_sourceRTHandle?.Release();
}
}
}
And here is the result showing the same black texture in the output:
I set the destination shader Graph Settings to Fullscreen with a URP Sample Buffer set to BlitSource, and the destination render texture now has color information in it, but it appears to be recursively rendering into itself…
Still struggling with this one. Anyone have any ideas?
Closing this thread in favor of this new one that is a more concise representation of the problem: cmd.Blit works, how to get Blitter to do the same thing? - Unity Engine - Unity Discussions