Distortion Issue w/ Shaders

Running Unity 2020.3.26f1 & Scriptable Render Pipeline.

Hey y’all, I’ve been experimenting with particle effects and I’m curious as to why this is.
I don’t really know shaders so for all I know, this could be common knowledge.

ickylightatlanticridleyturtle

Objects in this scene are using Unity’s built-in Unlit Particle shader to make up the 3 distortion effects, and Standard shader for everything else.
This only happens on objects using Fade Rendering Mode (which would be the grass and the blue tile).
I’d like to know why when moving one of these Particle shader objects away, ones closer to the camera will ignore geometry caught in between as if it were not there, and if there’s a way to prevent this, whilst keeping Rendering Mode set to Fade?

Seems like a transparency sorting issue. I’m assuming you’re using GrabPass to do the distortion?

A surface in Fade mode will be in the transparent Queue and sorted with other Transparent objects, which the particle shader is as well. When you move that particle far back, it ends up rendering before the grass and thus storing a GrabPass of the screen that doesn’t have the grass.

If you set the grass to an opaque mode or alphatest, does it fix the problem?

1 Like

Thank you for your reply!

These are all Unity’s own built-in shaders, but I’d imagine that’s how they did it, yes.
Indeed, setting them to Cutout/Opaque does prevent this from happening. But as I said, my goal was to keep the transparency from Fade mode.

You’re not using an SRP. “Scriptable Render Pipeline” is a feature of Unity that can be used to build a custom render pipeline, of which Unity provides two options. The URP and HDRP. You’re not using either, so you’re using the built in rendering path which does not use the SRP.

The issue you’re seeing is also an issue that’s explicitly only affects the built in rendering path and the built-in shaders. The URP and HDRP have their own issues with distortion.

The issue is with how the grab pass works along with transparency sorting.

For real time rendering, transparent surfaces have to be sorted in order of furthest to nearest. See this for something of an explanation as for why.
https://en.m.wikipedia.org/wiki/Painter's_algorithm

The grab pass is a feature of the built in rendering path that makes a copy of the frame target’s current state into a texture so it can be sampled for things like distortion. There are two ways you can make it work, either each time it’s used it makes a new copy of the screen, or the cheaper way is the first time it’s used is when it’s created and subsequent uses just reuse the original copy. This second behavior is how the built in shaders use it.

So this means as you move that one mesh the sorting order is changing so it’s being rendered, so it’s rendering before the grid, and then the layers of grass. This is a problem because the grab pass is being created before those other objects have rendered, so it’s just getting the sky and mountains in the background. Then every other object using that shader to do distortion only “sees” the background. Hence why the foreground distortions appear to make the grass and grid see-through.

There are two fixes for this. One is to use a custom shader that does the unique grab pass per material. The other is to set the render queue on the materials to force the render order so all objects you want to do distortion to render after the rest of the scene. Though the render order fix will mean objects that do distortion can’t “see” other distortion objects since they won’t be in the grab pass texture.

For the SRPs, support for the grab pass has been removed entirely. Instead distortion can only see opaque objects. And there’s no fix for that, as it’s the intended behavior.

1 Like

Pardon the confusion, Built-in Render Pipeline is what I meant, thank you. I had a feeling a custom shader would be the one true fix. Thank you for the insight and for clearing all this up, much appreciated!

You should be able to fix it by making a copy of the built in particle shader you’re using making the grab pass “unnamed”.


https://docs.unity3d.com/Manual/SL-GrabPass.html

1 Like

Thank you, I did as you said and it does solve the issue.
But it concerns me that every object, including every individual particle, using the material, would be doing their own grabpass. I wouldn’t want to do this if it would impact performance to this extent.

If this is possible (and provided I’m not spouting nonsense), I may look into some way of performing additional grabpasses only if necessary; such as at set increments of depth, potentially capped to a max. Or something akin to that, so as to not affect performance so much.

For instance, the furthest object performs a first grabpass. Then if another object, happens to be closer to the camera by x distance, compared to the first, only then it’d perform a second one and so on.

You’ve been a great help in understanding this so again, thanks!

It’s not quite as bad as every individual particle as it’ll run once per particle system, and might not even be as bad as each object if dynamic batching is enabled. But yes, it does still potentially have a significant performance impact. Which is why it doesn’t do that by default, and why the feature was removed from the SRPs entirely.

There also isn’t any way to add any additional logic to the system to limit how many grab passes are performed. It’s an all or nothing feature.

1 Like

I see, I’ve got a much better idea how this is all set up. If I may ask, how would batching help in this case? Frame Debug shows an individual ‘Grab RenderTexture’ for each batched object before rendering any of them. So they couldn’t get grouped into one call, right?

Then they’re not batched.

By batching I explicitly mean multiple objects are drawn as a single draw call, and thus would only produce a single “grab” for each group. Automatic dynamic batching may be disabled for shaders with a grab pass, but it’s still done for things like particle systems where all particles are batched into a single mesh draw or instanced draw. You can’t have a single particle system that renders each particle individually.

1 Like

I should have clarified, what I described were 6 static objects, at the same depth, using the same material from the custom particle shader, and sprite renderer. The particle systems do stay in a single draw, but all still perform a grabpasses for each system. So I take it I can’t static batch objects using grabpass shaders, in order for them to only use a single grab?

Static batching is … weird.

Dynamic batching combines multiple meshes into one mesh and draws them all with a single draw call.

Static batching combines multiple meshes into one mesh … and draws the the multiple meshes individually still. In the general case this is still quite fast and means you can toggle individual meshes on and off for things like LOD.

Manually batching can work around this, but is a lot of extra work.

1 Like

I see. Either way, you’ve been a great help in better understanding all this; thank you for your time and attention!