Search Unity

Bug Using NormalBuffer in a custom pass results in broken TAA

Discussion in 'High Definition Render Pipeline' started by Passeridae, Nov 18, 2021.

  1. Passeridae

    Passeridae

    Joined:
    Jun 16, 2019
    Posts:
    395
    If I create a custom pass that relies on the normal buffer, say, like this:
    Code (CSharp):
    1. float4 FullScreenPass(Varyings varyings) : SV_Target
    2.     {
    3.         NormalData normalData;
    4.         DecodeFromNormalBuffer(varyings.positionCS.xy, normalData);
    5.         float3 normals = normalData.normalWS;
    6.  
    7.         return float4(normals.rgb, 1);
    8.     }
    9.  
    Then I get this, when the custom pass writes directly to the camera and the camera moves:
    upload_2021-11-18_16-31-34.png
    Some artifacts like the black outline start to appear. There's also a little ghosting (watch the full video I'll link below).

    Then I enable TAA on the camera:
    upload_2021-11-18_16-33-31.png
    We get heavy TAA artifacts during any movement.

    Then I disable TAA and write the full-screen pass not to the camera but to the custom buffer, read it in shader graph and assign as the base color:
    upload_2021-11-18_16-34-44.png
    It's getting even worse

    And then the worst scenario: custom buffer + TAA enabled:
    upload_2021-11-18_16-35-31.png

    So, any custom pass effect that is based on the normal buffer looks terrible in the movement. How do I fix it?

    Full video:
     
  2. Passeridae

    Passeridae

    Joined:
    Jun 16, 2019
    Posts:
    395
    Surprisingly, if I add this empty custom pass after mine, the artifacts disappear. The issue might be related to the depth buffer.

    upload_2021-11-19_5-9-57.png
     
  3. antoinel_unity

    antoinel_unity

    Unity Technologies

    Joined:
    Jan 7, 2019
    Posts:
    262
    Hello,

    Outputting normal values like this in the shader will result in negative values in the color buffer which is an invalid input for the TAA algorithm. The solution here would be to clamp the normal or remap it to avoid having negative values in the color buffer.

    Also, note that you can only have negative values when the color buffer is using the R16G16b16A16 format for the color buffer in HDRP, you'll not have the issue with R11G11B10 because it automatically clamps
     
  4. Passeridae

    Passeridae

    Joined:
    Jun 16, 2019
    Posts:
    395
    Thanks! Will try that.
    What about the fact that there are artifacts even with TAA disabled? They are especially noticable when the custom pass outputs to the custom color buffer which is then sampled in SG.These are not typical ghosting/trailing artifacts. They look like "double-vision" effect, almost like the custom buffer lags behind or something, which results in what can be observed on the third screen from my original post.
     
    Last edited: Nov 19, 2021
  5. antoinel_unity

    antoinel_unity

    Unity Technologies

    Joined:
    Jan 7, 2019
    Posts:
    262
    For this, I'm not sure. it could be because you're using other effects that cause this issue in the post process.
    For the bug in the custom buffer, I suppose you have another pass where you read the result of that custom buffer and apply it to the color buffer. That might be a good place to start looking for an issue.

    In my custom pass sample repository, I have several examples using the normal buffer without much issues, you can take a look at them if you want some pointers: https://github.com/alelievr/HDRP-custom-passes
     
  6. Passeridae

    Passeridae

    Joined:
    Jun 16, 2019
    Posts:
    395
    Well, no. I have a single full screen custom pass with a single pass shader that writes to the custom color buffer which is sampled via the "Custom Color Buffer" node that is fed directly to the albedo input of the Lit SG which is applied to some objects in the scene. Which results in this:

    upload_2021-11-19_20-40-1.png
    upload_2021-11-19_20-40-30.png

    In movement. The same effect is absent when this pass is applied directly to the camera. So the problem lies in the custom color buffer, I guess. I've tried all three possible formats of this buffer, with no luck.

    I've also tried transparent lit SG, but it just can't correctly handle the custom color buffer at all. I've noticed that the problem is mostly present when two objects with the Lit SG where the sampling of the color buffer is performed overlap each other. Its' less visible when such objects overlap another with a different material and the issue goes away completely in the regions where such objects overlap the skybox:
    upload_2021-11-19_21-1-37.png
     
    Last edited: Nov 19, 2021
  7. Passeridae

    Passeridae

    Joined:
    Jun 16, 2019
    Posts:
    395
    Btw, they have the same issues.
    I created an empty scene with a pair of cubes, added your "scrolling formulas" custom pass, changed the picture of formulas to another texture. When it outputs to the camera - everything fine (just as with my custom pass):
    upload_2021-11-20_1-8-44.png
    Then I change the output to the custom buffer and plug this custom buffer straight to the albedo of the Lit graph which I apply to these cubes. And here we go:
    upload_2021-11-20_1-9-45.png
    This happens when I move my camera. This is exactly the same issue that I experience with my custom pass.
     
    Last edited: Nov 19, 2021
  8. Passeridae

    Passeridae

    Joined:
    Jun 16, 2019
    Posts:
    395
    @antoinel_unity Can you take a look at this please, and maybe recommend something?
    Thanks!
     
  9. Passeridae

    Passeridae

    Joined:
    Jun 16, 2019
    Posts:
    395
  10. RogueStargun

    RogueStargun

    Joined:
    Aug 5, 2018
    Posts:
    296
    I'm encountering some weirdness here too. Why doesn't the extracted normal buffer look the same as the "normals" extracted by AOV?
     
  11. Passeridae

    Passeridae

    Joined:
    Jun 16, 2019
    Posts:
    395
    I found that this is not related to the normal buffer specifically. It seems like that when you sample the custom buffers in SG you get the previous frame data instead of the current one. Therefore it lags. Maybe there's something else, but even when I reconstruct normals from depth (so, no sampling of normal buffer) this bug steel occurs.

    And there's literally no official example of a full screen custom pass writing to the custom color buffer and then SG successfully sampling from it. The documentation states that it's possible, but I don't see how it's possible without these artifacts.
     
  12. antoinel_unity

    antoinel_unity

    Unity Technologies

    Joined:
    Jan 7, 2019
    Posts:
    262
    Hello,

    I believe you're seeing these artifacts because the custom pass is executed after the rendering of opaque objects. In that case, it's completely normal to have the previous frame in the color buffer when you use it in a ShaderGraph. Keep in mind that opaque objects are rendered between the "After Opaque Depth And Normal" and "Before Pre Refraction" so if you want to use a buffer for opaque rendering, your custom pass needs to use the "After Opaque Depth And Normal" or "Before Rendering".
    Please refer to the HDRP frame diagram for more information
     

    Attached Files:

  13. Passeridae

    Passeridae

    Joined:
    Jun 16, 2019
    Posts:
    395
    Hi! It doesn't matter which injection point is used. Just for the sanity check I just went and checked again what happens if I use "After Opaque Depth And Normal". Nothing changes. The bug still there. Just as with any other injection point.
    Please, take a look at this forum thread: https://forum.unity.com/threads/sampling-custom-pass-in-sg-is-broken.1277297/
    It contains more info on the issue. For example, I've written there, that it doesn't matter which injection point is used.
    Btw, have you tried it yourself? I understand that it should work with "After Opaque Depth And Normal" in theory, but in practice it doesn't work this way.
     
  14. antoinel_unity

    antoinel_unity

    Unity Technologies

    Joined:
    Jan 7, 2019
    Posts:
    262
    Thanks for the detailed information. I've tried to reproduce that issue several times with the normal buffer but couldn't reproduce it.
    Here's the current setup I'm using:
    - Fullscreen custom pass that samples the normal buffer in the "After Opaque Depth and Normal injection point" and outputs it to the custom buffer
    - An HDRP Lit ShaderGraph samples the custom buffer and outputs it as color. In the scene, the object is opaque so it writes it's normal in the depth pre-pass.

    According to your post, it should repro the issue but I don't see it. Maybe you could share one of your scenes so I can try to figure out what the issue is?

    Also, note that in the example above if I use any other injection point I have ghosting but that's expected as I said in my previous message.
     
  15. antoinel_unity

    antoinel_unity

    Unity Technologies

    Joined:
    Jan 7, 2019
    Posts:
    262
    Update after I took a look at the repro project.

    There is indeed a 1 frame lag in the color displayed on the objects in the scene, but unfortunately, it's not possible to fix it with the current setup of the project.
    The "lag" comes from the custom color buffer being read in a ShaderGraph shader before being written in the custom pass later on (thus it reads the previous frame custom buffer as it's not cleared).

    This is happening because the project is setup using the deferred rendering path, deferred renders both the albedo and the normal buffer inside the "G-Buffer" pass. In this case, it causes issues because the custom pass that writes the information can only be executed after the G-Buffer pass (it needs the normal buffer information).

    To sum up we have these operations right now:
    - G-Buffer reads the custom color
    - G-Buffer writes normals
    - Custom Pass reads transfers normal to custom color

    As you can see there is a dependency problem between the custom color and the normal buffer.

    To fix this issue in your project, I recommend using a Forward rendering path if it's possible. Another solution would be to use transparent materials in ShaderGraphs instead of opaque since they are rendered after.
     
  16. Passeridae

    Passeridae

    Joined:
    Jun 16, 2019
    Posts:
    395
    Thank you for the investigation!

    Does it mean that this limitation makes custom passes virtually unusable for any more or less complex effects based on Normal / Roughness buffer when not in fully Forward mode then? If yes, you need to at least mention this in a number of places inside the docs very clearly.
     
  17. antoinel_unity

    antoinel_unity

    Unity Technologies

    Joined:
    Jan 7, 2019
    Posts:
    262
    It really depends on what you're trying to do, but if you really need to read the normal buffer from an opaque shader then it's not possible indeed. Unless you are okay with paying the cost of rendering all of your objects twice to prepare a normal buffer before the opaque objects pass (in a custom pass at the before rendering injection point for example).
     
  18. Passeridae

    Passeridae

    Joined:
    Jun 16, 2019
    Posts:
    395
    Anything that needs normal data. You have such examples in your github repo. For example "Scrolling Formulas" effect. It won't work correctly when outputted through SG.

    I guess using CustomPassUtils.RenderNormalFromCamera will do?
     
  19. antoinel_unity

    antoinel_unity

    Unity Technologies

    Joined:
    Jan 7, 2019
    Posts:
    262
    You already have access to the normal in ShaderGraph, so for this kind of effect, it's easy to use that and combine it with a triplanar node. Though I agree that if you need to sample the normal on another location than the current shaded pixel, it won't work (which would be a very rare use case during the computation of the object color).

    Yes