Search Unity

How to render opaque mesh A behind mesh B that it is physically in front of?

Discussion in 'Shaders' started by s_guy, Jul 18, 2017.

  1. s_guy

    s_guy

    Joined:
    Feb 27, 2013
    Posts:
    102
    I've had luck with manipulating render sorting of transparent things, both with the shader / material render queue value and also with the sorting groups and values. (BTW, can anyone explain how these two sorting systems interact?)

    Opaque objects on the other hand don't seem to accept these kinds of sorting hints to the camera, which is apparently an optimization. It isn't often desirable anyway because you usually want the physical position of the mesh to determine what draws in front of what.

    If you do have an unusual case where you want to always draw a given opaque mesh A behind a category of opaque and transparent meshes B despite the position and extents of mesh A what is the approach?

    Is there some way to force the render queue value or the sorting group to work on a mesh with an opaque material?

    Or is there some way to have a mesh with a transparent, sortable material appear effectively opaque? I've tried this with the standard shader, but the result looks self-transparent (see mesh internal geometry), even with alpha at 1.

    I've also tried playing with the offset units. This seems to be an unorthodox usage because I only see it mentioned in reference to decals and z-fighting problems and I'm using much larger values than the examples I've seen. It can achieve the intended result of "pushing" the object relatively farther back than its world position as far as rendering is concerned, but this solution has to be tuned for a specific camera distance and breaks if the camera moves too far (lose z buffer precision). Also I understand that offset units cannot be parameterized to work around that.

    I'm a shader newbie, and at this point it feels like I'm really going against the grain and missing something obvious. Any thoughts to share?

    Thanks!
     
    Last edited: Jul 18, 2017
  2. guillermoi

    guillermoi

    Joined:
    Sep 20, 2012
    Posts:
    27
    Hi.

    So if you have already changed the render queue of a material/shader then you could do the following:

    Draw B first (set the render queue value so he renders before everything)

    Draw A but with a shader that has:ZTest Always ( so it renders on top of B)

    Render the rest of the objects
     
    asdzxcv777 and s_guy like this.
  3. s_guy

    s_guy

    Joined:
    Feb 27, 2013
    Posts:
    102
    Thanks for this lead!

    It's a bit unfortunate if I have to modify all shaders for objects of both categories A and B, but I suspected that might be the case.
     
  4. jvo3dc

    jvo3dc

    Joined:
    Oct 11, 2013
    Posts:
    1,520
    Only for A really. For B you can just adjust the queue value in the materials with a script.
     
    asdzxcv777 and s_guy like this.
  5. s_guy

    s_guy

    Joined:
    Feb 27, 2013
    Posts:
    102
    good point
     
  6. daxiongmao

    daxiongmao

    Joined:
    Feb 2, 2016
    Posts:
    412
    Is it really only two objects?
    You could look at manually drawing them with unitys graphics.drawmesh
    https://docs.unity3d.com/ScriptReference/Graphics.DrawMesh.html
    Or command buffers.

    Also if you set alpha to 1 it should look the same as opaque. But the standard alpha shaders do not usually write depth. So you might need to make one that does.
     
    s_guy likes this.
  7. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,348
    So, a question: Are you looking to have one object always render on top of everything, or just a specific object (or set of objects)?

    If you want something to always render based on its render queue that can be done with just a single shader on one object using ZTest Always or a two pass approach.

    Code (CSharp):
    1. Shader "Custom/Opaque Render Queue Sorted" {
    2. Properties {
    3.     _Color ("Main Color", Color) = (1,1,1,1)
    4.     _MainTex ("Base (RGB)", 2D) = "white" {}
    5. }
    6. SubShader {
    7.     Tags { "RenderType"="Opaque" }
    8.     LOD 200
    9.  
    10.     Pass {
    11.         // Depth Clear Pass
    12.         ZWrite On
    13.         ZTest Always
    14.         ColorMask 0
    15.  
    16.         CGPROGRAM
    17.             #pragma vertex vert
    18.             #pragma fragment frag
    19.             #pragma target 2.0
    20.  
    21.             #include "UnityCG.cginc"
    22.        
    23.             float4 vert (float4 vertex : POSITION) : SV_POSITION
    24.             {
    25.                 return UnityObjectToClipPos(vertex);
    26.             }
    27.        
    28.             float frag () : SV_Depth
    29.             {
    30.                 #if defined(UNITY_REVERSED_Z)
    31.                     return 0;
    32.                 #else
    33.                     return 1;
    34.                 #endif
    35.             }
    36.         ENDCG
    37.     }
    38.  
    39.     CGPROGRAM
    40.     #pragma surface surf Lambert
    41.  
    42.     sampler2D _MainTex;
    43.     fixed4 _Color;
    44.  
    45.     struct Input {
    46.         float2 uv_MainTex;
    47.     };
    48.  
    49.     void surf (Input IN, inout SurfaceOutput o) {
    50.         fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
    51.         o.Albedo = c.rgb;
    52.         o.Alpha = c.a;
    53.     }
    54.     ENDCG
    55. }
    56.  
    57. Fallback "Legacy Shaders/VertexLit"
    58. }
    59.  
    Objects of the same queue will sort together like they do normally (if using Unity 5.5 or later, otherwise by depth sort). Objects of higher queues will render on top of everything rendered before them.

    This only works with forward rendering, not deferred! Deferred rendering does not respect render queues <2500!

    If you want an object to render over another, and only that one other, that will require special shaders on both objects. Specifically you would have to look into using stencils.
     
    captainfuzzyfeet and s_guy like this.
  8. s_guy

    s_guy

    Joined:
    Feb 27, 2013
    Posts:
    102
    I got this to work for both opaque and transparent objects in both categories A and B on test shaders. Thanks!

    Now to see if this change causes any problems when modifying the various existing shaders.

    Thanks for the leads. It's two categories of objects in a complex scene and they need to be imported artist meshes. These are drawn at similar distances to the camera, but one needs to always be behind the other despite sometimes being large enough to poke through.

    Command buffers look interesting, but it may be a deep rabbit hole in even less familiar territory. So, I'll shelve that research for now.

    Yeah, I would think alpha 1 on a transparent (and directly sortable) material would be the same as opaque. Shadow from facets inside or on the back of the mesh were showing through though. If I set it to write depth it solves that problem, but then no longer seems to sort by render queue or sorting group against other standard shaders.

    I'm looking to have a category of objects always render in front of another category of objects. Objects of both categories should render versus everything else in a standard way.

    Yep forward renderer.

    Thanks for the example code, caveats, and the stencil lead! So much to learn!