Check if a Collider is visible from a position

Hello,

Imagine I have a point, (we could say the eyes) and a collider I must check if it is visible or not. I have a small collider in the middle of the two colliders I must check. If I raycast to the center, the raycast is false because there is a small collider in the middle, despite it is visible in at the ends.

So, my question is, is there any method or function in the physics api to do something like:

bool CheckIfColliderIsVisibleInAnyPointFromPosition(Collider c, Vector3 position)

I attach a diagram to clarify.

Thank you

I think this is a very interesting topic.

Unity MonoBehaviour exposes some events that are invoked based on the visibility of an object to any camera:

These events use the Renderer, not the collider (no collider is necessary for them, indeed). However, as far as I know these events are based on the object’s bounds. This means that they will probably expose many false positives when the object is very close to the field of view, but still outside of it.

I’m not expert on this, but thinking on the problem I came to a possible solution: just check the visibility of all the vertices in the object. If the object has a single vertex inside the field of view, then it’s visible. Otherwise, it’s invisible.

  • Enumerate all vertices of the object (via either MeshFilter.sharedMesh or MeshCollider.sharedMesh).
  • Get the world position for each vertex (Transform.TransformPoint)
  • Convert the world position to viewport coordinates (Camera.WorldToViewportPoint). If the converted position meets these two conditions:
  • Z is greater than zero
  • X and Y are between 0 and 1
    Then the vertex is visible to the camera, and the object is visible. End.
  • Repeat with all vertices.
  • If no vertex is visible, object is not visible to this camera. End.

Note that this method produces a false positive if the object is occluded behind a larger object. This might be solved by adding an additional verification at the step 3 above. If a vertex results visible at the step 3, then do an additional check via Raycast:

  • Throw a raycast from the observer’s position to the vertex.
  • If it hits the object’s collider, then the vertex is visible and the object is visible. End.
  • If it hits a different collider, then the vertex is occluded behind another object. Vertex not visible.

Still, there are cases where this method fails. For example, a quad large enough for having all its vertices outside of the field of view while looking directly at it. The method would cause a false negative. Maybe adding some raycasts at the corners of the field of view might mitigate the problem.

As said, I’m not expert on this but I find the topic very interesting and I’d love to hear about some solution that works in all cases.

Looking at your picture, I would do multiple Raycasts covering the angle you want (from top to bottom green line) and store each (unique) object hit by the raycast in an array , this array would then hold the list of visible objects to the eyes

Thinking about the problem and with de suggestions of @sngdan and @Edy I have designed the followed algorithm.

The main idea is to scan/sweep the collider with raycasts. To do this, we use the collider bounds.
Note that bounds are a box aligned with coordinate axes, so they have not rotation.

So the algorithm raycasts from the max bound point to the min max bound point. If any raycast is possitive, the collider is visible. The number of raycasts can be set to increase accuracy

The algorithm has a big “but”. It can be improved by choosing a better starting and end direction than min/max bounding box, but I cannot think a better way right now. I have attached an image showing useless rays because of this. This problem causes even that the algorithm does not detect the visibility when the collider and the bounds are very different. I think the solution could be to use some kind of “oriented bounds” or something like that.

I am open to any improvement suggestion.

I attach the algorithm gizmo version. Remove gizmos lines to use normal use:

using UnityEngine;

public class CheckVisibility : MonoBehaviour {

    private void OnDrawGizmosSelected()
    {
        foreach (Collider collider in this.GetComponentsInChildren<Collider>())
        {
            Gizmos.color = Color.white;
            Bounds bounds = collider.bounds;
            Gizmos.DrawWireCube(bounds.center, bounds.size);

            Gizmos.color = Color.blue;

            Gizmos.DrawWireSphere(bounds.max, 0.1f);
            Gizmos.DrawWireSphere(bounds.min, 0.1f);

            ComplexRaycast(this.transform.position, collider, 5);
        }
    }

    public static  bool ComplexRaycast(Vector3 origin, Collider collider, int steps)
    {
        Bounds bounds = collider.bounds;
        Vector3 minDir = bounds.min - origin;
        Vector3 maxDir = bounds.max - origin;

        float maxDistance = maxDir.magnitude;
        float minDistance = minDir.magnitude;

        float distance = maxDistance > minDistance ? maxDistance : minDistance;
        distance *= 1.01f; //Distance + offset

        minDir = minDir.normalized;
        maxDir = maxDir.normalized;

        int targetLayer = collider.gameObject.layer;
        LayerMask colliderLayerMask = 1 << targetLayer;

        RaycastHit hit;
        for (int i = 0; i <= steps; i++)
        {
            float t = i / (float)steps;
            Vector3 dir = Vector3.Slerp(minDir, maxDir, t);

            if (Physics.Raycast(origin, dir, out hit, distance, colliderLayerMask,QueryTriggerInteraction.Ignore))
            {
                if (hit.collider == collider)
                {
                    Gizmos.color = Color.green;
                    Gizmos.DrawLine(origin, hit.point);
                    Gizmos.DrawWireSphere(hit.point, 0.1f);
                    return true;
                }
            }

            Gizmos.color = Color.red;
            Gizmos.DrawRay(origin, dir * distance);
  
        }

        return false;
    }
}

Image explanation:

  • Green boxes are the colliders we want to check its visibility
  • White boxes are its bounds
  • Blue Points are its min and max bounds point
  • Red lines are unsuccessful raycasts
  • Green lines are successful raycasts. When a raycast is positive, the collider is considerer visible and the algorithm finishs



1 Like

Hi,

I am not entirely sure of the overall setup (i.e. Number of objects, 2d/3D, desired accuracy, length of rays, etc) but going by the pictures (I have not looked at the code), the following ideas:

  • the problem you highlight does not necessarily look like a problem (I.e. If you want a say 60 degree field of view, some things that would be visible to a wider angle, are not) - it rather looks that no Ray is drawn at the angle that would detect the second object (by design)
  • instead of swiping at fixed steps, you could use the geometry of the objects, I.e. Once the ray hits something, use its corner points for the next Ray

Your pictures look like you are getting there, good stuff.

Hi

Add a new camera and use a script with image effect that detects if the object is shaded like this example http://wiki.unity3d.com/index.php?title=Silhouette-Outlined_Diffuse
Or modify this shader to shade the part that is not behind another object. It can be checking for any pixel having cyan color, and so, then the object is visible.

I will avoid all these calculations of raycast that can slow the frame updates.

Sorry for my bad english