using System; using System.Collections.Generic; using UnityEditor.SceneManagement; using UnityEngine; using UnityEngine.SceneManagement; using UnityEngine.Rendering.LookDev; namespace UnityEditor.Rendering.LookDev { //TODO: add undo support /// /// Class handling object of the scene with isolation from other scene based on culling /// class Stage : IDisposable { const int k_PreviewCullingLayerIndex = 31; //Camera.PreviewCullingLayer; //TODO: expose or reflection private readonly Scene m_PreviewScene; // Everything except camera private readonly List m_GameObjects = new List(); private readonly List m_PersistentGameObjects = new List(); private readonly Camera m_Camera; private readonly Light m_SunLight; /// Get access to the stage's camera public Camera camera => m_Camera; /// Get access to the stage's light public Light sunLight => m_SunLight; /// Get access to the stage's scene public Scene scene => m_PreviewScene; private StageRuntimeInterface SRI; /// The runtime interface on stage public StageRuntimeInterface runtimeInterface => SRI ?? (SRI = new StageRuntimeInterface( CreateGameObjectIntoStage, () => camera, () => sunLight)); /// /// Construct a new stage to let your object live. /// A stage is a scene with visibility isolation. /// /// Name of the scene used. public Stage(string sceneName) { if (string.IsNullOrEmpty(sceneName)) throw new System.ArgumentNullException("sceneName"); m_PreviewScene = EditorSceneManager.NewPreviewScene(); m_PreviewScene.name = sceneName; var camGO = EditorUtility.CreateGameObjectWithHideFlags("Look Dev Camera", HideFlags.HideAndDontSave, typeof(Camera)); MoveIntoStage(camGO, true); //position will be updated right before rendering camGO.layer = k_PreviewCullingLayerIndex; m_Camera = camGO.GetComponent(); m_Camera.cameraType = CameraType.Game; //cannot be preview in HDRP: too many things skiped m_Camera.enabled = false; m_Camera.clearFlags = CameraClearFlags.Depth; m_Camera.cullingMask = 1 << k_PreviewCullingLayerIndex; m_Camera.renderingPath = RenderingPath.DeferredShading; m_Camera.useOcclusionCulling = false; m_Camera.scene = m_PreviewScene; var lightGO = EditorUtility.CreateGameObjectWithHideFlags("Look Dev Sun", HideFlags.HideAndDontSave, typeof(Light)); MoveIntoStage(lightGO, true); //position will be updated right before rendering m_SunLight = lightGO.GetComponent(); m_SunLight.type = LightType.Directional; m_SunLight.shadows = LightShadows.Soft; m_SunLight.intensity = 0f; } /// /// Move a GameObject into the stage's scene at origin. /// /// The gameObject to move. /// /// [OPTIONAL] If true, the object is not recreated with the scene update. /// Default value: false. /// /// public void MoveIntoStage(GameObject gameObject, bool persistent = false) => MoveIntoStage(gameObject, Vector3.zero, gameObject.transform.rotation, persistent); /// /// Move a GameObject into the stage's scene at specific position and /// rotation. /// /// The gameObject to move. /// The new world position /// The new world rotation /// /// [OPTIONAL] If true, the object is not recreated with the scene update. /// Default value: false. /// /// public void MoveIntoStage(GameObject gameObject, Vector3 position, Quaternion rotation, bool persistent = false) { if (m_GameObjects.Contains(gameObject)) return; SceneManager.MoveGameObjectToScene(gameObject, m_PreviewScene); gameObject.transform.position = position; gameObject.transform.rotation = rotation; if (persistent) m_PersistentGameObjects.Add(gameObject); else m_GameObjects.Add(gameObject); InitAddedObjectsRecursively(gameObject); } /// /// Instantiate a scene GameObject or a prefab into the stage's scene. /// It is instantiated at origin. /// /// The element to instantiate /// /// [OPTIONAL] If true, the object is not recreated with the scene update. /// Default value: false. /// /// The instance /// public GameObject InstantiateIntoStage(GameObject prefabOrSceneObject, bool persistent = false) => InstantiateIntoStage(prefabOrSceneObject, Vector3.zero, prefabOrSceneObject.transform.rotation, persistent); /// /// Instantiate a scene GameObject or a prefab into the stage's scene /// at a specific position and rotation. /// /// The element to instantiate /// The new world position /// The new world rotation /// /// [OPTIONAL] If true, the object is not recreated with the scene update. /// Default value: false. /// /// The instance /// public GameObject InstantiateIntoStage(GameObject prefabOrSceneObject, Vector3 position, Quaternion rotation, bool persistent = false) { GameObject handle; if (PrefabUtility.IsPartOfAnyPrefab(prefabOrSceneObject)) { // Instantiate the prefab directly into the preview scene to prevent any issues // with systems that register with the main scene in Awake [Case 1399762]. handle = PrefabUtility.InstantiatePrefab(prefabOrSceneObject, m_PreviewScene) as GameObject; } else handle = GameObject.Instantiate(prefabOrSceneObject); MoveIntoStage(handle, position, rotation, persistent); return handle; } /// Create a GameObject into the stage. /// /// [OPTIONAL] If true, the object is not recreated with the scene update. /// Default value: false. /// /// The created GameObject public GameObject CreateGameObjectIntoStage(bool persistent = false) { var handle = new GameObject(); MoveIntoStage(handle, persistent); return handle; } /// Clear all scene object except camera. /// /// [OPTIONAL] If true, clears also persistent objects. /// Default value: false. /// public void Clear(bool persistent = false) { foreach (var go in m_GameObjects) UnityEngine.Object.DestroyImmediate(go); m_GameObjects.Clear(); if (persistent) { foreach (var go in m_PersistentGameObjects) UnityEngine.Object.DestroyImmediate(go); m_PersistentGameObjects.Clear(); } } static void InitAddedObjectsRecursively(GameObject go) { go.hideFlags = HideFlags.HideAndDontSave; go.layer = k_PreviewCullingLayerIndex; var meshRenderer = go.GetComponent(); if (meshRenderer != null) meshRenderer.lightProbeUsage = UnityEngine.Rendering.LightProbeUsage.Off; var skinnedMeshRenderer = go.GetComponent(); if (skinnedMeshRenderer != null) skinnedMeshRenderer.lightProbeUsage = UnityEngine.Rendering.LightProbeUsage.Off; var lineRenderer = go.GetComponent(); if (lineRenderer != null) lineRenderer.lightProbeUsage = UnityEngine.Rendering.LightProbeUsage.Off; var volumes = go.GetComponents(); foreach (var volume in volumes) volume.UpdateLayer(); //force update of layer now as the Update can be called after we unregister volume from manager foreach (Transform child in go.transform) InitAddedObjectsRecursively(child.gameObject); } /// Changes stage scene's objects visibility. /// /// True: make them visible. /// False: hide them. /// void SetGameObjectVisible(bool visible) { foreach (GameObject go in m_GameObjects) { if (go == null || go.Equals(null)) continue; foreach (UnityEngine.Renderer renderer in go.GetComponentsInChildren()) { if ((renderer.hideFlags & HideFlags.HideInInspector) == 0 && ((renderer.hideFlags & HideFlags.HideAndDontSave) == 0)) renderer.enabled = visible; } foreach (Light light in go.GetComponentsInChildren()) { if ((light.hideFlags & HideFlags.HideInInspector) == 0 && ((light.hideFlags & HideFlags.HideAndDontSave) == 0)) light.enabled = visible; } } // in case we add camera frontal light and such foreach (UnityEngine.Renderer renderer in m_Camera.GetComponentsInChildren()) { if ((renderer.hideFlags & HideFlags.HideInInspector) == 0 && ((renderer.hideFlags & HideFlags.HideAndDontSave) == 0)) renderer.enabled = visible; } foreach (Light light in m_Camera.GetComponentsInChildren()) { if ((light.hideFlags & HideFlags.HideInInspector) == 0 && ((light.hideFlags & HideFlags.HideAndDontSave) == 0)) light.enabled = visible; } } public void OnBeginRendering(IDataProvider dataProvider) { SetGameObjectVisible(true); dataProvider.OnBeginRendering(runtimeInterface); } public void OnEndRendering(IDataProvider dataProvider) { SetGameObjectVisible(false); dataProvider.OnEndRendering(runtimeInterface); } private bool disposedValue = false; // To detect redundant calls void CleanUp() { if (!disposedValue) { if (SRI != null) SRI.SRPData = null; SRI = null; EditorSceneManager.ClosePreviewScene(m_PreviewScene); disposedValue = true; } } ~Stage() => CleanUp(); /// Clear and close the stage's scene. public void Dispose() { CleanUp(); GC.SuppressFinalize(this); } } class StageCache : IDisposable { const string firstStageName = "LookDevFirstView"; const string secondStageName = "LookDevSecondView"; Stage[] m_Stages; IDataProvider m_CurrentDataProvider; public Stage this[ViewIndex index] => m_Stages[(int)index]; public bool initialized { get; private set; } public StageCache(IDataProvider dataProvider) { m_Stages = new Stage[2] { InitStage(ViewIndex.First, dataProvider), InitStage(ViewIndex.Second, dataProvider) }; initialized = true; } Stage InitStage(ViewIndex index, IDataProvider dataProvider) { Stage stage; switch (index) { case ViewIndex.First: stage = new Stage(firstStageName); stage.camera.backgroundColor = new Color32(5, 5, 5, 255); stage.camera.name += "_1"; break; case ViewIndex.Second: stage = new Stage(secondStageName); stage.camera.backgroundColor = new Color32(5, 5, 5, 255); stage.camera.name += "_2"; break; default: throw new ArgumentException("Unknown ViewIndex: " + index); } dataProvider.FirstInitScene(stage.runtimeInterface); m_CurrentDataProvider = dataProvider; return stage; } public void UpdateSceneObjects(ViewIndex index) { Stage stage = this[index]; stage.Clear(); var viewContent = LookDev.currentContext.GetViewContent(index); if (viewContent == null) { viewContent.viewedInstanceInPreview = null; return; } if (viewContent.viewedObjectReference != null && !viewContent.viewedObjectReference.Equals(null)) viewContent.viewedInstanceInPreview = stage.InstantiateIntoStage(viewContent.viewedObjectReference); } public void UpdateSceneLighting(ViewIndex index, IDataProvider provider) { Stage stage = this[index]; Environment environment = LookDev.currentContext.GetViewContent(index).environment; provider.UpdateSky(stage.camera, environment == null ? default : environment.sky, stage.runtimeInterface); } private bool disposedValue = false; // To detect redundant calls void CleanUp() { if (!disposedValue) { foreach (Stage stage in m_Stages) { m_CurrentDataProvider.Cleanup(stage.runtimeInterface); stage.Dispose(); } disposedValue = true; } } ~StageCache() => CleanUp(); public void Dispose() { CleanUp(); GC.SuppressFinalize(this); } } }