Image effect in scene editor views

Background:
Okay, I am working on integrating a physically based BRDF into Unity’s forward renderer. For that reason, I needed to make sure that I had a custom camera script that accumulated all lights in linear space into an ARGBHalf target, and then an image effect that tonemapped the accumulated results and converted them to gamma space. So far, I have this working in play mode only.

Question:
Below, you can see the correct look outlined in green, and the incorrect looks outlined in red. My question is, is there any way I can apply the aforementioned scripts to these views so that my artist can get an accurate preview without having to use the in-game view?

Thank you for any help you can provide. :wink:

grumble grumble
That’s about what I expected, but I thought I’d ask just to be safe. Thanks for the help dreamora.

n00body, have you looked into using the ExecuteInEditMode() attribute? Documentation Page (ExecuteInEditMode)

1 Like

This, I want image effects on the scene view

The scene view has a camera. You may be able to use some hacky editor-only gameobject with a script on it to get a reference to this, and attach the relevant components.

edit: Yep that works. Pop this on a GameObject in your scene, then add image effects as you would on a regular camera. Otherwise, choose a reference camera and tick UseReferenceCamera to use it’s components instead. Update by toggling the enabled state of the script:

using System;
using System.Linq;
using UnityEditor;
using UnityEngine;

[ExecuteInEditMode]
public class SceneViewCameraProxy : MonoBehaviour
{
#if UNITY_EDITOR
    public SceneView SceneView;
    public Camera Camera;

    public Camera ReferenceCamera;
    public bool UseReferenceCamera;

    public void OnEnable()
    {
        Camera = GetCamera();
        UpdateComponents();
    }

    private Camera GetCamera()
    {
        SceneView = EditorWindow.GetWindow<SceneView>();
        return SceneView.camera;
    }

    private Component[] GetComponents()
    {
        var result = UseReferenceCamera
                     ? ReferenceCamera.GetComponents<Component>()
                     : GetComponents<Component>();

        if (result != null  result.Length > 1) // Exclude Transform
        {
            result = result.Except(new[] {UseReferenceCamera ? ReferenceCamera.transform : transform}).ToArray();

            var hasCamera = UseReferenceCamera ? true : camera != null;
            if (hasCamera)
                result = UseReferenceCamera ? result.Except(new[] {ReferenceCamera}).ToArray() : result.Except(new[] {camera}).ToArray();
        }

        return result;
    }

    private void UpdateComponents()
    {
        if(Camera == null)
            Camera = GetCamera();

        if (Camera == null) // This shouldn't happen, but it does
            return;

        if(UseReferenceCamera  ReferenceCamera == null)
            throw new Exception("UseReferenceCamera enabled, but none chosen.");

        var components = GetComponents();
        if (components != null  components.Length > 1)
        {
            var cameraGo = Camera.gameObject;

            Debug.Log(cameraGo);
            Debug.Log(cameraGo.GetComponents(typeof(Component)).Length);

            for (var i = 0; i < components.Length; i++)
            {
                var c = components[i];
                var cType = c.GetType();
                
                var existing = cameraGo.GetComponent(cType) ?? cameraGo.AddComponent(cType);

                EditorUtility.CopySerialized(c, existing);
            }
        }
    }
#endif
}
1 Like

fixed for 5.3

using System.Collections.Generic;
using System;
using System.Linq;
using UnityEditor;
using UnityEngine;

[ExecuteInEditMode]
public class SceneViewCameraProxy : MonoBehaviour
{
    #if UNITY_EDITOR
    public SceneView SceneView;
    public Camera Camera;

    public Camera ReferenceCamera;
    public bool UseReferenceCamera;

    public void OnEnable()
    {
        Camera = GetCamera();
        UpdateComponents();
    }

    private Camera GetCamera()
    {
        SceneView = EditorWindow.GetWindow<SceneView>();
        return SceneView.camera;
    }

    private Component[] GetComponents()
    {
        var result = UseReferenceCamera
            ? ReferenceCamera.GetComponents<Component>()
            : GetComponents<Component>();

        if (result != null && result.Length > 1) // Exclude Transform
        {
            result = result.Except(new[] {UseReferenceCamera ? ReferenceCamera.transform : transform}).ToArray();

            var hasCamera = UseReferenceCamera ? true : GetComponent<Camera>() != null;
            if (hasCamera)
                result = UseReferenceCamera ? result.Except(new[] {ReferenceCamera}).ToArray() : result.Except(new[] {GetComponent<Camera>()}).ToArray();
        }

        return result;
    }

    private void UpdateComponents()
    {
        if(Camera == null)
            Camera = GetCamera();

        if (Camera == null) // This shouldn't happen, but it does
            return;

        if(UseReferenceCamera && ReferenceCamera == null)
            throw new Exception("UseReferenceCamera enabled, but none chosen.");

        var components = GetComponents();
        if (components != null && components.Length > 1)
        {
            var cameraGo = Camera.gameObject;

            Debug.Log(cameraGo);
            Debug.Log(cameraGo.GetComponents(typeof(Component)).Length);

            for (var i = 0; i < components.Length; i++)
            {
                var c = components[i];
                var cType = c.GetType();

                var existing = cameraGo.GetComponent(cType) ?? cameraGo.AddComponent(cType);

                EditorUtility.CopySerialized(c, existing);
            }
        }
    }
    #endif
}

Added fancy settings so normal scripts are not copied (checks for implementation of OnRenderImage), also fixing an error with copying GUILayer, and added support for automatically updating the image effects once a variable on the object has been changed, allowing for editing them in the scene view without having to disable and re-enable the component to update the image effects.
Nothing crucial, but I found it comes in really handy:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif

