Search Unity

The 'camera specific' MonoBehaviour methods (OnPreRender, etc) are a little clunky, can change it?

Discussion in 'Scripting' started by Prodigga, Oct 30, 2014.

  1. Prodigga

    Prodigga

    Joined:
    Apr 13, 2011
    Posts:
    1,123
    Edit: I screwed up the thread title! Oops. It should read 'can we change it?'

    This might not be the best example of why it is so clunky, but hear me out!

    Let's say I want an object in my scene to always face the camera - like a Billboard. I can create a script that will align an object to the camera. I would attach this script to the camera, and set the target object reference to the object I want aligned to the camera. So something like

    Code (CSharp):
    1. public Transform TargetObject
    2.  
    3. void OnPreRender()
    4. {
    5.      //align 'TargetObject' to this camera
    6. }
    This works fine for one camera. Not a great solution but it works. But now the camera has to be in charge of moving around and 'dealing with' other objects. Those objects should be handling themselves and aligning correctly without the help of the camera object. Not to mention that this system would be a pain to maintain with multiple cameras - each camera would need this script attached and the references would need to be updated on all of them every time you want to change the 'target' object.

    I think these camera methods are a little clunky for this reason. Sure, some scripts should run on one specific camera only. But it isn't always the case, so these 'camera specific' methods become pretty useless.

    Can we instead have a static event on the Camera class that we can register to for each of these 'camera specific' methods? It would work something like this"

    Code (CSharp):
    1.  
    2. void Awake()
    3. {
    4.     Camera.OnPreRender += OnPreRender;
    5. }
    6.  
    7. void OnPreRender(Camera currentCamera)
    8. {
    9.      //align my self to 'currentCamera', where 'currentCamera' is the camera being rendered
    10. }
    Unity would invoke the Camera.OnPreRender event once for every camera in the order that they are rendering. This means the MonoBehaviour can be attached to any object. In this example, the script above would be attached to the object that needs to be billboarded. The object will align itself correctly to all the cameras just before any of them render, because it will have its "OnPreRender(Camera)" methods invoked once for each camera.

    This solution would be 'backwards compatible', in that it wont break scripts that make use of any of the 'camera specific' methods. Unity will still invoke MonoBehaviour.OnPreRender() etc. on every MonoBehaviour attached to a camera as usual.

    These are all the 'camera specific' methods:
    • OnPostRender
    • OnPreCull
    • OnPreRender
    • OnRenderImage
    • OnRenderObject

    Thoughts?

    Edit:

    After some back and forward bellow, I think it would be nice to have (atleast) OnPreRender(Camera currentCamera) and OnPostRender(Camera currentCamera) methods 'automatically' invoked by Unity, regardless of where your script is attached.

    If your script wants to react to a specific camera, use the current system. You would attach this script to the camera that you are interested in:

    Code (CSharp):
    1. public class MyScript:MonoBehaviour
    2. {
    3.    void OnPreRender()
    4.    {}
    5.  
    6.    void OnPostRender()
    7.    {}
    8. }
    If you want your object to react to all cameras in the game, then you would do something like this and you would be able to attach this script on any game object (It doesn't have to be attached to a camera - Unity will invoke these methods regardless, just like Update/LateUpdate/FixedUpdate)

    Code (CSharp):
    1. public class MyScript:MonoBehaviour
    2. {
    3.    void OnPreRender(Camera currentCamera)
    4.    {}
    5.  
    6.    void OnPostRender(Camera currentCamera)
    7.    {}
    8. }
     
    Last edited: Oct 30, 2014
  2. hpjohn

    hpjohn

    Joined:
    Aug 14, 2012
    Posts:
    2,190
    Given that this isnt implemented, have you thought about other solutions?

    Have a script for the cameras that has a list of Transforms that need to face it.
    Then in preRender, iterate the list and align each object.
    Now you can just register Transforms to a camera by adding to that list from anywhere else.
     
  3. Prodigga

    Prodigga

    Joined:
    Apr 13, 2011
    Posts:
    1,123
    That is exactly what my first code snippit is doing (minus the 'List'). Yes it works, but it's not a very nice solution because now I have to maintain a list of transforms on all the cameras and I have to make sure every new camera has this script attached and I have to make sure I populate the scripts transform list every time I attach it to a camera. It's difficult to work with.

    Anyway, my first post was just a simple example to demonstrates why it would be helpful to have a global event for each of the camera specific methods. I don't actually need help with this billboarding 'problem'. :) I need to achieve some more complicated things myself - I know how to achieve what I want to do using the existing system but it would just make it so much easier if these global events were in place.
     
  4. Sharp-Development

    Sharp-Development

    Joined:
    Nov 14, 2013
    Posts:
    353
    Where would be the problem to implement such a system yourself?

    Generate a camera script which has a static event and which fetches the OnPreRender event of the camera it is attached to. It simply invokes the event with its camera. Other objects can just statically subscribe to it and get a notification for each camera's OnPreRender.

    Code (CSharp):
    1. using UnityEngine;
    2. using System;
    3.  
    4. public class CameraEvent : MonoBehaviour {
    5.  
    6.     public static event Action<Camera> OnPreRenderEvent;
    7.  
    8.  
    9.     private void OnPreRender() {
    10.         if( OnPreRenderEvent != null ) OnPreRenderEvent( camera );
    11.     }
    12. }
    13.  
    14.  
    15.  
    16. using UnityEngine;
    17.  
    18. public class CameraTarget : MonoBehaviour {
    19.  
    20.     private void Awake() {
    21.         CameraEvent.OnPreRenderEvent += HandleOnPreRenderEvent;
    22.     }
    23.  
    24.     private void HandleOnPreRenderEvent( Camera camera ) {
    25.         //do something
    26.     }
    27. }
     
  5. Prodigga

    Prodigga

    Joined:
    Apr 13, 2011
    Posts:
    1,123
    Yep, thats all well and good, except I need to add this script myself to every camera. Your code is almost exactly what I 'protoyped' before coming here with this request. I feel like the camera specific methods are just so useless on their own when it comes to doing something a little more involved.

    Heres my issue: I am the creator of a plugin on the asset store and I need my 'scripts to execute some code on OnPreRender for every camera. The problem with the solution above is that the user has to maintain this part of my plugin. I don't want the user to have to manage any of this. My plugin works 'out of the box', and if the user has to perform additional steps after purchasing my asset to get it working correctly I have failed my job. Plugins should ideally 'work out of the box'. I have not had any issues with achieving this so far because Unity is fairly flexible, but I am finding it really restrictive to work with cameras... which is a big deal, because our games are experienced through the camera. Right now, it is very hard to do anything more than single camera effects 'out of the box'.


    edit:

    It is just strange how the camera specific methods are treated 'differently' to Update/LateUpdate/etc. The camera specific methods, (as far as I can understand) are a part of the game loop - just like updates and late updates. Imagine if we had to ensure that MonoBehaviours were attached to "Updatable GameObjects" rather than just any "GameObject". It would be awkward. Similarly, I feel like every script should be able to execute something OnPreRender or OnPostRender. It's almost like every script that has defined a 'void OnPreRender(Camera)' method should get that method called automatically, just like their Update/LateUpdate/FixedUpdate methods. It just feels strange to say that 'only behaviours attached to the camera care about rendering'. No - most game objects at some point will be rendered on the screen so its just silly to lock them out of OnPreRender and OnPostRender.
     
    Last edited: May 6, 2017
  6. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    Find all the cameras and add that component to them yourself? I've seen other assets do something similar to that. It's only done once at each runtime so in the Editor the user doesn't get their Inspector clogged up with your stuff.
     
  7. Prodigga

    Prodigga

    Joined:
    Apr 13, 2011
    Posts:
    1,123
    See my edit above. I know how to work around this. But they are just work-arounds. I can't achieve this very 'cleanly' even though it feels like I really should be able to.

    Edit again:

    Imagine if a user instantiates a camera at run time. Do I scan every frame for new cameras? Or is it the users job to attach the script themselves at runtime by calling "AddComponent". Its silly, they shouldn't have to do that themselves.
     
  8. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    I'd argue that's cleaner than passing a random camera into an OnPreRender method. What happens if the user destroys the camera? What if the camera is disabled or rendering to a RenderTexture? What if they are calling Render() manually multiple times per frame to build up some effect?
     
  9. Prodigga

    Prodigga

    Joined:
    Apr 13, 2011
    Posts:
    1,123
    What do you mean? I don't understand where the issue is?

    Unity, internally, would call "OnPreRender" on every MonoBehaviour that defined the following method:
    Code (CSharp):
    1. void OnPreRender(Camera currentCamera)
    Similar to how the Update methods work. It wouldn't be a 'random' camera. It would be the camera currently being rendered.
     
  10. Prodigga

    Prodigga

    Joined:
    Apr 13, 2011
    Posts:
    1,123
    Sorry, just to add to my previous post: If the camera is disabled or (for whatever reason is not being rendered), Unity won't invoke OnPreRender/OnPostRender your scripts (because that camera is not being rendered).

    The 'issues' you've outlined are 'issues' in the current system as well. (ie: You can open up Unity right now, write a script that destroys the camera OnPreRender and attach it to a game object with a camera behaviour to see what happens - spoiler alert: Nothing interesting happens. The camera is 'deleted' the next frame, so it renders as normal for that frame)

    Edit: (Loving the edits tonight)

    With the change that I am suggesting, you shouldn't need to be worried about 'random' camera being passed into OnPreRender. If you only want something to happen on a specific camera, use the standard OnPreRender()/OnPostRender() methods and attach that script to the camera and everything works as it currently does. If you want your object to react to *all* the cameras, attach your script to any object and define the OnPreRender(Camera currentCamera)/OnPostRender(Camera currentCamera) methods. Unity will call it once for every camera being rendered.
     
    Last edited: Oct 30, 2014
  11. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    But what if I don't want your stuff to run when a certain one of my cameras is rendering? My point is, it's not the most awful thing in the world to ask your users to attach a script to the camera(s) they want to be a part of whatever effect you're creating and, if anything, allows them more control. I have cameras that only render UI and one that only renders a mask used for creating a shroud effect. Why should those cameras incur the performance penalty of whatever you're plugin is doing? Or worse, risk breaking my set up.
     
  12. Prodigga

    Prodigga

    Joined:
    Apr 13, 2011
    Posts:
    1,123
    It just depends on the way the script is written. It doesn't have to 'blindly' execute its code on every camera. Perhaps the plugin could have a settings panel that allows the user to pick cameras on certain layers to be ignored (Just an example). I can definitely see the down sides - but just because the user can do stupid things with the system it doesn't mean we should be locked out of doing it. Unity already gives us a huge degree of freedom - there is plenty of things I could do right now as a plugin author to break your setup. One of the nastier ones: I can create hidden objects in your scene using hide flags and not clean them up. Hide flags can be really nasty - but thats not to say they don't have their uses. Thanks to hide flags, I can write plugins that feel like they integrate right in to your Editor. I can have scripts that instantiate and manage objects in your scene hierarchy and (thanks to hide flags) I can hide those objects inside the hierarchy view. Again, that's just an example.
     
  13. Sharp-Development

    Sharp-Development

    Joined:
    Nov 14, 2013
    Posts:
    353
    Sometimes there are limitations one has to deal with. Even tho in your case I dont see a real problem. "Working out of the box" does not mean your users shouldnt attach a script to their camera. They aswell need to create certain effects I guess, so thats neither "out of the box".

    In the end it comes down to controll. Personally I wouldnt want someone to just attach some script on all of my cameras since I simply do not have any controll over that. If I want to archive a certain effect for a certain camera, I explicitely attach that effect script on only that camera. That way im sure its only operating on that certain camera and does not generate any uneccessary overhead/load.
     
  14. Prodigga

    Prodigga

    Joined:
    Apr 13, 2011
    Posts:
    1,123
    Well, if the plugin does that, then that is a bad plugin. The OnPreRender/OnPostRender feature I am outlining here is merely a tool to help you achieve a goal. You can't blame the tools you used to generate a poor solution for your poor solution. You are describing a plugin that will be making 'bad use' of this feature - going behind the users back and doing things without their consent. Maybe, instead, the plugin will require you to drop a prefab into the scene to activate the plugin. And maybe on that prefab is some settings where you can specify cameras to be ignored if they are on certain layers.

    Edit:
    This is like arguing against having a "DestroyImmediate" function because users can write plugins that literally delete every asset in your project. Sure, they can, but they shouldn't. But that's not the point.
     
  15. Sharp-Development

    Sharp-Development

    Joined:
    Nov 14, 2013
    Posts:
    353
    Exactly, thats not the point and you've been missing what my post was about. It is NOT about whether the plugin generates overhead or not, or if it does some fancy shaking with my project. Instead im speaking about wanting to explictely attach such a component on just that camera I want it to be on. I want to have controll over what components get attached to MY stuff. It doesnt matter if I can chose layers or not, the component might still get on cameras I dont want it to be on. In the end, the final solution would be to let the user register a certain camera in the respective settings to get such a component. But than a question arises, why not drop the component on the camera yourself in a direct fashion. Just because of some eye candy?
     
  16. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    We use a plugin to draw outlines around certain objects in our scene. This involves attaching a component to the thing getting the outline and also attaching one to the camera that is responsible for rendering the effect. If it were designed the way you are suggesting (assuming it were possible) and I had to explicitly exclude the other cameras by tagging them or putting them on a different layer then I wouldn't have purchased it. One because it's a hassle, two because it's fragile (if I change my set up or add a new camera then I need to go tinker with this plugin again), and three because the act of changing the tags or layers of these cameras would impact other aspects of the project (assuming we had enough free layers to do it in the first place).

    From the perspective of a customer the reasoning might be wrong in your mind but the reaction is never wrong.
     
    Sharp-Development likes this.
  17. Prodigga

    Prodigga

    Joined:
    Apr 13, 2011
    Posts:
    1,123
    Ok you two are focusing far too much on the examples I provided. My examples were not well thought, I didn't come here to talk about specific examples - that's why I only went with a very simple example in the first post. I feel like this would be a nice feature to have available to us in general.

    Here's what I am basically trying to get across: I feel that OnPreRender/OnPostRender is as important to every object as the Update/LateUpdate/FixedUpdate methods. It shouldn't be exclusive to those attached to a camera. Currently, there is no nice way to "hook into" the rendering events. What I am proposing is a way to fix that.
     
  18. Sharp-Development

    Sharp-Development

    Joined:
    Nov 14, 2013
    Posts:
    353
    Theres always the way to do it yourself, even the event system via reflection. However, its correct, we got carried away a bit. Having events for every object which notifies it when it gets rendered by a specific camera might be a good idea. Tho, the question always remains, is it really needed? Since I personally feel more comfortable with the approach I described at the beginning.
     
    Prodigga likes this.
  19. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    Perhaps, however -

    I disagree with this. The intent of those methods is to execute code before and after a camera renders; therefore, if there is no camera present on this object then these methods need not apply. To muddy the API with "When using the parameterless overload this applies to the camera doing the rendering. When passing a camera paremeter applies to the object about to be rendered by the camera passed in" is confusing and starts to cross-pollinate with OnWillRender, OnBecameVisibile, and OnBecameInvisible.
     
  20. Prodigga

    Prodigga

    Joined:
    Apr 13, 2011
    Posts:
    1,123
    So, for anyone coming here from Google: since 5.x, we have various camera callbacks - onPreCull, onPreRender, onPostRender.

    Anyone can subscribe to these callbacks, and the callbacks are called for every camera. This was exactly what I requested in this thread 2 years ago. I'm glad Unity recognised the usefulness of these callbacks.
     
  21. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    I'm glad you got what you wanted from the API.

    It's worth clarifying (because this discussion did get a bit muddied) that the main rub between us was exposing these callbacks at the MonoBehaviour level. Exposing them on Camera at which point the method call is contextual to the camera that is currently rendering seems like a good solution.
     
    Sharp-Development likes this.
  22. Taylor-Libonati

    Taylor-Libonati

    Joined:
    Jan 20, 2013
    Posts:
    16
    PandaArcade and Prodigga like this.