Search Unity

  1. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Question How to cull transparent objects within the shader

Discussion in 'Shaders' started by Kemorno, Sep 6, 2023.

  1. Kemorno


    Apr 5, 2017
    Hi, so im writing a shader for some engine plumes, and i want to add an opaque (transparent with cutoff) effect on top of it, but it would need ot be culled so that this doesnt happen

    As you can see, its appearing a over the effect below it, when it should be culled...
    I did some research and it seems like writing to the depth buffer might be the solution, but i dont see how i'd read it on the frag of my opaque shader...

    the idea here is:
    1. Transparent Multiplicative is on queue 3000 and writes to SV_DEPTH its closest depth
    2. Transparent Cuttoff reads SV_DEPTH on current pixel and decides wheter it should cull or not
    both shaders are volumetric, so i'm trying to cull just the density behind the other effects.
  2. bgolus


    Dec 7, 2012
    What are you expecting the
    value to be here? If you're expecting the value that's in the depth buffer, then that's not what you're getting. The input
    is the depth of that fragment as calculated by the GPU for that triangle. (It's actually the same value the fragment shader gets from
    's z component.) There's no way to read the current depth buffer value directly. If you want to clip against a specific depth, you need to make use of DepthTest and either output the wanted
    for your fragment, or preferably offset the vertex position's clip space Z as modifying
    disables early depth rejection.
  3. Kemorno


    Apr 5, 2017
    To put thing more clearly, both of these are on the Transparent Render queue, so its on 3000, so, at least afaik, all automatic depth rejection is already done.
    What i'm currently doing is that I set the
    as the first instance of the volumetric marching's depth (so i'm returning this value).
    The problem is that I dont know how i can read this value outputed depth value. I need to read it so that i can cull (or not) other instances of the same (or similar) shaders.

    Currently im doing this:
    Code (HLSL):
    1.             fragOutput frag(v2f i) : SV_Target {
    2.                 if(_Opacity == 0)
    3.                     discard;
    5.                 fragOutput o;
    6.                 o.depth = 1;
    7.                 o.color = 0;
    8.                 float3 rayPos = _WorldSpaceCameraPos - position;
    9.                 float viewLength = length(i.viewVector);
    10.                 float3 rayDir = normalize(i.worldPos - rayPos - position);
    11.                 float2 dstToBox = 0;
    12.                 float2 dstToBoxFar = 0;
    15.                 if(!LogBisect(orw(rayPos, 0), orw(rayDir,0), dstToBox, dstToBoxFar)){
    16.                     return o;
    17.                 }
    19.                 dstToBox = max(0, dstToBox);
    21.                 float2 dstInsideBox = dstToBoxFar - dstToBox;
    22.                 dstInsideBox = max(0, dstInsideBox);
    24.                 // point of intersection with the cloud container
    25.                 float3 entryPoint = rayPos + position + rayDir * dstToBox.x;
    26.                 float3 exitPoint = rayPos +position + rayDir * dstInsideBox.x;
    28.                 float dstTravelled = 0;
    29.                 float dstLimit = dstInsideBox.x;
    30.                 int stepCount = (1+_Resolution)*32;
    31.                 stepCount *= _ResolutionMultiplier;
    33.                 stepCount = max(_MinimumResolution, stepCount);
    34.                 float stepSize = dstInsideBox.x/(stepCount); //Create multiplier for step count to allow for modder customization
    35.                 int failSafeCount = stepCount;
    37.                 float density = 0;
    38.                 float4 entryClipPos = UnityWorldToClipPos(float4(entryPoint, 1));
    39.                 float4 screenPos = ComputeScreenPos(entryClipPos);
    40.                 float2 uv = screenPos.xy/screenPos.w;
    41.                 float depthSolid = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, uv));
    43.                 while(dstTravelled < dstLimit){
    44.                     failSafeCount -= 1;
    45.                     if(failSafeCount < 0)
    46.                         break;
    48.                     float3 currentPos = entryPoint + rayDir * dstTravelled;
    49.                     dstTravelled += stepSize;
    51.                     #ifdef CUSTOM_ZTEST
    52.                     float4 clipCurPos = UnityWorldToClipPos(float4(currentPos, 1));
    53.                     float depthCurPos = LinearEyeDepth(clipCurPos.z/clipCurPos.w);
    55.                     if(depthCurPos >= depthSolid)
    56.                         continue;
    57.                     else if (o.depth == 1)
    58.                         o.depth = depthCurPos;
    59.                     #endif
    60.                     density += SampleDensity(currentPos - position)* (stepSize);
    61.                 }
    63.                 fixed4 col = density;
    64.                 o.color = col;
    66.                 return o;
    67.             }
    As you can see, I set o.depth to watever the depth on the current position is IF its not occluded by any geometry. What i'd like is for a way to read this o.depth on other shaders in a way that i can check if the depth on x position is greater or not, so that i can discard that march step.
  4. bgolus


    Dec 7, 2012
    Nope. If you write to
    , depth rejection is done after the fragment shader, meaning you're paying the cost for rendering it when it's within the camera frustum, even if it's "not visible" because it's behind something else. And it means when you modify the depth it changes what occludes it... as otherwise there wouldn't be a real reason to use it.

    As for what you're trying to do, the short version is: you can't do that. Reading from the
    is the only viable way to read the current depth buffer, and that's not actually the current depth buffer as it's a texture generated by rendering all opaque queue objects using their shadowcaster pass before the scene actually renders anything. Any transparent queue materials that write to the depth will not be included in the depth texture, and there's no real way to update the depth texture after the fact to include new things, nor would you want to really. Even on mobile, which includes ways to read from the frame buffer directly, does not allow you to read the depth buffer.

    So, what can you do? That's harder to say. I honestly don't think I fully understand what you're trying to accomplish. If I were you, I'd render the alpha cutout effect you're trying to add as an actual opaque alpha cutout effect with a shadowcaster pass so it renders to the camera depth texture and let the volumetric effect do the rest of the work. I wouldn't have the volumetric effect do any depth writes at all if I can help it.

    Alternatively if you really must be able to read the depth of another material, you'll have to render that to a render texture manually yourself so you can sample it in the other shaders. There's no other option.
  5. Kemorno


    Apr 5, 2017
    to visualize better my issue i disabled the noise... Here's whats happening, first from the side, which looks right:

    but if i look at it from below, i can still see the whole "torus", which should be culled as you can see here

    I think that the alpha cutout might actually be a good idea, i'll test that, any examples on how to properly do it? i'm guessing that i could just make a
    ColorMask 0
    and write 1 where i want the alpha cutout to be? or should i write the actual depth?
  6. bgolus


    Dec 7, 2012
    Ah... hmm.

    What you're really looking for is order independent transparency. Which is a huge rabbit hole of expensive and/or incomplete solutions.

    I don't think any of the solutions you have in mind, nor the suggestion I had, will solve the issue you're experiencing. The one solution I can think of is to breakup your flame into two parts. One that's the same length as the "torus", and explicitly renders behind it, and a second that renders the rest of the flame separately and makes use of Unity's default transparency sorting to pick which renders over the other.