Search Unity

Question Weird reflections with Deferred rendering path

Discussion in 'General Graphics' started by edredar, Apr 25, 2021.

  1. edredar

    edredar

    Joined:
    Jul 26, 2012
    Posts:
    58
    Hi. I've stumbled upon a problem somehow related to smoothing groups of a mesh and reflections. In these two screenshots you can clearly see the problem - in deffered rendering path polygons look like they are separated (yet somehow preserve the smoothing groups), while in forward they are okay.

    deferred.jpg forward.jpg

    Scene setup is pretty simple:
    - New Unity project (2020.3.2)
    - Default empty scene
    - Linear color space
    - PC as target platform
    - Camera set to forward or deferred rendering path respectively
    - Simple material with Standard shader with metallic set to 1 and smoothness to .95 (tried with Specular setup, it's the same)
    - Modelling software - 3ds Max 2020

    What I've tried:
    - Recreated the project in Unity 5.5.2
    - Metallic and Specular setups with Standard shader
    - Different export file formats for the model (usually .fbx, tried .obj and .3ds)
    - Every relevant setting in .fbx exporter, including different versions of fbx exporter itself
    - Every combination of settings in Unity's mesh importer in Geometry section
    - Every setting in Graphics (current tier), Quality (left only one to be sure) and Player settings

    I really need the Deferred rendering path in my project, so I can't fall back to Forward. But I can't figure out what's going on with meshes, set to be metallic and highly glossy in Deferred.
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    Nothing to do with smoothing groups or really the mesh normals at all.

    The issue you're experiencing is one fundamental to deferred rendering. Or at least a fundamental issue with an optimization that Unity is using for its deferred renderer.

    First, a quick run down of how deferred shading works. The first step is to render all opaque objects to the gbuffers. A gbuffer (Geometry Buffer) is a full screen render texture that holds some information about the geometry being rendered. For Unity this is an albedo & AO gbuffer, a specular color & smoothness gbuffer, a normal gbuffer, a depth buffer, and an emissive gbuffer. The second step is to render the lights & reflections, which sample from the gbuffers to get the surface information for each pixel and produce the final lighting. What this means is when your objects are being rendered, they aren't doing any lighting (unless they're using baked lighting) or reflections, but they are rendering to multiple render targets (MRT) at the same time.

    Here's where one of the issues with deferred rendering comes in, and the optimization that's causing the issue you're seeing. Writing to multiple render targets at once is expensive, and the cost increases with how much data the render texture format uses. So a popular solution is to reduce the color depth of the target render textures as much as possible. In Unity's case they chose this layout:
    https://docs.unity3d.com/Manual/RenderTech-DeferredShading.html
    Note the RT2 gbuffer, the one used for normals, is an ARGB2101010 format, which has 10 bits per RGB channel. This is a very popular format for the normal gbuffer because it has significantly more precision than the default ARGB32 which only has 8 bits per RGB channel, but is still only a total of 32 bits meaning it's not any more expensive to render to. The next best option would be something like ARGBHalf, which is 16 bits per channel, or 64 bits total being rendered to. Technically there are also ways of storing the world normal in an RGHalf format, but that usually comes with additional problems either in the form of higher shader costs to calculate the value to write to the gbuffer and to decode from it, as well as new precision issues.

    The short version is what you're seeing is the limits of naively encoded 10 bit normals.

    And before you start thinking "that's terrible, this is clearly another example of why Unity sucks compared to ..." let me stop you because the engine you're thinking of likely also uses ARGB2101010 normals. Unreal Engine does for example, though they recently added an option to enable high quality normals, which likely switches to something equivalent to an ARGBHalf format. Unity's HDRP and several modern games even still an ARGB32(!) for their normals, but use various tricks to try to pack the data.


    So what's the solution? There isn't a good one really. Unity doesn't have an option to increase the normal gbuffer precision, so you're left with trying to make use of the precision available. You could write a custom shader that adds a small amount of noise to the normal which may help hide the precision limitations. Or you could look at implementing best fit normals ... which is kind of like adding noise to the normals but in a way that's making better use of the render texture precision. Just know that any of the examples you're likely to find out there for best fit normals are designed around 8 bpc normal gbuffers and not 10 bpc, though the same concept is applicable.
     
  3. edredar

    edredar

    Joined:
    Jul 26, 2012
    Posts:
    58
    Thank you for such useful and detailed answer. Well, I need extremely polished metal for my project, rendered with built-in pipeline + deferred. Since it's impossible to fix given the circumstances, I'll just disable deferred reflections (I don't need it done in deferred anyways).
     
    bgolus likes this.
  4. Itschotsch

    Itschotsch

    Joined:
    Jan 30, 2018
    Posts:
    2
  5. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352