Search Unity

Better Projector alternatives for object selection

Discussion in 'Shaders' started by Rukas90, Mar 20, 2019.

  1. Rukas90

    Rukas90

    Joined:
    Sep 20, 2015
    Posts:
    106
    Hello,
    I am working on this top down game and I have objects that I am able to select and when I do every object has this object selection circle that appears below them.
    I use Projectors mainly because they really nicely adapt to any surface. But I noticed that they are quite expensive. When I select a bunch of units my FPS drops quite massively.

    Are there any better alternatives? At first I used quads but they look off on non flat surface.
     
    Last edited: Mar 20, 2019
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    6,857
    Projectors are expensive because they re-render every object their frustum overlaps the bounds of. So, if you have terrain, the entire terrain is being re-rendered for every projector.

    The usual solutions are:
    Quads
    Or some other simple mesh. Align it to the ground plane and lift it off of the ground, or render it with ZTest Always so it renders no matter what. This is a surprisingly common solution. If you look closely at even big AAA RTS games that use per-character selection indicators, you may notice the selection circles clipping into things or hovering quite high off of the ground.

    Deferred Projectors
    You get the camera depth texture and project against that. Very similar to deferred spot lights. Much cheaper, but can be more difficult to control what the projector casts on without rendering out screen space masks so the selection circles don't also cast onto the units.

    Shader Magic: Multi Circle Projector
    A single normal projector with a special shader that renders multiple circles will be a lot cheaper than a single projector for each unit. Can still get expensive very quickly if you need to be able to do 100+ units.

    Quads Render Texture Projector
    Render an overhead view of just the selection quads to a render texture, then use a normal projector to project that render texture onto the terrain. Alternatively you could use a custom shader on the terrain, or even a full screen post process effect to do a deferred projector. Super cheap compared to deferred projectors or multi-circle shaders, but you'll have to be mindful of the render texture resolution, and likely want to try to keep the render texture and projected area's frustum tightly confined to what's visible to maximize the resolution.

    Distance Field Texture Projector
    How about having your selection circles merge together? Similar to the above option, but instead of each quad rendering out the circle, render out a blurred circle with a BlendOp Max shader. You'd be constructing a signed distance field texture that the projector's shader would then use to map a line to. Really no more expensive than the above option, lets close together selection circles merge together into a single outline, and can be much lower resolution without being obviously aliased.


    I wrote a lot of these kinds of systems for Planetary Annihilation (not Unity engine based). The selection rings that shipped are quads that hover above the ground by some per-unit controlled offset, though I had a change list that never made it in which did it using distance fields, but flying and water units would have been broken. The range rings (and fog of war) are done using distance fields though. Those range rings use a 1/4 screen resolution distance field render texture, but still look that sharp. Fog of war is 1/16th, maybe 1/32 resolution? I forget.
     
    SugoiDev likes this.
  3. Rukas90

    Rukas90

    Joined:
    Sep 20, 2015
    Posts:
    106
    Hey, thanks for a great answer!

    Unfortunately I'm kind of a beginner when it comes to shader programming. So I'm not sure how to implement most of this. I replaced the Projectors with just Quads and performance wise it's much better, but it doesn't really look so great..

    selection.PNG

    Quads are Intersecting with the terrain and also some objects are a bit deeper in the terrain so selection circles are below the terrain itself. I tried to use a shader that turns off ZTesting but it looks super weird. Cause nothing is blocking the selection circle anymore.

    I know that in HD render pipeline you can use new Decal system that is supposedly much more performant than Projectors and they both work in a similar way. But the problem is that I don't work with HD render pipeline so I can not use it. I switched to Lightweight rendering pipeline so I mostly use Shader Graph right now.

    Is there a way to achieve something like this with Shader Graph? It doesn't have to be of course with Shader Graph, but I have no idea how to write Lightweight shader's so that's why I only use Shader Graph at the moment.

    asas.png

    Shader that would wrap the texture around any terrain surface and would always draw the texture on the surface no matter the y position of a quad. I would then position a circle at the top of the object so it would always draw no matter how more deep the object is in the terrain.

    That would be the perfect solution, but have no idea how to achieve this unfortunately.. Could you give me some advice on this? I would really appreciate it!
     
    Last edited: Mar 21, 2019
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    6,857
    This makes things harder ... LWRP means some "simple" ways to solve some of the above techniques are removed and you have to have a bit more graphics programming knowledge to get things done. The LWRP frustrates me a bit as, due to the addition of the Shader Graph for the LWRP and HDRP, people without graphics programming knowledge are itching to move their projects to it. But there are a ton of techniques that the SRPs are simply not capable of because of conscious decisions made by the Unity team to remove those options.

    For example, the quad render texture method for the built in rendering path only requires placing a second camera in the scene with a render texture target and a projector attached to that. Move it around in script and you're done. But that doesn't work in the LWRP since AFAIK secondary cameras are ignored. For a graphics programmer this is trivial to overcome, and the solution is significantly more performant anyway, but it still requires understanding graphics programming.

    Not related to this, but there are a ton of effects that use multiple shader passes to achieve. For the built in rendering paths you just write a shader with multiple passes and you're done. But that doesn't work in LWRP since it explicitly and intentionally doesn't support multi-pass shaders, and it's not possible to make multi-pass shaders with Shader Graph either. Again, with some graphics programming chops this is possible to work around, and can even be brute forced using multiple renderers, but it's far less streamlined.

    More related to this, there are techniques that make use of the stencil buffer, or depth offsets, but the Shader Graph doesn't support setting stencils or depth offsets, so you'd still have to write these as vertex fragment shaders. For unlit stuff, this isn't a total dead end as the LWRP still supports most single pass unlit shaders, but to effectively use the stencil option requires having custom shaders on your objects too.



    Anyways. /rant

    Wrapping around a surface requires a projector of some kind, either a mesh based one like what the built in projector component does, or a deferred / clustered one, like what the HDRP uses. This is sort of possible with Shader Graph, but the issue of drawing on things that aren't the terrain remains and is essentially unsolvable when using the LWRP without true blue graphics programming.

    Ignoring wrapping, otherwise positioning a quad so it's on the surface of the terrain is also outside the realm of possibilities with Shader Graph (or really even hand written vertex fragment shaders) with out some level of graphics programming chops. A shader has no knowledge of anything but the thing it's rendering at that moment. In the case of a vertex fragment shader (which Shader Graph also is, really), the shader only knows about a single vertex of the mesh at a time during the vertex shader stage, or interpolated data of a single triangle on a single screen pixel during the fragment shader stage, and whatever information the material has passed to it that are constant across all parts of the shader (textures and arbitrary float values). To get a shader that offsets itself and aligns itself to terrain means you would need to get the height map(s) from the terrain and somehow pass that texture along with any information needed to "decode" it (its position in space, height offset, height scale, etc.) and use that to figure out the terrain height at the quad's center, and then at some number of points nearby to determine the terrain's slope orientation.

    Honestly, I'd suggest just placing quads and using a C# script to do the terrain height sampling unless you're looking to upgrade your graphics programming and shader writing chops. Just call Terrain.SampleHeight() in a c# script and be done with it. If that gets too expensive when there's a lot of units, make sure you're only doing it when you can see the unit and when you're relatively close, otherwise just put the quad around your unit's waists.
     
  5. Rukas90

    Rukas90

    Joined:
    Sep 20, 2015
    Posts:
    106
    Thanks for the info!

    I switched to Lightweight cause of the performance boost, but I didn't knew it was that limited.. I will switch back to built in rendering path cause there are more things that I want to do that Lightweight unfortunately can not easily supply at the moment.

    You mentioned that for the built in rendering path you can just simply write a shader with multiple passes and that's it. How does that work and would that solve my problem and would work the way I am trying to make it work?
    If so could you give me some example for the head start? Like I mentioned I'm a total beginner when it comes to shader's, so not really sure how to begin writing this unfortunately.
     
  6. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    6,857
    No, that's why the start of that paragraph is:
    The "this" was the effect you're trying to achieve.
     
  7. Rukas90

    Rukas90

    Joined:
    Sep 20, 2015
    Posts:
    106
    Oh shoot, my bad.. Is it okay if I ask one last question? I was thinking if it's possible to render Quad on top of terrain, meaning that it would ignore ZTesting with the terrain but not with other objects. And other objects would be like usual with the Terrain meaning that intersecting parts of the object would not be visible.

    Something like in this example:
    Capture.PNG
    Capture2.PNG

    I tried to change the Render Queue. But no matter what I change it's always the same. Maybe I'm doing something incorrectly.
     
    Last edited: Mar 21, 2019
  8. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    6,857
    Should be possible. You'd need to modify the render queues or sortingOrder to make sure the terrain renders first, then your selection circles with ZTest Always, then everything else.

    I can't remember if terrain pays attention to material queues or renderer sortingOrder, but you could try those. If you're using the built in terrain material, try adding a script component on the terrain to set the sortingOrder to -2, and another on the selection quads to use -1, everything else defaults to 0. Otherwise you may need to set the queue on all your materials to be 2002 and the quads to 2001.
     
    Rukas90 likes this.