AI Sight (Go away from raycasting Line of Sight)

Hello,

i have started to work on a Sight or Visibility System for AI.

The reason i want to continue in the ShaderLab is that i want to find a way to let the GPU figure out, what AI should be able to see.
Regarding to Detect light/shadow falling on object, Specially to the “Using RenderTexture” method, i want to know if its Possible to:

  1. Have a Shader that calculates what is been seen from a certain position (like where light from a Point Light at this position falls to the Objects)
  2. Store the Planes/Points of what can be seen/lighted from this position
  3. Pool for the Planes/Points (i think this would work for example with Material.GetMatrix()

Are there any limitations from Unity’s side? Then maybe we can discover a way by external Processing…

Here the Thread in the Scripting Forums:
http://forum.unity3d.com/threads/222326-AI-Sight-(Go-away-from-Line-of-Sight)

Even if you’re targeting mobile devices, you are far better off using raycasts for your purpose. The best you can do with RenderTextures is get some sort of image from the point of view of a light or camera. You can’t get stats from it unless you feed it back to the CPU to analyze it. Feeding it back is expensive, and the analysis you can do is limited. Since all you can get back is pixel colours, you’re looking at counting coverage in much the same way as you might when using raycasts.

You can do a lot of raycasts before you start taking as much time as GPU readback.

I have a little Scene setup with 100 Capsule’s that Physics.Raycast 16 times each other until one hits the current target then follows Physics.SphereCast to check if there is also space (for example a bullet, rocket, etc) in between - if SphereCast hits the target then break… …it does a little bit more, see bellow…
I think there can be done some optimizations to half them maybe…

Depending on the situation of the Unit it take from 50ms up to 11.000ms for each Unit to find a proper target…
If the unit starts to scan the 2 HugeSpaceShips without success then 11secs is the result.

As far as i know for example the light calculations never wont take this time and they are pixel-perfect.

Do i understand right that, for example Material.GetMatrix(), is expensive or changing the variable in the Shader?

Can u help me Setting up a scene for testing?

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class RayCastBenchmark : MonoBehaviour
{

    //For the Capsuls in scene
    public int CapsulsX = 20, CapsulsZ = 20;
    internal int currentCapsulX = 1, currentCapsulZ = 1;

    //For Raycast performance
    public float minimumFPS = 60f, raycastDistance = 100f;
    public bool showStats = true, debugLines = true;
    public float precision = 0.5f;

    internal string raycastDistanceString, precisionString;
    internal int currentCapsule;
    internal bool instanciated = false, calculateRaycastsPerObject = true;
    internal string debugOut;
    internal GameObject template;
    internal List<GameObject> capsules = new List<GameObject>();
    internal Collider[] nearColliders;
    internal float minRaycastsPerObject = Mathf.Infinity, maxRaycastsPerObject;
    internal bool showMenu;
    internal Rect menuWindow = new Rect(10, 10, 300, 200);

    void Start()
    {
        template = GameObject.CreatePrimitive(PrimitiveType.Capsule);
        template.tag = "Respawn";

        GameObject aHugeSpaceShip = GameObject.CreatePrimitive(PrimitiveType.Cube);
        aHugeSpaceShip.tag = "Respawn";
        aHugeSpaceShip.transform.position = new Vector3(50f, 30f, 50f);
        aHugeSpaceShip.transform.localScale = new Vector3(10f, 10f, 10f);

        aHugeSpaceShip = GameObject.CreatePrimitive(PrimitiveType.Cube);
        aHugeSpaceShip.tag = "Respawn";
        aHugeSpaceShip.transform.position = new Vector3(37f, 30f, 31f);
        aHugeSpaceShip.transform.localScale = new Vector3(10f, 10f, 10f);

        currentCapsule = 0;
        raycastDistanceString = raycastDistance.ToString();
        precisionString = precision.ToString();
    }

    void Update()
    {
        if (showMenu)
            return;

        if (showStats)
            debugOut = "";

        float time = Time.realtimeSinceStartup + (1f / minimumFPS);

        if (!instanciated)
        {
            while (time >= Time.realtimeSinceStartup)
            {
                RaycastHit hit;

                if (Physics.Raycast(new Vector3(currentCapsulX * 10f, 100, currentCapsulZ * 10f), Vector3.down, out hit, 200))
                {
                    GameObject capsule = Instantiate(template, new Vector3(hit.point.x, hit.point.y + 1, hit.point.z), Quaternion.identity) as GameObject;
                    capsule.name = "Capsule" + (((currentCapsulZ - 1) * CapsulsX) + currentCapsulX);
                    capsules.Add(capsule);
                }

                if (currentCapsulX == CapsulsX  currentCapsulZ == CapsulsZ)
                {
                    instanciated = true;
                    currentCapsulX = 1;
                    currentCapsulZ = 1;
                    break;
                }

                if (currentCapsulX == CapsulsX)
                {
                    currentCapsulZ++;
                    currentCapsulX = 0;
                }

                currentCapsulX++;
            }

            debugOut = "Instanciating... " + Mathf.Round(100f / (CapsulsX * CapsulsZ) * (((currentCapsulZ - 1) * CapsulsX) + currentCapsulX)) + " %";
        }
        else if (!showMenu)
        {
            int raycastsPerUpdate = 0;

            while (time >= Time.realtimeSinceStartup)
            {
                if (currentCapsule >= capsules.Count)
                {
                    calculateRaycastsPerObject = false;
                    currentCapsule = 0;
                }

                Vector3 pos = capsules[currentCapsule].transform.position;
                pos.y += 0.5f;
                Collider[] colliders = Physics.OverlapSphere(pos, raycastDistance);
                Bounds boundToCheck;

                foreach (Collider col in colliders)
                {
                    bool hasTarget = false;

                    if (col.transform.tag == "Respawn"  col.gameObject != capsules[currentCapsule])
                    {
                        //TODO Some optimizations 
                        boundToCheck = col.renderer.bounds;

                        float x_min = Mathf.Min(boundToCheck.min.x, boundToCheck.max.x);
                        float x_max = Mathf.Max(boundToCheck.min.x, boundToCheck.max.x);

                        float y_min = Mathf.Min(boundToCheck.min.y, boundToCheck.max.y);
                        float y_max = Mathf.Max(boundToCheck.min.y, boundToCheck.max.y);

                        float z_min = Mathf.Min(boundToCheck.min.z, boundToCheck.max.z);
                        float z_max = Mathf.Max(boundToCheck.min.z, boundToCheck.max.z);

                        if (calculateRaycastsPerObject)
                        {
                            int currentRayCastsPerObject = Mathf.RoundToInt(((x_max - x_min) / precision) * ((y_max - y_min) / precision) * ((z_max - z_min) / precision));
                            if (currentRayCastsPerObject > 1)
                            {
                                minRaycastsPerObject = Mathf.Min(currentRayCastsPerObject, minRaycastsPerObject);
                                maxRaycastsPerObject = Mathf.Max(currentRayCastsPerObject, maxRaycastsPerObject);
                            }
                        }

                        debugOut = minRaycastsPerObject + "-" + maxRaycastsPerObject + " raycasts are needed per Object. Calculated = " + !calculateRaycastsPerObject + "\n";

                        for (float x_current = x_min; x_current <= x_max  !hasTarget; x_current += precision + 0.1f)
                        {
                            for (float y_current = y_min; y_current <= y_max  !hasTarget; y_current += precision + 0.1f)
                            {
                                for (float z_current = z_min; z_current <= z_max  !hasTarget; z_current += precision + 0.1f)
                                {
                                    if (Input.GetKeyDown(KeyCode.Escape))
                                    {
                                        showMenu = true;
                                        return;
                                    }
                                    Vector3 currentPoint = new Vector3(x_current, y_current, z_current);
                                    Vector3 dir = (currentPoint - pos).normalized;
                                    RaycastHit hit;

                                    if (Physics.Raycast(pos, dir, out hit, raycastDistance))
                                    {
                                        raycastsPerUpdate++;
                                        if (hit.collider == col)
                                        {
                                            if (Physics.SphereCast(pos, 2f, dir, out hit, raycastDistance))
                                            {
                                                if (hit.collider == col)
                                                {
                                                    Debug.DrawLine(pos, hit.point, Color.green, .02f);

                                                    hasTarget = true;
                                                }
                                            }
                                            raycastsPerUpdate++;
                                        }
                                        else if (debugLines)
                                        {
                                            Debug.DrawLine(pos, currentPoint, Color.red, .02f);
                                        }
                                    }
                                }
                            }
                        }

                        if (hasTarget)
                        {
                            break;
                        }
                        else if (debugLines)
                        {
                            //Debug.DrawLine(pos, col.transform.position, Color.red, .5f);
                        }
                    }
                }

                currentCapsule++;
            }

            if (showStats)
                debugOut += "RayCasts per secount: " + raycastsPerUpdate / (1f / minimumFPS);

        }

        if (showStats)
            debugOut += "\n Instanciated: " + instanciated;
    }

    void OnGUI()
    {
        GUILayout.Label("If u get Stuck or you want to see Config press ESC");

        if (showMenu)
        {
            menuWindow = GUI.Window(1, menuWindow, (id) =>
                {
                    GUILayout.Label("Maximum Range: (" + raycastDistance + ")");
                    raycastDistance = GUILayout.HorizontalSlider(raycastDistance, 10f, 200f);

                    GUILayout.Label("Precision: (" + precision + ")");
                    precision = 1f - GUILayout.HorizontalSlider(1f - precision, 0f, 1f);

                    calculateRaycastsPerObject = GUILayout.Toggle(calculateRaycastsPerObject, " Calculate Raycasts per Object?");

                    GUILayout.FlexibleSpace();

                    if (GUILayout.Button("Continue"))
                        showMenu = false;

                    GUI.DragWindow();
                }, "PAUSE");




        }

        if (showStats)
        {
            GUILayout.Label(debugOut);
        }


    }

    void OnDrawGizmos()
    {
        if (instanciated  capsules.Count > currentCapsule)
            Gizmos.DrawWireSphere(capsules[currentCapsule].transform.position, raycastDistance);
    }
    
}

I’m pretty sure that rendering the view from every ship to its potential target is going to be more expensive than casting a few rays. What is the total number of rays cast in your eleven second scenario?

This depends on the situation and size of the bounding boxes inside of the OverlapSphere’s posible targets. Colliders from OverlapSphere will be sorted by thread (harm, weakness and remaining hp).

Unit with most thread will be scanned first and if this Unit is a HugeSpaceShip that is completely covered by another Unit or Obstacles we have a Tons of Raycasts. With my script above and a Bumpy terrain in the Scene i have 1216 Raycasts and 201 SpereCasts for the first Capsule find a Target and 11146 Raycasts and 2384 SpereCasts for the secound. The secound Unit is behind a obstacle and have scans about 85 bounding boxes… To scan the HugeSpaceShips for example it needs 8000 Raycasts…

What about the Low-level Native Plugin Interface??

And to understand me Correct - in this Project i don’t need anything else then the AI making decisions like in this case : telling a Unit-GameObject a proper Vector3 direction for a Target-GameObject in a Unity Scene.

Even the complete Project doesn’t need to produce one image for me / you / everyone to look at…