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?
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
}
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)
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?