[ExecuteInEditMode]
public class SceneViewCameraProxy : MonoBehaviour
{
    #if UNITY_EDITOR
    private SceneView SceneView;
    public Camera SceneCamera;

    public Camera ReferenceCamera;
    public bool UseReferenceCamera;

    public bool ReflectionCheckForIE = true;
    public bool CheckForStandardIE = true;

    public bool UpdateOnChange = true;
    public bool ResetIEOnDisable = true;
    public bool DebugImageEffects = false;

    // Used only for Update
    private int lastComponentCount;
    private Component[] cachedComponents;

    private GameObject IEsourceGO { get { return UseReferenceCamera? ReferenceCamera.gameObject : gameObject; } }

    public void OnEnable()
    {
        UpdateImageEffects();
    }

    public void OnValidate()
    { // Update when a variable on this script was changed
        if (!UpdateOnChange)
            OnEnable ();
    }

    public void OnDisable ()
    { // Reset image effects on disabling this component if desired
        if (ResetIEOnDisable)
            ResetImageEffects ();
    }

    public void Update ()
    {
        if (UpdateOnChange && Selection.activeGameObject == IEsourceGO)
        { // Update scene camera with changed image effects using cached components, as long as none are added or removed
            if (DebugImageEffects)
                Debug.Log("Updating reference camera due to changed components!");
            int componentCount = IEsourceGO.GetComponents<Component>().Length;
            if (lastComponentCount != componentCount)
            { // Image Effects might have been added or removed, so refetch them
                lastComponentCount = componentCount;
                cachedComponents = GetImageEffectComponents(IEsourceGO);
            }
            UpdateSceneCamera ();
            if (SceneCamera != null)
                InternalCopyComponents (cachedComponents, SceneCamera.gameObject);
        }
    }

    private void UpdateSceneCamera()
    {
        if (UnityEditor.SceneView.lastActiveSceneView != null)
            SceneView = UnityEditor.SceneView.lastActiveSceneView;
        SceneCamera = SceneView == null? null : SceneView.camera;
    }

    /// <summary>
    /// Returns all components filtered for image effects
    /// </summary>
    private Component[] GetImageEffectComponents(GameObject GO)
    {
        Component[] components = GO.GetComponents<Component>();
        if (components != null && components.Length > 0)
        { // Exclude Transform and Camera components
            if (ReflectionCheckForIE)
            { // Check if component implements OnRenderImage used for image postprocessing -> Perfect check!
                components = components.Where((Component c) => c.GetType ().GetMethod ("OnRenderImage", BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public) != null).ToArray();
            }
            else if (CheckForStandardIE)
            { // Check if it is an standard image effects; unfortunately does not always work on 3rd party components!
                components = components.Where ((Component c) => c.GetType ().IsSubclassOf (typeof(UnityStandardAssets.ImageEffects.PostEffectsBase))).ToArray ();
            }
            else
            { // Check for all Components possibly being image effects, but may include normal scripts!
                components = components.Where((Component c) => {
                    Type cT = c.GetType ();
                    return c != this && cT != typeof(Transform) && cT != typeof(GUILayer) && cT != typeof(Camera);
                }).ToArray();
            }
        }
        return components;
    }

    /// <summary>
    /// Updates the image effects found on the proxy object to the scene camera
    /// </summary>
    private void UpdateImageEffects()
    {
        UpdateSceneCamera ();
        if (SceneCamera == null)
            return;

        if(UseReferenceCamera && ReferenceCamera == null)
            throw new Exception("UseReferenceCamera enabled, but none chosen.");

        if (DebugImageEffects)
            Debug.Log ("Applying image effects to '" + SceneCamera.gameObject + "':");

        lastComponentCount = IEsourceGO.GetComponents<Component>().Length;
        cachedComponents = GetImageEffectComponents(IEsourceGO);
        InternalCopyComponents (cachedComponents, SceneCamera.gameObject);
    }

    /// <summary>
    /// Resets all image effects found on the scene camera
    /// </summary>
    private void ResetImageEffects()
    {
        UpdateSceneCamera ();
        if (SceneCamera == null)
            return;

        if (DebugImageEffects)
            Debug.Log ("Resetting image effects of '" + SceneCamera.gameObject + "':");

        Component[] components = GetImageEffectComponents (SceneCamera.gameObject);
        for (int i = 0; i < components.Length; i++)
        {
            Component comp = components[i];
            if (DebugImageEffects)
                Debug.Log(comp.GetType().Name);
            DestroyImmediate (comp);
        }
    }

    private void InternalCopyComponents (Component[] components, GameObject target)
    {
        if (components != null && components.Length > 0)
        {
            for (int i = 0; i < components.Length; i++)
            {
                Component comp = components[i];
                Type cType = comp.GetType();
                if (DebugImageEffects)
                    Debug.Log(cType.Name);
                // Copy component values
                Component existingComp = target.GetComponent(cType) ?? target.AddComponent(cType);
                EditorUtility.CopySerialized(comp, existingComp);
            }
        }
    }

    #endif
}

EDIT: Fixed calling GetSceneCamera when updating ImageEffects automatically interrupting current keyboard input and randomly opening SceneViews (won’t automatically open one if user does not want)

1 Like

@Seneral_1 Nice work :slight_smile:

(and BTW, your Erosion simulation is beautiful)

Thanks:)
Will develop it further after the release of my first tool (in like a month).
Edit: Just announced said tool;)

1 Like

I’ve tried adding an image effect to the editor camera with @Seneral_1 's script, which seems to work well since awake runs. The problem is OnRenderImage does not seem to run at all, I’ve added Debug.Log and nothing gets printed nor the effect is visualized. The same image effect works properly in the main camera. Am I missing something?

Thanks in advance

EDIT: Apparently its a problem with the newest versions of unity as discussed in Image effects not showing in the editor camera .