Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

Opposite of OnWillRenderObejct?

Discussion in 'Scripting' started by Denisowator, May 26, 2017.

  1. Denisowator

    Denisowator

    Joined:
    Apr 22, 2014
    Posts:
    918
    So I thought I solved my previous problem of figuring out if an object is visible. I used isVisible and turned off both casting and receiving of shadows for that object, so it wouldn't be rendered along with Occlusion Culling.

    What I didn't realize, is that I forgot to actually test for it being fully obstructed. And when I did it today, it still triggers the relevant code.

    I found out about "OnWillRenderObejct" which if I'm reading the documentation right, is exactly what I need. But I don't know how to activate code if the opposite is true (if the object will not render).

    Anyone any ideas?
     
    Last edited: May 26, 2017
  2. methos5k

    methos5k

    Joined:
    Aug 3, 2015
    Posts:
    8,712
    Try this ..
    http://www.dras.biz/download/Occluder/Occluder.unitypackage
    from here: https://forum.unity3d.com/threads/check-wheter-something-is-visible-for-the-main-camera.205245/

    I didn't read it all or really test it myself (I ran the scene for 2 mins and it appeared to be doing something right). Beyond that, and what you can use to make it work in your own game, I didn't get that far.

    Other than that, I tried for a while to setup OnWillRenderObject and despite what the docs say about culling, it seemed in my code I could not get it to work, and it always said it was visible. It's possible my code was bad, but I saw this:
    http://answers.unity3d.com/questions/980457/visibility-of-objects.html

    that had the same problem I found in my tests.

    lol all in all wish ya good luck. That sample project I linked above seemed to decently remove the little spheres as I moved around, so I reckon there's a way for you to get what you want out of it. :) Cheers.
     
  3. JoshuaMcKenzie

    JoshuaMcKenzie

    Joined:
    Jun 20, 2015
    Posts:
    897
    I've noticed that you been make several posts recently about if an object is visible and whatnot..

    have you ever considered using the CullingGroup API?

    it allows you a direct line into the rendering pipeline with occlusion and frustum culling.

    I realize that the CullingGroup doesn't give you anything you can immeadiately use so heres some code I wrote from a previous project that should help get you started.

    Code (CSharp):
    1. public interface ICullingGroupListener
    2. {
    3.     BoundingSphere BoundingSphere{get;}
    4.     void WhenStateChanges(CullingGroupEvent sphere);
    5. }
    6. public ActionEvent: UnityEvent{}
    7. public IntEvent: UnityEvent<int>{}
    8.  
    Code (CSharp):
    1.  
    2.     [AddComponentMenu("Culling Group")]
    3.     public class CullingGroupListener: MonoBehaviour, ICullingGroupListener
    4.     {
    5.         public CullingGroupData CullingGroup;
    6.  
    7.         public float radius;
    8.         protected int currentBand = -1;
    9.  
    10.         public IntEvent    OnDistanceBandChange = new IntEvent();
    11.         public ActionEvent OnVisible            = new ActionEvent();
    12.         public ActionEvent OnInvisible          = new ActionEvent();
    13.  
    14.         #region ICullingGroupListener implementation
    15.  
    16.         public virtual BoundingSphere BoundingSphere
    17.         {
    18.             get
    19.             {
    20.                 BoundingSphere sphere;
    21.                 sphere.position = transform.position;
    22.                 sphere.radius = radius;
    23.                 return sphere;
    24.             }
    25.         }
    26.  
    27.         public virtual void WhenStateChanges(CullingGroupEvent sphere)
    28.         {
    29.             if(sphere.hasBecomeVisible)    OnVisible.Invoke();
    30.             if(sphere.hasBecomeInvisible) OnInvisible.Invoke();
    31.  
    32.             if(sphere.currentDistance != currentBand) OnDistanceBandChange.Invoke(sphere.currentDistance);
    33.  
    34.             currentBand = sphere.currentDistance;
    35.         }
    36.  
    37.         #endregion
    38.  
    39.         protected virtual void OnEnable()
    40.         {
    41.             if(!CullingGroup) return;
    42.  
    43.             CullingGroup.AddSphere(this);
    44.         }
    45.         protected virtual void OnDisable()
    46.         {
    47.             if(!CullingGroup) return;
    48.  
    49.             CullingGroup.RemoveSphere(this);
    50.         }
    51.  
    52.         protected virtual void OnTransformParentChanged()
    53.         {
    54.             if(!CullingGroup) return;
    55.             CullingGroup.UpdateSphere(this);
    56.         }
    57.         protected virtual void OnTransformChildrenChanged()
    58.         {
    59.             if(!CullingGroup) return;
    60.             CullingGroup.UpdateSphere(this);
    61.         }
    62.  
    63.         protected virtual void Update()
    64.         {
    65.             if(!transform.hasChanged) return;
    66.             if(!CullingGroup) return;
    67.  
    68.             CullingGroup.UpdateSphere(this);
    69.         }
    70.  
    71.     }
    Code (CSharp):
    1.  
    2.     [CreateAssetMenu(menuName = "ScriptableObject Asset/Culling Group")]
    3.     public class CullingGroupData : ScriptableObject
    4.     {
    5.  
    6.         private CullingGroup m_group;
    7.         public int MaximumBoundingSpheres=100;
    8.         public float[] BoundingDistances={1f,10f,50f};
    9.         private BoundingSphere[] spheres;
    10.         private ICullingGroupListener[] listeners;
    11.         private int sphereCount = 0;
    12.         private Dictionary<ICullingGroupListener,int> sphereMap = new  Dictionary<ICullingGroupListener,int>();
    13.         private List<int> freeIndexes = new List<int>();
    14.  
    15.         private void OnEnable()
    16.         {
    17.             spheres = new BoundingSphere[MaximumBoundingSpheres];
    18.             listeners = new ICullingGroupListener[MaximumBoundingSpheres];
    19.      
    20.             m_group = new CullingGroup();
    21.             m_group.onStateChanged += WhenStateChanges;
    22.             m_group.SetBoundingSphereCount(sphereCount);
    23.             m_group.SetBoundingDistances(BoundingDistances);
    24.             m_group.SetBoundingSpheres(spheres);
    25.             m_group.SetDistanceReferencePoint(Vector3.zero);
    26.         }
    27.  
    28.         virtual protected void OnDisable()
    29.         {
    30.  
    31.             m_group.onStateChanged -= WhenStateChanges;
    32.             sphereCount = 0;
    33.             sphereMap.Clear();
    34.             freeIndexes.Clear();
    35.             m_group.Dispose();
    36.             m_group = null;
    37.         }
    38.  
    39.  
    40.         public void Pause()             { m_group.enabled = !m_group.enabled; }
    41.         public void Pause(bool enabled) { m_group.enabled = enabled; }
    42.  
    43.  
    44.         public void AddSphere(ICullingGroupListener listener)
    45.         {
    46.             if(sphereMap.ContainsKey(listener))//if listener already in group then update the bounding sphere data
    47.             {
    48.                 spheres[sphereMap[listener]] = listener.BoundingSphere;
    49.                 return;
    50.             }
    51.      
    52.             if(sphereCount++ == spheres.Length)
    53.             {
    54.                 MaximumBoundingSpheres += Mathf.Max(100, Mathf.FloorToInt(spheres.Length * 0.5f));
    55.                 var newSpheres  = new BoundingSphere[MaximumBoundingSpheres];
    56.                 var newListeners = new ICullingGroupListener[MaximumBoundingSpheres];
    57.          
    58.                 spheres.CopyTo(newSpheres,0);
    59.                 listeners.CopyTo(newListeners,0);
    60.                 spheres = newSpheres;
    61.                 listeners = newListeners;
    62.                 m_group.SetBoundingSpheres(spheres);
    63.             }
    64.      
    65.             int targetIndex = sphereCount-1;
    66.             if(freeIndexes.Count>0)
    67.             {
    68.                 targetIndex = freeIndexes[freeIndexes.Count-1];
    69.                 freeIndexes.RemoveAt(freeIndexes.Count-1);
    70.             }
    71.             else
    72.             {
    73.                 m_group.SetBoundingSphereCount(sphereCount);
    74.             }
    75.      
    76.             spheres[targetIndex] = listener.BoundingSphere;
    77.             listeners[targetIndex] = listener;
    78.             sphereMap.Add(listener,targetIndex);
    79.         }
    80.  
    81.         public void UpdateSphere(ICullingGroupListener listener)
    82.         {
    83.             int index;
    84.             if(!sphereMap.TryGetValue(listener, out index))
    85.             {
    86.                 AddSphere(listener);
    87.                 index = sphereMap[listener];
    88.             }
    89.  
    90.             spheres[index] = listener.BoundingSphere;
    91.         }
    92.  
    93.         public void RemoveSphere(ICullingGroupListener listener)
    94.         {
    95.             int index;
    96.             if(!sphereMap.TryGetValue(listener,out index))
    97.             {
    98.                 // if listener not in group nothing needs to be done
    99.                 return;
    100.             }
    101.             sphereMap.Remove(listener);
    102.             listeners[index] = null;
    103.             sphereCount--;
    104.      
    105.      
    106.             if(sphereCount>index)
    107.             {
    108.                 freeIndexes.Add(index);
    109.             }
    110.             else
    111.             {
    112.                 m_group.SetBoundingSphereCount(sphereCount);
    113.             }
    114.      
    115.         }
    116.         public void SetDispatcher(Transform dispatcher)
    117.         {
    118.             m_group.SetDistanceReferencePoint(dispatcher);
    119.         }
    120.         public void SetCamera(Camera targetCamera) { m_group.targetCamera = targetCamera; }
    121.         public void SetCamera(GameObject targetCamera)
    122.         {
    123.             if(!targetCamera)
    124.             {
    125.                 Debug.LogWarning("Target Camera Gameobject is null. Setting Target Camera for Culling Group to null");
    126.                 m_group.targetCamera = null;
    127.                 return;
    128.             }
    129.  
    130.             Camera cam = targetCamera.GetComponent<Camera>();
    131.             if(!cam)
    132.             {
    133.                 Debug.LogWarning("Target Gameobject does not have a Camera Component. Setting Target Camera for Culling Group to null");
    134.                 m_group.targetCamera = null;
    135.                 return;
    136.             }
    137.             m_group.targetCamera = cam;
    138.         }
    139.         public void SetToCurrentCamera()
    140.         {
    141.             m_group.targetCamera = Camera.main;
    142.         }
    143.  
    144.         private void WhenStateChanges(CullingGroupEvent sphere)
    145.         {
    146.             ICullingGroupListener listener = listeners[sphere.index];
    147.      
    148.             if(listener == null) return;
    149.      
    150.             listener.WhenStateChanges(sphere);
    151.         }
    152.  
    153.     }
    154.  

    one limitation of the CullingGroup is that it doesn't support Dynamic Occluders. just against objects you flag as static Occluder (in the static dropdown next to the Layers dropdown for an object).

    If you need Dynamic Occlusion you should look into Occlusion Culling and mess with the Occlusion Area component
     
    Last edited: May 27, 2017
  4. methos5k

    methos5k

    Joined:
    Aug 3, 2015
    Posts:
    8,712
    Great answer. I saw (even kinda tried both of those), but not nearly well enough, it would seem.
    Hopefully that helps him out. :)
     
  5. Denisowator

    Denisowator

    Joined:
    Apr 22, 2014
    Posts:
    918
    I'm getting a lot of "could not be found. Are you missing an assembly reference" errors.

    Do I need to implement the "API stuff" part into those two scripts? If so, how (where to put them)?
     
  6. Denisowator

    Denisowator

    Joined:
    Apr 22, 2014
    Posts:
    918
    It's honestly crazy how something as simple as "check if this is visible" can be so complex and hard to implement. Specifically while taking occlusion into consideration.
     
  7. methos5k

    methos5k

    Joined:
    Aug 3, 2015
    Posts:
    8,712
    Those could go in any script -- just not inside a class. (re: the API stuff). They're declaring 1 interface & 2 classes.
     
  8. Denisowator

    Denisowator

    Joined:
    Apr 22, 2014
    Posts:
    918
    When I put it outside of a class, "ActionEvent" underlines and I'm getting the "Unexpected symbol" error.
     
  9. methos5k

    methos5k

    Joined:
    Aug 3, 2015
    Posts:
    8,712
    Pretty sure it should be:
    Code (csharp):
    1.  
    2. // if you didn't have this already..
    3. using UnityEngine.Events;
    4. // minor correction:
    5. public class ActionEvent: UnityEvent{}
    6. public class IntEvent: UnityEvent<int>{}
    7.  
     
  10. Denisowator

    Denisowator

    Joined:
    Apr 22, 2014
    Posts:
    918
    That did it.

    Should I be putting this in both of the scripts?
    Because I did and now I'm getting three new errors.
    upload_2017-5-27_1-29-0.png

    Also, once the errors are fixed, what exactly am I supposed to do with those scripts? Like what do I attach them to? Do I reference them in stuff? etc.

    I feel like I'm just being given bags of cement, and told "make a skyscraper", while not being given any blueprints. I don't mean to be rude, but I have no idea what to do with these two scripts to get what I need out of the API. Also, I have absolutely no experience in using APIs (other than the standard one). So giving me something of this scale, expecting me to be able to make something out of it, is like giving a pilgrim an iPhone and expecting them to not call you a witch and accuse you of sorcery.
     

    Attached Files:

    Last edited: May 27, 2017
  11. JoshuaMcKenzie

    JoshuaMcKenzie

    Joined:
    Jun 20, 2015
    Posts:
    897
    no. you define them once I didn't write them twice...
     
  12. Denisowator

    Denisowator

    Joined:
    Apr 22, 2014
    Posts:
    918
    Okay, removed it from one of the scripts, and now all the errors are gone... Now what? As I said, I have absolutely no clue what I'm supposed to do with these scripts now.
     
  13. Denisowator

    Denisowator

    Joined:
    Apr 22, 2014
    Posts:
    918
    I found this old thread, and a Unity employee is basically stating that "isVisible" only returns true if the object is used during rendering. I have been wrestling with "isVisible" for a while, and I already thought that was the case, even before finding the thread.

    The reason I wasn't 100% sure about it, is because even though I have receiving and casting shadows off for my object, and it is being culled, it is still causing "isVisible" to return true. Any idea what might be wrong?

    Because if I get this working, then it might just be what I need. Instead of having to dive into APIs.

    EDIT: Just realized my object isn't even being culled properly. It's only culled when I look away, but when I look at the giant wall (cube object) covering it, it appears.
     
    Last edited: May 27, 2017
  14. JoshuaMcKenzie

    JoshuaMcKenzie

    Joined:
    Jun 20, 2015
    Posts:
    897
    Yeah, you're looking for Occlusion Culling. You get Frustum Culling out of the box cause the math for that is simple (by comparison). Occlusion Culling on the other hand is computationally expensive to do so it needs to be manually setup and pre-calculated and cached (the term for this is "Bake") in the editor. This way its quick and smooth at run-time.

    You're not going to get Occlusion Culling (with renderers, Culling Groups, whatever) straight out of the box without first doing some baking. Please understand its a ton of math it has to do. its inconceivable for it to attempt such calculations at run-time.

    You will need to dive into the API. You will need to define Static Occluders, and Static Occludees. You will need to set up Occlusion Areas (so unity knows where to focus its math).
     
  15. Denisowator

    Denisowator

    Joined:
    Apr 22, 2014
    Posts:
    918
    But I can't define static occludees, since the object is going to move.
     
  16. JoshuaMcKenzie

    JoshuaMcKenzie

    Joined:
    Jun 20, 2015
    Posts:
    897
    read closely from the Occlusion Culling page
    Moving objects can be culled. Static Occludees are just cheaper to cull so you should flag them when possible.

    Moving Objects cannot be occluders (they can't cull other objects) at least as far as I'm aware.
     
    Denisowator likes this.
  17. Denisowator

    Denisowator

    Joined:
    Apr 22, 2014
    Posts:
    918
    So what would be the advantage of using the API over Occlusion Culling in that case?

    When I read your previous reply, I understood it as "it's not possible to cull moving objects, as it would be increadibly performance heavy".
     
  18. Denisowator

    Denisowator

    Joined:
    Apr 22, 2014
    Posts:
    918
    Welp, I got it working finally. Had to remove every cache file with Command Prompt, and just re-baked it, and made sure to mark the object only as "Occludee Static".

    I'd still love to hear about the API. I usually try to avoid getting into things way out of my experience level, but I'm just curious as to the advantages and such, of using the API.
     
  19. JoshuaMcKenzie

    JoshuaMcKenzie

    Joined:
    Jun 20, 2015
    Posts:
    897
    The Culling Groups don't require a renderer, nor a Transform nor a GameObject, just a point in space and a radius. plus they implement Distance Bands which is typically used for LOD, but you can hook into it for other means.

    With the exception of occlusion culling the other features work immediately without the need of baking. You can subdivide portions of a GameObject into multiple bounding spheres and share those spheres over multiple cameras and set up a complex visibility result. You can end up with some unique effects.

    one example is the Zelda "Lens of Truth" effect, where an object is only interactive when seen with a special item and within a certain distance band. CullingGroups provide much more control with behavior than OnBecameVisible/OnBecameInvisible, however those two functions have the benefit of working without the need of extra setup.

    Also Culling Groups can be generated at run-time.

    and no my previous reply said its expensive, so it needs to be baked and cached.
     
    Denisowator likes this.
  20. okluskyond

    okluskyond

    Joined:
    Dec 6, 2017
    Posts:
    34
    Hi, sorry for bumping this old thread, but it seems that you know what you are talking about.

    I’m trying to implement custom LODSystem for Unity Renderers based on distance using culling groups. Only way I have found is to hook to Camera.OnPreCull and Camera.OnPostRender and update visibility there.

    In Camera.OnPreCull step I use computed visibility to disable/enable renderers for defined LODs
    In Camera.PostRender step restore renderers previous state for next camera that may or may not implement LODSystem

    Problem is that disable/enable renderer generate a lot off subsequent garbage (mainly from AnimatorControllers). I have also tried to modify render.gameObject.layer to use benefits of layerCullingMask, but this approach didn’t work at all.Do you know about any faster approach how to modify culling result for specified camera?

    Thanks