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. Have a look at our Games Focus blog post series which will show what Unity is doing for all game developers – now, next year, and in the future.
    Dismiss Notice

Question Trying to use Depth in Renderer Features, Render Objects

Discussion in 'Shaders' started by Yoraiz0r, Aug 8, 2021.

  1. Yoraiz0r


    Apr 26, 2015
    Hello all,

    I've been experimenting with the creation of a Renderer Feature, using Unity 2020.1.12f1 and URP 8.2.0.
    In my experiment, I reached the conclusion that I likely want to render specific objects - but only at the depth that they had already drawn before, with a different shader.

    Before going further, I think it is important to explain what my effect tries to achieve - visually.
    I am trying to - after the skybox render ends - isolate all objects of a specific layer mask, plus the sky , into a mask that I can then modify the color of.

    The intent is to adjust the colors of everything except specific objects (player characters, attacks), to achieve unique feels like 'Witch time' from Bayonetta, 'Time Fracture' from Honkai Impact, and other, similar effects akin to fighter games 'scene switching for a special move'.

    For example, using a recent unity made game - Genshin impact, you can see a similar effect in these two attacks:

    Kotomi Elemental Burst
    • When the character uses the attack, the terrain becomes tinted blue and there's supposedly a projector doing waves on the floor - However, the character colors remain mostly unchanged.

    Sara Elemental Burst
    • When the character uses the attack, the terrain loses its global light, and all brightness values become inverse, momentarily - the character and attacks keep their normal colors, though. (I assume this also writes to the shadowmaps to do the light disappearance)

    Honkai Impact - Various Effects
    • At 0:08, when the alarm sound plays, the entire environment far into the depth gets tinted in red colors, though the nearby area and character remain mostly unaffected (I assume this can be a simple effect using depth and recoloring by it?)
    • At 0:14, when the character dodges, the entire field, and enemy, become tinted purple, but the character and their attacks glow white. particles seem largely unaffected.

    My understanding, in high concept is:
    1. I want to create a mask texture for where my colors would be allowed to change, after rendering skybox, but before transparent effects.
    2. To do that, I create a render target of white color, and then draw what should not be affected in black, over it.
    3. I then sample that render target as a mask for whatever visual modifications I want to do.

    My understanding of how the implementation should be done, so far, is:
    1. I insert a ScriptableRenderPass, at the 'After Rendering Skybox' event
    2. I create a temporary 'camera color holder' texture, and blit the camera color texture into it.
    3. I set a temporary render target for color, and for depth. I then clear them both. with the clear color being white.
    4. I blit the camera's depth texture into my depth render target, so that it will have kept what it already drew depth wise. (Done using a custom copy depth pass, because supposedly there is no clean way to do it with a simple blit?)
    5. I draw the objects I'd like not to be affected, using ScriptableRenderContext.DrawRenderers, with a custom material and pass index, using Opaque queue, Zwrite off, ZTest Equal, and rendering simple black color.
    6. I switch the active render targets back to the camera's color and depth textures
    7. I then draw my 'camera color holder' texture, modified by any visual effect I want, with the temporary mask I created with whites&blacks, into the camera's color texture. (using the holder, to prevent drawing camera color into camera color)

    I want to believe I have this all correctly.... except that during implementation, it does not work.
    I have spent over 15 hours trying to get the effect working, to no avail. I don't know what am I doing wrong, but I believe it is a problem in my understanding, rather than my code.

    Here are some questions I have, that I'd love answered:

    1. What does ZTest use, to work? I'd like to believe it is the depth texture, and that this depth texture is properly ready at the 'after drawing skybox' event.... is this correct?

    2. Assuming #1 is correct, what is the proper way to copy said depth texture from one target to another? Also, I assume the render texture settings are important in this case. Are there any specifics I must set?
    Right now I use
    GetTemporaryRT(myDepthDescriptor, width, height, 24, FilterMode.Bilinear,  RenderTextureFormat.Depth);
    , is this correct?

    3. To sample and copy the depth texture, I use this pass, mostly hacked together from what I could find on the internet, for the lack of documentation... is it correct?
    Code (CSharp):
    2.         Pass // 1
    3.         {
    4.             Name "Copy Depth"
    5.             Cull Off
    6.             ColorMask 0
    7.             ZWrite On
    8.             ZTest Always
    9.             HLSLPROGRAM
    10.             #pragma vertex vert2
    11.             #pragma fragment frag2
    13.             struct Varyings2 {
    14.                 float4 vertex : SV_POSITION;
    15.                 float2 uv : VAR_FX_UV;
    16.             };
    17.             Varyings2 vert2 (uint vertexID : SV_VertexID) {
    18.                 Varyings2 output;
    19.                 output.vertex = float4(
    20.                     vertexID <= 1 ? -1.0 : 3.0,
    21.                     vertexID == 1 ? 3.0 : -1.0,
    22.                     0.0, 1.0
    23.                 );
    24.                 output.uv = float2(
    25.                     vertexID <= 1 ? 0.0 : 2.0,
    26.                     vertexID == 1 ? 2.0 : 0.0
    27.                 );
    28.                 if (_ProjectionParams.x < 0.0) {
    29.                     output.uv.y = 1.0 - output.uv.y;
    30.                 }
    31.                 return output;
    32.             }
    33.             float frag2 (Varyings2 i) : SV_DEPTH {
    34.                 float depth = SAMPLE_DEPTH_TEXTURE(_MainTex, sampler_MainTex, i.uv);
    35.                 return depth;
    36.             }
    37.             ENDHLSL
    38.         }
    4. Is there any easier way to do all of this? Also, what would be the most minimal form of a pass to draw the black objects? Right now I use this, though it seems to ignore depth and just draw the objects in whole...
    Code (CSharp):
    2.         Pass // 2
    3.         {
    4.             Name "PaintBlack"
    5.             Blend SrcAlpha OneMinusSrcAlpha
    6.             Cull Off
    7.             ZWrite On
    8.             ZTest Equal
    10.             HLSLPROGRAM
    11.             #pragma vertex vert
    12.             #pragma fragment Frag
    14.             half4 Frag(Varyings input) : SV_Target
    15.             {
    16.                 return float4(0,0,0,1);
    17.             }
    18.             ENDHLSL
    19.         }

    I'd really appreciate any possible help on this! Thank you for reading!
  2. gwelkind


    Sep 16, 2015
    Bump, I'd love to know this.