Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Feature Request Setting multiple render targets without a depth buffer?

Discussion in 'General Graphics' started by SamOld, Jul 2, 2022.

  1. SamOld

    SamOld

    Joined:
    Aug 17, 2018
    Posts:
    325
    The only overloads of
    CommandBuffer.SetRenderTarget
    that allow for MRT (multiple targets) also require a depth buffer to be specified. For example,
    SetRenderTarget(RenderTargetIdentifier[] colors, RenderTargetIdentifier depth)
    .

    What is the proper value to pass for depth when there is no depth buffer?

    I'm working on the builtin render pipeline, on 2021.3.1.

    I've tried the obvious things, like
    (RenderTargetIdentifier)RenderTargetIdentifier.AllDepthSlices
    , but without success. In fact, this appears to lead to buggy behaviour. As confirmed by RenderDoc, this sometimes does what I'm wanting, and sometimes errors with
    Invalid output merger - Depth target is different size or MS count to render target(s)
    . The behaviour appears 50/50 random between frames.

    Note that that's an error showing up in RenderDoc. Unity itself doesn't display any error, it simply fails to switch render targets, and clears the previously bound one instead.

    My comlete test repro, there's nothing else in this project interfering here.

    Code (CSharp):
    1. var cb = new CommandBuffer();
    2. var textureId = Shader.PropertyToID("RenderTextureName");
    3. cb.GetTemporaryRT(textureId, new RenderTextureDescriptor(
    4.     width: 1024,
    5.     height: 1024,
    6.     colorFormat: RenderTextureFormat.ARGB32,
    7.     depthBufferBits: 0,
    8.     mipCount: 0
    9.  ));
    10.  
    11. // This works fine
    12. //cb.SetRenderTarget(textureId);
    13.  
    14. // This breaks
    15. //cb.SetRenderTarget(textureId, (RenderTargetIdentifier)RenderTargetIdentifier.AllDepthSlices);
    16.  
    17. // As does this, which is what I'm actually trying to do
    18. cb.SetRenderTarget(
    19.     new RenderTargetIdentifier[] {
    20.         textureId,
    21.         /*and others*/
    22.     },
    23.     (RenderTargetIdentifier)RenderTargetIdentifier.AllDepthSlices
    24. );
    25.  
    26. cb.ClearRenderTarget(false, true, Color.blue);
    27. Graphics.ExecuteCommandBuffer(cb);
    I'm hoping this is a me problem, and not another case of finding an engine bug within an hour of trying to do some interesting rendering work.
     
    Last edited: Jul 3, 2022
  2. georgerh

    georgerh

    Joined:
    Feb 28, 2020
    Posts:
    72
    Try BuiltinRenderTextureType.None and BuiltinRenderTextureType.CurrentActive.

    Don't remember exactly what happened when I tried it, but my guess
    - none probably didn't work
    - current active does work but could either bind nothing, the current target which happens to be the right size or the current target which gives a warning if it's the wrong size

    If you use CurrentActive, you have to make sure to disable depth testing and writing (ZWrite Off, ZTest Always).
     
    Last edited: Jul 3, 2022
    SamOld likes this.
  3. SamOld

    SamOld

    Joined:
    Aug 17, 2018
    Posts:
    325
    BuiltinRenderTextureType.None
    is one I had already tried.

    BuiltinRenderTextureType.CurrentActive
    is a new idea I believe, and it sort of works! A shader with
    ZWrite Off
    ,
    ZTest Always
    appears to work fine, but a simple
    ClearRenderTarget(false, true, color)
    is still broken. I can work around that obviously, but it still feels like I'm doing this The Wrong Way.

    Given the strange behaviour, I'm a little concerned that this solution is going to have platform compatibility or stability problems, or something like that. It seems very strange that there isn't simply a depth free overload. If I were able to use the single target version, all of these things would work seamlessly, so clearly the engine is doing something different internally in that case.

    Thanks for the pointer @georgerh, I think this has gotten me far enough to progress for now, but I would still like to hear whether there's a more The Right Way way of doing this. Perhaps a Unity staff member feels like charming in? :)
     
  4. georgerh

    georgerh

    Joined:
    Feb 28, 2020
    Posts:
    72
    Maybe you could do two SetRenderTarget calls, one to unbind the depth target and then one to bind the MRT targets with CurrentActive.

    But yeah, would be great to hear from Unity.
     
    SamOld likes this.
  5. georgerh

    georgerh

    Joined:
    Feb 28, 2020
    Posts:
    72
    It's also worth nothing that it's not documented how SetRenderTarget(RenderTexture rt) behaves exactly.

    Render textures may contain a color AND/OR depth buffer. Does SetRenderTarget bind whatever the render texture contains and unbind what it does not contain? Or does it leave the other target alone?

    var colorOnly = new RenderTexture(width, height, 0, RenderTextureFormat.ARGB32);
    var depthOnly = new RenderTexture(width, height, 16, RenderTextureFormat.Depth);
    var colorAndDepth = new RenderTexture(width, height, 16, RenderTextureFormat.ARGB32);

    SetRenderTarget(colorOnly) // does this unbind the current depth texture?
    SetRenderTarget(depthOnly) // does this unbind the current color texture?
    SetRenderTarget(colorAndDepth) // does this bind both color and depth?
     
    SamOld likes this.
  6. joshuacwilde

    joshuacwilde

    Joined:
    Feb 4, 2018
    Posts:
    692
    I think you can just pick any of your existing render textures and bind the RenderTexture.depthBuffer. Preferably one where RenderTexture.depth == 0;
     
    forestrf likes this.
  7. SamOld

    SamOld

    Joined:
    Aug 17, 2018
    Posts:
    325
    I'm actually finding that
    CurrentActive
    or binding any other existing buffer throws an exception if the sizes don't match. I've been able to just about shift things around to work because I got lucky that I've got some other RTs of the right size, but this isn't a robust or proper solution.
    CurrentActive
    is particularly unstable, because it relies on state that may be coming from other parts of the application.

    I'd appreciate hearing from Unity about what the proper way to do this is!

    Another workaround would probably be to just give the
    RenderTexture
    depth and not use it, but that's no fun.
     
  8. SamOld

    SamOld

    Joined:
    Aug 17, 2018
    Posts:
    325
    I've decided to change this thread to being marked as a feature request for now, as pending some revelation of The Right Way, I think Unity could really use an extra overload on
    CommandBuffer.SetRenderTarget
    that takes an array without a separate depth buffer.

    And somewhat off topic, but overloads that take
    Span
    would be nice too, just to avoid having to awkwardly keep track of a reusable array to avoid GC.
     
    guycalledfrank and georgerh like this.
  9. SamOld

    SamOld

    Joined:
    Aug 17, 2018
    Posts:
    325
    So it turns out I'm a giant dumb dumb head.

    You can just pass the any of the
    RenderTargetIdentifiers
    for the
    RenderTexture
    you're targeting as depth. If they have 0 bits of depth, that's fine.

    I swear that was the first thing I tried... But it seems to work now. Maybe there's some other bug that I hit the first time, as I've changed the renderer about quite a bit since then.
     
    guycalledfrank likes this.
  10. guoxx_

    guoxx_

    Joined:
    Mar 16, 2021
    Posts:
    51
    I meet the same issue of setting MRT without a depth buffer, it's not allowed in Unity (tested with 2021.3).
    It's pretty often that in a full-screen post-process writing to MRT, that case requiring a depth buffer is very inconvenient.