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.