raycast with transparent shader

Hello there unity community! This will be my first question and I fear it is one without a good answer.

I’m creating a game with a slightly isometric perspective, but featuring 3d objects with interiors. You issue orders to your soldiers by clicking the map, they create a path and head towards it (using navmesh and agents)

Now I have this problem… you need to be able to issue orders to move inside the buildings, between floords… but… there are roofs.

So to tackle this is I wrote a cutaway shader that I can control. This works beautifully. However it presents another problem. To actually tell the agents where in 3d space to move, i raycast from the camera and get a position of first collision. However, because this transparency is being created in a shader, this raycast still sees the object as if it has a roof (which makes sense…)

My question to the brilliant minds of the unity community:

Is there an elegant solution for dealing with raycasting through transparent objects?

16821-transparentshader.gif

I come from a visual effects background so… i’ve got some ideas of generating a world position pass, then figuring out the pixel color under the mouse, then using this to generate the actual location in 3d space… somehow all offscreen…

but i know this issue has come up before in game development. I think about all the times that i’ve fired a rife through a fence (that is made with a transparent texture) when the texture is present, the bullet collides with the fence, when firing through the gaps, it strikes the character behind. How are they doing this?

So try this:

  1. Flag the floor/ceiling objects with a tag of “Floor”

  2. Use RaycastAll to get a list of all of the hits

  3. Work through that list ignoring floor tagged objects that are above your cutaway position and take the first non-cutaway object as being your hit.

    using System.Linq;
    ...
    bool Cast(Vector3 position, Vector3 direction, float distance, out RaycastHit result, LayerMask mask, float cutOffPoint)
    {
    
       var hits = Physics.RaycastAll(position, direction, distance, mask).OrderBy(h=>h.distance);
       foreach(var hit in hits)
       {
           if(!hit.collider.CompareTag("Floor") || hit.point.y < cutOffPoint)
           {
                 result = hit;
                 return true;
           }
       }
       return false;
    
    }