Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

GrabPass, planes and lack of backface culling

Discussion in 'Shaders' started by Noisecrime, Jun 4, 2013.

  1. Noisecrime

    Noisecrime

    Joined:
    Apr 7, 2010
    Posts:
    2,050
    Hi,

    This is a little bit of a 'public information service' I thought might be interesting and helpful.


    So I was playing with a pet project that used the Unity Pro Stained Glass shader effect applied to 36 planes in a scene arrange in a grid. Unsurprisingly when all the planes are in view the performance plummets as the way the shader is set up it fetches a new grabPass for every instance.This was obvious through checking the profiler results and seeing 36 calls to GrabPass. ;)

    Now fixing 36 grabPass calls is easy, according to the docs you just needed to add a specified texturename to the pass in the shader. It instantly fixed performance issues (jumped from 120 fps to 450 fps). However something else was bugging me about the profiler results.

    You see the arrangement of the planes is based on a 3x3 grid of cubes, where the stained glass material was applied to each side (not top or bottom) of the cubes ( so 4 sides), hence a total of 36 planes. Except that in this configuration you should never be able to view more than 18 planes at a time, its physically impossible to see any more due to the convex nature of a cube, at least it should be excluding some weird precision error edge cases.

    So why was the profiler always telling me that it had to perform 36 grabPasses every frame?
    Then it hit me, and is rather obvious too!

    Planes are not back-face culled by Unity, instead I assume like all other meshes these days they are simply sent to the gpu and backface culling is done there instead. After all its a pretty efficient operation on the gpu and sometimes you may want to cull what are considered back facing other times front facing polygons, so backface culling on the cpu would be a pretty mad thing to do except in special circumstances.

    So now the issue was obvious, since the planes were not being culled when back facing (from the camera viewpoint) before the gpu, and since grabPass has no notion of back/front facing or whether a polygon is visible at all, its just going to go on blindly grabbing the contents of the buffer every time the shader is encountered. Hence why I had 36 grabPass calls.


    Now clearly in terms of performance i'd already addressed the issue by ensuring the Stained Glass shader only did it once per frame, however what if it was the case that you couldn't do that, what if there are other cases and other grabpass shaders where you need it to happen per plane, what if you have a single grabPass on a plane that 90% of the time doesn't face the player?

    It seems obvious to me now that any time you are in these situations it will be beneficial to cull the plane yourself in code and toggle its renderer on/off. Of course this issue is only restricted to pure planes (so planar mirror like effects) or if the mesh using the grabPass predominately faces on direction and is not a 'closed' mesh. If a mesh is closed, so that at any time at least one of its polygons is facing the camera (and within its frustum) then it cannot be culled so is irrelevant to the discussion.


    It all seems so obvious now I think about, at least assuming I'm correct that Unity doesn't backface cull planes before deciding to send the mesh to the gpu (though I don't see why it would). I was tempted to log it as a bug, but despite the fact that it can potentially catch anyone out and cause severe performance problems, I think its too much of an edge case for Unity to handler.

    Hence why I felt I should make this post, just to bring it the attention of developers, to be something that is considered and perhaps explicitly code for on a project by project basis. After all it might be obvious what is happening, but sometimes the most obvious thing can completely slip by when you are working hard to complete a project.
     
    Last edited: Jun 4, 2013
  2. RC-1290

    RC-1290

    Joined:
    Jul 2, 2012
    Posts:
    639
    I guess the confusion starts with the place where GrabPass is performed. If you just sample it from the fragment shader, you would expect it to not take up time if the face is culled, and the texture lookup from the grabpass texture is never performed. While it still has to be done, in case the vertex shader needs it.
    I wonder if it would be nice if GrabPass were a lazy operation, where it doesn't do anything until you actually sample the texture. I don't know if the required information is even available at that point, and if it might actually decrease performance.
     
  3. Daniel_Brauer

    Daniel_Brauer

    Unity Technologies

    Joined:
    Aug 11, 2006
    Posts:
    3,355
    I think the issue here is knowing where back-face culling happens, and how that's different from frustum culling. Also, it's important to know that graphics cards are almost exclusively one-way devices for data. You tell something to get rendered and it goes through the pipeline in order, and communication of any results back to earlier stages or back to the CPU are extremely uncommon.

    Frustum culling happens before Unity asks the graphics card to render anything, and is performed on the CPU. It prevents draw calls for objects that are entirely outside the camera frustum. Anything frustum culled will not be rendered at all. Only the bounds of the mesh are used for frustum culling–no orientation or vertex data.

    Back-face culling is a hardware feature that allows the GPU to discard triangles which are not facing the camera. This happens on a per-triangle basis in the complicated area of the GPU pipeline between vertex and fragment shaders.

    This is why back-face culling won't prevent a GrabPass from happening as long as the mesh is in the view frustum.

    In your case, you could probably have made your cube shapes out of single meshes rather than multiple planes. This would mean that the GrabPass performed for every cube would always be used, because at least one face would be visible to the player (assuming the player is at the same height as the cubes).
     
  4. Noisecrime

    Noisecrime

    Joined:
    Apr 7, 2010
    Posts:
    2,050
    True, but there might be a reason why I can't do this, though i'm not sure if that's actually the case any more, things keep changing in the project.

    However the purpose of my post wasn't about specific problems with my scene, just rather in general if you are using shaders with a grabPass that are used on a predominately planar object ( e.g. like a plane for a mirror) then unless you explicitly back-face cull it yourself you'll likely to be burning up processing power on a grabPass that isn't always needed.

    Its like RC-1290 says unless you start to think about and as you point out have knowledge about the split between what is done on the cpu vs gpu, it would be very easy to miss this and end up using processing time and resources when you don't need to.

    It would be nice if unity could deal with this, but I don't think there really is any way to achieve that. You could have Unity back-face cull Unity's plane primitives but that wont catch all cases. Hence why I made the post, hoping that developers come across it and if necessary implement the back-face culling themselves.