浏览代码

Embedded Packages with renderer refactor branch embedded

/demo-work-customrenderer
André McGrail 5 年前
当前提交
f083bd65
共有 3452 个文件被更改,包括 11016 次插入0 次删除
  1. 7
      Packages/com.unity.render-pipelines.core/.npmignore
  2. 88
      Packages/com.unity.render-pipelines.core/CHANGELOG.md
  3. 7
      Packages/com.unity.render-pipelines.core/CHANGELOG.md.meta
  4. 11
      Packages/com.unity.render-pipelines.core/Documentation~/Camera-Switcher.md
  5. 16
      Packages/com.unity.render-pipelines.core/Documentation~/Free-Camera.md
  6. 11
      Packages/com.unity.render-pipelines.core/Documentation~/Images/CameraSwitcher1.png
  7. 19
      Packages/com.unity.render-pipelines.core/Documentation~/Images/FreeCamera1.png
  8. 4
      Packages/com.unity.render-pipelines.core/Documentation~/TableOfContents.md
  9. 13
      Packages/com.unity.render-pipelines.core/Documentation~/index.md
  10. 9
      Packages/com.unity.render-pipelines.core/Editor.meta
  11. 100
      Packages/com.unity.render-pipelines.core/Editor/CameraEditorUtils.cs
  12. 11
      Packages/com.unity.render-pipelines.core/Editor/CameraEditorUtils.cs.meta
  13. 449
      Packages/com.unity.render-pipelines.core/Editor/CoreEditorDrawers.cs
  14. 11
      Packages/com.unity.render-pipelines.core/Editor/CoreEditorDrawers.cs.meta
  15. 43
      Packages/com.unity.render-pipelines.core/Editor/CoreEditorStyles.cs
  16. 11
      Packages/com.unity.render-pipelines.core/Editor/CoreEditorStyles.cs.meta
  17. 774
      Packages/com.unity.render-pipelines.core/Editor/CoreEditorUtils.cs
  18. 13
      Packages/com.unity.render-pipelines.core/Editor/CoreEditorUtils.cs.meta
  19. 8
      Packages/com.unity.render-pipelines.core/Editor/Debugging.meta
  20. 106
      Packages/com.unity.render-pipelines.core/Editor/Debugging/DebugState.cs
  21. 11
      Packages/com.unity.render-pipelines.core/Editor/Debugging/DebugState.cs.meta
  22. 457
      Packages/com.unity.render-pipelines.core/Editor/Debugging/DebugUIDrawer.Builtins.cs
  23. 11
      Packages/com.unity.render-pipelines.core/Editor/Debugging/DebugUIDrawer.Builtins.cs.meta
  24. 63
      Packages/com.unity.render-pipelines.core/Editor/Debugging/DebugUIDrawer.cs
  25. 11
      Packages/com.unity.render-pipelines.core/Editor/Debugging/DebugUIDrawer.cs.meta
  26. 79
      Packages/com.unity.render-pipelines.core/Editor/Debugging/DebugUIHandlerCanvasEditor.cs
  27. 11
      Packages/com.unity.render-pipelines.core/Editor/Debugging/DebugUIHandlerCanvasEditor.cs.meta
  28. 580
      Packages/com.unity.render-pipelines.core/Editor/Debugging/DebugWindow.cs
  29. 11
      Packages/com.unity.render-pipelines.core/Editor/Debugging/DebugWindow.cs.meta
  30. 34
      Packages/com.unity.render-pipelines.core/Editor/Debugging/UIFoldoutEditor.cs
  31. 11
      Packages/com.unity.render-pipelines.core/Editor/Debugging/UIFoldoutEditor.cs.meta
  32. 46
      Packages/com.unity.render-pipelines.core/Editor/EditorPrefBoolFlags.cs
  33. 11
      Packages/com.unity.render-pipelines.core/Editor/EditorPrefBoolFlags.cs.meta
  34. 58
      Packages/com.unity.render-pipelines.core/Editor/ExpandedState.cs
  35. 11
      Packages/com.unity.render-pipelines.core/Editor/ExpandedState.cs.meta
  36. 696
      Packages/com.unity.render-pipelines.core/Editor/FilterWindow.cs
  37. 11
      Packages/com.unity.render-pipelines.core/Editor/FilterWindow.cs.meta
  38. 8
      Packages/com.unity.render-pipelines.core/Editor/Gizmo.meta
  39. 397
      Packages/com.unity.render-pipelines.core/Editor/Gizmo/HierarchicalBox.cs
  40. 11
      Packages/com.unity.render-pipelines.core/Editor/Gizmo/HierarchicalBox.cs.meta
  41. 175
      Packages/com.unity.render-pipelines.core/Editor/Gizmo/HierarchicalSphere.cs
  42. 11
      Packages/com.unity.render-pipelines.core/Editor/Gizmo/HierarchicalSphere.cs.meta
  43. 20
      Packages/com.unity.render-pipelines.core/Editor/Gizmo/UnlitGizmo.shader
  44. 9
      Packages/com.unity.render-pipelines.core/Editor/Gizmo/UnlitGizmo.shader.meta
  45. 814
      Packages/com.unity.render-pipelines.core/Editor/InspectorCurveEditor.cs
  46. 11
      Packages/com.unity.render-pipelines.core/Editor/InspectorCurveEditor.cs.meta
  47. 8
      Packages/com.unity.render-pipelines.core/Editor/Lighting.meta
  48. 626
      Packages/com.unity.render-pipelines.core/Editor/Lighting/CoreLightEditorUtilities.cs
  49. 11
      Packages/com.unity.render-pipelines.core/Editor/Lighting/CoreLightEditorUtilities.cs.meta
  50. 8
      Packages/com.unity.render-pipelines.core/Editor/LookDev.meta
  51. 493
      Packages/com.unity.render-pipelines.core/Editor/LookDev/CameraController.cs
  52. 11
      Packages/com.unity.render-pipelines.core/Editor/LookDev/CameraController.cs.meta
  53. 117
      Packages/com.unity.render-pipelines.core/Editor/LookDev/CameraState.cs
  54. 11
      Packages/com.unity.render-pipelines.core/Editor/LookDev/CameraState.cs.meta
  55. 240
      Packages/com.unity.render-pipelines.core/Editor/LookDev/ComparisonGizmoController.cs
  56. 11
      Packages/com.unity.render-pipelines.core/Editor/LookDev/ComparisonGizmoController.cs.meta
  57. 117
      Packages/com.unity.render-pipelines.core/Editor/LookDev/ComparisonGizmoState.cs
  58. 11
      Packages/com.unity.render-pipelines.core/Editor/LookDev/ComparisonGizmoState.cs.meta
  59. 401
      Packages/com.unity.render-pipelines.core/Editor/LookDev/Compositor.cs
  60. 11
      Packages/com.unity.render-pipelines.core/Editor/LookDev/Compositor.cs.meta
  61. 457
      Packages/com.unity.render-pipelines.core/Editor/LookDev/Compositor.shader
  62. 9
      Packages/com.unity.render-pipelines.core/Editor/LookDev/Compositor.shader.meta
  63. 400
      Packages/com.unity.render-pipelines.core/Editor/LookDev/Context.cs
  64. 11
      Packages/com.unity.render-pipelines.core/Editor/LookDev/Context.cs.meta
  65. 133
      Packages/com.unity.render-pipelines.core/Editor/LookDev/CubeToLatlong.shader
  66. 9
      Packages/com.unity.render-pipelines.core/Editor/LookDev/CubeToLatlong.shader.meta
  67. 34
      Packages/com.unity.render-pipelines.core/Editor/LookDev/DisplayWindow-PersonalSkin.uss
  68. 11
      Packages/com.unity.render-pipelines.core/Editor/LookDev/DisplayWindow-PersonalSkin.uss.meta
  69. 958
      Packages/com.unity.render-pipelines.core/Editor/LookDev/DisplayWindow.cs
  70. 11
      Packages/com.unity.render-pipelines.core/Editor/LookDev/DisplayWindow.cs.meta
  71. 325
      Packages/com.unity.render-pipelines.core/Editor/LookDev/DisplayWindow.uss
  72. 11
      Packages/com.unity.render-pipelines.core/Editor/LookDev/DisplayWindow.uss.meta
  73. 103
      Packages/com.unity.render-pipelines.core/Editor/LookDev/DropArea.cs
  74. 11
      Packages/com.unity.render-pipelines.core/Editor/LookDev/DropArea.cs.meta
  75. 542
      Packages/com.unity.render-pipelines.core/Editor/LookDev/Environment.cs
  76. 11
      Packages/com.unity.render-pipelines.core/Editor/LookDev/Environment.cs.meta
  77. 198
      Packages/com.unity.render-pipelines.core/Editor/LookDev/EnvironmentLibrary.cs
  78. 11
      Packages/com.unity.render-pipelines.core/Editor/LookDev/EnvironmentLibrary.cs.meta
  79. 8
      Packages/com.unity.render-pipelines.core/Editor/LookDev/Icons.meta
  80. 6
      Packages/com.unity.render-pipelines.core/Editor/LookDev/Icons/LookDevCenterLight.png
  81. 77
      Packages/com.unity.render-pipelines.core/Editor/LookDev/Icons/LookDevCenterLight.png.meta
  82. 4
      Packages/com.unity.render-pipelines.core/Editor/LookDev/Icons/LookDevCenterLightl@2x.png
  83. 77
      Packages/com.unity.render-pipelines.core/Editor/LookDev/Icons/LookDevCenterLightl@2x.png.meta
  84. 3
      Packages/com.unity.render-pipelines.core/Editor/LookDev/Icons/LookDevClose.png
  85. 77
      Packages/com.unity.render-pipelines.core/Editor/LookDev/Icons/LookDevClose.png.meta
  86. 4
      Packages/com.unity.render-pipelines.core/Editor/LookDev/Icons/LookDevClose@2x.png
  87. 77
      Packages/com.unity.render-pipelines.core/Editor/LookDev/Icons/LookDevClose@2x.png.meta
  88. 6
      Packages/com.unity.render-pipelines.core/Editor/LookDev/Icons/LookDevEnvRotation.png
  89. 77
      Packages/com.unity.render-pipelines.core/Editor/LookDev/Icons/LookDevEnvRotation.png.meta

7
Packages/com.unity.render-pipelines.core/.npmignore


sub-package.*
upm-ci~/**
.Editor/**
.yamato/**
*.zip*
TestRunnerOptions.json
.idea/**

88
Packages/com.unity.render-pipelines.core/CHANGELOG.md


# Changelog
All notable changes to this package will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
## [7.1.1] - 2019-XX-XX
## [7.0.1] - 2019-07-25
### Fixed
- Fixed a precision issue with the ACES tonemapper on mobile platforms.
## [7.0.0] - 2019-07-17
### Added
- First experimental version of the LookDev. Works with all SRP. Only branched on HDRP at the moment.
- LookDev out of experimental
## [6.7.0-preview] - 2019-05-16
## [6.6.0] - 2019-04-01
### Fixed
- Fixed compile errors in XRGraphics.cs when ENABLE_VR is not defined
## [6.5.0] - 2019-03-07
## [6.4.0] - 2019-02-21
### Added
- Enabled support for CBUFFER on OpenGL Core and OpenGL ES 3 backends.
## [6.3.0] - 2019-02-18
## [6.2.0] - 2019-02-15
## [6.1.0] - 2019-02-13
## [6.0.0] - 2019-02-23
### Fixed
- Fixed a typo in ERROR_ON_UNSUPPORTED_FUNCTION() that was causing the shader compiler to run out of memory in GLES2. [Case 1104271] (https://issuetracker.unity3d.com/issues/mobile-os-restarts-because-of-high-memory-usage-when-compiling-shaders-for-opengles2)
## [5.2.0] - 2018-11-27
## [5.1.0] - 2018-11-19
### Added
- Added a define for determining if any instancing path is taken.
### Changed
- The Core SRP package is no longer in preview.
## [5.0.0-preview] - 2018-10-18
### Changed
- XRGraphicConfig has been changed from a read-write control of XRSettings to XRGraphics, a read-only accessor to XRSettings. This improves consistency of XR behavior between the legacy render pipeline and SRP.
- XRGraphics members have been renamed to match XRSettings, and XRGraphics has been modified to only contain accessors potentially useful to SRP
- You can now have up to 16 additional shadow-casting lights.
### Fixed
- LWRP no longer executes shadow passes when there are no visible shadow casters in a Scene. Previously, this made the Scene render as too dark, overall.
## [4.0.0-preview] - 2018-09-28
### Added
- Space transform functions are now defined in `ShaderLibrary/SpaceTransforms.hlsl`.
### Changed
- Removed setting shader inclue path via old API, use package shader include paths
## [3.3.0]
## [3.2.0]
## [3.1.0]
### Added
- Add PCSS shadow filter
- Added Core EditMode tests
- Added Core unsafe utilities
### Improvements
- Improved volume UI & styling
- Fixed CoreUtils.QuickSort infinite loop when two elements in the list are equals.
### Changed
- Moved root files into folders for easier maintenance
## [0.1.6] - 2018-xx-yy
### Changelog starting
Started Changelog

7
Packages/com.unity.render-pipelines.core/CHANGELOG.md.meta


fileFormatVersion: 2
guid: 20456a2cc8a214f9d9846725cff9fea4
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

11
Packages/com.unity.render-pipelines.core/Documentation~/Camera-Switcher.md


# Camera Switcher
The **CameraSwitcher** component allows you to define a List of Cameras in the Scene and then use the Debug Window to switch between them in Play Mode. This is useful when you want a set of different fixed views for profiling purposes where you need to guarantee that the Camera view is in the same position between sessions.
## Properties
![](Images/CameraSwitcher1.png)
| **Property** | **Description** |
| ------------ | ------------------------------------------------------------ |
| **Cameras** | Drag and drop GameObjects that have a Camera component attached to add them to this List of Cameras. The Debug Window can switch between the Cameras in this List. |

16
Packages/com.unity.render-pipelines.core/Documentation~/Free-Camera.md


# Free Camera
The **FreeCamera** component provides you with an implementation for a simple free camera. When you add this component to a Camera, you can use the keyboard and mouse, or a controller, to control the Camera's position and rotation in Play Mode.
## Properties
![](Images/FreeCamera1.png)
| **Property** | **Description** |
| ------------------------- | ------------------------------------------------------------ |
| **Look Speed Controller** | Set the look speed of the Camera when using a controller. |
| **Look Speed Mouse** | Set the look speed of the Camera when using a mouse. |
| **Move Speed** | Set the speed at which the Camera moves. |
| **Move Speed Increment** | Set the value of the increment that you can increase or decrease the **Move Speed** by. This is useful if you have a large Scene and the current **Move Speed** is not enough to easily traverse it. |
| **Turbo** | Set the value that this component multiplies the **Move Speed** by when you press the key or button assigned to "Fire1". |

11
Packages/com.unity.render-pipelines.core/Documentation~/Images/CameraSwitcher1.png

之前 之后
宽度: 402  |  高度: 65  |  大小: 4.3 KiB

19
Packages/com.unity.render-pipelines.core/Documentation~/Images/FreeCamera1.png

之前 之后
宽度: 402  |  高度: 145  |  大小: 6.6 KiB

4
Packages/com.unity.render-pipelines.core/Documentation~/TableOfContents.md


* [Scriptable Render Pipeline Core](index)
* Camera components
* [Free Camera](Free-Camera)
* [Camera Switcher](Camera-Switcher)

13
Packages/com.unity.render-pipelines.core/Documentation~/index.md


# Scriptable Render Pipeline
![](https://blogs.unity3d.com/wp-content/uploads/2018/01/image5_rs.png)
## What is the Scriptable Render Pipeline
The Scriptable Render Pipeline (SRP) is a feature that gives you full control over Unity's render pipeline and provides the tools you need to create modern, high-fidelity graphics in Unity.
SRP allows you to write C# scripts to control the way Unity renders each frame. Exposing the render pipeline to you in C# makes Unity less of a “black box” when it comes to rendering. Unlike the original built-in render pipeline, SRP allows you to see and control exactly what happens during the rendering process.
Unity provides you with two prebuilt Scriptable Render Pipelines which you can use in your Projector as a base for your own custom SRP:
* The Lightweight Render Pipeline (LWRP) offers graphics that scale from mobile platforms to higher-end consoles and PCs.
* The High Definition Render Pipeline (HDRP) utilizes physically-based lighting techniques to offer high-fidelity graphics to target modern, Compute Shader compatible, platforms.
Rather than developing your own SRP from scratch, you can use either of these prebuilt SRPs as a base to modify and adapt to your own requirements.

9
Packages/com.unity.render-pipelines.core/Editor.meta


fileFormatVersion: 2
guid: 74ae8146f4a01491ba1306f3db78139d
folderAsset: yes
timeCreated: 1479851675
licenseType: Pro
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

100
Packages/com.unity.render-pipelines.core/Editor/CameraEditorUtils.cs


using UnityEngine;
using Object = UnityEngine.Object;
namespace UnityEditor.Rendering
{
public static class CameraEditorUtils
{
public delegate Camera GetPreviewCamera(Camera sourceCamera, Vector2 previewSize);
const float k_PreviewNormalizedSize = 0.2f;
internal static Material s_GUITextureBlit2SRGBMaterial;
public static Material GUITextureBlit2SRGBMaterial
{
get
{
if (!s_GUITextureBlit2SRGBMaterial)
{
Shader shader = EditorGUIUtility.LoadRequired("SceneView/GUITextureBlit2SRGB.shader") as Shader;
s_GUITextureBlit2SRGBMaterial = new Material(shader);
s_GUITextureBlit2SRGBMaterial.hideFlags = HideFlags.HideAndDontSave;
}
s_GUITextureBlit2SRGBMaterial.SetFloat("_ManualTex2SRGB", QualitySettings.activeColorSpace == ColorSpace.Linear ? 1.0f : 0.0f);
return s_GUITextureBlit2SRGBMaterial;
}
}
public static void DrawCameraSceneViewOverlay(Object target, SceneView sceneView, GetPreviewCamera previewCameraGetter)
{
if (target == null) return;
// cache some deep values
var c = (Camera)target;
var previewSize = Handles.GetMainGameViewSize();
if (previewSize.x < 0f)
{
// Fallback to Scene View of not a valid game view size
previewSize.x = sceneView.position.width;
previewSize.y = sceneView.position.height;
}
// Apply normalizedviewport rect of camera
var normalizedViewPortRect = c.rect;
previewSize.x *= Mathf.Max(normalizedViewPortRect.width, 0f);
previewSize.y *= Mathf.Max(normalizedViewPortRect.height, 0f);
// Prevent using invalid previewSize
if (previewSize.x <= 0f || previewSize.y <= 0f)
return;
var aspect = previewSize.x / previewSize.y;
// Scale down (fit to scene view)
previewSize.y = k_PreviewNormalizedSize * sceneView.position.height;
previewSize.x = previewSize.y * aspect;
if (previewSize.y > sceneView.position.height * 0.5f)
{
previewSize.y = sceneView.position.height * 0.5f;
previewSize.x = previewSize.y * aspect;
}
if (previewSize.x > sceneView.position.width * 0.5f)
{
previewSize.x = sceneView.position.width * 0.5f;
previewSize.y = previewSize.x / aspect;
}
// Get and reserve rect
Rect cameraRect = GUILayoutUtility.GetRect(previewSize.x, previewSize.y);
if (Event.current.type == EventType.Repaint)
{
var previewCamera = previewCameraGetter(c, previewSize);
if (previewCamera.targetTexture == null)
{
Debug.LogError("The preview camera must render in a render target");
return;
}
sceneView.drawGizmos = false;
previewCamera.Render();
sceneView.drawGizmos = true;
Graphics.DrawTexture(cameraRect, previewCamera.targetTexture, new Rect(0, 0, 1, 1), 0, 0, 0, 0, GUI.color, GUITextureBlit2SRGBMaterial);
// We set target texture to null after this call otherwise if both sceneview and gameview are visible and we have a preview camera wwe
// get this error: "Releasing render texture that is set as Camera.targetTexture!"
previewCamera.targetTexture = null;
}
}
public static bool IsViewPortRectValidToRender(Rect normalizedViewPortRect)
{
if (normalizedViewPortRect.width <= 0f || normalizedViewPortRect.height <= 0f)
return false;
if (normalizedViewPortRect.x >= 1f || normalizedViewPortRect.xMax <= 0f)
return false;
if (normalizedViewPortRect.y >= 1f || normalizedViewPortRect.yMax <= 0f)
return false;
return true;
}
}
}

11
Packages/com.unity.render-pipelines.core/Editor/CameraEditorUtils.cs.meta


fileFormatVersion: 2
guid: c9fabbcdc8ab50c44a8112c70c67735c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

449
Packages/com.unity.render-pipelines.core/Editor/CoreEditorDrawers.cs


using System;
using System.Collections.Generic;
using UnityEngine;
namespace UnityEditor.Rendering
{
[Flags]
public enum FoldoutOption
{
None = 0,
Indent = 1 << 0,
Boxed = 1 << 2,
SubFoldout = 1 << 3,
NoSpaceAtEnd = 1 << 4
}
[Flags]
public enum GroupOption
{
None = 0,
Indent = 1 << 0
}
/// <summary>
/// Utility class to draw inspectors
/// </summary>
/// <typeparam name="TData">Type of class containing data needed to draw inspector</typeparam>
public static class CoreEditorDrawer<TData>
{
/// <summary> Abstraction that have the Draw hability </summary>
public interface IDrawer
{
void Draw(TData p, Editor owner);
}
public delegate bool Enabler(TData data, Editor owner);
public delegate void SwitchEnabler(TData data, Editor owner);
public delegate T2Data DataSelect<T2Data>(TData data, Editor owner);
public delegate void ActionDrawer(TData data, Editor owner);
/// <summary> Equivalent to EditorGUILayout.Space that can be put in a drawer group </summary>
public static readonly IDrawer space = Group((data, owner) => EditorGUILayout.Space());
/// <summary> Use it when IDrawer required but no operation should be done </summary>
public static readonly IDrawer noop = Group((data, owner) => { });
/// <summary>
/// Conditioned drawer that will only be drawn if its enabler function is null or return true
/// </summary>
/// <param name="enabler">Enable the drawing if null or return true</param>
/// <param name="contentDrawers">The content of the group</param>
public static IDrawer Conditional(Enabler enabler, params IDrawer[] contentDrawers)
{
return new ConditionalDrawerInternal(enabler, contentDrawers.Draw);
}
/// <summary>
/// Conditioned drawer that will only be drawn if its enabler function is null or return true
/// </summary>
/// <param name="enabler">Enable the drawing if null or return true</param>
/// <param name="contentDrawers">The content of the group</param>
public static IDrawer Conditional(Enabler enabler, params ActionDrawer[] contentDrawers)
{
return new ConditionalDrawerInternal(enabler, contentDrawers);
}
class ConditionalDrawerInternal : IDrawer
{
ActionDrawer[] actionDrawers { get; set; }
Enabler m_Enabler;
public ConditionalDrawerInternal(Enabler enabler = null, params ActionDrawer[] actionDrawers)
{
this.actionDrawers = actionDrawers;
m_Enabler = enabler;
}
void IDrawer.Draw(TData data, Editor owner)
{
if (m_Enabler != null && !m_Enabler(data, owner))
return;
for (var i = 0; i < actionDrawers.Length; i++)
actionDrawers[i](data, owner);
}
}
/// <summary>
/// Group of drawing function for inspector.
/// They will be drawn one after the other.
/// </summary>
/// <param name="contentDrawers">The content of the group</param>
public static IDrawer Group(params IDrawer[] contentDrawers)
{
return new GroupDrawerInternal(-1f, GroupOption.None, contentDrawers.Draw);
}
/// <summary>
/// Group of drawing function for inspector.
/// They will be drawn one after the other.
/// </summary>
/// <param name="contentDrawers">The content of the group</param>
public static IDrawer Group(params ActionDrawer[] contentDrawers)
{
return new GroupDrawerInternal(-1f, GroupOption.None, contentDrawers);
}
/// <summary> Group of drawing function for inspector with a set width for labels </summary>
/// <param name="labelWidth">Width used for all labels in the group</param>
/// <param name="contentDrawers">The content of the group</param>
public static IDrawer Group(float labelWidth, params IDrawer[] contentDrawers)
{
return new GroupDrawerInternal(labelWidth, GroupOption.None, contentDrawers.Draw);
}
/// <summary> Group of drawing function for inspector with a set width for labels </summary>
/// <param name="labelWidth">Width used for all labels in the group</param>
/// <param name="contentDrawers">The content of the group</param>
public static IDrawer Group(float labelWidth, params ActionDrawer[] contentDrawers)
{
return new GroupDrawerInternal(labelWidth, GroupOption.None, contentDrawers);
}
/// <summary>
/// Group of drawing function for inspector.
/// They will be drawn one after the other.
/// </summary>
/// <param name="options">Allow to add indentation on this group</param>
/// <param name="contentDrawers">The content of the group</param>
public static IDrawer Group(GroupOption options, params IDrawer[] contentDrawers)
{
return new GroupDrawerInternal(-1f, options, contentDrawers.Draw);
}
/// <summary>
/// Group of drawing function for inspector.
/// They will be drawn one after the other.
/// </summary>
/// <param name="options">Allow to add indentation on this group</param>
/// <param name="contentDrawers">The content of the group</param>
public static IDrawer Group(GroupOption options, params ActionDrawer[] contentDrawers)
{
return new GroupDrawerInternal(-1f, options, contentDrawers);
}
/// <summary> Group of drawing function for inspector with a set width for labels </summary>
/// <param name="labelWidth">Width used for all labels in the group</param>
/// <param name="options">Allow to add indentation on this group</param>
/// <param name="contentDrawers">The content of the group</param>
public static IDrawer Group(float labelWidth, GroupOption options, params IDrawer[] contentDrawers)
{
return new GroupDrawerInternal(labelWidth, options, contentDrawers.Draw);
}
/// <summary> Group of drawing function for inspector with a set width for labels </summary>
/// <param name="labelWidth">Width used for all labels in the group</param>
/// <param name="options">Allow to add indentation on this group</param>
/// <param name="contentDrawers">The content of the group</param>
public static IDrawer Group(float labelWidth, GroupOption options, params ActionDrawer[] contentDrawers)
{
return new GroupDrawerInternal(labelWidth, options, contentDrawers);
}
class GroupDrawerInternal : IDrawer
{
ActionDrawer[] actionDrawers { get; set; }
float m_LabelWidth;
bool isIndented;
public GroupDrawerInternal(float labelWidth = -1f, GroupOption options = GroupOption.None, params ActionDrawer[] actionDrawers)
{
this.actionDrawers = actionDrawers;
m_LabelWidth = labelWidth;
isIndented = (options & GroupOption.Indent) != 0;
}
void IDrawer.Draw(TData data, Editor owner)
{
if (isIndented)
++EditorGUI.indentLevel;
var currentLabelWidth = EditorGUIUtility.labelWidth;
if (m_LabelWidth >= 0f)
{
EditorGUIUtility.labelWidth = m_LabelWidth;
}
for (var i = 0; i < actionDrawers.Length; i++)
actionDrawers[i](data, owner);
if (m_LabelWidth >= 0f)
{
EditorGUIUtility.labelWidth = currentLabelWidth;
}
if (isIndented)
--EditorGUI.indentLevel;
}
}
/// <summary> Create an IDrawer based on an other data container </summary>
/// <param name="dataSelect">The data new source for the inner drawers</param>
/// <param name="otherDrawers">Inner drawers drawed with given data sources</param>
/// <returns></returns>
public static IDrawer Select<T2Data>(
DataSelect<T2Data> dataSelect,
params CoreEditorDrawer<T2Data>.IDrawer[] otherDrawers)
{
return new SelectDrawerInternal<T2Data>(dataSelect, otherDrawers.Draw);
}
/// <summary> Create an IDrawer based on an other data container </summary>
/// <param name="dataSelect">The data new source for the inner drawers</param>
/// <param name="otherDrawers">Inner drawers drawed with given data sources</param>
/// <returns></returns>
public static IDrawer Select<T2Data>(
DataSelect<T2Data> dataSelect,
params CoreEditorDrawer<T2Data>.ActionDrawer[] otherDrawers)
{
return new SelectDrawerInternal<T2Data>(dataSelect, otherDrawers);
}
class SelectDrawerInternal<T2Data> : IDrawer
{
DataSelect<T2Data> m_DataSelect;
CoreEditorDrawer<T2Data>.ActionDrawer[] m_SourceDrawers;
public SelectDrawerInternal(DataSelect<T2Data> dataSelect,
params CoreEditorDrawer<T2Data>.ActionDrawer[] otherDrawers)
{
m_SourceDrawers = otherDrawers;
m_DataSelect = dataSelect;
}
void IDrawer.Draw(TData data, Editor o)
{
var p2 = m_DataSelect(data, o);
for (var i = 0; i < m_SourceDrawers.Length; i++)
m_SourceDrawers[i](p2, o);
}
}
/// <summary>
/// Create an IDrawer foldout header using an ExpandedState.
/// The default option is Indent in this version.
/// </summary>
/// <param name="title">Title wanted for this foldout header</param>
/// <param name="mask">Bit mask (enum) used to define the boolean saving the state in ExpandedState</param>
/// <param name="state">The ExpandedState describing the component</param>
/// <param name="contentDrawers">The content of the foldout header</param>
public static IDrawer FoldoutGroup<TEnum, TState>(string title, TEnum mask, ExpandedState<TEnum, TState> state, params IDrawer[] contentDrawers)
where TEnum : struct, IConvertible
{
return FoldoutGroup(title, mask, state, contentDrawers.Draw);
}
/// <summary>
/// Create an IDrawer foldout header using an ExpandedState.
/// The default option is Indent in this version.
/// </summary>
/// <param name="title">Title wanted for this foldout header</param>
/// <param name="mask">Bit mask (enum) used to define the boolean saving the state in ExpandedState</param>
/// <param name="state">The ExpandedState describing the component</param>
/// <param name="contentDrawers">The content of the foldout header</param>
public static IDrawer FoldoutGroup<TEnum, TState>(string title, TEnum mask, ExpandedState<TEnum, TState> state, params ActionDrawer[] contentDrawers)
where TEnum : struct, IConvertible
{
return FoldoutGroup(EditorGUIUtility.TrTextContent(title), mask, state, contentDrawers);
}
/// <summary> Create an IDrawer foldout header using an ExpandedState </summary>
/// <param name="title">Title wanted for this foldout header</param>
/// <param name="mask">Bit mask (enum) used to define the boolean saving the state in ExpandedState</param>
/// <param name="state">The ExpandedState describing the component</param>
/// <param name="contentDrawers">The content of the foldout header</param>
public static IDrawer FoldoutGroup<TEnum, TState>(string title, TEnum mask, ExpandedState<TEnum, TState> state, FoldoutOption options, params IDrawer[] contentDrawers)
where TEnum : struct, IConvertible
{
return FoldoutGroup(title, mask, state, options, contentDrawers.Draw);
}
/// <summary> Create an IDrawer foldout header using an ExpandedState </summary>
/// <param name="title">Title wanted for this foldout header</param>
/// <param name="mask">Bit mask (enum) used to define the boolean saving the state in ExpandedState</param>
/// <param name="state">The ExpandedState describing the component</param>
/// <param name="contentDrawers">The content of the foldout header</param>
public static IDrawer FoldoutGroup<TEnum, TState>(string title, TEnum mask, ExpandedState<TEnum, TState> state, FoldoutOption options, params ActionDrawer[] contentDrawers)
where TEnum : struct, IConvertible
{
return FoldoutGroup(EditorGUIUtility.TrTextContent(title), mask, state, options, contentDrawers);
}
/// <summary>
/// Create an IDrawer foldout header using an ExpandedState.
/// The default option is Indent in this version.
/// </summary>
/// <param name="title">Title wanted for this foldout header</param>
/// <param name="mask">Bit mask (enum) used to define the boolean saving the state in ExpandedState</param>
/// <param name="state">The ExpandedState describing the component</param>
/// <param name="contentDrawers">The content of the foldout header</param>
public static IDrawer FoldoutGroup<TEnum, TState>(GUIContent title, TEnum mask, ExpandedState<TEnum, TState> state, params IDrawer[] contentDrawers)
where TEnum : struct, IConvertible
{
return FoldoutGroup(title, mask, state, contentDrawers.Draw);
}
/// <summary>
/// Create an IDrawer foldout header using an ExpandedState.
/// The default option is Indent in this version.
/// </summary>
/// <param name="title">Title wanted for this foldout header</param>
/// <param name="mask">Bit mask (enum) used to define the boolean saving the state in ExpandedState</param>
/// <param name="state">The ExpandedState describing the component</param>
/// <param name="contentDrawers">The content of the foldout header</param>
public static IDrawer FoldoutGroup<TEnum, TState>(GUIContent title, TEnum mask, ExpandedState<TEnum, TState> state, params ActionDrawer[] contentDrawers)
where TEnum : struct, IConvertible
{
return FoldoutGroup(title, mask, state, FoldoutOption.Indent, contentDrawers);
}
/// <summary> Create an IDrawer foldout header using an ExpandedState </summary>
/// <param name="title">Title wanted for this foldout header</param>
/// <param name="mask">Bit mask (enum) used to define the boolean saving the state in ExpandedState</param>
/// <param name="state">The ExpandedState describing the component</param>
/// <param name="contentDrawers">The content of the foldout header</param>
public static IDrawer FoldoutGroup<TEnum, TState>(GUIContent title, TEnum mask, ExpandedState<TEnum, TState> state, FoldoutOption options, params IDrawer[] contentDrawers)
where TEnum : struct, IConvertible
{
return FoldoutGroup(title, mask, state, options, contentDrawers.Draw);
}
/// <summary> Create an IDrawer foldout header using an ExpandedState </summary>
/// <param name="title">Title wanted for this foldout header</param>
/// <param name="mask">Bit mask (enum) used to define the boolean saving the state in ExpandedState</param>
/// <param name="state">The ExpandedState describing the component</param>
/// <param name="contentDrawers">The content of the foldout header</param>
public static IDrawer FoldoutGroup<TEnum, TState>(GUIContent title, TEnum mask, ExpandedState<TEnum, TState> state, FoldoutOption options, params ActionDrawer[] contentDrawers)
where TEnum : struct, IConvertible
{
return FoldoutGroup(title, mask, state, options, null, null, contentDrawers);
}
// This one is private as we do not want to have unhandled advanced switch. Change it if necessary.
static IDrawer FoldoutGroup<TEnum, TState>(GUIContent title, TEnum mask, ExpandedState<TEnum, TState> state, FoldoutOption options, Enabler isAdvanced, SwitchEnabler switchAdvanced, params ActionDrawer[] contentDrawers)
where TEnum : struct, IConvertible
{
return Group((data, owner) =>
{
bool isBoxed = (options & FoldoutOption.Boxed) != 0;
bool isIndented = (options & FoldoutOption.Indent) != 0;
bool isSubFoldout = (options & FoldoutOption.SubFoldout) != 0;
bool noSpaceAtEnd = (options & FoldoutOption.NoSpaceAtEnd) != 0;
bool expended = state[mask];
bool newExpended = expended;
if (isSubFoldout)
{
newExpended = CoreEditorUtils.DrawSubHeaderFoldout(title, expended, isBoxed,
isAdvanced == null ? (Func<bool>)null : () => isAdvanced(data, owner),
switchAdvanced == null ? (Action)null : () => switchAdvanced(data, owner));
}
else
{
CoreEditorUtils.DrawSplitter(isBoxed);
newExpended = CoreEditorUtils.DrawHeaderFoldout(title, expended, isBoxed,
isAdvanced == null ? (Func<bool>)null : () => isAdvanced(data, owner),
switchAdvanced == null ? (Action)null : () => switchAdvanced(data, owner));
}
if (newExpended ^ expended)
state[mask] = newExpended;
if (newExpended)
{
if (isIndented)
++EditorGUI.indentLevel;
for (var i = 0; i < contentDrawers.Length; i++)
contentDrawers[i](data, owner);
if (isIndented)
--EditorGUI.indentLevel;
if (!noSpaceAtEnd)
EditorGUILayout.Space();
}
});
}
/// <summary> Helper to draw a foldout with an advanced switch on it. </summary>
/// <param name="title">Title wanted for this foldout header</param>
/// <param name="mask">Bit mask (enum) used to define the boolean saving the state in ExpandedState</param>
/// <param name="state">The ExpandedState describing the component</param>
/// <param name="isAdvanced"> Delegate allowing to check if advanced mode is active. </param>
/// <param name="switchAdvanced"> Delegate to know what to do when advance is switched. </param>
/// <param name="normalContent"> The content of the foldout header always visible if expended. </param>
/// <param name="advancedContent"> The content of the foldout header only visible if advanced mode is active and if foldout is expended. </param>
public static IDrawer AdvancedFoldoutGroup<TEnum, TState>(GUIContent foldoutTitle, TEnum foldoutMask, ExpandedState<TEnum, TState> foldoutState, Enabler isAdvanced, SwitchEnabler switchAdvanced, IDrawer normalContent, IDrawer advancedContent, FoldoutOption options = FoldoutOption.Indent)
where TEnum : struct, IConvertible
{
return AdvancedFoldoutGroup(foldoutTitle, foldoutMask, foldoutState, isAdvanced, switchAdvanced, normalContent.Draw, advancedContent.Draw, options);
}
/// <summary> Helper to draw a foldout with an advanced switch on it. </summary>
/// <param name="title">Title wanted for this foldout header</param>
/// <param name="mask">Bit mask (enum) used to define the boolean saving the state in ExpandedState</param>
/// <param name="state">The ExpandedState describing the component</param>
/// <param name="isAdvanced"> Delegate allowing to check if advanced mode is active. </param>
/// <param name="switchAdvanced"> Delegate to know what to do when advance is switched. </param>
/// <param name="normalContent"> The content of the foldout header always visible if expended. </param>
/// <param name="advancedContent"> The content of the foldout header only visible if advanced mode is active and if foldout is expended. </param>
public static IDrawer AdvancedFoldoutGroup<TEnum, TState>(GUIContent foldoutTitle, TEnum foldoutMask, ExpandedState<TEnum, TState> foldoutState, Enabler isAdvanced, SwitchEnabler switchAdvanced, ActionDrawer normalContent, IDrawer advancedContent, FoldoutOption options = FoldoutOption.Indent)
where TEnum : struct, IConvertible
{
return AdvancedFoldoutGroup(foldoutTitle, foldoutMask, foldoutState, isAdvanced, switchAdvanced, normalContent, advancedContent.Draw, options);
}
/// <summary> Helper to draw a foldout with an advanced switch on it. </summary>
/// <param name="title">Title wanted for this foldout header</param>
/// <param name="mask">Bit mask (enum) used to define the boolean saving the state in ExpandedState</param>
/// <param name="state">The ExpandedState describing the component</param>
/// <param name="isAdvanced"> Delegate allowing to check if advanced mode is active. </param>
/// <param name="switchAdvanced"> Delegate to know what to do when advance is switched. </param>
/// <param name="normalContent"> The content of the foldout header always visible if expended. </param>
/// <param name="advancedContent"> The content of the foldout header only visible if advanced mode is active and if foldout is expended. </param>
public static IDrawer AdvancedFoldoutGroup<TEnum, TState>(GUIContent foldoutTitle, TEnum foldoutMask, ExpandedState<TEnum, TState> foldoutState, Enabler isAdvanced, SwitchEnabler switchAdvanced, IDrawer normalContent, ActionDrawer advancedContent, FoldoutOption options = FoldoutOption.Indent)
where TEnum : struct, IConvertible
{
return AdvancedFoldoutGroup(foldoutTitle, foldoutMask, foldoutState, isAdvanced, switchAdvanced, normalContent.Draw, advancedContent, options);
}
/// <summary> Helper to draw a foldout with an advanced switch on it. </summary>
/// <param name="title">Title wanted for this foldout header</param>
/// <param name="mask">Bit mask (enum) used to define the boolean saving the state in ExpandedState</param>
/// <param name="state">The ExpandedState describing the component</param>
/// <param name="isAdvanced"> Delegate allowing to check if advanced mode is active. </param>
/// <param name="switchAdvanced"> Delegate to know what to do when advance is switched. </param>
/// <param name="normalContent"> The content of the foldout header always visible if expended. </param>
/// <param name="advancedContent"> The content of the foldout header only visible if advanced mode is active and if foldout is expended. </param>
public static IDrawer AdvancedFoldoutGroup<TEnum, TState>(GUIContent foldoutTitle, TEnum foldoutMask, ExpandedState<TEnum, TState> foldoutState, Enabler isAdvanced, SwitchEnabler switchAdvanced, ActionDrawer normalContent, ActionDrawer advancedContent, FoldoutOption options = FoldoutOption.Indent)
where TEnum : struct, IConvertible
{
return FoldoutGroup(foldoutTitle, foldoutMask, foldoutState, options, isAdvanced, switchAdvanced,
normalContent,
Conditional((serialized, owner) => isAdvanced(serialized, owner) && foldoutState[foldoutMask], advancedContent).Draw
);
}
}
public static class CoreEditorDrawersExtensions
{
/// <summary> Concatenate a collection of IDrawer as a unique IDrawer </summary>
public static void Draw<TData>(this IEnumerable<CoreEditorDrawer<TData>.IDrawer> drawers, TData data, Editor owner)
{
foreach (var drawer in drawers)
drawer.Draw(data, owner);
}
}
}

11
Packages/com.unity.render-pipelines.core/Editor/CoreEditorDrawers.cs.meta


fileFormatVersion: 2
guid: 7e9fe0ff34cd022408422f6b92ad7df6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

43
Packages/com.unity.render-pipelines.core/Editor/CoreEditorStyles.cs


using UnityEngine;
namespace UnityEditor.Rendering
{
public static class CoreEditorStyles
{
public static readonly GUIStyle smallTickbox;
public static readonly GUIStyle miniLabelButton;
static readonly Texture2D paneOptionsIconDark;
static readonly Texture2D paneOptionsIconLight;
public static Texture2D paneOptionsIcon { get { return EditorGUIUtility.isProSkin ? paneOptionsIconDark : paneOptionsIconLight; } }
static CoreEditorStyles()
{
smallTickbox = new GUIStyle("ShurikenToggle");
var transparentTexture = new Texture2D(1, 1, TextureFormat.ARGB32, false);
transparentTexture.SetPixel(0, 0, Color.clear);
transparentTexture.Apply();
miniLabelButton = new GUIStyle(EditorStyles.miniLabel);
miniLabelButton.normal = new GUIStyleState
{
background = transparentTexture,
scaledBackgrounds = null,
textColor = Color.grey
};
var activeState = new GUIStyleState
{
background = transparentTexture,
scaledBackgrounds = null,
textColor = Color.white
};
miniLabelButton.active = activeState;
miniLabelButton.onNormal = activeState;
miniLabelButton.onActive = activeState;
paneOptionsIconDark = (Texture2D)EditorGUIUtility.Load("Builtin Skins/DarkSkin/Images/pane options.png");
paneOptionsIconLight = (Texture2D)EditorGUIUtility.Load("Builtin Skins/LightSkin/Images/pane options.png");
}
}
}

11
Packages/com.unity.render-pipelines.core/Editor/CoreEditorStyles.cs.meta


fileFormatVersion: 2
guid: 106fd77ef6b30234597e56c849578bad
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

774
Packages/com.unity.render-pipelines.core/Editor/CoreEditorUtils.cs


using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using UnityEngine;
using UnityEngine.Rendering;
namespace UnityEditor.Rendering
{
using UnityObject = UnityEngine.Object;
public static class CoreEditorUtils
{
class Styles
{
static readonly Color k_Normal_AllTheme = new Color32(0, 0, 0, 0);
//static readonly Color k_Hover_Dark = new Color32(70, 70, 70, 255);
//static readonly Color k_Hover = new Color32(193, 193, 193, 255);
static readonly Color k_Active_Dark = new Color32(80, 80, 80, 255);
static readonly Color k_Active = new Color32(216, 216, 216, 255);
static readonly int s_MoreOptionsHash = "MoreOptions".GetHashCode();
static public GUIContent moreOptionsLabel { get; private set; }
static public GUIStyle moreOptionsStyle { get; private set; }
static public GUIStyle moreOptionsLabelStyle { get; private set; }
static Styles()
{
moreOptionsLabel = EditorGUIUtility.TrIconContent("MoreOptions", "More Options");
moreOptionsStyle = new GUIStyle(GUI.skin.toggle);
Texture2D normalColor = new Texture2D(1, 1);
normalColor.SetPixel(1, 1, k_Normal_AllTheme);
moreOptionsStyle.normal.background = normalColor;
moreOptionsStyle.onActive.background = normalColor;
moreOptionsStyle.onFocused.background = normalColor;
moreOptionsStyle.onNormal.background = normalColor;
moreOptionsStyle.onHover.background = normalColor;
moreOptionsStyle.active.background = normalColor;
moreOptionsStyle.focused.background = normalColor;
moreOptionsStyle.hover.background = normalColor;
moreOptionsLabelStyle = new GUIStyle(GUI.skin.label);
moreOptionsLabelStyle.padding = new RectOffset(0, 0, 0, -1);
}
//Note:
// - GUIStyle seams to be broken: all states have same state than normal light theme
// - Hover with event will not be updated right when we enter the rect
//-> Removing hover for now. Keep theme color for refactoring with UIElement later
static public bool DrawMoreOptions(Rect rect, bool active)
{
int id = GUIUtility.GetControlID(s_MoreOptionsHash, FocusType.Passive, rect);
var evt = Event.current;
switch (evt.type)
{
case EventType.Repaint:
Color background = k_Normal_AllTheme;
if (active)
background = EditorGUIUtility.isProSkin ? k_Active_Dark : k_Active;
EditorGUI.DrawRect(rect, background);
GUI.Label(rect, moreOptionsLabel, moreOptionsLabelStyle);
break;
case EventType.KeyDown:
bool anyModifiers = (evt.alt || evt.shift || evt.command || evt.control);
if ((evt.keyCode == KeyCode.Space || evt.keyCode == KeyCode.Return || evt.keyCode == KeyCode.KeypadEnter) && !anyModifiers && GUIUtility.keyboardControl == id)
{
evt.Use();
GUI.changed = true;
return !active;
}
break;
case EventType.MouseDown:
if (rect.Contains(evt.mousePosition))
{
GrabMouseControl(id);
evt.Use();
}
break;
case EventType.MouseUp:
if (HasMouseControl(id))
{
ReleaseMouseControl();
evt.Use();
if (rect.Contains(evt.mousePosition))
{
GUI.changed = true;
return !active;
}
}
break;
case EventType.MouseDrag:
if (HasMouseControl(id))
evt.Use();
break;
}
return active;
}
static int s_GrabbedID = -1;
static void GrabMouseControl(int id) => s_GrabbedID = id;
static void ReleaseMouseControl() => s_GrabbedID = -1;
static bool HasMouseControl(int id) => s_GrabbedID == id;
}
static GraphicsDeviceType[] m_BuildTargets;
public static GraphicsDeviceType[] buildTargets => m_BuildTargets ?? (m_BuildTargets = PlayerSettings.GetGraphicsAPIs(EditorUserBuildSettings.activeBuildTarget));
static CoreEditorUtils()
{
LoadSkinAndIconMethods();
}
// Serialization helpers
/// <summary>
/// To use with extreme caution. It not really get the property but try to find a field with similar name
/// Hence inheritance override of property is not supported.
/// Also variable rename will silently break the search.
/// </summary>
public static string FindProperty<T, TValue>(Expression<Func<T, TValue>> expr)
{
// Get the field path as a string
MemberExpression me;
switch (expr.Body.NodeType)
{
case ExpressionType.MemberAccess:
me = expr.Body as MemberExpression;
break;
default:
throw new InvalidOperationException();
}
var members = new List<string>();
while (me != null)
{
// For field, get the field name
// For properties, get the name of the backing field
var name = me.Member is FieldInfo
? me.Member.Name
: "m_" + me.Member.Name.Substring(0, 1).ToUpper() + me.Member.Name.Substring(1);
members.Add(name);
me = me.Expression as MemberExpression;
}
var sb = new StringBuilder();
for (int i = members.Count - 1; i >= 0; i--)
{
sb.Append(members[i]);
if (i > 0) sb.Append('.');
}
return sb.ToString();
}
// UI Helpers
public static void DrawFixMeBox(string text, Action action)
{
EditorGUILayout.HelpBox(text, MessageType.Warning);
GUILayout.Space(-32);
using (new EditorGUILayout.HorizontalScope())
{
GUILayout.FlexibleSpace();
if (GUILayout.Button("Fix", GUILayout.Width(60)))
action();
GUILayout.Space(8);
}
GUILayout.Space(11);
}
public static void DrawMultipleFields(string label, SerializedProperty[] ppts, GUIContent[] lbls)
=> DrawMultipleFields(EditorGUIUtility.TrTextContent(label), ppts, lbls);
public static void DrawMultipleFields(GUIContent label, SerializedProperty[] ppts, GUIContent[] lbls)
{
var labelWidth = EditorGUIUtility.labelWidth;
using (new EditorGUILayout.HorizontalScope())
{
EditorGUILayout.PrefixLabel(label);
using (new EditorGUILayout.VerticalScope())
{
EditorGUIUtility.labelWidth = 40;
EditorGUI.indentLevel--;
for (var i = 0; i < ppts.Length; ++i)
EditorGUILayout.PropertyField(ppts[i], lbls[i]);
EditorGUI.indentLevel++;
}
}
EditorGUIUtility.labelWidth = labelWidth;
}
public static void DrawSplitter(bool isBoxed = false)
{
var rect = GUILayoutUtility.GetRect(1f, 1f);
// Splitter rect should be full-width
rect.xMin = 0f;
rect.width += 4f;
if (isBoxed)
{
rect.xMin = EditorGUIUtility.singleLineHeight - 2;
rect.width -= 1;
}
if (Event.current.type != EventType.Repaint)
return;
EditorGUI.DrawRect(rect, !EditorGUIUtility.isProSkin
? new Color(0.6f, 0.6f, 0.6f, 1.333f)
: new Color(0.12f, 0.12f, 0.12f, 1.333f));
}
public static void DrawHeader(string title)
=> DrawHeader(EditorGUIUtility.TrTextContent(title));
public static void DrawHeader(GUIContent title)
{
var backgroundRect = GUILayoutUtility.GetRect(1f, 17f);
var labelRect = backgroundRect;
labelRect.xMin += 16f;
labelRect.xMax -= 20f;
var foldoutRect = backgroundRect;
foldoutRect.y += 1f;
foldoutRect.width = 13f;
foldoutRect.height = 13f;
// Background rect should be full-width
backgroundRect.xMin = 0f;
backgroundRect.width += 4f;
// Background
float backgroundTint = EditorGUIUtility.isProSkin ? 0.1f : 1f;
EditorGUI.DrawRect(backgroundRect, new Color(backgroundTint, backgroundTint, backgroundTint, 0.2f));
// Title
EditorGUI.LabelField(labelRect, title, EditorStyles.boldLabel);
}
/// <summary> Draw a foldout header </summary>
/// <param name="title"> The title of the header </param>
/// <param name="state"> The state of the header </param>
/// <param name="isBoxed"> [optional] is the eader contained in a box style ? </param>
/// <param name="hasMoreOptions"> [optional] Delegate used to draw the right state of the advanced button. If null, no button drawn. </param>
/// <param name="toggleMoreOption"> [optional] Callback call when advanced button clicked. Should be used to toggle its state. </param>
public static bool DrawHeaderFoldout(string title, bool state, bool isBoxed = false, Func<bool> hasMoreOptions = null, Action toggleMoreOption = null)
=> DrawHeaderFoldout(EditorGUIUtility.TrTextContent(title), state, isBoxed, hasMoreOptions, toggleMoreOption);
/// <summary> Draw a foldout header </summary>
/// <param name="title"> The title of the header </param>
/// <param name="state"> The state of the header </param>
/// <param name="isBoxed"> [optional] is the eader contained in a box style ? </param>
/// <param name="hasMoreOptions"> [optional] Delegate used to draw the right state of the advanced button. If null, no button drawn. </param>
/// <param name="toggleMoreOptions"> [optional] Callback call when advanced button clicked. Should be used to toggle its state. </param>
public static bool DrawHeaderFoldout(GUIContent title, bool state, bool isBoxed = false, Func<bool> hasMoreOptions = null, Action toggleMoreOptions = null)
{
const float height = 17f;
var backgroundRect = GUILayoutUtility.GetRect(1f, height);
var labelRect = backgroundRect;
labelRect.xMin += 16f;
labelRect.xMax -= 20f;
var foldoutRect = backgroundRect;
foldoutRect.y += 1f;
foldoutRect.width = 13f;
foldoutRect.height = 13f;
// More options 1/2
var moreOptionsRect = new Rect();
if (hasMoreOptions != null)
{
moreOptionsRect = backgroundRect;
moreOptionsRect.x += moreOptionsRect.width - 16 - 1;
moreOptionsRect.height = 15;
moreOptionsRect.width = 16;
}
// Background rect should be full-width
backgroundRect.xMin = 0f;
backgroundRect.width += 4f;
if (isBoxed)
{
labelRect.xMin += 5;
foldoutRect.xMin += 5;
backgroundRect.xMin = EditorGUIUtility.singleLineHeight;
backgroundRect.width -= 3;
}
// Background
float backgroundTint = EditorGUIUtility.isProSkin ? 0.1f : 1f;
EditorGUI.DrawRect(backgroundRect, new Color(backgroundTint, backgroundTint, backgroundTint, 0.2f));
// More options 2/2
if (hasMoreOptions != null)
{
EditorGUI.BeginChangeCheck();
Styles.DrawMoreOptions(moreOptionsRect, hasMoreOptions());
if (EditorGUI.EndChangeCheck() && toggleMoreOptions != null)
{
toggleMoreOptions();
}
}
// Title
EditorGUI.LabelField(labelRect, title, EditorStyles.boldLabel);
// Active checkbox
state = GUI.Toggle(foldoutRect, state, GUIContent.none, EditorStyles.foldout);
var e = Event.current;
if (e.type == EventType.MouseDown && backgroundRect.Contains(e.mousePosition) && !moreOptionsRect.Contains(e.mousePosition) && e.button == 0)
{
state = !state;
e.Use();
}
return state;
}
/// <summary> Draw a foldout header </summary>
/// <param name="title"> The title of the header </param>
/// <param name="state"> The state of the header </param>
/// <param name="isBoxed"> [optional] is the eader contained in a box style ? </param>
/// <param name="hasMoreOption"> [optional] Delegate used to draw the right state of the advanced button. If null, no button drawn. </param>
/// <param name="toggleMoreOptions"> [optional] Callback call when advanced button clicked. Should be used to toggle its state. </param>
public static bool DrawSubHeaderFoldout(string title, bool state, bool isBoxed = false, Func<bool> hasMoreOptions = null, Action toggleMoreOptions = null)
=> DrawSubHeaderFoldout(EditorGUIUtility.TrTextContent(title), state, isBoxed, hasMoreOptions, toggleMoreOptions);
/// <summary> Draw a foldout header </summary>
/// <param name="title"> The title of the header </param>
/// <param name="state"> The state of the header </param>
/// <param name="isBoxed"> [optional] is the eader contained in a box style ? </param>
/// <param name="hasMoreOptions"> [optional] Delegate used to draw the right state of the advanced button. If null, no button drawn. </param>
/// <param name="toggleMoreOptions"> [optional] Callback call when advanced button clicked. Should be used to toggle its state. </param>
public static bool DrawSubHeaderFoldout(GUIContent title, bool state, bool isBoxed = false, Func<bool> hasMoreOptions = null, Action toggleMoreOptions = null)
{
const float height = 17f;
var backgroundRect = GUILayoutUtility.GetRect(1f, height);
var labelRect = backgroundRect;
labelRect.xMin += 16f;
labelRect.xMax -= 20f;
var foldoutRect = backgroundRect;
foldoutRect.y += 1f;
foldoutRect.x += 15 * EditorGUI.indentLevel; //GUI do not handle indent. Handle it here
foldoutRect.width = 13f;
foldoutRect.height = 13f;
// More options
var advancedRect = new Rect();
if (hasMoreOptions != null)
{
advancedRect = backgroundRect;
advancedRect.x += advancedRect.width - 16 - 1;
advancedRect.height = 16;
advancedRect.width = 16;
bool moreOptions = hasMoreOptions();
bool newMoreOptions = Styles.DrawMoreOptions(advancedRect, moreOptions);
if (moreOptions ^ newMoreOptions)
toggleMoreOptions?.Invoke();
}
// Background rect should be full-width
backgroundRect.xMin = 0f;
backgroundRect.width += 4f;
if (isBoxed)
{
labelRect.xMin += 5;
foldoutRect.xMin += 5;
backgroundRect.xMin = EditorGUIUtility.singleLineHeight;
backgroundRect.width -= 3;
}
// Title
EditorGUI.LabelField(labelRect, title, EditorStyles.boldLabel);
// Active checkbox
state = GUI.Toggle(foldoutRect, state, GUIContent.none, EditorStyles.foldout);
var e = Event.current;
if (e.type == EventType.MouseDown && backgroundRect.Contains(e.mousePosition) && !advancedRect.Contains(e.mousePosition) && e.button == 0)
{
state = !state;
e.Use();
}
return state;
}
public static bool DrawHeaderToggle(string title, SerializedProperty group, SerializedProperty activeField, Action<Vector2> contextAction = null, Func<bool> hasMoreOptions = null, Action toggleMoreOptions = null)
=> DrawHeaderToggle(EditorGUIUtility.TrTextContent(title), group, activeField, contextAction, hasMoreOptions, toggleMoreOptions);
public static bool DrawHeaderToggle(GUIContent title, SerializedProperty group, SerializedProperty activeField, Action<Vector2> contextAction = null, Func<bool> hasMoreOptions = null, Action toggleMoreOptions = null)
{
var backgroundRect = GUILayoutUtility.GetRect(1f, 17f);
var labelRect = backgroundRect;
labelRect.xMin += 32f;
labelRect.xMax -= 20f + 16 + 5;
var foldoutRect = backgroundRect;
foldoutRect.y += 1f;
foldoutRect.width = 13f;
foldoutRect.height = 13f;
var toggleRect = backgroundRect;
toggleRect.x += 16f;
toggleRect.y += 2f;
toggleRect.width = 13f;
toggleRect.height = 13f;
// More options 1/2
var moreOptionsRect = new Rect();
if (hasMoreOptions != null)
{
moreOptionsRect = backgroundRect;
moreOptionsRect.x += moreOptionsRect.width - 16 - 1 - 16 - 5;
moreOptionsRect.height = 15;
moreOptionsRect.width = 16;
}
// Background rect should be full-width
backgroundRect.xMin = 0f;
backgroundRect.width += 4f;
// Background
float backgroundTint = EditorGUIUtility.isProSkin ? 0.1f : 1f;
EditorGUI.DrawRect(backgroundRect, new Color(backgroundTint, backgroundTint, backgroundTint, 0.2f));
// Title
using (new EditorGUI.DisabledScope(!activeField.boolValue))
EditorGUI.LabelField(labelRect, title, EditorStyles.boldLabel);
// Foldout
group.serializedObject.Update();
group.isExpanded = GUI.Toggle(foldoutRect, group.isExpanded, GUIContent.none, EditorStyles.foldout);
group.serializedObject.ApplyModifiedProperties();
// Active checkbox
activeField.serializedObject.Update();
activeField.boolValue = GUI.Toggle(toggleRect, activeField.boolValue, GUIContent.none, CoreEditorStyles.smallTickbox);
activeField.serializedObject.ApplyModifiedProperties();
// More options 2/2
if (hasMoreOptions != null)
{
bool moreOptions = hasMoreOptions();
bool newMoreOptions = Styles.DrawMoreOptions(moreOptionsRect, moreOptions);
if (moreOptions ^ newMoreOptions)
toggleMoreOptions?.Invoke();
}
// Context menu
var menuIcon = CoreEditorStyles.paneOptionsIcon;
var menuRect = new Rect(labelRect.xMax + 3f + 16 + 5 , labelRect.y + 1f, menuIcon.width, menuIcon.height);
if (contextAction != null)
GUI.DrawTexture(menuRect, menuIcon);
// Handle events
var e = Event.current;
if (e.type == EventType.MouseDown)
{
if (contextAction != null && menuRect.Contains(e.mousePosition))
{
contextAction(new Vector2(menuRect.x, menuRect.yMax));
e.Use();
}
else if (labelRect.Contains(e.mousePosition))
{
if (e.button == 0)
group.isExpanded = !group.isExpanded;
else if (contextAction != null)
contextAction(e.mousePosition);
e.Use();
}
}
return group.isExpanded;
}
static readonly GUIContent[] k_DrawVector6_Label =
{
new GUIContent("X"),
new GUIContent("Y"),
new GUIContent("Z"),
};
const int k_DrawVector6Slider_LabelSize = 60;
const int k_DrawVector6Slider_FieldSize = 80;
public static void DrawVector6(GUIContent label, SerializedProperty positive, SerializedProperty negative, Vector3 min, Vector3 max, Color[] colors = null, SerializedProperty multiplicator = null)
{
if (colors != null && (colors.Length != 6))
throw new System.ArgumentException("Colors must be a 6 element array. [+X, +Y, +X, -X, -Y, -Z]");
GUILayout.BeginVertical();
Rect rect = EditorGUI.IndentedRect(GUILayoutUtility.GetRect(0, float.MaxValue, EditorGUIUtility.singleLineHeight, EditorGUIUtility.singleLineHeight));
if (label != GUIContent.none)
{
var labelRect = rect;
labelRect.x -= 15f * EditorGUI.indentLevel;
labelRect.width = EditorGUIUtility.labelWidth;
EditorGUI.LabelField(labelRect, label);
rect.x += EditorGUIUtility.labelWidth - 1f - 15f * EditorGUI.indentLevel;
rect.width -= EditorGUIUtility.labelWidth - 1f - 15f * EditorGUI.indentLevel;
}
DrawVector3(rect, k_DrawVector6_Label, positive, min, max, false, colors == null ? null : new Color[] { colors[0], colors[1], colors[2] }, multiplicator);
GUILayout.Space(EditorGUIUtility.standardVerticalSpacing);
rect = EditorGUI.IndentedRect(GUILayoutUtility.GetRect(0, float.MaxValue, EditorGUIUtility.singleLineHeight, EditorGUIUtility.singleLineHeight));
rect.x += EditorGUIUtility.labelWidth - 1f - 15f * EditorGUI.indentLevel;
rect.width -= EditorGUIUtility.labelWidth - 1f - 15f * EditorGUI.indentLevel;
DrawVector3(rect, k_DrawVector6_Label, negative, min, max, true, colors == null ? null : new Color[] { colors[3], colors[4], colors[5] }, multiplicator);
GUILayout.EndVertical();
}
static void DrawVector3(Rect rect, GUIContent[] labels, SerializedProperty value, Vector3 min, Vector3 max, bool addMinusPrefix, Color[] colors, SerializedProperty multiplicator = null)
{
float[] multifloat = multiplicator == null
? new float[] { value.vector3Value.x, value.vector3Value.y, value.vector3Value.z }
: new float[] { value.vector3Value.x * multiplicator.vector3Value.x, value.vector3Value.y * multiplicator.vector3Value.y, value.vector3Value.z * multiplicator.vector3Value.z };
float fieldWidth = rect.width / 3f;
EditorGUI.showMixedValue = value.hasMultipleDifferentValues;
EditorGUI.BeginChangeCheck();
EditorGUI.MultiFloatField(rect, labels, multifloat);
if(EditorGUI.EndChangeCheck())
{
value.vector3Value = multiplicator == null
? new Vector3(
Mathf.Clamp(multifloat[0], min.x, max.x),
Mathf.Clamp(multifloat[1], min.y, max.y),
Mathf.Clamp(multifloat[2], min.z, max.z)
)
: new Vector3(
Mathf.Clamp((multiplicator.vector3Value.x < -0.00001 || 0.00001 < multiplicator.vector3Value.x) ? multifloat[0] / multiplicator.vector3Value.x : 0f, min.x, max.x),
Mathf.Clamp((multiplicator.vector3Value.y < -0.00001 || 0.00001 < multiplicator.vector3Value.y) ? multifloat[1] / multiplicator.vector3Value.y : 0f, min.y, max.y),
Mathf.Clamp((multiplicator.vector3Value.z < -0.00001 || 0.00001 < multiplicator.vector3Value.z) ? multifloat[2] / multiplicator.vector3Value.z : 0f, min.z, max.z)
);
}
EditorGUI.showMixedValue = false;
//Suffix is a hack as sublabel only work with 1 character
if (addMinusPrefix)
{
Rect suffixRect = new Rect(rect.x - 4 - 15 * EditorGUI.indentLevel, rect.y, 100, rect.height);
for(int i = 0; i < 3; ++i)
{
EditorGUI.LabelField(suffixRect, "-");
suffixRect.x += fieldWidth + .66f;
}
}
//Color is a hack as nothing is done to handle this at the moment
if(colors != null)
{
if (colors.Length != 3)
throw new System.ArgumentException("colors must have 3 elements.");
Rect suffixRect = new Rect(rect.x + 7 - 15 * EditorGUI.indentLevel, rect.y, 100, rect.height);
GUIStyle colorMark = new GUIStyle(EditorStyles.label);
colorMark.normal.textColor = colors[0];
EditorGUI.LabelField(suffixRect, "|", colorMark);
suffixRect.x += 1;
EditorGUI.LabelField(suffixRect, "|", colorMark);
suffixRect.x += fieldWidth - .5f;
colorMark.normal.textColor = colors[1];
EditorGUI.LabelField(suffixRect, "|", colorMark);
suffixRect.x += 1;
EditorGUI.LabelField(suffixRect, "|", colorMark);
suffixRect.x += fieldWidth;
colorMark.normal.textColor = colors[2];
EditorGUI.LabelField(suffixRect, "|", colorMark);
suffixRect.x += 1;
EditorGUI.LabelField(suffixRect, "|", colorMark);
}
}
public static void DrawPopup(GUIContent label, SerializedProperty property, string[] options)
{
var mode = property.intValue;
EditorGUI.BeginChangeCheck();
if (mode >= options.Length)
Debug.LogError(string.Format("Invalid option while trying to set {0}", label.text));
mode = EditorGUILayout.Popup(label, mode, options);
if (EditorGUI.EndChangeCheck())
property.intValue = mode;
}
/// <summary>
/// Draw an EnumPopup handling multiEdition
/// </summary>
/// <param name="property"></param>
/// <param name="type"></param>
/// <param name="label"></param>
public static void DrawEnumPopup(SerializedProperty property, System.Type type, GUIContent label = null)
{
EditorGUI.showMixedValue = property.hasMultipleDifferentValues;
EditorGUI.BeginChangeCheck();
var name = System.Enum.GetName(type, property.intValue);
var index = System.Array.FindIndex(System.Enum.GetNames(type), n => n == name);
var input = (System.Enum)System.Enum.GetValues(type).GetValue(index);
var rawResult = EditorGUILayout.EnumPopup(label ?? EditorGUIUtility.TrTextContent(ObjectNames.NicifyVariableName(property.name)), input);
var result = ((System.IConvertible)rawResult).ToInt32(System.Globalization.CultureInfo.CurrentCulture);
if (EditorGUI.EndChangeCheck())
property.intValue = result;
EditorGUI.showMixedValue = false;
}
public static void RemoveMaterialKeywords(Material material)
=> material.shaderKeywords = null;
public static T[] GetAdditionalData<T>(UnityEngine.Object[] targets, Action<T> initDefault = null)
where T : Component
{
// Handles multi-selection
var data = targets.Cast<Component>()
.Select(t => t.GetComponent<T>())
.ToArray();
for (int i = 0; i < data.Length; i++)
{
if (data[i] == null)
{
data[i] = Undo.AddComponent<T>(((Component)targets[i]).gameObject);
if (initDefault != null)
{
initDefault(data[i]);
}
}
}
return data;
}
static public GameObject CreateGameObject(GameObject parent, string name, params Type[] types)
=> ObjectFactory.CreateGameObject(GameObjectUtility.GetUniqueNameForSibling(parent != null ? parent.transform : null, name), types);
static public string GetCurrentProjectVersion()
{
string[] readText = File.ReadAllLines("ProjectSettings/ProjectVersion.txt");
// format is m_EditorVersion: 2018.2.0b7
string[] versionText = readText[0].Split(' ');
return versionText[1];
}
static public void CheckOutFile(bool VCSEnabled, UnityObject mat)
{
if (VCSEnabled)
{
UnityEditor.VersionControl.Task task = UnityEditor.VersionControl.Provider.Checkout(mat, UnityEditor.VersionControl.CheckoutMode.Both);
if (!task.success)
{
Debug.Log(task.text + " " + task.resultCode);
}
}
}
#region IconAndSkin
internal enum Skin
{
Auto,
Personnal,
Professional,
}
static Func<int> GetInternalSkinIndex;
static Func<float> GetGUIStatePixelsPerPoint;
static Func<Texture2D, float> GetTexturePixelPerPoint;
static Action<Texture2D, float> SetTexturePixelPerPoint;
static void LoadSkinAndIconMethods()
{
var internalSkinIndexInfo = typeof(EditorGUIUtility).GetProperty("skinIndex", BindingFlags.NonPublic | BindingFlags.Static);
var internalSkinIndexLambda = Expression.Lambda<Func<int>>(Expression.Property(null, internalSkinIndexInfo));
GetInternalSkinIndex = internalSkinIndexLambda.Compile();
var guiStatePixelsPerPointInfo = typeof(GUIUtility).GetProperty("pixelsPerPoint", BindingFlags.NonPublic | BindingFlags.Static);
var guiStatePixelsPerPointLambda = Expression.Lambda<Func<float>>(Expression.Property(null, guiStatePixelsPerPointInfo));
GetGUIStatePixelsPerPoint = guiStatePixelsPerPointLambda.Compile();
var pixelPerPointParam = Expression.Parameter(typeof(float), "pixelPerPoint");
var texture2DProperty = Expression.Parameter(typeof(Texture2D), "texture2D");
var texture2DPixelsPerPointInfo = typeof(Texture2D).GetProperty("pixelsPerPoint", BindingFlags.NonPublic | BindingFlags.Instance);
var texture2DPixelsPerPointProperty = Expression.Property(texture2DProperty, texture2DPixelsPerPointInfo);
var texture2DGetPixelsPerPointLambda = Expression.Lambda<Func<Texture2D, float>>(texture2DPixelsPerPointProperty, texture2DProperty);
GetTexturePixelPerPoint = texture2DGetPixelsPerPointLambda.Compile();
var texture2DSetPixelsPerPointLambda = Expression.Lambda<Action<Texture2D, float>>(Expression.Assign(texture2DPixelsPerPointProperty, pixelPerPointParam), texture2DProperty, pixelPerPointParam);
SetTexturePixelPerPoint = texture2DSetPixelsPerPointLambda.Compile();
}
/// <summary>Get the skin currently in use</summary>
static Skin currentSkin
=> GetInternalSkinIndex() == 0 ? Skin.Personnal : Skin.Professional;
// /!\ UIElement do not support well pixel per point at the moment. For this, use the hack forceLowRes
/// <summary>
/// Load an icon regarding skin and editor resolution.
/// Icon should be stored as legacy icon resources:
/// - "d_" prefix for Professional theme
/// - "@2x" suffix for high resolution
/// </summary>
/// <param name="path">Path to seek the icon from Assets/ folder</param>
/// <param name="name">Icon name without suffix, prefix or extention</param>
/// <param name="extention">[Optional] Extention of file (png per default)</param>
/// <param name="skin">[Optional] Load icon for this skin (Auto per default take current skin)</param>
public static Texture2D LoadIcon(string path, string name, string extention = ".png", bool forceLowRes = false)
{
if (String.IsNullOrEmpty(path) || String.IsNullOrEmpty(name))
return null;
string prefix = "";
var skin = currentSkin;
if (skin == Skin.Professional)
prefix = "d_";
Texture2D icon = null;
float pixelsPerPoint = GetGUIStatePixelsPerPoint();
if (pixelsPerPoint > 1.0f && !forceLowRes)
{
icon = EditorGUIUtility.Load(String.Format("{0}/{1}{2}@2x{3}", path, prefix, name, extention)) as Texture2D;
if (icon == null && !string.IsNullOrEmpty(prefix))
icon = EditorGUIUtility.Load(String.Format("{0}/{1}@2x{2}", path, name, extention)) as Texture2D;
if (icon != null)
SetTexturePixelPerPoint(icon, 2.0f);
}
if (icon == null)
icon = EditorGUIUtility.Load(String.Format("{0}/{1}{2}{3}", path, prefix, name, extention)) as Texture2D;
if (icon == null && !string.IsNullOrEmpty(prefix))
icon = EditorGUIUtility.Load(String.Format("{0}/{1}{2}", path, name, extention)) as Texture2D;
if (icon != null &&
!Mathf.Approximately(GetTexturePixelPerPoint(icon), pixelsPerPoint) && //scaling are different
!Mathf.Approximately(pixelsPerPoint % 1, 0)) //screen scaling is non-integer
{
icon.filterMode = FilterMode.Bilinear;
}
return icon;
}
#endregion
}
}

13
Packages/com.unity.render-pipelines.core/Editor/CoreEditorUtils.cs.meta


fileFormatVersion: 2
guid: 744ceabda269e6c469964dda8c490d0d
timeCreated: 1507109827
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

8
Packages/com.unity.render-pipelines.core/Editor/Debugging.meta


fileFormatVersion: 2
guid: dca094a63ee8e4df7b043d29c2c100e4
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

106
Packages/com.unity.render-pipelines.core/Editor/Debugging/DebugState.cs


using System;
using UnityEngine;
using UnityEngine.Rendering;
namespace UnityEditor.Rendering
{
[Serializable]
public abstract class DebugState : ScriptableObject
{
[SerializeField]
protected string m_QueryPath;
// We need this to keep track of the state modified in the current frame.
// This helps reduces the cost of re-applying states to original widgets and is also needed
// when two states point to the same value (e.g. when using split enums like HDRP does for
// the `fullscreenDebugMode`.
internal static DebugState m_CurrentDirtyState;
public string queryPath
{
get { return m_QueryPath; }
internal set { m_QueryPath = value; }
}
public abstract object GetValue();
public abstract void SetValue(object value, DebugUI.IValueField field);
public virtual void OnEnable()
{
hideFlags = HideFlags.HideAndDontSave;
}
}
[Serializable]
public class DebugState<T> : DebugState
{
[SerializeField]
protected T m_Value;
public virtual T value
{
get { return m_Value; }
set { m_Value = value; }
}
public override object GetValue()
{
return value;
}
public override void SetValue(object value, DebugUI.IValueField field)
{
this.value = (T)field.ValidateValue(value);
}
public override int GetHashCode()
{
unchecked
{
int hash = 13;
hash = hash * 23 + m_QueryPath.GetHashCode();
hash = hash * 23 + m_Value.GetHashCode();
return hash;
}
}
}
public sealed class DebugStateAttribute : Attribute
{
public readonly Type[] types;
public DebugStateAttribute(params Type[] types)
{
this.types = types;
}
}
// Builtins
[Serializable, DebugState(typeof(DebugUI.BoolField), typeof(DebugUI.Foldout), typeof(DebugUI.HistoryBoolField))]
public sealed class DebugStateBool : DebugState<bool> {}
[Serializable, DebugState(typeof(DebugUI.IntField), typeof(DebugUI.EnumField), typeof(DebugUI.HistoryEnumField))]
public sealed class DebugStateInt : DebugState<int> {}
[Serializable, DebugState(typeof(DebugUI.BitField))]
public sealed class DebugStateFlags : DebugState<Enum> { }
[Serializable, DebugState(typeof(DebugUI.UIntField))]
public sealed class DebugStateUInt : DebugState<uint> {}
[Serializable, DebugState(typeof(DebugUI.FloatField))]
public sealed class DebugStateFloat : DebugState<float> {}
[Serializable, DebugState(typeof(DebugUI.ColorField))]
public sealed class DebugStateColor : DebugState<Color> {}
[Serializable, DebugState(typeof(DebugUI.Vector2Field))]
public sealed class DebugStateVector2 : DebugState<Vector2> {}
[Serializable, DebugState(typeof(DebugUI.Vector3Field))]
public sealed class DebugStateVector3 : DebugState<Vector3> {}
[Serializable, DebugState(typeof(DebugUI.Vector4Field))]
public sealed class DebugStateVector4 : DebugState<Vector4> {}
}

11
Packages/com.unity.render-pipelines.core/Editor/Debugging/DebugState.cs.meta


fileFormatVersion: 2
guid: 7270f3dffc138834da5642e5943f5072
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

457
Packages/com.unity.render-pipelines.core/Editor/Debugging/DebugUIDrawer.Builtins.cs


using System;
using UnityEngine;
using UnityEngine.Rendering;
namespace UnityEditor.Rendering
{
[DebugUIDrawer(typeof(DebugUI.Value))]
public sealed class DebugUIDrawerValue : DebugUIDrawer
{
public override bool OnGUI(DebugUI.Widget widget, DebugState state)
{
var w = Cast<DebugUI.Value>(widget);
var rect = PrepareControlRect();
EditorGUI.LabelField(rect, EditorGUIUtility.TrTextContent(w.displayName), EditorGUIUtility.TrTextContent(w.GetValue().ToString()));
return true;
}
}
[DebugUIDrawer(typeof(DebugUI.Button))]
public sealed class DebugUIDrawerButton : DebugUIDrawer
{
public override bool OnGUI(DebugUI.Widget widget, DebugState state)
{
var w = Cast<DebugUI.Button>(widget);
var rect = EditorGUI.IndentedRect(EditorGUILayout.GetControlRect());
if (GUI.Button(rect, w.displayName, EditorStyles.miniButton))
{
if (w.action != null)
w.action();
}
return true;
}
}
[DebugUIDrawer(typeof(DebugUI.BoolField))]
public sealed class DebugUIDrawerBoolField : DebugUIDrawer
{
public override bool OnGUI(DebugUI.Widget widget, DebugState state)
{
var w = Cast<DebugUI.BoolField>(widget);
var s = Cast<DebugStateBool>(state);
EditorGUI.BeginChangeCheck();
var rect = PrepareControlRect();
bool value = EditorGUI.Toggle(rect, EditorGUIUtility.TrTextContent(w.displayName), w.GetValue());
if (EditorGUI.EndChangeCheck())
Apply(w, s, value);
return true;
}
}
[DebugUIDrawer(typeof(DebugUI.HistoryBoolField))]
public sealed class DebugUIDrawerHistoryBoolField : DebugUIDrawer
{
public override bool OnGUI(DebugUI.Widget widget, DebugState state)
{
var w = Cast<DebugUI.HistoryBoolField>(widget);
var s = Cast<DebugStateBool>(state);
EditorGUI.BeginChangeCheck();
var rect = PrepareControlRect();
var labelRect = rect;
labelRect.width = EditorGUIUtility.labelWidth;
const int oneValueWidth = 70;
var valueRects = new Rect[w.historyDepth + 1];
for(int i = 0; i < w.historyDepth + 1; i++)
{
valueRects[i] = rect;
valueRects[i].x += EditorGUIUtility.labelWidth + i * oneValueWidth;
valueRects[i].width = oneValueWidth;
}
EditorGUI.LabelField(labelRect, EditorGUIUtility.TrTextContent(w.displayName));
int indent = EditorGUI.indentLevel;
EditorGUI.indentLevel = 0; //be at left of rects
bool value = EditorGUI.Toggle(valueRects[0], w.GetValue());
using (new EditorGUI.DisabledScope(true))
{
for (int i = 0; i < w.historyDepth; i++)
EditorGUI.Toggle(valueRects[i + 1], w.GetHistoryValue(i));
}
EditorGUI.indentLevel = indent;
if (EditorGUI.EndChangeCheck())
Apply(w, s, value);
return true;
}
}
[DebugUIDrawer(typeof(DebugUI.IntField))]
public sealed class DebugUIDrawerIntField : DebugUIDrawer
{
public override bool OnGUI(DebugUI.Widget widget, DebugState state)
{
var w = Cast<DebugUI.IntField>(widget);
var s = Cast<DebugStateInt>(state);
EditorGUI.BeginChangeCheck();
var rect = PrepareControlRect();
int value = w.min != null && w.max != null
? EditorGUI.IntSlider(rect, EditorGUIUtility.TrTextContent(w.displayName), w.GetValue(), w.min(), w.max())
: EditorGUI.IntField(rect, EditorGUIUtility.TrTextContent(w.displayName), w.GetValue());
if (EditorGUI.EndChangeCheck())
Apply(w, s, value);
return true;
}
}
[DebugUIDrawer(typeof(DebugUI.UIntField))]
public sealed class DebugUIDrawerUIntField : DebugUIDrawer
{
public override bool OnGUI(DebugUI.Widget widget, DebugState state)
{
var w = Cast<DebugUI.UIntField>(widget);
var s = Cast<DebugStateUInt>(state);
EditorGUI.BeginChangeCheck();
// No UIntField so we need to max to 0 ourselves or the value will wrap around
var rect = PrepareControlRect();
int tmp = w.min != null && w.max != null
? EditorGUI.IntSlider(rect, EditorGUIUtility.TrTextContent(w.displayName), Mathf.Max(0, (int)w.GetValue()), Mathf.Max(0, (int)w.min()), Mathf.Max(0, (int)w.max()))
: EditorGUI.IntField(rect, EditorGUIUtility.TrTextContent(w.displayName), Mathf.Max(0, (int)w.GetValue()));
uint value = (uint)Mathf.Max(0, tmp);
if (EditorGUI.EndChangeCheck())
Apply(w, s, value);
return true;
}
}
[DebugUIDrawer(typeof(DebugUI.FloatField))]
public sealed class DebugUIDrawerFloatField : DebugUIDrawer
{
public override bool OnGUI(DebugUI.Widget widget, DebugState state)
{
var w = Cast<DebugUI.FloatField>(widget);
var s = Cast<DebugStateFloat>(state);
EditorGUI.BeginChangeCheck();
var rect = PrepareControlRect();
float value = w.min != null && w.max != null
? EditorGUI.Slider(rect, EditorGUIUtility.TrTextContent(w.displayName), w.GetValue(), w.min(), w.max())
: EditorGUI.FloatField(rect, EditorGUIUtility.TrTextContent(w.displayName), w.GetValue());
if (EditorGUI.EndChangeCheck())
Apply(w, s, value);
return true;
}
}
[DebugUIDrawer(typeof(DebugUI.EnumField))]
public sealed class DebugUIDrawerEnumField : DebugUIDrawer
{
public override bool OnGUI(DebugUI.Widget widget, DebugState state)
{
var w = Cast<DebugUI.EnumField>(widget);
var s = Cast<DebugStateInt>(state);
if (w.indexes == null)
w.InitIndexes();
EditorGUI.BeginChangeCheck();
int index = -1;
int value = w.GetValue();
if (w.enumNames == null || w.enumValues == null)
{
EditorGUILayout.LabelField("Can't draw an empty enumeration.");
}
else
{
var rect = PrepareControlRect();
index = w.currentIndex;
// Fallback just in case, we may be handling sub/sectionned enums here
if (index < 0)
index = 0;
index = EditorGUI.IntPopup(rect, EditorGUIUtility.TrTextContent(w.displayName), index, w.enumNames, w.indexes);
value = w.enumValues[index];
}
if (EditorGUI.EndChangeCheck())
{
Apply(w, s, value);
if (index > -1)
w.currentIndex = index;
}
return true;
}
}
[DebugUIDrawer(typeof(DebugUI.HistoryEnumField))]
public sealed class DebugUIDrawerHistoryEnumField : DebugUIDrawer
{
public override bool OnGUI(DebugUI.Widget widget, DebugState state)
{
var w = Cast<DebugUI.HistoryEnumField>(widget);
var s = Cast<DebugStateInt>(state);
if (w.indexes == null)
w.InitIndexes();
EditorGUI.BeginChangeCheck();
int index = -1;
int value = w.GetValue();
if (w.enumNames == null || w.enumValues == null)
{
EditorGUILayout.LabelField("Can't draw an empty enumeration.");
}
else
{
var rect = PrepareControlRect();
index = w.currentIndex;
// Fallback just in case, we may be handling sub/sectionned enums here
if (index < 0)
index = 0;
var labelRect = rect;
labelRect.width = EditorGUIUtility.labelWidth;
const int oneValueWidth = 70;
var valueRects = new Rect[w.historyDepth + 1];
for (int i = 0; i < w.historyDepth + 1; i++)
{
valueRects[i] = rect;
valueRects[i].x += EditorGUIUtility.labelWidth + i * oneValueWidth;
valueRects[i].width = oneValueWidth;
}
EditorGUI.LabelField(labelRect, EditorGUIUtility.TrTextContent(w.displayName));
int indent = EditorGUI.indentLevel;
EditorGUI.indentLevel = 0; //be at left of rects
index = EditorGUI.IntPopup(valueRects[0], index, w.enumNames, w.indexes);
value = w.enumValues[index];
using (new EditorGUI.DisabledScope(true))
{
for (int i = 0; i < w.historyDepth; i++)
EditorGUI.IntPopup(valueRects[i + 1], w.GetHistoryValue(i), w.enumNames, w.indexes);
}
EditorGUI.indentLevel = indent;
}
if (EditorGUI.EndChangeCheck())
{
Apply(w, s, value);
if (index > -1)
w.currentIndex = index;
}
return true;
}
}
[DebugUIDrawer(typeof(DebugUI.BitField))]
public sealed class DebugUIDrawerBitField : DebugUIDrawer
{
public override bool OnGUI(DebugUI.Widget widget, DebugState state)
{
var w = Cast<DebugUI.BitField>(widget);
var s = Cast<DebugStateFlags>(state);
EditorGUI.BeginChangeCheck();
Enum value = w.GetValue();
var rect = PrepareControlRect();
value = EditorGUI.EnumFlagsField(rect, EditorGUIUtility.TrTextContent(w.displayName), value);
if (EditorGUI.EndChangeCheck())
Apply(w, s, value);
return true;
}
}
[DebugUIDrawer(typeof(DebugUI.Foldout))]
public sealed class DebugUIDrawerFoldout : DebugUIDrawer
{
public override void Begin(DebugUI.Widget widget, DebugState state)
{
var w = Cast<DebugUI.Foldout>(widget);
var s = Cast<DebugStateBool>(state);
EditorGUI.BeginChangeCheck();
Rect rect = PrepareControlRect();
bool value = EditorGUI.Foldout(rect, w.GetValue(), EditorGUIUtility.TrTextContent(w.displayName), true);
Rect drawRect = GUILayoutUtility.GetLastRect();
if (w.columnLabels != null && value)
{
const int oneColumnWidth = 70;
int indent = EditorGUI.indentLevel;
EditorGUI.indentLevel = 0; //be at left of rects
for (int i = 0; i < w.columnLabels.Length; i++)
{
var columnRect = drawRect;
columnRect.x += EditorGUIUtility.labelWidth + i * oneColumnWidth;
columnRect.width = oneColumnWidth;
EditorGUI.LabelField(columnRect, w.columnLabels[i] ?? "", EditorStyles.miniBoldLabel);
}
EditorGUI.indentLevel = indent;
}
if (EditorGUI.EndChangeCheck())
Apply(w, s, value);
EditorGUI.indentLevel++;
}
public override bool OnGUI(DebugUI.Widget node, DebugState state)
{
var s = Cast<DebugStateBool>(state);
return s.value;
}
public override void End(DebugUI.Widget node, DebugState state)
{
EditorGUI.indentLevel--;
}
}
[DebugUIDrawer(typeof(DebugUI.ColorField))]
public sealed class DebugUIDrawerColorField : DebugUIDrawer
{
public override bool OnGUI(DebugUI.Widget widget, DebugState state)
{
var w = Cast<DebugUI.ColorField>(widget);
var s = Cast<DebugStateColor>(state);
EditorGUI.BeginChangeCheck();
var rect = PrepareControlRect();
var value = EditorGUI.ColorField(rect, EditorGUIUtility.TrTextContent(w.displayName), w.GetValue(), w.showPicker, w.showAlpha, w.hdr);
if (EditorGUI.EndChangeCheck())
Apply(w, s, value);
return true;
}
}
[DebugUIDrawer(typeof(DebugUI.Vector2Field))]
public sealed class DebugUIDrawerVector2Field : DebugUIDrawer
{
public override bool OnGUI(DebugUI.Widget widget, DebugState state)
{
var w = Cast<DebugUI.Vector2Field>(widget);
var s = Cast<DebugStateVector2>(state);
EditorGUI.BeginChangeCheck();
var value = EditorGUILayout.Vector2Field(w.displayName, w.GetValue());
if (EditorGUI.EndChangeCheck())
Apply(w, s, value);
return true;
}
}
[DebugUIDrawer(typeof(DebugUI.Vector3Field))]
public sealed class DebugUIDrawerVector3Field : DebugUIDrawer
{
public override bool OnGUI(DebugUI.Widget widget, DebugState state)
{
var w = Cast<DebugUI.Vector3Field>(widget);
var s = Cast<DebugStateVector3>(state);
EditorGUI.BeginChangeCheck();
var value = EditorGUILayout.Vector3Field(w.displayName, w.GetValue());
if (EditorGUI.EndChangeCheck())
Apply(w, s, value);
return true;
}
}
[DebugUIDrawer(typeof(DebugUI.Vector4Field))]
public sealed class DebugUIDrawerVector4Field : DebugUIDrawer
{
public override bool OnGUI(DebugUI.Widget widget, DebugState state)
{
var w = Cast<DebugUI.Vector4Field>(widget);
var s = Cast<DebugStateVector4>(state);
EditorGUI.BeginChangeCheck();
var value = EditorGUILayout.Vector4Field(w.displayName, w.GetValue());
if (EditorGUI.EndChangeCheck())
Apply(w, s, value);
return true;
}
}
[DebugUIDrawer(typeof(DebugUI.Container))]
public sealed class DebugUIDrawerContainer : DebugUIDrawer
{
public override void Begin(DebugUI.Widget widget, DebugState state)
{
if (!string.IsNullOrEmpty(widget.displayName))
EditorGUILayout.LabelField(widget.displayName, EditorStyles.boldLabel);
EditorGUI.indentLevel++;
}
public override void End(DebugUI.Widget widget, DebugState state)
{
EditorGUI.indentLevel--;
}
}
[DebugUIDrawer(typeof(DebugUI.HBox))]
public sealed class DebugUIDrawerHBox : DebugUIDrawer
{
public override void Begin(DebugUI.Widget widget, DebugState state)
{
EditorGUILayout.BeginHorizontal();
}
public override void End(DebugUI.Widget widget, DebugState state)
{
EditorGUILayout.EndHorizontal();
}
}
[DebugUIDrawer(typeof(DebugUI.VBox))]
public sealed class DebugUIDrawerVBox : DebugUIDrawer
{
public override void Begin(DebugUI.Widget widget, DebugState state)
{
EditorGUILayout.BeginVertical();
}
public override void End(DebugUI.Widget widget, DebugState state)
{
EditorGUILayout.EndVertical();
}
}
}

11
Packages/com.unity.render-pipelines.core/Editor/Debugging/DebugUIDrawer.Builtins.cs.meta


fileFormatVersion: 2
guid: 7f9e548e5e2920b47987881c21171ef6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

63
Packages/com.unity.render-pipelines.core/Editor/Debugging/DebugUIDrawer.cs


using System;
using UnityEngine;
using UnityEngine.Rendering;
namespace UnityEditor.Rendering
{
public class DebugUIDrawerAttribute : Attribute
{
public readonly Type type;
public DebugUIDrawerAttribute(Type type)
{
this.type = type;
}
}
public class DebugUIDrawer
{
protected T Cast<T>(object o)
where T : class
{
var casted = o as T;
string typeName = o == null ? "null" : o.GetType().ToString();
if (casted == null)
throw new InvalidOperationException("Can't cast " + typeName + " to " + typeof(T));
return casted;
}
public virtual void Begin(DebugUI.Widget widget, DebugState state)
{}
public virtual bool OnGUI(DebugUI.Widget widget, DebugState state)
{
return true;
}
public virtual void End(DebugUI.Widget widget, DebugState state)
{}
protected void Apply(DebugUI.IValueField widget, DebugState state, object value)
{
Undo.RegisterCompleteObjectUndo(state, "Debug Property Change");
state.SetValue(value, widget);
widget.SetValue(value);
EditorUtility.SetDirty(state);
DebugState.m_CurrentDirtyState = state;
UnityEditorInternal.InternalEditorUtility.RepaintAllViews();
}
protected Rect PrepareControlRect(float height = -1)
{
if (height < 0)
height = EditorGUIUtility.singleLineHeight;
var rect = GUILayoutUtility.GetRect(1f, 1f, height, height);
rect.width -= 2f;
rect.xMin += 2f;
EditorGUIUtility.labelWidth = rect.width / 2f;
return rect;
}
}
}

11
Packages/com.unity.render-pipelines.core/Editor/Debugging/DebugUIDrawer.cs.meta


fileFormatVersion: 2
guid: 3581d3ff7a65eee458feb865b7f29154
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

79
Packages/com.unity.render-pipelines.core/Editor/Debugging/DebugUIHandlerCanvasEditor.cs


using System;
using System.Linq;
using UnityEditor;
using UnityEditor.Rendering;
using UnityEditorInternal;
using UnityEngine.Rendering;
namespace UnityEngine.Rendering.UI
{
[CustomEditor(typeof(DebugUIHandlerCanvas))]
public sealed class DebugUIHandlerCanvasEditor : Editor
{
SerializedProperty m_PanelPrefab;
SerializedProperty m_Prefabs;
ReorderableList m_PrefabList;
static string[] s_Types; // Assembly qualified names
static string[] s_DisplayTypes; // Pretty names
static DebugUIHandlerCanvasEditor()
{
s_Types = CoreUtils.GetAllTypesDerivedFrom<DebugUI.Widget>()
.Where(t => !t.IsAbstract)
.Select(t => t.AssemblyQualifiedName)
.ToArray();
s_DisplayTypes = new string[s_Types.Length];
for (int i = 0; i < s_Types.Length; i++)
s_DisplayTypes[i] = Type.GetType(s_Types[i]).Name;
}
void OnEnable()
{
var o = new PropertyFetcher<DebugUIHandlerCanvas>(serializedObject);
m_PanelPrefab = o.Find(x => x.panelPrefab);
m_Prefabs = o.Find(x => x.prefabs);
m_PrefabList = new ReorderableList(serializedObject, m_Prefabs, true, true, true, true)
{
drawHeaderCallback = rect => EditorGUI.LabelField(rect, "Widget Prefabs"),
drawElementCallback = (rect, index, isActive, isFocused) =>
{
var element = m_PrefabList.serializedProperty.GetArrayElementAtIndex(index);
rect.y += 2f;
const float kTypeWidth = 100f;
// Type selector
var typeProp = element.FindPropertyRelative("type");
int typeIndex = ArrayUtility.IndexOf(s_Types, typeProp.stringValue);
typeIndex = Mathf.Max(typeIndex, 0);
typeIndex = EditorGUI.Popup(new Rect(rect.x, rect.y, kTypeWidth, EditorGUIUtility.singleLineHeight), typeIndex, s_DisplayTypes);
typeProp.stringValue = s_Types[typeIndex];
// Prefab
EditorGUI.PropertyField(
new Rect(rect.x + kTypeWidth + 2f, rect.y, rect.width - kTypeWidth - 2f, EditorGUIUtility.singleLineHeight),
element.FindPropertyRelative("prefab"), GUIContent.none);
},
onSelectCallback = list =>
{
var prefab = list.serializedProperty.GetArrayElementAtIndex(list.index).FindPropertyRelative("prefab").objectReferenceValue as GameObject;
if (prefab)
EditorGUIUtility.PingObject(prefab.gameObject);
}
};
}
public override void OnInspectorGUI()
{
serializedObject.Update();
EditorGUILayout.PropertyField(m_PanelPrefab);
EditorGUILayout.Space();
m_PrefabList.DoLayoutList();
serializedObject.ApplyModifiedProperties();
}
}
}

11
Packages/com.unity.render-pipelines.core/Editor/Debugging/DebugUIHandlerCanvasEditor.cs.meta


fileFormatVersion: 2
guid: f1d41f85c15ac6048a850b51ff36c098
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

580
Packages/com.unity.render-pipelines.core/Editor/Debugging/DebugWindow.cs


using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor.Callbacks;
using UnityEngine;
using UnityEngine.Rendering;
namespace UnityEditor.Rendering
{
#pragma warning disable 414
[Serializable]
sealed class WidgetStateDictionary : SerializedDictionary<string, DebugState> {}
sealed class DebugWindowSettings : ScriptableObject
{
// Keep these settings in a separate scriptable object so we can handle undo/redo on them
// without the rest of the debug window interfering
public int currentStateHash;
public int selectedPanel;
void OnEnable()
{
hideFlags = HideFlags.HideAndDontSave;
}
}
public sealed class DebugWindow : EditorWindow
{
static readonly GUIContent k_ResetButtonContent = new GUIContent("Reset");
//static readonly GUIContent k_SaveButtonContent = new GUIContent("Save");
//static readonly GUIContent k_LoadButtonContent = new GUIContent("Load");
//static bool isMultiview = false;
static Styles s_Styles;
static GUIStyle s_SplitterLeft;
static float splitterPos = 150f;
const float minSideBarWidth = 100;
const float minContentWidth = 100;
bool dragging = false;
[SerializeField]
WidgetStateDictionary m_WidgetStates;
[SerializeField]
DebugWindowSettings m_Settings;
[SerializeField]
int m_DebugTreeState;
bool m_IsDirty;
Vector2 m_PanelScroll;
Vector2 m_ContentScroll;
static bool s_TypeMapDirty;
static Dictionary<Type, Type> s_WidgetStateMap; // DebugUI.Widget type -> DebugState type
static Dictionary<Type, DebugUIDrawer> s_WidgetDrawerMap; // DebugUI.Widget type -> DebugUIDrawer
static bool s_Open;
public static bool open
{
get => s_Open;
private set
{
if (s_Open ^ value)
OnDebugWindowToggled?.Invoke(value);
s_Open = value;
}
}
static event Action<bool> OnDebugWindowToggled;
[DidReloadScripts]
static void OnEditorReload()
{
s_TypeMapDirty = true;
//find if it where open, relink static event end propagate the info
open = (Resources.FindObjectsOfTypeAll<DebugWindow>()?.Length ?? 0) > 0;
if (OnDebugWindowToggled == null)
OnDebugWindowToggled += DebugManager.instance.ToggleEditorUI;
DebugManager.instance.ToggleEditorUI(open);
}
static void RebuildTypeMaps()
{
// Map states to widget (a single state can map to several widget types if the value to
// serialize is the same)
var attrType = typeof(DebugStateAttribute);
var stateTypes = CoreUtils.GetAllTypesDerivedFrom<DebugState>()
.Where(
t => t.IsDefined(attrType, false)
&& !t.IsAbstract
);
s_WidgetStateMap = new Dictionary<Type, Type>();
foreach (var stateType in stateTypes)
{
var attr = (DebugStateAttribute)stateType.GetCustomAttributes(attrType, false)[0];
foreach (var t in attr.types)
s_WidgetStateMap.Add(t, stateType);
}
// Drawers
attrType = typeof(DebugUIDrawerAttribute);
var types = CoreUtils.GetAllTypesDerivedFrom<DebugUIDrawer>()
.Where(
t => t.IsDefined(attrType, false)
&& !t.IsAbstract
);
s_WidgetDrawerMap = new Dictionary<Type, DebugUIDrawer>();
foreach (var t in types)
{
var attr = (DebugUIDrawerAttribute)t.GetCustomAttributes(attrType, false)[0];
var inst = (DebugUIDrawer)Activator.CreateInstance(t);
s_WidgetDrawerMap.Add(attr.type, inst);
}
// Done
s_TypeMapDirty = false;
}
[MenuItem("Window/Render Pipeline/Render Pipeline Debug", priority = 10005)] // 112 is hardcoded number given by the UxTeam to fit correctly in the Windows menu
static void Init()
{
var window = GetWindow<DebugWindow>();
window.titleContent = new GUIContent("Debug");
if(OnDebugWindowToggled == null)
OnDebugWindowToggled += DebugManager.instance.ToggleEditorUI;
open = true;
}
void OnEnable()
{
DebugManager.instance.refreshEditorRequested = false;
hideFlags = HideFlags.HideAndDontSave;
autoRepaintOnSceneChange = true;
if (m_Settings == null)
m_Settings = CreateInstance<DebugWindowSettings>();
// States are ScriptableObjects (necessary for Undo/Redo) but are not saved on disk so when the editor is closed then reopened, any existing debug window will have its states set to null
// Since we don't care about persistance in this case, we just re-init everything.
if (m_WidgetStates == null || !AreWidgetStatesValid())
m_WidgetStates = new WidgetStateDictionary();
if (s_WidgetStateMap == null || s_WidgetDrawerMap == null || s_TypeMapDirty)
RebuildTypeMaps();
Undo.undoRedoPerformed += OnUndoRedoPerformed;
DebugManager.instance.onSetDirty += MarkDirty;
// First init
m_DebugTreeState = DebugManager.instance.GetState();
UpdateWidgetStates();
EditorApplication.update -= Repaint;
var panels = DebugManager.instance.panels;
var selectedPanelIndex = m_Settings.selectedPanel;
if (selectedPanelIndex >= 0
&& selectedPanelIndex < panels.Count
&& panels[selectedPanelIndex].editorForceUpdate)
EditorApplication.update += Repaint;
}
// Note: this won't get called if the window is opened when the editor itself is closed
void OnDestroy()
{
open = false;
DebugManager.instance.onSetDirty -= MarkDirty;
Undo.ClearUndo(m_Settings);
DestroyWidgetStates();
}
public void DestroyWidgetStates()
{
if (m_WidgetStates != null)
{
// Clear all the states from memory
foreach (var state in m_WidgetStates)
{
var s = state.Value;
Undo.ClearUndo(s); // Don't leave dangling states in the global undo/redo stack
DestroyImmediate(s);
}
m_WidgetStates.Clear();
}
}
bool AreWidgetStatesValid()
{
foreach (var state in m_WidgetStates)
{
if (state.Value == null)
{
return false;
}
}
return true;
}
void MarkDirty()
{
m_IsDirty = true;
}
// We use item states to keep a cached value of each serializable debug items in order to
// handle domain reloads, play mode entering/exiting and undo/redo
// Note: no removal of orphan states
void UpdateWidgetStates()
{
foreach (var panel in DebugManager.instance.panels)
UpdateWidgetStates(panel);
}
void UpdateWidgetStates(DebugUI.IContainer container)
{
// Skip runtime only containers, we won't draw them so no need to serialize them either
var actualWidget = container as DebugUI.Widget;
if (actualWidget != null && actualWidget.isInactiveInEditor)
return;
// Recursively update widget states
foreach (var widget in container.children)
{
// Skip non-serializable widgets but still traverse them in case one of their
// children needs serialization support
var valueField = widget as DebugUI.IValueField;
if (valueField != null)
{
// Skip runtime & readonly only items
if (widget.isInactiveInEditor)
return;
var widgetType = widget.GetType();
string guid = widget.queryPath;
Type stateType;
s_WidgetStateMap.TryGetValue(widgetType, out stateType);
// Create missing states & recreate the ones that are null
if (stateType != null)
{
if (!m_WidgetStates.ContainsKey(guid) || m_WidgetStates[guid] == null)
{
var inst = (DebugState)CreateInstance(stateType);
inst.queryPath = guid;
inst.SetValue(valueField.GetValue(), valueField);
m_WidgetStates[guid] = inst;
}
}
}
// Recurse if the widget is a container
var containerField = widget as DebugUI.IContainer;
if (containerField != null)
UpdateWidgetStates(containerField);
}
}
public void ApplyStates(bool forceApplyAll = false)
{
if (!forceApplyAll && DebugState.m_CurrentDirtyState != null)
{
ApplyState(DebugState.m_CurrentDirtyState.queryPath, DebugState.m_CurrentDirtyState);
DebugState.m_CurrentDirtyState = null;
return;
}
foreach (var state in m_WidgetStates)
ApplyState(state.Key, state.Value);
DebugState.m_CurrentDirtyState = null;
}
void ApplyState(string queryPath, DebugState state)
{
var widget = DebugManager.instance.GetItem(queryPath) as DebugUI.IValueField;
if (widget == null)
return;
widget.SetValue(state.GetValue());
}
void OnUndoRedoPerformed()
{
int stateHash = ComputeStateHash();
// Something has been undone / redone, re-apply states to the debug tree
if (stateHash != m_Settings.currentStateHash)
{
ApplyStates(true);
m_Settings.currentStateHash = stateHash;
}
Repaint();
}
int ComputeStateHash()
{
unchecked
{
int hash = 13;
foreach (var state in m_WidgetStates)
hash = hash * 23 + state.Value.GetHashCode();
return hash;
}
}
void Update()
{
// If the render pipeline asset has been reloaded we force-refresh widget states in case
// some debug values need to be refresh/recreated as well (e.g. frame settings on HD)
if (DebugManager.instance.refreshEditorRequested)
{
DestroyWidgetStates();
DebugManager.instance.refreshEditorRequested = false;
}
int treeState = DebugManager.instance.GetState();
if (m_DebugTreeState != treeState || m_IsDirty)
{
UpdateWidgetStates();
ApplyStates();
m_DebugTreeState = treeState;
m_IsDirty = false;
}
}
void OnGUI()
{
if (s_Styles == null)
{
s_Styles = new Styles();
s_SplitterLeft = new GUIStyle();
}
var panels = DebugManager.instance.panels;
int itemCount = panels.Count(x => !x.isInactiveInEditor && x.children.Count(w => !w.isInactiveInEditor) > 0);
if (itemCount == 0)
{
EditorGUILayout.HelpBox("No debug item found.", MessageType.Info);
return;
}
// Background color
var wrect = position;
wrect.x = 0;
wrect.y = 0;
var oldColor = GUI.color;
GUI.color = s_Styles.skinBackgroundColor;
GUI.DrawTexture(wrect, EditorGUIUtility.whiteTexture);
GUI.color = oldColor;
GUILayout.BeginHorizontal(EditorStyles.toolbar);
//isMultiview = GUILayout.Toggle(isMultiview, "multiview", EditorStyles.toolbarButton);
//if (isMultiview)
// EditorGUILayout.Popup(0, new[] { new GUIContent("SceneView 1"), new GUIContent("SceneView 2") }, EditorStyles.toolbarDropDown, GUILayout.Width(100f));
GUILayout.FlexibleSpace();
//GUILayout.Button(k_LoadButtonContent, EditorStyles.toolbarButton);
//GUILayout.Button(k_SaveButtonContent, EditorStyles.toolbarButton);
if (GUILayout.Button(k_ResetButtonContent, EditorStyles.toolbarButton))
DebugManager.instance.Reset();
GUILayout.EndHorizontal();
using (new EditorGUILayout.HorizontalScope())
{
// Side bar
using (var scrollScope = new EditorGUILayout.ScrollViewScope(m_PanelScroll, s_Styles.sectionScrollView, GUILayout.Width(splitterPos)))
{
GUILayout.Space(40f);
if (m_Settings.selectedPanel >= panels.Count)
m_Settings.selectedPanel = 0;
// Validate container id
while (panels[m_Settings.selectedPanel].isInactiveInEditor || panels[m_Settings.selectedPanel].children.Count(x => !x.isInactiveInEditor) == 0)
{
m_Settings.selectedPanel++;
if (m_Settings.selectedPanel >= panels.Count)
m_Settings.selectedPanel = 0;
}
// Root children are containers
for (int i = 0; i < panels.Count; i++)
{
var panel = panels[i];
if (panel.isInactiveInEditor)
continue;
if (panel.children.Count(x => !x.isInactiveInEditor) == 0)
continue;
var elementRect = GUILayoutUtility.GetRect(EditorGUIUtility.TrTextContent(panel.displayName), s_Styles.sectionElement, GUILayout.ExpandWidth(true));
if (m_Settings.selectedPanel == i && Event.current.type == EventType.Repaint)
s_Styles.selected.Draw(elementRect, false, false, false, false);
EditorGUI.BeginChangeCheck();
GUI.Toggle(elementRect, m_Settings.selectedPanel == i, panel.displayName, s_Styles.sectionElement);
if (EditorGUI.EndChangeCheck())
{
Undo.RegisterCompleteObjectUndo(m_Settings, "Debug Panel Selection");
var previousPanel = m_Settings.selectedPanel >= 0 && m_Settings.selectedPanel < panels.Count
? panels[m_Settings.selectedPanel]
: null;
if (previousPanel != null && previousPanel.editorForceUpdate && !panel.editorForceUpdate)
EditorApplication.update -= Repaint;
else if ((previousPanel == null || !previousPanel.editorForceUpdate) && panel.editorForceUpdate)
EditorApplication.update += Repaint;
m_Settings.selectedPanel = i;
}
}
m_PanelScroll = scrollScope.scrollPosition;
}
Rect splitterRect = new Rect(splitterPos - 3, 0, 6, Screen.height);
GUI.Box(splitterRect, "", s_SplitterLeft);
GUILayout.Space(10f);
// Main section - traverse current container
using (var changedScope = new EditorGUI.ChangeCheckScope())
{
using (new EditorGUILayout.VerticalScope())
{
var selectedPanel = panels[m_Settings.selectedPanel];
GUILayout.Label(selectedPanel.displayName, s_Styles.sectionHeader);
GUILayout.Space(10f);
using (var scrollScope = new EditorGUILayout.ScrollViewScope(m_ContentScroll))
{
TraverseContainerGUI(selectedPanel);
m_ContentScroll = scrollScope.scrollPosition;
}
}
if (changedScope.changed)
{
m_Settings.currentStateHash = ComputeStateHash();
DebugManager.instance.ReDrawOnScreenDebug();
}
}
// Splitter events
if (Event.current != null)
{
switch (Event.current.rawType)
{
case EventType.MouseDown:
if (splitterRect.Contains(Event.current.mousePosition))
{
dragging = true;
}
break;
case EventType.MouseDrag:
if (dragging)
{
splitterPos += Event.current.delta.x;
splitterPos = Mathf.Clamp(splitterPos, minSideBarWidth, Screen.width - minContentWidth);
Repaint();
}
break;
case EventType.MouseUp:
if (dragging)
{
dragging = false;
}
break;
}
}
EditorGUIUtility.AddCursorRect(splitterRect, MouseCursor.ResizeHorizontal);
}
}
void OnWidgetGUI(DebugUI.Widget widget)
{
if (widget.isInactiveInEditor)
return;
DebugState state; // State will be null for stateless widget
m_WidgetStates.TryGetValue(widget.queryPath, out state);
DebugUIDrawer drawer;
if (!s_WidgetDrawerMap.TryGetValue(widget.GetType(), out drawer))
{
EditorGUILayout.LabelField("Drawer not found (" + widget.GetType() + ").");
}
else
{
drawer.Begin(widget, state);
if (drawer.OnGUI(widget, state))
{
var container = widget as DebugUI.IContainer;
if (container != null)
TraverseContainerGUI(container);
}
drawer.End(widget, state);
}
}
void TraverseContainerGUI(DebugUI.IContainer container)
{
// /!\ SHAAAAAAAME ALERT /!\
// A container can change at runtime because of the way IMGUI works and how we handle
// onValueChanged on widget so we have to take this into account while iterating
try
{
foreach (var widget in container.children)
OnWidgetGUI(widget);
}
catch (InvalidOperationException)
{
Repaint();
}
}
public class Styles
{
public static float s_DefaultLabelWidth = 0.5f;
public readonly GUIStyle sectionScrollView = "PreferencesSectionBox";
public readonly GUIStyle sectionElement = new GUIStyle("PreferencesSection");
public readonly GUIStyle selected = "OL SelectedRow";
public readonly GUIStyle sectionHeader = new GUIStyle(EditorStyles.largeLabel);
public readonly Color skinBackgroundColor;
public Styles()
{
sectionScrollView = new GUIStyle(sectionScrollView);
sectionScrollView.overflow.bottom += 1;
sectionElement.alignment = TextAnchor.MiddleLeft;
sectionHeader.fontStyle = FontStyle.Bold;
sectionHeader.fontSize = 18;
sectionHeader.margin.top = 10;
sectionHeader.margin.left += 1;
sectionHeader.normal.textColor = !EditorGUIUtility.isProSkin
? new Color(0.4f, 0.4f, 0.4f, 1.0f)
: new Color(0.7f, 0.7f, 0.7f, 1.0f);
if (EditorGUIUtility.isProSkin)
{
sectionHeader.normal.textColor = new Color(0.7f, 0.7f, 0.7f, 1.0f);
skinBackgroundColor = Color.gray * new Color(0.3f, 0.3f, 0.3f, 0.5f);
}
else
{
sectionHeader.normal.textColor = new Color(0.4f, 0.4f, 0.4f, 1.0f);
skinBackgroundColor = Color.gray * new Color(1f, 1f, 1f, 0.32f);
}
}
}
}
#pragma warning restore 414
}

11
Packages/com.unity.render-pipelines.core/Editor/Debugging/DebugWindow.cs.meta


fileFormatVersion: 2
guid: 41147144ff556e246b736135eb26f185
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

34
Packages/com.unity.render-pipelines.core/Editor/Debugging/UIFoldoutEditor.cs


using UnityEngine.Rendering.UI;
namespace UnityEditor.Rendering.UI
{
[CustomEditor(typeof(UIFoldout), true)]
sealed class UIFoldoutEditor : Editor
{
SerializedProperty m_IsOn;
SerializedProperty m_Content;
SerializedProperty m_ArrowClosed;
SerializedProperty m_ArrowOpened;
void OnEnable()
{
var o = new PropertyFetcher<UIFoldout>(serializedObject);
m_IsOn = o.Find("m_IsOn");
m_Content = o.Find(x => x.content);
m_ArrowClosed = o.Find(x => x.arrowClosed);
m_ArrowOpened = o.Find(x => x.arrowOpened);
}
public override void OnInspectorGUI()
{
serializedObject.Update();
EditorGUILayout.PropertyField(m_IsOn);
EditorGUILayout.PropertyField(m_Content);
EditorGUILayout.PropertyField(m_ArrowClosed);
EditorGUILayout.PropertyField(m_ArrowOpened);
serializedObject.ApplyModifiedProperties();
}
}
}

11
Packages/com.unity.render-pipelines.core/Editor/Debugging/UIFoldoutEditor.cs.meta


fileFormatVersion: 2
guid: e89075dc59755d8479f34df9e20926e1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

46
Packages/com.unity.render-pipelines.core/Editor/EditorPrefBoolFlags.cs


using System;
namespace UnityEditor.Rendering
{
public struct EditorPrefBoolFlags<T> : IEquatable<T>, IEquatable<EditorPrefBoolFlags<T>>
where T : struct, IConvertible
{
readonly string m_Key;
public T value
{ get => (T)(object)EditorPrefs.GetInt(m_Key); set => EditorPrefs.SetInt(m_Key, (int)(object)value); }
public uint rawValue
{ get => (uint)EditorPrefs.GetInt(m_Key); set => EditorPrefs.SetInt(m_Key, (int)value); }
public EditorPrefBoolFlags(string key) => m_Key = key;
public bool Equals(T other) => (int)(object)value == (int)(object)other;
public bool Equals(EditorPrefBoolFlags<T> other) => m_Key == other.m_Key;
public bool HasFlag(T v) => ((uint)(int)(object)v & rawValue) == (uint)(int)(object)v;
public bool SetFlag(T f, bool v)
{
if (v) rawValue |= (uint)(int)(object)f;
else rawValue &= ~(uint)(int)(object)f;
return v;
}
public static explicit operator T(EditorPrefBoolFlags<T> v) => v.value;
public static EditorPrefBoolFlags<T> operator |(EditorPrefBoolFlags<T> l, T r)
{
l.rawValue |= (uint)(int)(object)r;
return l;
}
public static EditorPrefBoolFlags<T> operator &(EditorPrefBoolFlags<T> l, T r)
{
l.rawValue &= (uint)(int)(object)r;
return l;
}
public static EditorPrefBoolFlags<T> operator ^(EditorPrefBoolFlags<T> l, T r)
{
l.rawValue ^= (uint)(int)(object)r;
return l;
}
}
}

11
Packages/com.unity.render-pipelines.core/Editor/EditorPrefBoolFlags.cs.meta


fileFormatVersion: 2
guid: 1f766cc928a2d60469867f03d60c9f01
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

58
Packages/com.unity.render-pipelines.core/Editor/ExpandedState.cs


using System;
namespace UnityEditor.Rendering
{
/// <summary>Used in editor drawer part to store the state of expendable areas.</summary>
/// <typeparam name="TState">An enum to use to describe the state.</typeparam>
/// <typeparam name="TTarget">A type given to automatically compute the key.</typeparam>
public struct ExpandedState<TState, TTarget>
where TState : struct, IConvertible
{
EditorPrefBoolFlags<TState> m_State;
/// <summary>Constructor will create the key to store in the EditorPref the state given generic type passed.</summary>
/// <param name="defaultValue">If key did not exist, it will be created with this value for initialization.</param>
public ExpandedState(TState defaultValue, string prefix = "CoreRP")
{
String Key = string.Format("{0}:{1}:UI_State", prefix, typeof(TTarget).Name);
m_State = new EditorPrefBoolFlags<TState>(Key);
//register key if not already there
if (!EditorPrefs.HasKey(Key))
{
EditorPrefs.SetInt(Key, (int)(object)defaultValue);
}
}
/// <summary>Get or set the state given the mask.</summary>
public bool this[TState mask]
{
get { return m_State.HasFlag(mask); }
set { m_State.SetFlag(mask, value); }
}
/// <summary>Accessor to the expended state of this specific mask.</summary>
public bool GetExpandedAreas(TState mask)
{
return m_State.HasFlag(mask);
}
/// <summary>Setter to the expended state.</summary>
public void SetExpandedAreas(TState mask, bool value)
{
m_State.SetFlag(mask, value);
}
/// <summary> Utility to set all states to true </summary>
public void ExpandAll()
{
m_State.rawValue = ~(-1);
}
/// <summary> Utility to set all states to false </summary>
public void CollapseAll()
{
m_State.rawValue = 0;
}
}
}

11
Packages/com.unity.render-pipelines.core/Editor/ExpandedState.cs.meta


fileFormatVersion: 2
guid: 1f4f20df9923ed04ea15d89f8456b8f0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

696
Packages/com.unity.render-pipelines.core/Editor/FilterWindow.cs


using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEngine;
namespace UnityEditor.Rendering
{
[InitializeOnLoad]
public class FilterWindow : EditorWindow
{
public interface IProvider
{
Vector2 position { get; set; }
void CreateComponentTree(List<Element> tree);
bool GoToChild(Element element, bool addIfComponent);
}
public static readonly float DefaultWidth = 250f;
public static readonly float DefaultHeight = 300f;
#region BaseElements
public class Element : IComparable
{
public int level;
public GUIContent content;
public string name
{
get { return content.text; }
}
public int CompareTo(object o)
{
return name.CompareTo((o as Element).name);
}
}
[Serializable]
public class GroupElement : Element
{
public Vector2 scroll;
public int selectedIndex;
public bool WantsFocus { get; protected set; }
public virtual bool ShouldDisable
{
get { return false; }
}
public GroupElement(int level, string name)
{
this.level = level;
content = new GUIContent(name);
}
public virtual bool HandleKeyboard(Event evt, FilterWindow window, Action goToParent)
{
return false;
}
public virtual bool OnGUI(FilterWindow sFilterWindow)
{
return false;
}
}
#endregion
// Styles
class Styles
{
public GUIStyle header = (GUIStyle)typeof(EditorStyles).GetProperty("inspectorBig", BindingFlags.Static | BindingFlags.NonPublic).GetValue(null, null);
public GUIStyle componentButton = new GUIStyle("PR Label");
public GUIStyle groupButton;
public GUIStyle background = "grey_border";
public GUIStyle rightArrow = "AC RightArrow";
public GUIStyle leftArrow = "AC LeftArrow";
public Styles()
{
header.font = EditorStyles.boldLabel.font;
componentButton.alignment = TextAnchor.MiddleLeft;
componentButton.fixedHeight = 20;
componentButton.imagePosition = ImagePosition.ImageAbove;
groupButton = new GUIStyle(componentButton);
}
}
const int k_HeaderHeight = 30;
const int k_WindowHeight = 400 - 80;
const int k_HelpHeight = 80 * 0;
const string k_ComponentSearch = "NodeSearchString";
static Styles s_Styles;
static FilterWindow s_FilterWindow;
static long s_LastClosedTime;
static bool s_DirtyList;
IProvider m_Provider;
Element[] m_Tree;
Element[] m_SearchResultTree;
List<GroupElement> m_Stack = new List<GroupElement>();
float m_Anim = 1;
int m_AnimTarget = 1;
long m_LastTime;
bool m_ScrollToSelected;
string m_DelayedSearch;
string m_Search = "";
bool m_HasSearch { get { return !string.IsNullOrEmpty(m_Search); } }
GroupElement m_ActiveParent { get { return m_Stack[m_Stack.Count - 2 + m_AnimTarget]; } }
Element[] m_ActiveTree { get { return m_HasSearch ? m_SearchResultTree : m_Tree; } }
Element m_ActiveElement
{
get
{
if (m_ActiveTree == null)
return null;
var children = GetChildren(m_ActiveTree, m_ActiveParent);
return children.Count == 0
? null
: children[m_ActiveParent.selectedIndex];
}
}
bool m_IsAnimating { get { return !Mathf.Approximately(m_Anim, m_AnimTarget); } }
static FilterWindow()
{
s_DirtyList = true;
}
void OnEnable()
{
s_FilterWindow = this;
m_Search = "";
}
void OnDisable()
{
s_LastClosedTime = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;
s_FilterWindow = null;
}
internal static bool ValidateAddComponentMenuItem()
{
return true;
}
internal static bool Show(Vector2 position, IProvider provider)
{
// If the window is already open, close it instead.
var wins = Resources.FindObjectsOfTypeAll(typeof(FilterWindow));
if (wins.Length > 0)
{
try
{
((EditorWindow)wins[0]).Close();
return false;
}
catch (Exception)
{
s_FilterWindow = null;
}
}
// We could not use realtimeSinceStartUp since it is set to 0 when entering/exitting
// playmode, we assume an increasing time when comparing time.
long nowMilliSeconds = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;
bool justClosed = nowMilliSeconds < s_LastClosedTime + 50;
if (!justClosed)
{
Event.current.Use();
if (s_FilterWindow == null)
{
s_FilterWindow = CreateInstance<FilterWindow>();
s_FilterWindow.hideFlags = HideFlags.HideAndDontSave;
}
s_FilterWindow.Init(position, provider);
return true;
}
return false;
}
static object Invoke(Type t, object inst, string method, params object[] args)
{
var flags = (inst == null ? BindingFlags.Static : BindingFlags.Instance) | BindingFlags.NonPublic;
var mi = t.GetMethod(method, flags);
return mi.Invoke(inst, args);
}
void Init(Vector2 position, IProvider provider)
{
m_Provider = provider;
// Has to be done before calling Show / ShowWithMode
m_Provider.position = position;
position = GUIUtility.GUIToScreenPoint(position);
var buttonRect = new Rect(position.x - DefaultWidth / 2, position.y - 16, DefaultWidth, 1);
CreateComponentTree();
ShowAsDropDown(buttonRect, new Vector2(buttonRect.width, k_WindowHeight));
Focus();
wantsMouseMove = true;
}
void CreateComponentTree()
{
var tree = new List<Element>();
m_Provider.CreateComponentTree(tree);
m_Tree = tree.ToArray();
// Rebuild stack
if (m_Stack.Count == 0)
{
m_Stack.Add(m_Tree[0] as GroupElement);
}
else
{
// The root is always the match for level 0
var match = m_Tree[0] as GroupElement;
int level = 0;
while (true)
{
// Assign the match for the current level
var oldElement = m_Stack[level];
m_Stack[level] = match;
m_Stack[level].selectedIndex = oldElement.selectedIndex;
m_Stack[level].scroll = oldElement.scroll;
// See if we reached last element of stack
level++;
if (level == m_Stack.Count)
break;
// Try to find a child of the same name as we had before
var children = GetChildren(m_ActiveTree, match);
var childMatch = children.FirstOrDefault(c => c.name == m_Stack[level].name);
if (childMatch is GroupElement)
{
match = childMatch as GroupElement;
}
else
{
// If we couldn't find the child, remove all further elements from the stack
while (m_Stack.Count > level)
m_Stack.RemoveAt(level);
}
}
}
s_DirtyList = false;
RebuildSearch();
}
internal void OnGUI()
{
// Avoids errors in the console if a domain reload is triggered while the filter window
// is opened
if (m_Provider == null)
return;
if (s_Styles == null)
s_Styles = new Styles();
GUI.Label(new Rect(0, 0, position.width, position.height), GUIContent.none, s_Styles.background);
if (s_DirtyList)
CreateComponentTree();
// Keyboard
HandleKeyboard();
GUILayout.Space(7);
// Search
if (!m_ActiveParent.WantsFocus)
{
EditorGUI.FocusTextInControl("ComponentSearch");
Focus();
}
var searchRect = GUILayoutUtility.GetRect(10, 20);
searchRect.x += 8;
searchRect.width -= 16;
GUI.SetNextControlName("ComponentSearch");
using (new EditorGUI.DisabledScope(m_ActiveParent.ShouldDisable))
{
string newSearch = (string)Invoke(typeof(EditorGUI), null, "SearchField", searchRect, m_DelayedSearch ?? m_Search);
if (newSearch != m_Search || m_DelayedSearch != null)
{
if (!m_IsAnimating)
{
m_Search = m_DelayedSearch ?? newSearch;
EditorPrefs.SetString(k_ComponentSearch, m_Search);
RebuildSearch();
m_DelayedSearch = null;
}
else
{
m_DelayedSearch = newSearch;
}
}
}
// Show lists
ListGUI(m_ActiveTree, m_Anim, GetElementRelative(0), GetElementRelative(-1));
if (m_Anim < 1)
ListGUI(m_ActiveTree, m_Anim + 1, GetElementRelative(-1), GetElementRelative(-2));
// Animate
if (m_IsAnimating && Event.current.type == EventType.Repaint)
{
long now = DateTime.Now.Ticks;
float deltaTime = (now - m_LastTime) / (float)TimeSpan.TicksPerSecond;
m_LastTime = now;
m_Anim = Mathf.MoveTowards(m_Anim, m_AnimTarget, deltaTime * 4);
if (m_AnimTarget == 0 && Mathf.Approximately(m_Anim, 0))
{
m_Anim = 1;
m_AnimTarget = 1;
m_Stack.RemoveAt(m_Stack.Count - 1);
}
Repaint();
}
}
void HandleKeyboard()
{
var evt = Event.current;
if (evt.type == EventType.KeyDown)
{
// Special handling when in new script panel
if (!m_ActiveParent.HandleKeyboard(evt, s_FilterWindow, GoToParent))
{
// Always do these
if (evt.keyCode == KeyCode.DownArrow)
{
m_ActiveParent.selectedIndex++;
m_ActiveParent.selectedIndex = Mathf.Min(m_ActiveParent.selectedIndex, GetChildren(m_ActiveTree, m_ActiveParent).Count - 1);
m_ScrollToSelected = true;
evt.Use();
}
if (evt.keyCode == KeyCode.UpArrow)
{
m_ActiveParent.selectedIndex--;
m_ActiveParent.selectedIndex = Mathf.Max(m_ActiveParent.selectedIndex, 0);
m_ScrollToSelected = true;
evt.Use();
}
if (evt.keyCode == KeyCode.Return || evt.keyCode == KeyCode.KeypadEnter)
{
GoToChild(m_ActiveElement, true);
evt.Use();
}
// Do these if we're not in search mode
if (!m_HasSearch)
{
if (evt.keyCode == KeyCode.LeftArrow || evt.keyCode == KeyCode.Backspace)
{
GoToParent();
evt.Use();
}
if (evt.keyCode == KeyCode.RightArrow)
{
GoToChild(m_ActiveElement, false);
evt.Use();
}
if (evt.keyCode == KeyCode.Escape)
{
Close();
evt.Use();
}
}
}
}
}
const string k_SearchHeader = "Search";
void RebuildSearch()
{
if (!m_HasSearch)
{
m_SearchResultTree = null;
if (m_Stack[m_Stack.Count - 1].name == k_SearchHeader)
{
m_Stack.Clear();
m_Stack.Add(m_Tree[0] as GroupElement);
}
m_AnimTarget = 1;
m_LastTime = DateTime.Now.Ticks;
return;
}
// Support multiple search words separated by spaces.
var searchWords = m_Search.ToLower().Split(' ');
// We keep two lists. Matches that matches the start of an item always get first priority.
var matchesStart = new List<Element>();
var matchesWithin = new List<Element>();
foreach (var e in m_Tree)
{
if (e is GroupElement)
continue;
string name = e.name.ToLower().Replace(" ", "");
bool didMatchAll = true;
bool didMatchStart = false;
// See if we match ALL the seaarch words.
for (int w = 0; w < searchWords.Length; w++)
{
string search = searchWords[w];
if (name.Contains(search))
{
// If the start of the item matches the first search word, make a note of that.
if (w == 0 && name.StartsWith(search))
didMatchStart = true;
}
else
{
// As soon as any word is not matched, we disregard this item.
didMatchAll = false;
break;
}
}
// We always need to match all search words.
// If we ALSO matched the start, this item gets priority.
if (didMatchAll)
{
if (didMatchStart)
matchesStart.Add(e);
else
matchesWithin.Add(e);
}
}
matchesStart.Sort();
matchesWithin.Sort();
// Create search tree
var tree = new List<Element>();
// Add parent
tree.Add(new GroupElement(0, k_SearchHeader));
// Add search results
tree.AddRange(matchesStart);
tree.AddRange(matchesWithin);
// Create search result tree
m_SearchResultTree = tree.ToArray();
m_Stack.Clear();
m_Stack.Add(m_SearchResultTree[0] as GroupElement);
// Always select the first search result when search is changed (e.g. a character was typed in or deleted),
// because it's usually the best match.
if (GetChildren(m_ActiveTree, m_ActiveParent).Count >= 1)
m_ActiveParent.selectedIndex = 0;
else
m_ActiveParent.selectedIndex = -1;
}
GroupElement GetElementRelative(int rel)
{
int i = m_Stack.Count + rel - 1;
if (i < 0)
return null;
return m_Stack[i];
}
void GoToParent()
{
if (m_Stack.Count > 1)
{
m_AnimTarget = 0;
m_LastTime = DateTime.Now.Ticks;
}
}
void ListGUI(Element[] tree, float anim, GroupElement parent, GroupElement grandParent)
{
// Smooth the fractional part of the anim value
anim = Mathf.Floor(anim) + Mathf.SmoothStep(0, 1, Mathf.Repeat(anim, 1));
// Calculate rect for animated area
var animRect = position;
animRect.x = position.width * (1 - anim) + 1;
animRect.y = k_HeaderHeight;
animRect.height -= k_HeaderHeight + k_HelpHeight;
animRect.width -= 2;
// Start of animated area (the part that moves left and right)
GUILayout.BeginArea(animRect);
// Header
var headerRect = GUILayoutUtility.GetRect(10, 25);
string name = parent.name;
GUI.Label(headerRect, name, s_Styles.header);
// Back button
if (grandParent != null)
{
var arrowRect = new Rect(headerRect.x + 4, headerRect.y + 7, 13, 13);
var e = Event.current;
if (e.type == EventType.Repaint)
s_Styles.leftArrow.Draw(arrowRect, false, false, false, false);
if (e.type == EventType.MouseDown && headerRect.Contains(e.mousePosition))
{
GoToParent();
e.Use();
}
}
if (!parent.OnGUI(s_FilterWindow))
ListGUI(tree, parent);
GUILayout.EndArea();
}
void GoToChild(Element e, bool addIfComponent)
{
if (m_Provider.GoToChild(e, addIfComponent))
{
Close();
}
else if (!m_HasSearch)
{
m_LastTime = DateTime.Now.Ticks;
if (m_AnimTarget == 0)
{
m_AnimTarget = 1;
}
else if (Mathf.Approximately(m_Anim, 1f))
{
m_Anim = 0;
m_Stack.Add(e as GroupElement);
}
}
}
void ListGUI(Element[] tree, GroupElement parent)
{
// Start of scroll view list
parent.scroll = GUILayout.BeginScrollView(parent.scroll);
EditorGUIUtility.SetIconSize(new Vector2(16, 16));
var children = GetChildren(tree, parent);
var selectedRect = new Rect();
var evt = Event.current;
// Iterate through the children
for (int i = 0; i < children.Count; i++)
{
var e = children[i];
var r = GUILayoutUtility.GetRect(16, 20, GUILayout.ExpandWidth(true));
// Select the element the mouse cursor is over.
// Only do it on mouse move - keyboard controls are allowed to overwrite this until the next time the mouse moves.
if (evt.type == EventType.MouseMove || evt.type == EventType.MouseDown)
{
if (parent.selectedIndex != i && r.Contains(evt.mousePosition))
{
parent.selectedIndex = i;
Repaint();
}
}
bool selected = false;
// Handle selected item
if (i == parent.selectedIndex)
{
selected = true;
selectedRect = r;
}
// Draw element
if (evt.type == EventType.Repaint)
{
var labelStyle = (e is GroupElement) ? s_Styles.groupButton : s_Styles.componentButton;
labelStyle.Draw(r, e.content, false, false, selected, selected);
if (e is GroupElement)
{
var arrowRect = new Rect(r.x + r.width - 13, r.y + 4, 13, 13);
s_Styles.rightArrow.Draw(arrowRect, false, false, false, false);
}
}
if (evt.type == EventType.MouseDown && r.Contains(evt.mousePosition))
{
evt.Use();
parent.selectedIndex = i;
GoToChild(e, true);
}
}
EditorGUIUtility.SetIconSize(Vector2.zero);
GUILayout.EndScrollView();
// Scroll to show selected
if (m_ScrollToSelected && evt.type == EventType.Repaint)
{
m_ScrollToSelected = false;
var scrollRect = GUILayoutUtility.GetLastRect();
if (selectedRect.yMax - scrollRect.height > parent.scroll.y)
{
parent.scroll.y = selectedRect.yMax - scrollRect.height;
Repaint();
}
if (selectedRect.y < parent.scroll.y)
{
parent.scroll.y = selectedRect.y;
Repaint();
}
}
}
List<Element> GetChildren(Element[] tree, Element parent)
{
var children = new List<Element>();
int level = -1;
int i;
for (i = 0; i < tree.Length; i++)
{
if (tree[i] == parent)
{
level = parent.level + 1;
i++;
break;
}
}
if (level == -1)
return children;
for (; i < tree.Length; i++)
{
var e = tree[i];
if (e.level < level)
break;
if (e.level > level && !m_HasSearch)
continue;
children.Add(e);
}
return children;
}
}
}

11
Packages/com.unity.render-pipelines.core/Editor/FilterWindow.cs.meta


fileFormatVersion: 2
guid: e1dbde6eddba87f4ea272a5d3af5b7ac
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

8
Packages/com.unity.render-pipelines.core/Editor/Gizmo.meta


fileFormatVersion: 2
guid: bce47e7c42ffde449812bbbf027555d7
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

397
Packages/com.unity.render-pipelines.core/Editor/Gizmo/HierarchicalBox.cs


using System;
using UnityEngine;
using System.Reflection;
namespace UnityEditor.Rendering
{
/// <summary>
/// Provide a gizmo/handle representing a box where all face can be moved independently.
/// Also add a contained sub gizmo/handle box if contained is used at creation.
/// </summary>
/// <example>
/// <code>
/// class MyComponentEditor : Editor
/// {
/// static HierarchicalBox box;
/// static HierarchicalBox containedBox;
///
/// static MyComponentEditor()
/// {
/// Color[] handleColors = new Color[]
/// {
/// Color.red,
/// Color.green,
/// Color.Blue,
/// new Color(0.5f, 0f, 0f, 1f),
/// new Color(0f, 0.5f, 0f, 1f),
/// new Color(0f, 0f, 0.5f, 1f)
/// };
/// box = new HierarchicalBox(new Color(1f, 1f, 1f, 0.25), handleColors);
/// containedBox = new HierarchicalBox(new Color(1f, 0f, 1f, 0.25), handleColors, container: box);
/// }
///
/// [DrawGizmo(GizmoType.Selected|GizmoType.Active)]
/// void DrawGizmo(MyComponent comp, GizmoType gizmoType)
/// {
/// box.center = comp.transform.position;
/// box.size = comp.transform.scale;
/// box.DrawHull(gizmoType == GizmoType.Selected);
///
/// containedBox.center = comp.innerposition;
/// containedBox.size = comp.innerScale;
/// containedBox.DrawHull(gizmoType == GizmoType.Selected);
/// }
///
/// void OnSceneGUI()
/// {
/// EditorGUI.BeginChangeCheck();
///
/// //container box must be also set for contained box for clamping
/// box.center = comp.transform.position;
/// box.size = comp.transform.scale;
/// box.DrawHandle();
///
/// containedBox.DrawHandle();
/// containedBox.center = comp.innerposition;
/// containedBox.size = comp.innerScale;
///
/// if(EditorGUI.EndChangeCheck())
/// {
/// comp.innerposition = containedBox.center;
/// comp.innersize = containedBox.size;
/// }
/// }
/// }
/// </code>
/// </example>
public class HierarchicalBox
{
const float k_HandleSizeCoef = 0.05f;
static Material k_Material_Cache;
static Material k_Material => (k_Material_Cache == null || k_Material_Cache.Equals(null) ? (k_Material_Cache = new Material(Shader.Find("Hidden/UnlitTransparentColored"))) : k_Material_Cache);
static Mesh k_MeshQuad_Cache;
static Mesh k_MeshQuad => k_MeshQuad_Cache == null || k_MeshQuad_Cache.Equals(null) ? (k_MeshQuad_Cache = Resources.GetBuiltinResource<Mesh>("Quad.fbx")) : k_MeshQuad_Cache;
enum NamedFace { Right, Top, Front, Left, Bottom, Back, None }
Material m_Material;
readonly Color[] m_PolychromeHandleColor;
readonly HierarchicalBox m_Parent;
Color m_MonochromeHandleColor;
Color m_WireframeColor;
Color m_WireframeColorBehind;
int[] m_ControlIDs = new int[6] { 0, 0, 0, 0, 0, 0 };
bool m_MonoHandle = true;
Material material => m_Material == null || m_Material.Equals(null)
? (m_Material = new Material(k_Material))
: m_Material;
/// <summary>
/// Allow to switch between the mode where all axis are controlled together or not
/// Note that if there is several handles, they will use the polychrome colors.
/// </summary>
public bool monoHandle { get => m_MonoHandle; set => m_MonoHandle = value; }
/// <summary>The position of the center of the box in Handle.matrix space.</summary>
public Vector3 center { get; set; }
/// <summary>The size of the box in Handle.matrix space.</summary>
public Vector3 size { get; set; }
/// <summary>The baseColor used to fill hull. All other colors are deduced from it except specific handle colors.</summary>
public Color baseColor
{
get { return material.color; }
set
{
value.a = 8f / 255;
material.color = value;
value.a = 1f;
m_MonochromeHandleColor = value;
value.a = 0.7f;
m_WireframeColor = value;
value.a = 0.2f;
m_WireframeColorBehind = value;
}
}
//Note: Handles.Slider not allow to use a specific ControlID.
//Thus Slider1D is used (with reflection)
static Type k_Slider1D = Type.GetType("UnityEditorInternal.Slider1D, UnityEditor");
static MethodInfo k_Slider1D_Do = k_Slider1D
.GetMethod(
"Do",
BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public,
null,
CallingConventions.Any,
new[] { typeof(int), typeof(Vector3), typeof(Vector3), typeof(float), typeof(Handles.CapFunction), typeof(float) },
null);
static void Slider1D(int controlID, ref Vector3 handlePosition, Vector3 handleOrientation, float snapScale, Color color)
{
using (new Handles.DrawingScope(color))
{
handlePosition = (Vector3)k_Slider1D_Do.Invoke(null, new object[]
{
controlID,
handlePosition,
handleOrientation,
HandleUtility.GetHandleSize(handlePosition) * k_HandleSizeCoef,
new Handles.CapFunction(Handles.DotHandleCap),
snapScale
});
}
}
/// <summary>Constructor. Used to setup colors and also the container if any.</summary>
/// <param name="baseColor">The color of each face of the box. Other colors are deduced from it.</param>
/// <param name="polychromeHandleColors">The color of handle when they are separated. When they are grouped, they use a variation of the faceColor instead.</param>
/// <param name="parent">The HierarchicalBox containing this box. If null, the box will not be limited in size.</param>
public HierarchicalBox(Color baseColor, Color[] polychromeHandleColors = null, HierarchicalBox parent = null)
{
if (polychromeHandleColors != null && polychromeHandleColors.Length != 6)
throw new ArgumentException("polychromeHandleColors must be null or have a size of 6.");
m_Parent = parent;
m_Material = new Material(k_Material);
this.baseColor = baseColor;
m_PolychromeHandleColor = polychromeHandleColors ?? new Color[]
{
Handles.xAxisColor, Handles.yAxisColor, Handles.zAxisColor,
Handles.xAxisColor, Handles.yAxisColor, Handles.zAxisColor
};
}
/// <summary>Draw the hull which means the boxes without the handles</summary>
public void DrawHull(bool filled)
{
Color previousColor = Handles.color;
if (filled)
{
// Draw the hull
var xSize = new Vector3(size.z, size.y, 1f);
material.SetPass(0);
Graphics.DrawMeshNow(k_MeshQuad, Handles.matrix * Matrix4x4.TRS(center + size.x * .5f * Vector3.left, Quaternion.FromToRotation(Vector3.forward, Vector3.left), xSize));
Graphics.DrawMeshNow(k_MeshQuad, Handles.matrix * Matrix4x4.TRS(center + size.x * .5f * Vector3.right, Quaternion.FromToRotation(Vector3.forward, Vector3.right), xSize));
var ySize = new Vector3(size.x, size.z, 1f);
Graphics.DrawMeshNow(k_MeshQuad, Handles.matrix * Matrix4x4.TRS(center + size.y * .5f * Vector3.up, Quaternion.FromToRotation(Vector3.forward, Vector3.up), ySize));
Graphics.DrawMeshNow(k_MeshQuad, Handles.matrix * Matrix4x4.TRS(center + size.y * .5f * Vector3.down, Quaternion.FromToRotation(Vector3.forward, Vector3.down), ySize));
var zSize = new Vector3(size.x, size.y, 1f);
Graphics.DrawMeshNow(k_MeshQuad, Handles.matrix * Matrix4x4.TRS(center + size.z * .5f * Vector3.forward, Quaternion.identity, zSize));
Graphics.DrawMeshNow(k_MeshQuad, Handles.matrix * Matrix4x4.TRS(center + size.z * .5f * Vector3.back, Quaternion.FromToRotation(Vector3.forward, Vector3.back), zSize));
//if as a parent, also draw handle distance to the parent
if (m_Parent != null)
{
var centerDiff = center - m_Parent.center;
var xRecal = centerDiff;
var yRecal = centerDiff;
var zRecal = centerDiff;
xRecal.x = 0;
yRecal.y = 0;
zRecal.z = 0;
Handles.color = GetHandleColor(NamedFace.Left);
Handles.DrawLine(m_Parent.center + xRecal + m_Parent.size.x * .5f * Vector3.left, center + size.x * .5f * Vector3.left);
Handles.color = GetHandleColor(NamedFace.Right);
Handles.DrawLine(m_Parent.center + xRecal + m_Parent.size.x * .5f * Vector3.right, center + size.x * .5f * Vector3.right);
Handles.color = GetHandleColor(NamedFace.Top);
Handles.DrawLine(m_Parent.center + yRecal + m_Parent.size.y * .5f * Vector3.up, center + size.y * .5f * Vector3.up);
Handles.color = GetHandleColor(NamedFace.Bottom);
Handles.DrawLine(m_Parent.center + yRecal + m_Parent.size.y * .5f * Vector3.down, center + size.y * .5f * Vector3.down);
Handles.color = GetHandleColor(NamedFace.Front);
Handles.DrawLine(m_Parent.center + zRecal + m_Parent.size.z * .5f * Vector3.forward, center + size.z * .5f * Vector3.forward);
Handles.color = GetHandleColor(NamedFace.Back);
Handles.DrawLine(m_Parent.center + zRecal + m_Parent.size.z * .5f * Vector3.back, center + size.z * .5f * Vector3.back);
}
}
Handles.color = m_WireframeColor;
Handles.zTest = UnityEngine.Rendering.CompareFunction.LessEqual;
Handles.DrawWireCube(center, size);
Handles.color = m_WireframeColorBehind;
Handles.zTest = UnityEngine.Rendering.CompareFunction.Greater;
Handles.DrawWireCube(center, size);
Handles.zTest = UnityEngine.Rendering.CompareFunction.Always;
Handles.color = previousColor;
}
/// <summary>Draw the manipulable handles</summary>
public void DrawHandle()
{
Event evt = Event.current;
bool useHomothety = evt.shift;
bool useSymetry = evt.alt || evt.command;
// Note: snapping is handled natively on ctrl for each Slider1D
for (int i = 0, count = m_ControlIDs.Length; i < count; ++i)
m_ControlIDs[i] = GUIUtility.GetControlID("HierarchicalBox".GetHashCode() + i, FocusType.Passive);
EditorGUI.BeginChangeCheck();
var leftPosition = center + size.x * .5f * Vector3.left;
var rightPosition = center + size.x * .5f * Vector3.right;
var topPosition = center + size.y * .5f * Vector3.up;
var bottomPosition = center + size.y * .5f * Vector3.down;
var frontPosition = center + size.z * .5f * Vector3.forward;
var backPosition = center + size.z * .5f * Vector3.back;
var theChangedFace = NamedFace.None;
EditorGUI.BeginChangeCheck();
Slider1D(m_ControlIDs[(int)NamedFace.Left], ref leftPosition, Vector3.left, EditorSnapSettings.scale, GetHandleColor(NamedFace.Left));
if (EditorGUI.EndChangeCheck())
theChangedFace = NamedFace.Left;
EditorGUI.BeginChangeCheck();
Slider1D(m_ControlIDs[(int)NamedFace.Right], ref rightPosition, Vector3.right, EditorSnapSettings.scale, GetHandleColor(NamedFace.Right));
if (EditorGUI.EndChangeCheck())
theChangedFace = NamedFace.Right;
EditorGUI.BeginChangeCheck();
Slider1D(m_ControlIDs[(int)NamedFace.Top], ref topPosition, Vector3.up, EditorSnapSettings.scale, GetHandleColor(NamedFace.Top));
if (EditorGUI.EndChangeCheck())
theChangedFace = NamedFace.Top;
EditorGUI.BeginChangeCheck();
Slider1D(m_ControlIDs[(int)NamedFace.Bottom], ref bottomPosition, Vector3.down, EditorSnapSettings.scale, GetHandleColor(NamedFace.Bottom));
if (EditorGUI.EndChangeCheck())
theChangedFace = NamedFace.Bottom;
EditorGUI.BeginChangeCheck();
Slider1D(m_ControlIDs[(int)NamedFace.Front], ref frontPosition, Vector3.forward, EditorSnapSettings.scale, GetHandleColor(NamedFace.Front));
if (EditorGUI.EndChangeCheck())
theChangedFace = NamedFace.Front;
EditorGUI.BeginChangeCheck();
Slider1D(m_ControlIDs[(int)NamedFace.Back], ref backPosition, Vector3.back, EditorSnapSettings.scale, GetHandleColor(NamedFace.Back));
if (EditorGUI.EndChangeCheck())
theChangedFace = NamedFace.Back;
if (EditorGUI.EndChangeCheck())
{
float delta = 0f;
switch (theChangedFace)
{
case NamedFace.Left: delta = (leftPosition - center - size.x * .5f * Vector3.left).x; break;
case NamedFace.Right: delta = -(rightPosition - center - size.x * .5f * Vector3.right).x; break;
case NamedFace.Top: delta = -(topPosition - center - size.y * .5f * Vector3.up).y; break;
case NamedFace.Bottom: delta = (bottomPosition - center - size.y * .5f * Vector3.down).y; break;
case NamedFace.Front: delta = -(frontPosition - center - size.z * .5f * Vector3.forward).z; break;
case NamedFace.Back: delta = (backPosition - center - size.z * .5f * Vector3.back).z; break;
}
if (monoHandle || useHomothety && useSymetry)
{
var tempSize = size - Vector3.one * delta;
//ensure that the box face are still facing outside
for (int axis = 0; axis < 3; ++axis)
{
if (tempSize[axis] < 0)
{
delta += tempSize[axis];
tempSize = size - Vector3.one * delta;
}
}
//ensure containedBox do not exit container
if (m_Parent != null)
{
for (int axis = 0; axis < 3; ++axis)
{
if (tempSize[axis] > m_Parent.size[axis])
tempSize[axis] = m_Parent.size[axis];
}
}
size = tempSize;
}
else
{
if (useSymetry)
{
switch (theChangedFace)
{
case NamedFace.Left: rightPosition.x -= delta; break;
case NamedFace.Right: leftPosition.x += delta; break;
case NamedFace.Top: bottomPosition.y += delta; break;
case NamedFace.Bottom: topPosition.y -= delta; break;
case NamedFace.Front: backPosition.z += delta; break;
case NamedFace.Back: frontPosition.z -= delta; break;
}
}
if (useHomothety)
{
float halfDelta = delta * 0.5f;
switch (theChangedFace)
{
case NamedFace.Left:
case NamedFace.Right:
bottomPosition.y += halfDelta;
topPosition.y -= halfDelta;
backPosition.z += halfDelta;
frontPosition.z -= halfDelta;
break;
case NamedFace.Top:
case NamedFace.Bottom:
rightPosition.x -= halfDelta;
leftPosition.x += halfDelta;
backPosition.z += halfDelta;
frontPosition.z -= halfDelta;
break;
case NamedFace.Front:
case NamedFace.Back:
rightPosition.x -= halfDelta;
leftPosition.x += halfDelta;
bottomPosition.y += halfDelta;
topPosition.y -= halfDelta;
break;
}
}
var max = new Vector3(rightPosition.x, topPosition.y, frontPosition.z);
var min = new Vector3(leftPosition.x, bottomPosition.y, backPosition.z);
//ensure that the box face are still facing outside
for (int axis = 0; axis < 3; ++axis)
{
if (min[axis] > max[axis])
{
// Control IDs in m_ControlIDs[0-3[ are for positive axes
if (GUIUtility.hotControl == m_ControlIDs[axis])
max[axis] = min[axis];
else
min[axis] = max[axis];
}
}
//ensure containedBox do not exit container
if (m_Parent != null)
{
for (int axis = 0; axis < 3; ++axis)
{
if (min[axis] < m_Parent.center[axis] - m_Parent.size[axis] * 0.5f)
min[axis] = m_Parent.center[axis] - m_Parent.size[axis] * 0.5f;
if (max[axis] > m_Parent.center[axis] + m_Parent.size[axis] * 0.5f)
max[axis] = m_Parent.center[axis] + m_Parent.size[axis] * 0.5f;
}
}
center = (max + min) * .5f;
size = max - min;
}
}
}
Color GetHandleColor(NamedFace name) => monoHandle ? m_MonochromeHandleColor : m_PolychromeHandleColor[(int)name];
}
}

11
Packages/com.unity.render-pipelines.core/Editor/Gizmo/HierarchicalBox.cs.meta


fileFormatVersion: 2
guid: 9c0adda657f204c4199b6870832abb80
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

175
Packages/com.unity.render-pipelines.core/Editor/Gizmo/HierarchicalSphere.cs


using UnityEngine;
namespace UnityEditor.Rendering
{
/// <summary>
/// Provide a gizmo/handle representing a box where all face can be moved independently.
/// Also add a contained sub gizmo/handle box if contained is used at creation.
/// </summary>
/// <example>
/// <code>
/// class MyComponentEditor : Editor
/// {
/// static HierarchicalSphere sphere;
/// static HierarchicalSphere containedSphere;
///
/// static MyComponentEditor()
/// {
/// Color[] handleColors = new Color[]
/// {
/// Color.red,
/// Color.green,
/// Color.Blue,
/// new Color(0.5f, 0f, 0f, 1f),
/// new Color(0f, 0.5f, 0f, 1f),
/// new Color(0f, 0f, 0.5f, 1f)
/// };
/// sphere = new HierarchicalSphere(new Color(1f, 1f, 1f, 0.25));
/// containedSphere = new HierarchicalSphere(new Color(1f, 0f, 1f, 0.25), container: sphere);
/// }
///
/// [DrawGizmo(GizmoType.Selected|GizmoType.Active)]
/// void DrawGizmo(MyComponent comp, GizmoType gizmoType)
/// {
/// sphere.center = comp.transform.position;
/// sphere.size = comp.transform.scale;
/// sphere.DrawHull(gizmoType == GizmoType.Selected);
///
/// containedSphere.center = comp.innerposition;
/// containedSphere.size = comp.innerScale;
/// containedSphere.DrawHull(gizmoType == GizmoType.Selected);
/// }
///
/// void OnSceneGUI()
/// {
/// EditorGUI.BeginChangeCheck();
///
/// //container sphere must be also set for contained sphere for clamping
/// sphere.center = comp.transform.position;
/// sphere.size = comp.transform.scale;
/// sphere.DrawHandle();
///
/// containedSphere.center = comp.innerposition;
/// containedSphere.size = comp.innerScale;
/// containedSphere.DrawHandle();
///
/// if(EditorGUI.EndChangeCheck())
/// {
/// comp.innerposition = containedSphere.center;
/// comp.innersize = containedSphere.size;
/// }
/// }
/// }
/// </code>
/// </example>
public class HierarchicalSphere
{
const float k_HandleSizeCoef = 0.05f;
static Material k_Material_Cache;
static Material k_Material => (k_Material_Cache == null || k_Material_Cache.Equals(null) ? (k_Material_Cache = new Material(Shader.Find("Hidden/UnlitTransparentColored"))) : k_Material_Cache);
static Mesh k_MeshSphere_Cache;
static Mesh k_MeshSphere => k_MeshSphere_Cache == null || k_MeshSphere_Cache.Equals(null) ? (k_MeshSphere_Cache = Resources.GetBuiltinResource<Mesh>("New-Sphere.fbx")) : k_MeshSphere_Cache;
Material m_Material;
readonly HierarchicalSphere m_Parent;
Color m_HandleColor;
Color m_WireframeColor;
Color m_WireframeColorBehind;
Material material => m_Material == null || m_Material.Equals(null)
? (m_Material = new Material(k_Material))
: m_Material;
/// <summary>The position of the center of the box in Handle.matrix space.</summary>
public Vector3 center { get; set; }
/// <summary>The size of the box in Handle.matrix space.</summary>
public float radius { get; set; }
/// <summary>The baseColor used to fill hull. All other colors are deduced from it.</summary>
public Color baseColor
{
get { return material.color; }
set
{
value.a = 8f / 255;
material.color = value;
value.a = 1f;
m_HandleColor = value;
value.a = 0.7f;
m_WireframeColor = value;
value.a = 0.2f;
m_WireframeColorBehind = value;
}
}
/// <summary>Constructor. Used to setup colors and also the container if any.</summary>
/// <param name="baseColor">The color of filling. All other colors are deduced from it.</param>
/// <param name="parent">The HierarchicalSphere containing this sphere. If null, the sphere will not be limited in size.</param>
public HierarchicalSphere(Color baseColor, HierarchicalSphere parent = null)
{
m_Parent = parent;
m_Material = new Material(k_Material);
this.baseColor = baseColor;
}
/// <summary>Draw the hull which means the boxes without the handles</summary>
public void DrawHull(bool filled)
{
Color wireframeColor = m_HandleColor;
wireframeColor.a = 0.8f;
using (new Handles.DrawingScope(m_WireframeColor, Matrix4x4.TRS((Vector3)Handles.matrix.GetColumn(3) + center, Quaternion.identity, Vector3.one)))
{
if (filled)
{
material.SetPass(0);
Matrix4x4 drawMatrix = Matrix4x4.TRS((Vector3)Handles.matrix.GetColumn(3), Quaternion.identity, Vector3.one * radius * 2f);
Graphics.DrawMeshNow(k_MeshSphere, drawMatrix);
}
var drawCenter = Vector3.zero;
var viewPlaneNormal = Vector3.zero;
var drawnRadius = radius;
if (Camera.current.orthographic)
viewPlaneNormal = Camera.current.transform.forward;
else
{
viewPlaneNormal = (Vector3)Handles.matrix.GetColumn(3) - Camera.current.transform.position;
var sqrDist = viewPlaneNormal.sqrMagnitude; // squared distance from camera to center
var sqrRadius = radius * radius; // squared radius
var sqrOffset = sqrRadius * sqrRadius / sqrDist; // squared distance from actual center to drawn disc center
var insideAmount = sqrOffset / sqrRadius;
// If we are not inside the sphere, calculate where to draw the periphery
if (insideAmount < 1)
{
drawnRadius = Mathf.Sqrt(sqrRadius - sqrOffset); // the radius of the drawn disc
drawCenter -= (sqrRadius / sqrDist) * viewPlaneNormal;
}
}
Handles.zTest = UnityEngine.Rendering.CompareFunction.LessEqual;
Handles.DrawWireDisc(Vector3.zero, Vector3.up, radius);
Handles.DrawWireDisc(drawCenter, viewPlaneNormal, drawnRadius);
Handles.color = m_WireframeColorBehind;
Handles.zTest = UnityEngine.Rendering.CompareFunction.Greater;
Handles.DrawWireDisc(Vector3.zero, Vector3.up, radius);
Handles.DrawWireDisc(drawCenter, viewPlaneNormal, drawnRadius);
Handles.zTest = UnityEngine.Rendering.CompareFunction.Always;
}
}
/// <summary>Draw the manipulable handles</summary>
public void DrawHandle()
{
using (new Handles.DrawingScope(m_HandleColor))
{
radius = Handles.RadiusHandle(Quaternion.identity, center, radius, handlesOnly: true);
if(m_Parent != null)
radius = Mathf.Min(radius, m_Parent.radius - Vector3.Distance(center, m_Parent.center));
}
}
}
}

11
Packages/com.unity.render-pipelines.core/Editor/Gizmo/HierarchicalSphere.cs.meta


fileFormatVersion: 2
guid: 7ecd4b4b0c2b54742baf8d885609f3b9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

20
Packages/com.unity.render-pipelines.core/Editor/Gizmo/UnlitGizmo.shader


Shader "Hidden/UnlitTransparentColored" {
Properties {
_Color ("Main Color", Color) = (1,1,1,1)
}
SubShader {
Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}
ZWrite Off
Lighting Off
Fog { Mode Off }
Cull Off
Blend SrcAlpha OneMinusSrcAlpha
Pass {
Color [_Color]
}
}
}

9
Packages/com.unity.render-pipelines.core/Editor/Gizmo/UnlitGizmo.shader.meta


fileFormatVersion: 2
guid: 2df96b66b5510f94b98df2ac6f926ef4
ShaderImporter:
externalObjects: {}
defaultTextures: []
nonModifiableTextures: []
userData:
assetBundleName:
assetBundleVariant:

814
Packages/com.unity.render-pipelines.core/Editor/InspectorCurveEditor.cs


using System;
using System.Collections.Generic;
using UnityEngine;
namespace UnityEditor.Rendering
{
// A good chunk of this class is recycled from Post-processing v2 and could use a cleaning pass
public sealed class InspectorCurveEditor
{
enum EditMode
{
None,
Moving,
TangentEdit
}
enum Tangent
{
In,
Out
}
public struct Settings
{
public Rect bounds;
public RectOffset padding;
public Color selectionColor;
public float curvePickingDistance;
public float keyTimeClampingDistance;
public static Settings defaultSettings => new Settings
{
bounds = new Rect(0f, 0f, 1f, 1f),
padding = new RectOffset(0, 0, 0, 0),
selectionColor = Color.yellow,
curvePickingDistance = 6f,
keyTimeClampingDistance = 1e-4f
};
}
public struct CurveState
{
public bool visible;
public bool editable;
public uint minPointCount;
public float zeroKeyConstantValue;
public Color color;
public float width;
public float handleWidth;
public bool showNonEditableHandles;
public bool onlyShowHandlesOnSelection;
public bool loopInBounds;
public static CurveState defaultState => new CurveState
{
visible = true,
editable = true,
minPointCount = 2,
zeroKeyConstantValue = 0f,
color = Color.white,
width = 2f,
handleWidth = 2f,
showNonEditableHandles = true,
onlyShowHandlesOnSelection = false,
loopInBounds = false
};
}
public struct Selection
{
public SerializedProperty curve;
public int keyframeIndex;
public Keyframe? keyframe;
public Selection(SerializedProperty curve, int keyframeIndex, Keyframe? keyframe)
{
this.curve = curve;
this.keyframeIndex = keyframeIndex;
this.keyframe = keyframe;
}
}
internal struct MenuAction
{
internal SerializedProperty curve;
internal int index;
internal Vector3 position;
internal MenuAction(SerializedProperty curve)
{
this.curve = curve;
this.index = -1;
this.position = Vector3.zero;
}
internal MenuAction(SerializedProperty curve, int index)
{
this.curve = curve;
this.index = index;
this.position = Vector3.zero;
}
internal MenuAction(SerializedProperty curve, Vector3 position)
{
this.curve = curve;
this.index = -1;
this.position = position;
}
}
public readonly Settings settings;
readonly Dictionary<SerializedProperty, CurveState> m_Curves;
Rect m_CurveArea;
SerializedProperty m_SelectedCurve;
int m_SelectedKeyframeIndex = -1;
EditMode m_EditMode = EditMode.None;
Tangent m_TangentEditMode;
bool m_Dirty;
public InspectorCurveEditor()
: this(Settings.defaultSettings) { }
public InspectorCurveEditor(Settings settings)
{
this.settings = settings;
m_Curves = new Dictionary<SerializedProperty, CurveState>();
}
public void Add(params SerializedProperty[] curves)
{
foreach (var curve in curves)
Add(curve, CurveState.defaultState);
}
public void Add(SerializedProperty curve)
{
Add(curve, CurveState.defaultState);
}
public void Add(SerializedProperty curve, CurveState state)
{
// Make sure the property is in fact an AnimationCurve
var animCurve = curve.animationCurveValue;
if (animCurve == null)
throw new ArgumentException("curve");
if (m_Curves.ContainsKey(curve))
Debug.LogWarning("Curve has already been added to the editor");
m_Curves.Add(curve, state);
}
public void Remove(SerializedProperty curve)
{
m_Curves.Remove(curve);
}
public void RemoveAll()
{
m_Curves.Clear();
}
public CurveState GetCurveState(SerializedProperty curve)
{
if (!m_Curves.TryGetValue(curve, out var state))
throw new KeyNotFoundException("curve");
return state;
}
public void SetCurveState(SerializedProperty curve, CurveState state)
{
if (!m_Curves.ContainsKey(curve))
throw new KeyNotFoundException("curve");
m_Curves[curve] = state;
}
public Selection GetSelection()
{
Keyframe? key = null;
if (m_SelectedKeyframeIndex > -1)
{
var curve = m_SelectedCurve.animationCurveValue;
if (m_SelectedKeyframeIndex >= curve.length)
m_SelectedKeyframeIndex = -1;
else
key = curve[m_SelectedKeyframeIndex];
}
return new Selection(m_SelectedCurve, m_SelectedKeyframeIndex, key);
}
public void SetKeyframe(SerializedProperty curve, int keyframeIndex, Keyframe keyframe)
{
var animCurve = curve.animationCurveValue;
SetKeyframe(animCurve, keyframeIndex, keyframe);
SaveCurve(curve, animCurve);
}
public bool OnGUI(Rect rect)
{
if (Event.current.type == EventType.Repaint)
m_Dirty = false;
GUI.BeginClip(rect);
{
var area = new Rect(Vector2.zero, rect.size);
m_CurveArea = settings.padding.Remove(area);
foreach (var curve in m_Curves)
OnCurveGUI(area, curve.Key, curve.Value);
OnGeneralUI();
}
GUI.EndClip();
return m_Dirty;
}
void OnCurveGUI(Rect rect, SerializedProperty curve, CurveState state)
{
// Discard invisible curves
if (!state.visible)
return;
var animCurve = curve.animationCurveValue;
var keys = animCurve.keys;
var length = keys.Length;
// Curve drawing
// Slightly dim non-editable curves
var color = state.color;
if (!state.editable || !GUI.enabled)
color.a *= 0.5f;
Handles.color = color;
var bounds = settings.bounds;
if (length == 0)
{
var p1 = CurveToCanvas(new Vector3(bounds.xMin, state.zeroKeyConstantValue));
var p2 = CurveToCanvas(new Vector3(bounds.xMax, state.zeroKeyConstantValue));
Handles.DrawAAPolyLine(state.width, p1, p2);
}
else if (length == 1)
{
var p1 = CurveToCanvas(new Vector3(bounds.xMin, keys[0].value));
var p2 = CurveToCanvas(new Vector3(bounds.xMax, keys[0].value));
Handles.DrawAAPolyLine(state.width, p1, p2);
}
else
{
var prevKey = keys[0];
for (int k = 1; k < length; k++)
{
var key = keys[k];
var pts = BezierSegment(prevKey, key);
if (float.IsInfinity(prevKey.outTangent) || float.IsInfinity(key.inTangent))
{
var s = HardSegment(prevKey, key);
Handles.DrawAAPolyLine(state.width, s[0], s[1], s[2]);
}
else Handles.DrawBezier(pts[0], pts[3], pts[1], pts[2], color, null, state.width);
prevKey = key;
}
// Curve extents & loops
if (keys[0].time > bounds.xMin)
{
if (state.loopInBounds)
{
var p1 = keys[length - 1];
p1.time -= settings.bounds.width;
var p2 = keys[0];
var pts = BezierSegment(p1, p2);
if (float.IsInfinity(p1.outTangent) || float.IsInfinity(p2.inTangent))
{
var s = HardSegment(p1, p2);
Handles.DrawAAPolyLine(state.width, s[0], s[1], s[2]);
}
else Handles.DrawBezier(pts[0], pts[3], pts[1], pts[2], color, null, state.width);
}
else
{
var p1 = CurveToCanvas(new Vector3(bounds.xMin, keys[0].value));
var p2 = CurveToCanvas(keys[0]);
Handles.DrawAAPolyLine(state.width, p1, p2);
}
}
if (keys[length - 1].time < bounds.xMax)
{
if (state.loopInBounds)
{
var p1 = keys[length - 1];
var p2 = keys[0];
p2.time += settings.bounds.width;
var pts = BezierSegment(p1, p2);
if (float.IsInfinity(p1.outTangent) || float.IsInfinity(p2.inTangent))
{
var s = HardSegment(p1, p2);
Handles.DrawAAPolyLine(state.width, s[0], s[1], s[2]);
}
else Handles.DrawBezier(pts[0], pts[3], pts[1], pts[2], color, null, state.width);
}
else
{
var p1 = CurveToCanvas(keys[length - 1]);
var p2 = CurveToCanvas(new Vector3(bounds.xMax, keys[length - 1].value));
Handles.DrawAAPolyLine(state.width, p1, p2);
}
}
}
// Make sure selection is correct (undo can break it)
bool isCurrentlySelectedCurve = curve == m_SelectedCurve;
if (isCurrentlySelectedCurve && m_SelectedKeyframeIndex >= length)
m_SelectedKeyframeIndex = -1;
if (!state.editable)
m_SelectedKeyframeIndex = -1;
float enabledFactor = GUI.enabled ? 1f : 0.8f;
// Handles & keys
for (int k = 0; k < length; k++)
{
bool isCurrentlySelectedKeyframe = k == m_SelectedKeyframeIndex;
var e = Event.current;
var pos = CurveToCanvas(keys[k]);
var hitRect = new Rect(pos.x - 8f, pos.y - 8f, 16f, 16f);
var offset = isCurrentlySelectedCurve
? new RectOffset(5, 5, 5, 5)
: new RectOffset(6, 6, 6, 6);
var outTangent = pos + CurveTangentToCanvas(keys[k].outTangent).normalized * 40f;
var inTangent = pos - CurveTangentToCanvas(keys[k].inTangent).normalized * 40f;
var inTangentHitRect = new Rect(inTangent.x - 7f, inTangent.y - 7f, 14f, 14f);
var outTangentHitrect = new Rect(outTangent.x - 7f, outTangent.y - 7f, 14f, 14f);
// Draw
if (state.editable || state.showNonEditableHandles)
{
if (e.type == EventType.Repaint)
{
var selectedColor = (isCurrentlySelectedCurve && isCurrentlySelectedKeyframe)
? settings.selectionColor
: state.color;
// Keyframe
EditorGUI.DrawRect(offset.Remove(hitRect), selectedColor * enabledFactor);
// Tangents
if (length > 1 && isCurrentlySelectedCurve && (!state.onlyShowHandlesOnSelection || (state.onlyShowHandlesOnSelection && isCurrentlySelectedKeyframe)))
{
Handles.color = selectedColor * enabledFactor;
if (k > 0 || state.loopInBounds)
{
Handles.DrawAAPolyLine(state.handleWidth, pos, inTangent);
EditorGUI.DrawRect(offset.Remove(inTangentHitRect), selectedColor);
}
if (k < length - 1 || state.loopInBounds)
{
Handles.DrawAAPolyLine(state.handleWidth, pos, outTangent);
EditorGUI.DrawRect(offset.Remove(outTangentHitrect), selectedColor);
}
}
}
}
// Events
if (state.editable)
{
// Keyframe move
if (m_EditMode == EditMode.Moving && e.type == EventType.MouseDrag && isCurrentlySelectedCurve && isCurrentlySelectedKeyframe)
{
EditMoveKeyframe(animCurve, keys, k);
}
// Tangent editing
if (length > 1 && m_EditMode == EditMode.TangentEdit && e.type == EventType.MouseDrag && isCurrentlySelectedCurve && isCurrentlySelectedKeyframe)
{
bool alreadyBroken = !(Mathf.Approximately(keys[k].inTangent, keys[k].outTangent) || (float.IsInfinity(keys[k].inTangent) && float.IsInfinity(keys[k].outTangent)));
EditMoveTangent(animCurve, keys, k, m_TangentEditMode, e.shift || !(alreadyBroken || e.control));
}
// Keyframe selection & context menu
if (e.type == EventType.MouseDown && rect.Contains(e.mousePosition))
{
if (hitRect.Contains(e.mousePosition))
{
if (e.button == 0)
{
SelectKeyframe(curve, k);
m_EditMode = EditMode.Moving;
e.Use();
}
else if (e.button == 1)
{
// Keyframe context menu
var menu = new GenericMenu();
menu.AddItem(new GUIContent("Delete Key"), false, (x) =>
{
var action = (MenuAction)x;
var curveValue = action.curve.animationCurveValue;
action.curve.serializedObject.Update();
RemoveKeyframe(curveValue, action.index);
m_SelectedKeyframeIndex = -1;
SaveCurve(action.curve, curveValue);
action.curve.serializedObject.ApplyModifiedProperties();
}, new MenuAction(curve, k));
menu.ShowAsContext();
e.Use();
}
}
}
// Tangent selection & edit mode
if (e.type == EventType.MouseDown && length > 1 && rect.Contains(e.mousePosition))
{
if (inTangentHitRect.Contains(e.mousePosition) && (k > 0 || state.loopInBounds))
{
SelectKeyframe(curve, k);
m_EditMode = EditMode.TangentEdit;
m_TangentEditMode = Tangent.In;
e.Use();
}
else if (outTangentHitrect.Contains(e.mousePosition) && (k < length - 1 || state.loopInBounds))
{
SelectKeyframe(curve, k);
m_EditMode = EditMode.TangentEdit;
m_TangentEditMode = Tangent.Out;
e.Use();
}
}
// Mouse up - clean up states
if (e.rawType == EventType.MouseUp && m_EditMode != EditMode.None)
{
m_EditMode = EditMode.None;
}
// Set cursors
{
EditorGUIUtility.AddCursorRect(hitRect, MouseCursor.MoveArrow);
if (k > 0 || state.loopInBounds)
EditorGUIUtility.AddCursorRect(inTangentHitRect, MouseCursor.RotateArrow);
if (k < length - 1 || state.loopInBounds)
EditorGUIUtility.AddCursorRect(outTangentHitrect, MouseCursor.RotateArrow);
}
}
}
Handles.color = Color.white;
SaveCurve(curve, animCurve);
}
void OnGeneralUI()
{
var e = Event.current;
// Selection
if (e.type == EventType.MouseDown)
{
GUI.FocusControl(null);
m_SelectedCurve = null;
m_SelectedKeyframeIndex = -1;
bool used = false;
var hit = CanvasToCurve(e.mousePosition);
float curvePickValue = CurveToCanvas(hit).y;
// Try and select a curve
foreach (var curve in m_Curves)
{
if (!curve.Value.editable || !curve.Value.visible)
continue;
var prop = curve.Key;
var state = curve.Value;
var animCurve = prop.animationCurveValue;
float hitY = animCurve.length == 0
? state.zeroKeyConstantValue
: animCurve.Evaluate(hit.x);
var curvePos = CurveToCanvas(new Vector3(hit.x, hitY));
if (Mathf.Abs(curvePos.y - curvePickValue) < settings.curvePickingDistance)
{
m_SelectedCurve = prop;
if (e.clickCount == 2 && e.button == 0)
{
// Create a keyframe on double-click on this curve
EditCreateKeyframe(animCurve, hit, true, state.zeroKeyConstantValue);
SaveCurve(prop, animCurve);
}
else if (e.button == 1)
{
// Curve context menu
var menu = new GenericMenu();
menu.AddItem(new GUIContent("Add Key"), false, (x) =>
{
var action = (MenuAction)x;
var curveValue = action.curve.animationCurveValue;
action.curve.serializedObject.Update();
EditCreateKeyframe(curveValue, hit, true, 0f);
SaveCurve(action.curve, curveValue);
action.curve.serializedObject.ApplyModifiedProperties();
}, new MenuAction(prop, hit));
menu.ShowAsContext();
e.Use();
used = true;
}
}
}
if (e.clickCount == 2 && e.button == 0 && m_SelectedCurve == null)
{
// Create a keyframe on every curve on double-click
foreach (var curve in m_Curves)
{
if (!curve.Value.editable || !curve.Value.visible)
continue;
var prop = curve.Key;
var state = curve.Value;
var animCurve = prop.animationCurveValue;
EditCreateKeyframe(animCurve, hit, e.alt, state.zeroKeyConstantValue);
SaveCurve(prop, animCurve);
}
}
else if (!used && e.button == 1)
{
// Global context menu
var menu = new GenericMenu();
menu.AddItem(new GUIContent("Add Key At Position"), false, () => ContextMenuAddKey(hit, false));
menu.AddItem(new GUIContent("Add Key On Curves"), false, () => ContextMenuAddKey(hit, true));
menu.ShowAsContext();
}
e.Use();
}
// Delete selected key(s)
if (e.type == EventType.KeyDown && (e.keyCode == KeyCode.Delete || e.keyCode == KeyCode.Backspace))
{
if (m_SelectedKeyframeIndex != -1 && m_SelectedCurve != null)
{
var animCurve = m_SelectedCurve.animationCurveValue;
var length = animCurve.length;
if (m_Curves[m_SelectedCurve].minPointCount < length && length >= 0)
{
EditDeleteKeyframe(animCurve, m_SelectedKeyframeIndex);
m_SelectedKeyframeIndex = -1;
SaveCurve(m_SelectedCurve, animCurve);
}
e.Use();
}
}
}
void SaveCurve(SerializedProperty prop, AnimationCurve curve)
{
prop.animationCurveValue = curve;
}
void Invalidate()
{
m_Dirty = true;
}
void SelectKeyframe(SerializedProperty curve, int keyframeIndex)
{
m_SelectedKeyframeIndex = keyframeIndex;
m_SelectedCurve = curve;
Invalidate();
}
void ContextMenuAddKey(Vector3 hit, bool createOnCurve)
{
SerializedObject serializedObject = null;
foreach (var curve in m_Curves)
{
if (!curve.Value.editable || !curve.Value.visible)
continue;
var prop = curve.Key;
var state = curve.Value;
if (serializedObject == null)
{
serializedObject = prop.serializedObject;
serializedObject.Update();
}
var animCurve = prop.animationCurveValue;
EditCreateKeyframe(animCurve, hit, createOnCurve, state.zeroKeyConstantValue);
SaveCurve(prop, animCurve);
}
if (serializedObject != null)
serializedObject.ApplyModifiedProperties();
Invalidate();
}
void EditCreateKeyframe(AnimationCurve curve, Vector3 position, bool createOnCurve, float zeroKeyConstantValue)
{
float tangent = EvaluateTangent(curve, position.x);
if (createOnCurve)
{
position.y = curve.length == 0
? zeroKeyConstantValue
: curve.Evaluate(position.x);
}
AddKeyframe(curve, new Keyframe(position.x, position.y, tangent, tangent));
}
void EditDeleteKeyframe(AnimationCurve curve, int keyframeIndex)
{
RemoveKeyframe(curve, keyframeIndex);
}
void AddKeyframe(AnimationCurve curve, Keyframe newValue)
{
curve.AddKey(newValue);
Invalidate();
}
void RemoveKeyframe(AnimationCurve curve, int keyframeIndex)
{
curve.RemoveKey(keyframeIndex);
Invalidate();
}
void SetKeyframe(AnimationCurve curve, int keyframeIndex, Keyframe newValue)
{
var keys = curve.keys;
if (keyframeIndex > 0)
newValue.time = Mathf.Max(keys[keyframeIndex - 1].time + settings.keyTimeClampingDistance, newValue.time);
if (keyframeIndex < keys.Length - 1)
newValue.time = Mathf.Min(keys[keyframeIndex + 1].time - settings.keyTimeClampingDistance, newValue.time);
curve.MoveKey(keyframeIndex, newValue);
Invalidate();
}
void EditMoveKeyframe(AnimationCurve curve, Keyframe[] keys, int keyframeIndex)
{
var key = CanvasToCurve(Event.current.mousePosition);
float inTgt = keys[keyframeIndex].inTangent;
float outTgt = keys[keyframeIndex].outTangent;
SetKeyframe(curve, keyframeIndex, new Keyframe(key.x, key.y, inTgt, outTgt));
}
void EditMoveTangent(AnimationCurve curve, Keyframe[] keys, int keyframeIndex, Tangent targetTangent, bool linkTangents)
{
var pos = CanvasToCurve(Event.current.mousePosition);
float time = keys[keyframeIndex].time;
float value = keys[keyframeIndex].value;
pos -= new Vector3(time, value);
if (targetTangent == Tangent.In && pos.x > 0f)
pos.x = 0f;
if (targetTangent == Tangent.Out && pos.x < 0f)
pos.x = 0f;
float tangent;
if (Mathf.Approximately(pos.x, 0f))
tangent = float.PositiveInfinity;
else
tangent = pos.y / pos.x;
float inTangent = keys[keyframeIndex].inTangent;
float outTangent = keys[keyframeIndex].outTangent;
if (targetTangent == Tangent.In || linkTangents)
inTangent = tangent;
if (targetTangent == Tangent.Out || linkTangents)
outTangent = tangent;
SetKeyframe(curve, keyframeIndex, new Keyframe(time, value, inTangent, outTangent));
}
Vector3 CurveToCanvas(Keyframe keyframe)
{
return CurveToCanvas(new Vector3(keyframe.time, keyframe.value));
}
Vector3 CurveToCanvas(Vector3 position)
{
var bounds = settings.bounds;
var output = new Vector3((position.x - bounds.x) / (bounds.xMax - bounds.x), (position.y - bounds.y) / (bounds.yMax - bounds.y));
output.x = output.x * (m_CurveArea.xMax - m_CurveArea.xMin) + m_CurveArea.xMin;
output.y = (1f - output.y) * (m_CurveArea.yMax - m_CurveArea.yMin) + m_CurveArea.yMin;
return output;
}
Vector3 CanvasToCurve(Vector3 position)
{
var bounds = settings.bounds;
var output = position;
output.x = (output.x - m_CurveArea.xMin) / (m_CurveArea.xMax - m_CurveArea.xMin);
output.y = (output.y - m_CurveArea.yMin) / (m_CurveArea.yMax - m_CurveArea.yMin);
output.x = Mathf.Lerp(bounds.x, bounds.xMax, output.x);
output.y = Mathf.Lerp(bounds.yMax, bounds.y, output.y);
return output;
}
Vector3 CurveTangentToCanvas(float tangent)
{
if (!float.IsInfinity(tangent))
{
var bounds = settings.bounds;
float ratio = (m_CurveArea.width / m_CurveArea.height) / ((bounds.xMax - bounds.x) / (bounds.yMax - bounds.y));
return new Vector3(1f, -tangent / ratio).normalized;
}
return Vector3.up; // Positive infinity
}
Vector3[] BezierSegment(Keyframe start, Keyframe end)
{
var segment = new Vector3[4];
segment[0] = CurveToCanvas(new Vector3(start.time, start.value));
segment[3] = CurveToCanvas(new Vector3(end.time, end.value));
float middle = start.time + ((end.time - start.time) * 0.333333f);
float middle2 = start.time + ((end.time - start.time) * 0.666666f);
segment[1] = CurveToCanvas(new Vector3(middle, ProjectTangent(start.time, start.value, start.outTangent, middle)));
segment[2] = CurveToCanvas(new Vector3(middle2, ProjectTangent(end.time, end.value, end.inTangent, middle2)));
return segment;
}
Vector3[] HardSegment(Keyframe start, Keyframe end)
{
var segment = new Vector3[3];
segment[0] = CurveToCanvas(start);
segment[1] = CurveToCanvas(new Vector3(end.time, start.value));
segment[2] = CurveToCanvas(end);
return segment;
}
float ProjectTangent(float inPosition, float inValue, float inTangent, float projPosition)
{
return inValue + ((projPosition - inPosition) * inTangent);
}
float EvaluateTangent(AnimationCurve curve, float time)
{
int prev = -1, next = 0;
for (int i = 0; i < curve.keys.Length; i++)
{
if (time > curve.keys[i].time)
{
prev = i;
next = i + 1;
}
else break;
}
if (next == 0)
return 0f;
if (prev == curve.keys.Length - 1)
return 0f;
const float kD = 1e-3f;
float tp = Mathf.Max(time - kD, curve.keys[prev].time);
float tn = Mathf.Min(time + kD, curve.keys[next].time);
float vp = curve.Evaluate(tp);
float vn = curve.Evaluate(tn);
if (Mathf.Approximately(tn, tp))
return float.PositiveInfinity;
return (vn - vp) / (tn - tp);
}
}
}

11
Packages/com.unity.render-pipelines.core/Editor/InspectorCurveEditor.cs.meta


fileFormatVersion: 2
guid: 513fb6069b4efa74ea0f21f894eed362
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

8
Packages/com.unity.render-pipelines.core/Editor/Lighting.meta


fileFormatVersion: 2
guid: a20277bec4b2cc14dbfbc6e401f1f8f1
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

626
Packages/com.unity.render-pipelines.core/Editor/Lighting/CoreLightEditorUtilities.cs


using System;
using UnityEngine;
namespace UnityEditor.Rendering
{
public static class CoreLightEditorUtilities
{
static Vector2 SliderPlaneHandle(Vector3 origin, Vector3 axis1, Vector3 axis2, Vector2 position)
{
Vector3 pos = origin + position.x * axis1 + position.y * axis2;
float sizeHandle = HandleUtility.GetHandleSize(pos);
bool temp = GUI.changed;
GUI.changed = false;
pos = Handles.Slider2D(pos, Vector3.forward, axis1, axis2, sizeHandle * 0.03f, Handles.DotHandleCap, 0f);
if (GUI.changed)
{
position = new Vector2(Vector3.Dot(pos, axis1), Vector3.Dot(pos, axis2));
}
GUI.changed |= temp;
return position;
}
static float SliderLineHandle(Vector3 position, Vector3 direction, float value)
{
Vector3 pos = position + direction * value;
float sizeHandle = HandleUtility.GetHandleSize(pos);
bool temp = GUI.changed;
GUI.changed = false;
pos = Handles.Slider(pos, direction, sizeHandle * 0.03f, Handles.DotHandleCap, 0f);
if (GUI.changed)
{
value = Vector3.Dot(pos - position, direction);
}
GUI.changed |= temp;
return value;
}
static float SliderCircleHandle(Vector3 position, Vector3 normal, Vector3 zeroValueDirection, float angleValue, float radius)
{
zeroValueDirection.Normalize();
normal.Normalize();
Quaternion rot = Quaternion.AngleAxis(angleValue, normal);
Vector3 pos = position + rot * zeroValueDirection * radius;
float sizeHandle = HandleUtility.GetHandleSize(pos);
bool temp = GUI.changed;
GUI.changed = false;
Vector3 tangeant = Vector3.Cross(normal, (pos - position).normalized);
pos = Handles.Slider(pos, tangeant, sizeHandle * 0.03f, Handles.DotHandleCap, 0f);
if (GUI.changed)
{
Vector3 dir = (pos - position).normalized;
Vector3 cross = Vector3.Cross(zeroValueDirection, dir);
int sign = ((cross - normal).sqrMagnitude < (-cross - normal).sqrMagnitude) ? 1 : -1;
angleValue = Mathf.Acos(Vector3.Dot(zeroValueDirection, dir)) * Mathf.Rad2Deg * sign;
}
GUI.changed |= temp;
return angleValue;
}
static int s_SliderSpotAngleId;
static float SizeSliderSpotAngle(Vector3 position, Vector3 forward, Vector3 axis, float range, float spotAngle)
{
if (Math.Abs(spotAngle) <= 0.05f && GUIUtility.hotControl != s_SliderSpotAngleId)
return spotAngle;
var angledForward = Quaternion.AngleAxis(Mathf.Max(spotAngle, 0.05f) * 0.5f, axis) * forward;
var centerToLeftOnSphere = (angledForward * range + position) - (position + forward * range);
bool temp = GUI.changed;
GUI.changed = false;
var newMagnitude = Mathf.Max(0f, SliderLineHandle(position + forward * range, centerToLeftOnSphere.normalized, centerToLeftOnSphere.magnitude));
if (GUI.changed)
{
s_SliderSpotAngleId = GUIUtility.hotControl;
centerToLeftOnSphere = centerToLeftOnSphere.normalized * newMagnitude;
angledForward = (centerToLeftOnSphere + (position + forward * range) - position).normalized;
spotAngle = Mathf.Clamp(Mathf.Acos(Vector3.Dot(forward, angledForward)) * Mathf.Rad2Deg * 2, 0f, 179f);
if (spotAngle <= 0.05f || float.IsNaN(spotAngle))
spotAngle = 0f;
}
GUI.changed |= temp;
return spotAngle;
}
public static Color GetLightHandleColor(Color wireframeColor)
{
Color color = wireframeColor;
color.a = Mathf.Clamp01(color.a * 2);
return (QualitySettings.activeColorSpace == ColorSpace.Linear) ? color.linear : color;
}
public static Color GetLightBehindObjectWireframeColor(Color wireframeColor)
{
Color color = wireframeColor;
color.a = 0.2f;
return (QualitySettings.activeColorSpace == ColorSpace.Linear) ? color.linear : color;
}
// Don't use Handles.Disc as it break the highlight of the gizmo axis, use our own draw disc function instead for gizmo
public static void DrawWireDisc(Quaternion q, Vector3 position, Vector3 axis, float radius)
{
Matrix4x4 rotation = Matrix4x4.TRS(Vector3.zero, q, Vector3.one);
Gizmos.color = Color.white;
float theta = 0.0f;
float x = radius * Mathf.Cos(theta);
float y = radius * Mathf.Sin(theta);
Vector3 pos = rotation * new Vector3(x, y, 0);
pos += position;
Vector3 newPos = pos;
Vector3 lastPos = pos;
for (theta = 0.1f; theta < 2.0f * Mathf.PI; theta += 0.1f)
{
x = radius * Mathf.Cos(theta);
y = radius * Mathf.Sin(theta);
newPos = rotation * new Vector3(x, y, 0);
newPos += position;
Gizmos.DrawLine(pos, newPos);
pos = newPos;
}
Gizmos.DrawLine(pos, lastPos);
}
public static void DrawSpotlightWireframe(Vector3 outerAngleInnerAngleRange, float shadowPlaneDistance = -1f)
{
float outerAngle = outerAngleInnerAngleRange.x;
float innerAngle = outerAngleInnerAngleRange.y;
float range = outerAngleInnerAngleRange.z;
var outerDiscRadius = range * Mathf.Sin(outerAngle * Mathf.Deg2Rad * 0.5f);
var outerDiscDistance = Mathf.Cos(Mathf.Deg2Rad * outerAngle * 0.5f) * range;
var vectorLineUp = Vector3.Normalize(Vector3.forward * outerDiscDistance + Vector3.up * outerDiscRadius);
var vectorLineLeft = Vector3.Normalize(Vector3.forward * outerDiscDistance + Vector3.left * outerDiscRadius);
if(innerAngle>0f)
{
var innerDiscRadius = range * Mathf.Sin(innerAngle * Mathf.Deg2Rad * 0.5f);
var innerDiscDistance = Mathf.Cos(Mathf.Deg2Rad * innerAngle * 0.5f) * range;
DrawConeWireframe(innerDiscRadius, innerDiscDistance);
}
DrawConeWireframe(outerDiscRadius, outerDiscDistance);
Handles.DrawWireArc(Vector3.zero, Vector3.right, vectorLineUp, outerAngle, range);
Handles.DrawWireArc(Vector3.zero, Vector3.up, vectorLineLeft, outerAngle, range);
if (shadowPlaneDistance > 0)
{
var shadowDiscRadius = shadowPlaneDistance * Mathf.Sin(outerAngle * Mathf.Deg2Rad * 0.5f);
var shadowDiscDistance = Mathf.Cos(Mathf.Deg2Rad * outerAngle / 2) * shadowPlaneDistance;
Handles.DrawWireDisc(Vector3.forward * shadowDiscDistance, Vector3.forward, shadowDiscRadius);
}
}
static void DrawConeWireframe(float radius, float height)
{
var rangeCenter = Vector3.forward * height;
var rangeUp = rangeCenter + Vector3.up * radius;
var rangeDown = rangeCenter - Vector3.up * radius;
var rangeRight = rangeCenter + Vector3.right * radius;
var rangeLeft = rangeCenter - Vector3.right * radius;
//Draw Lines
Handles.DrawLine(Vector3.zero, rangeUp);
Handles.DrawLine(Vector3.zero, rangeDown);
Handles.DrawLine(Vector3.zero, rangeRight);
Handles.DrawLine(Vector3.zero, rangeLeft);
Handles.DrawWireDisc(Vector3.forward * height, Vector3.forward, radius);
}
public static Vector3 DrawSpotlightHandle(Vector3 outerAngleInnerAngleRange)
{
float outerAngle = outerAngleInnerAngleRange.x;
float innerAngle = outerAngleInnerAngleRange.y;
float range = outerAngleInnerAngleRange.z;
if (innerAngle > 0f)
{
innerAngle = SizeSliderSpotAngle(Vector3.zero, Vector3.forward, Vector3.right, range, innerAngle);
innerAngle = SizeSliderSpotAngle(Vector3.zero, Vector3.forward, Vector3.left, range, innerAngle);
innerAngle = SizeSliderSpotAngle(Vector3.zero, Vector3.forward, Vector3.up, range, innerAngle);
innerAngle = SizeSliderSpotAngle(Vector3.zero, Vector3.forward, Vector3.down, range, innerAngle);
}
outerAngle = SizeSliderSpotAngle(Vector3.zero, Vector3.forward, Vector3.right, range, outerAngle);
outerAngle = SizeSliderSpotAngle(Vector3.zero, Vector3.forward, Vector3.left, range, outerAngle);
outerAngle = SizeSliderSpotAngle(Vector3.zero, Vector3.forward, Vector3.up, range, outerAngle);
outerAngle = SizeSliderSpotAngle(Vector3.zero, Vector3.forward, Vector3.down, range, outerAngle);
range = SliderLineHandle(Vector3.zero, Vector3.forward, range);
return new Vector3(outerAngle, innerAngle, range);
}
public static void DrawAreaLightWireframe(Vector2 rectangleSize)
{
Handles.DrawWireCube(Vector3.zero, rectangleSize);
}
public static Vector2 DrawAreaLightHandle(Vector2 rectangleSize, bool withYAxis)
{
float halfWidth = rectangleSize.x * 0.5f;
float halfHeight = rectangleSize.y * 0.5f;
EditorGUI.BeginChangeCheck();
halfWidth = SliderLineHandle(Vector3.zero, Vector3.right, halfWidth);
halfWidth = SliderLineHandle(Vector3.zero, Vector3.left, halfWidth);
if (EditorGUI.EndChangeCheck())
{
halfWidth = Mathf.Max(0f, halfWidth);
}
if (withYAxis)
{
EditorGUI.BeginChangeCheck();
halfHeight = SliderLineHandle(Vector3.zero, Vector3.up, halfHeight);
halfHeight = SliderLineHandle(Vector3.zero, Vector3.down, halfHeight);
if (EditorGUI.EndChangeCheck())
{
halfHeight = Mathf.Max(0f, halfHeight);
}
}
return new Vector2(halfWidth * 2f, halfHeight * 2f);
}
// Same as Gizmo.DrawFrustum except that when aspect is below one, fov represent fovX instead of fovY
// Use to match our light frustum pyramid behavior
public static void DrawPyramidFrustumWireframe(Vector4 aspectFovMaxRangeMinRange, float distanceTruncPlane = 0f)
{
float aspect = aspectFovMaxRangeMinRange.x;
float fov = aspectFovMaxRangeMinRange.y;
float maxRange = aspectFovMaxRangeMinRange.z;
float minRange = aspectFovMaxRangeMinRange.w;
float tanfov = Mathf.Tan(Mathf.Deg2Rad * fov * 0.5f);
var startAngles = new Vector3[4];
if (minRange > 0.0f)
{
startAngles = GetFrustrumProjectedRectAngles(minRange, aspect, tanfov);
Handles.DrawLine(startAngles[0], startAngles[1]);
Handles.DrawLine(startAngles[1], startAngles[2]);
Handles.DrawLine(startAngles[2], startAngles[3]);
Handles.DrawLine(startAngles[3], startAngles[0]);
}
if (distanceTruncPlane > 0f)
{
var truncAngles = GetFrustrumProjectedRectAngles(distanceTruncPlane, aspect, tanfov);
Handles.DrawLine(truncAngles[0], truncAngles[1]);
Handles.DrawLine(truncAngles[1], truncAngles[2]);
Handles.DrawLine(truncAngles[2], truncAngles[3]);
Handles.DrawLine(truncAngles[3], truncAngles[0]);
}
var endAngles = GetFrustrumProjectedRectAngles(maxRange, aspect, tanfov);
Handles.DrawLine(endAngles[0], endAngles[1]);
Handles.DrawLine(endAngles[1], endAngles[2]);
Handles.DrawLine(endAngles[2], endAngles[3]);
Handles.DrawLine(endAngles[3], endAngles[0]);
Handles.DrawLine(startAngles[0], endAngles[0]);
Handles.DrawLine(startAngles[1], endAngles[1]);
Handles.DrawLine(startAngles[2], endAngles[2]);
Handles.DrawLine(startAngles[3], endAngles[3]);
}
// Same as Gizmo.DrawFrustum except that when aspect is below one, fov represent fovX instead of fovY
// Use to match our light frustum pyramid behavior
public static void DrawSpherePortionWireframe(Vector4 aspectFovMaxRangeMinRange, float distanceTruncPlane = 0f)
{
float aspect = aspectFovMaxRangeMinRange.x;
float fov = aspectFovMaxRangeMinRange.y;
float maxRange = aspectFovMaxRangeMinRange.z;
float minRange = aspectFovMaxRangeMinRange.w;
float tanfov = Mathf.Tan(Mathf.Deg2Rad * fov * 0.5f);
var startAngles = new Vector3[4];
if (minRange > 0f)
{
startAngles = GetFrustrumProjectedRectAngles(minRange, aspect, tanfov);
Handles.DrawLine(startAngles[0], startAngles[1]);
Handles.DrawLine(startAngles[1], startAngles[2]);
Handles.DrawLine(startAngles[2], startAngles[3]);
Handles.DrawLine(startAngles[3], startAngles[0]);
}
if (distanceTruncPlane > 0f)
{
var truncAngles = GetFrustrumProjectedRectAngles(distanceTruncPlane, aspect, tanfov);
Handles.DrawLine(truncAngles[0], truncAngles[1]);
Handles.DrawLine(truncAngles[1], truncAngles[2]);
Handles.DrawLine(truncAngles[2], truncAngles[3]);
Handles.DrawLine(truncAngles[3], truncAngles[0]);
}
var endAngles = GetSphericalProjectedRectAngles(maxRange, aspect, tanfov);
var planProjectedCrossNormal0 = new Vector3(endAngles[0].y, -endAngles[0].x, 0).normalized;
var planProjectedCrossNormal1 = new Vector3(endAngles[1].y, -endAngles[1].x, 0).normalized;
Vector3[] faceNormals = new[] {
Vector3.right - Vector3.Dot((endAngles[3] + endAngles[0]).normalized, Vector3.right) * (endAngles[3] + endAngles[0]).normalized,
Vector3.up - Vector3.Dot((endAngles[0] + endAngles[1]).normalized, Vector3.up) * (endAngles[0] + endAngles[1]).normalized,
Vector3.left - Vector3.Dot((endAngles[1] + endAngles[2]).normalized, Vector3.left) * (endAngles[1] + endAngles[2]).normalized,
Vector3.down - Vector3.Dot((endAngles[2] + endAngles[3]).normalized, Vector3.down) * (endAngles[2] + endAngles[3]).normalized,
//cross
planProjectedCrossNormal0 - Vector3.Dot((endAngles[1] + endAngles[3]).normalized, planProjectedCrossNormal0) * (endAngles[1] + endAngles[3]).normalized,
planProjectedCrossNormal1 - Vector3.Dot((endAngles[0] + endAngles[2]).normalized, planProjectedCrossNormal1) * (endAngles[0] + endAngles[2]).normalized,
};
float[] faceAngles = new[] {
Vector3.Angle(endAngles[3], endAngles[0]),
Vector3.Angle(endAngles[0], endAngles[1]),
Vector3.Angle(endAngles[1], endAngles[2]),
Vector3.Angle(endAngles[2], endAngles[3]),
Vector3.Angle(endAngles[1], endAngles[3]),
Vector3.Angle(endAngles[0], endAngles[2]),
};
Handles.DrawWireArc(Vector3.zero, faceNormals[0], endAngles[0], faceAngles[0], maxRange);
Handles.DrawWireArc(Vector3.zero, faceNormals[1], endAngles[1], faceAngles[1], maxRange);
Handles.DrawWireArc(Vector3.zero, faceNormals[2], endAngles[2], faceAngles[2], maxRange);
Handles.DrawWireArc(Vector3.zero, faceNormals[3], endAngles[3], faceAngles[3], maxRange);
Handles.DrawWireArc(Vector3.zero, faceNormals[4], endAngles[0], faceAngles[4], maxRange);
Handles.DrawWireArc(Vector3.zero, faceNormals[5], endAngles[1], faceAngles[5], maxRange);
Handles.DrawLine(startAngles[0], endAngles[0]);
Handles.DrawLine(startAngles[1], endAngles[1]);
Handles.DrawLine(startAngles[2], endAngles[2]);
Handles.DrawLine(startAngles[3], endAngles[3]);
}
static Vector3[] GetFrustrumProjectedRectAngles(float distance, float aspect, float tanFOV)
{
Vector3 sizeX;
Vector3 sizeY;
float minXYTruncSize = distance * tanFOV;
if (aspect >= 1.0f)
{
sizeX = new Vector3(minXYTruncSize * aspect, 0, 0);
sizeY = new Vector3(0, minXYTruncSize, 0);
}
else
{
sizeX = new Vector3(minXYTruncSize, 0, 0);
sizeY = new Vector3(0, minXYTruncSize / aspect, 0);
}
Vector3 center = new Vector3(0, 0, distance);
Vector3[] angles =
{
center + sizeX + sizeY,
center - sizeX + sizeY,
center - sizeX - sizeY,
center + sizeX - sizeY
};
return angles;
}
static Vector3[] GetSphericalProjectedRectAngles(float distance, float aspect, float tanFOV)
{
var angles = GetFrustrumProjectedRectAngles(distance, aspect, tanFOV);
for (int index = 0; index < 4; ++index)
angles[index] = angles[index].normalized * distance;
return angles;
}
public static Vector4 DrawPyramidFrustumHandle(Vector4 aspectFovMaxRangeMinRange, bool useNearPlane, float minAspect = 0.05f, float maxAspect = 20f, float minFov = 1f)
{
float aspect = aspectFovMaxRangeMinRange.x;
float fov = aspectFovMaxRangeMinRange.y;
float maxRange = aspectFovMaxRangeMinRange.z;
float minRange = aspectFovMaxRangeMinRange.w;
float tanfov = Mathf.Tan(Mathf.Deg2Rad * fov * 0.5f);
var e = GetFrustrumProjectedRectAngles(maxRange, aspect, tanfov);
if (useNearPlane)
{
minRange = SliderLineHandle(Vector3.zero, Vector3.forward, minRange);
}
maxRange = SliderLineHandle(Vector3.zero, Vector3.forward, maxRange);
float distanceRight = HandleUtility.DistanceToLine(e[0], e[3]);
float distanceLeft = HandleUtility.DistanceToLine(e[1], e[2]);
float distanceUp = HandleUtility.DistanceToLine(e[0], e[1]);
float distanceDown = HandleUtility.DistanceToLine(e[2], e[3]);
int pointIndex = 0;
if (distanceRight < distanceLeft)
{
if (distanceUp < distanceDown)
pointIndex = 0;
else
pointIndex = 3;
}
else
{
if (distanceUp < distanceDown)
pointIndex = 1;
else
pointIndex = 2;
}
Vector2 send = e[pointIndex];
Vector3 farEnd = new Vector3(0, 0, maxRange);
EditorGUI.BeginChangeCheck();
Vector2 received = SliderPlaneHandle(farEnd, Vector3.right, Vector3.up, send);
if (EditorGUI.EndChangeCheck())
{
bool fixedFov = Event.current.control && !Event.current.shift;
bool fixedAspect = Event.current.shift && !Event.current.control;
//work on positive quadrant
int xSign = send.x < 0f ? -1 : 1;
int ySign = send.y < 0f ? -1 : 1;
Vector2 corrected = new Vector2(received.x * xSign, received.y * ySign);
//fixed aspect correction
if (fixedAspect)
{
corrected.x = corrected.y * aspect;
}
//remove aspect deadzone
if (corrected.x > maxAspect * corrected.y)
{
corrected.y = corrected.x * minAspect;
}
if (corrected.x < minAspect * corrected.y)
{
corrected.x = corrected.y / maxAspect;
}
//remove fov deadzone
float deadThresholdFoV = Mathf.Tan(Mathf.Deg2Rad * minFov * 0.5f) * maxRange;
corrected.x = Mathf.Max(corrected.x, deadThresholdFoV);
corrected.y = Mathf.Max(corrected.y, deadThresholdFoV, Mathf.Epsilon * 100); //prevent any division by zero
if (!fixedAspect)
{
aspect = corrected.x / corrected.y;
}
float min = Mathf.Min(corrected.x, corrected.y);
if (!fixedFov && maxRange > Mathf.Epsilon * 100)
{
fov = Mathf.Atan(min / maxRange) * 2f * Mathf.Rad2Deg;
}
}
return new Vector4(aspect, fov, maxRange, minRange);
}
public static Vector4 DrawSpherePortionHandle(Vector4 aspectFovMaxRangeMinRange, bool useNearPlane, float minAspect = 0.05f, float maxAspect = 20f, float minFov = 1f)
{
float aspect = aspectFovMaxRangeMinRange.x;
float fov = aspectFovMaxRangeMinRange.y;
float maxRange = aspectFovMaxRangeMinRange.z;
float minRange = aspectFovMaxRangeMinRange.w;
float tanfov = Mathf.Tan(Mathf.Deg2Rad * fov * 0.5f);
var endAngles = GetSphericalProjectedRectAngles(maxRange, aspect, tanfov);
if (useNearPlane)
{
minRange = SliderLineHandle(Vector3.zero, Vector3.forward, minRange);
}
maxRange = SliderLineHandle(Vector3.zero, Vector3.forward, maxRange);
float distanceRight = HandleUtility.DistanceToLine(endAngles[0], endAngles[3]);
float distanceLeft = HandleUtility.DistanceToLine(endAngles[1], endAngles[2]);
float distanceUp = HandleUtility.DistanceToLine(endAngles[0], endAngles[1]);
float distanceDown = HandleUtility.DistanceToLine(endAngles[2], endAngles[3]);
int pointIndex = 0;
if (distanceRight < distanceLeft)
{
if (distanceUp < distanceDown)
pointIndex = 0;
else
pointIndex = 3;
}
else
{
if (distanceUp < distanceDown)
pointIndex = 1;
else
pointIndex = 2;
}
Vector2 send = endAngles[pointIndex];
Vector3 farEnd = new Vector3(0, 0, endAngles[0].z);
EditorGUI.BeginChangeCheck();
Vector2 received = SliderPlaneHandle(farEnd, Vector3.right, Vector3.up, send);
if (EditorGUI.EndChangeCheck())
{
bool fixedFov = Event.current.control && !Event.current.shift;
bool fixedAspect = Event.current.shift && !Event.current.control;
//work on positive quadrant
int xSign = send.x < 0f ? -1 : 1;
int ySign = send.y < 0f ? -1 : 1;
Vector2 corrected = new Vector2(received.x * xSign, received.y * ySign);
//fixed aspect correction
if (fixedAspect)
{
corrected.x = corrected.y * aspect;
}
//remove aspect deadzone
if (corrected.x > maxAspect * corrected.y)
{
corrected.y = corrected.x * minAspect;
}
if (corrected.x < minAspect * corrected.y)
{
corrected.x = corrected.y / maxAspect;
}
//remove fov deadzone
float deadThresholdFoV = Mathf.Tan(Mathf.Deg2Rad * minFov * 0.5f) * maxRange;
corrected.x = Mathf.Max(corrected.x, deadThresholdFoV);
corrected.y = Mathf.Max(corrected.y, deadThresholdFoV, Mathf.Epsilon * 100); //prevent any division by zero
if (!fixedAspect)
{
aspect = corrected.x / corrected.y;
}
float min = Mathf.Min(corrected.x, corrected.y);
if (!fixedFov && maxRange > Mathf.Epsilon * 100)
{
fov = Mathf.Atan(min / maxRange) * 2f * Mathf.Rad2Deg;
}
}
return new Vector4(aspect, fov, maxRange, minRange);
}
public static void DrawOrthoFrustumWireframe(Vector4 widthHeightMaxRangeMinRange, float distanceTruncPlane = 0f)
{
float halfWidth = widthHeightMaxRangeMinRange.x * 0.5f;
float halfHeight = widthHeightMaxRangeMinRange.y * 0.5f;
float maxRange = widthHeightMaxRangeMinRange.z;
float minRange = widthHeightMaxRangeMinRange.w;
Vector3 sizeX = new Vector3(halfWidth, 0, 0);
Vector3 sizeY = new Vector3(0, halfHeight, 0);
Vector3 nearEnd = new Vector3(0, 0, minRange);
Vector3 farEnd = new Vector3(0, 0, maxRange);
Vector3 s1 = nearEnd + sizeX + sizeY;
Vector3 s2 = nearEnd - sizeX + sizeY;
Vector3 s3 = nearEnd - sizeX - sizeY;
Vector3 s4 = nearEnd + sizeX - sizeY;
Vector3 e1 = farEnd + sizeX + sizeY;
Vector3 e2 = farEnd - sizeX + sizeY;
Vector3 e3 = farEnd - sizeX - sizeY;
Vector3 e4 = farEnd + sizeX - sizeY;
Handles.DrawLine(s1, s2);
Handles.DrawLine(s2, s3);
Handles.DrawLine(s3, s4);
Handles.DrawLine(s4, s1);
Handles.DrawLine(e1, e2);
Handles.DrawLine(e2, e3);
Handles.DrawLine(e3, e4);
Handles.DrawLine(e4, e1);
Handles.DrawLine(s1, e1);
Handles.DrawLine(s2, e2);
Handles.DrawLine(s3, e3);
Handles.DrawLine(s4, e4);
if (distanceTruncPlane> 0f)
{
Vector3 truncPoint = new Vector3(0, 0, distanceTruncPlane);
Vector3 t1 = truncPoint + sizeX + sizeY;
Vector3 t2 = truncPoint - sizeX + sizeY;
Vector3 t3 = truncPoint - sizeX - sizeY;
Vector3 t4 = truncPoint + sizeX - sizeY;
Handles.DrawLine(t1, t2);
Handles.DrawLine(t2, t3);
Handles.DrawLine(t3, t4);
Handles.DrawLine(t4, t1);
}
}
public static Vector4 DrawOrthoFrustumHandle(Vector4 widthHeightMaxRangeMinRange, bool useNearHandle)
{
float halfWidth = widthHeightMaxRangeMinRange.x * 0.5f;
float halfHeight = widthHeightMaxRangeMinRange.y * 0.5f;
float maxRange = widthHeightMaxRangeMinRange.z;
float minRange = widthHeightMaxRangeMinRange.w;
Vector3 farEnd = new Vector3(0, 0, maxRange);
if (useNearHandle)
{
minRange = SliderLineHandle(Vector3.zero, Vector3.forward, minRange);
}
maxRange = SliderLineHandle(Vector3.zero, Vector3.forward, maxRange);
EditorGUI.BeginChangeCheck();
halfWidth = SliderLineHandle(farEnd, Vector3.right, halfWidth);
halfWidth = SliderLineHandle(farEnd, Vector3.left, halfWidth);
if (EditorGUI.EndChangeCheck())
{
halfWidth = Mathf.Max(0f, halfWidth);
}
EditorGUI.BeginChangeCheck();
halfHeight = SliderLineHandle(farEnd, Vector3.up, halfHeight);
halfHeight = SliderLineHandle(farEnd, Vector3.down, halfHeight);
if (EditorGUI.EndChangeCheck())
{
halfHeight = Mathf.Max(0f, halfHeight);
}
return new Vector4(halfWidth * 2f, halfHeight * 2f, maxRange, minRange);
}
}
}

11
Packages/com.unity.render-pipelines.core/Editor/Lighting/CoreLightEditorUtilities.cs.meta


fileFormatVersion: 2
guid: 8f89186f308429942b61ec29b405e2dd
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

8
Packages/com.unity.render-pipelines.core/Editor/LookDev.meta


fileFormatVersion: 2
guid: 2d4bb8a5c15072e4baaabe7e161d42b6
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

493
Packages/com.unity.render-pipelines.core/Editor/LookDev/CameraController.cs


using System;
using UnityEngine;
using UnityEditor.ShortcutManagement;
using UnityEngine.UIElements;
namespace UnityEditor.Rendering.LookDev
{
class CameraController : Manipulator
{
float m_StartZoom = 0.0f;
float m_ZoomSpeed = 0.0f;
float m_TotalMotion = 0.0f;
Vector3 m_MotionDirection = new Vector3();
float m_FlySpeedNormalized = .5f;
float m_FlySpeed = 1f;
float m_FlySpeedAccelerated = 0f;
const float m_FlySpeedMin = .01f;
const float m_FlySpeedMax = 2f;
//[TODO: check if necessary to add hability to deactivate acceleration]
const float k_FlyAcceleration = 1.1f;
bool m_ShiftBoostedFly = false;
bool m_InFlyMotion;
bool m_IsDragging;
ViewTool m_BehaviorState;
static TimeHelper s_Timer = new TimeHelper();
protected CameraState m_CameraState;
DisplayWindow m_Window;
protected Action m_Focused;
Rect screen => target.contentRect;
bool inFlyMotion
{
get => m_InFlyMotion;
set
{
if (value ^ m_InFlyMotion)
{
if (value)
{
s_Timer.Begin();
EditorApplication.update += UpdateMotion;
}
else
{
m_FlySpeedAccelerated = 0f;
m_MotionDirection = Vector3.zero;
m_ShiftBoostedFly = false;
EditorApplication.update -= UpdateMotion;
}
m_InFlyMotion = value;
}
}
}
float flySpeedNormalized
{
get => m_FlySpeedNormalized;
set
{
m_FlySpeedNormalized = Mathf.Clamp01(value);
float speed = Mathf.Lerp(m_FlySpeedMin, m_FlySpeedMax, m_FlySpeedNormalized);
// Round to nearest decimal: 2 decimal points when between [0.01, 0.1]; 1 decimal point when between [0.1, 10]; integral between [10, 99]
speed = (float)(System.Math.Round((double)speed, speed < 0.1f ? 2 : speed < 10f ? 1 : 0));
m_FlySpeed = Mathf.Clamp(speed, m_FlySpeedMin, m_FlySpeedMax);
}
}
float flySpeed
{
get => m_FlySpeed;
set => flySpeedNormalized = Mathf.InverseLerp(m_FlySpeedMin, m_FlySpeedMax, value);
}
virtual protected bool isDragging
{
get => m_IsDragging;
set
{
//As in scene view, stop dragging as first button is release in case of multiple button down
if (value ^ m_IsDragging)
{
if (value)
{
target.RegisterCallback<MouseMoveEvent>(OnMouseDrag);
target.CaptureMouse();
EditorGUIUtility.SetWantsMouseJumping(1);
}
else
{
EditorGUIUtility.SetWantsMouseJumping(0);
target.ReleaseMouse();
target.UnregisterCallback<MouseMoveEvent>(OnMouseDrag);
}
m_IsDragging = value;
}
}
}
public CameraController(CameraState cameraState, DisplayWindow window, Action focused)
{
m_CameraState = cameraState;
m_Window = window;
m_Focused = focused;
}
private void ResetCameraControl()
{
isDragging = false;
inFlyMotion = false;
m_BehaviorState = ViewTool.None;
}
protected virtual void OnScrollWheel(WheelEvent evt)
{
// See UnityEditor.SceneViewMotion.HandleScrollWheel
switch (m_BehaviorState)
{
case ViewTool.FPS: OnChangeFPSCameraSpeed(evt); break;
default: OnZoom(evt); break;
}
}
void OnMouseDrag(MouseMoveEvent evt)
{
switch (m_BehaviorState)
{
case ViewTool.Orbit: OnMouseDragOrbit(evt); break;
case ViewTool.FPS: OnMouseDragFPS(evt); break;
case ViewTool.Pan: OnMouseDragPan(evt); break;
case ViewTool.Zoom: OnMouseDragZoom(evt); break;
default: break;
}
}
void OnKeyDown(KeyDownEvent evt)
{
OnKeyDownFPS(evt);
OnKeyDownReset(evt);
}
void OnChangeFPSCameraSpeed(WheelEvent evt)
{
float scrollWheelDelta = evt.delta.y;
flySpeedNormalized -= scrollWheelDelta * .01f;
string cameraSpeedDisplayValue = flySpeed.ToString(flySpeed < 0.1f ? "F2" : flySpeed < 10f ? "F1" : "F0");
if (flySpeed < 0.1f)
cameraSpeedDisplayValue = cameraSpeedDisplayValue.TrimStart(new Char[] { '0' });
GUIContent cameraSpeedContent = EditorGUIUtility.TrTempContent(
$"{cameraSpeedDisplayValue}x");
m_Window.ShowNotification(cameraSpeedContent, .5f);
evt.StopPropagation();
}
void OnZoom(WheelEvent evt)
{
float scrollWheelDelta = evt.delta.y;
float relativeDelta = m_CameraState.viewSize * scrollWheelDelta * .015f;
const float deltaCutoff = .3f;
if (relativeDelta > 0 && relativeDelta < deltaCutoff)
relativeDelta = deltaCutoff;
else if (relativeDelta < 0 && relativeDelta > -deltaCutoff)
relativeDelta = -deltaCutoff;
m_CameraState.viewSize += relativeDelta;
evt.StopPropagation();
}
void OnMouseDragOrbit(MouseMoveEvent evt)
{
Quaternion rotation = m_CameraState.rotation;
rotation = Quaternion.AngleAxis(evt.mouseDelta.y * .003f * Mathf.Rad2Deg, rotation * Vector3.right) * rotation;
rotation = Quaternion.AngleAxis(evt.mouseDelta.x * .003f * Mathf.Rad2Deg, Vector3.up) * rotation;
m_CameraState.rotation = rotation;
evt.StopPropagation();
}
void OnMouseDragFPS(MouseMoveEvent evt)
{
Vector3 camPos = m_CameraState.pivot - m_CameraState.rotation * Vector3.forward * m_CameraState.distanceFromPivot;
Quaternion rotation = m_CameraState.rotation;
rotation = Quaternion.AngleAxis(evt.mouseDelta.y * .003f * Mathf.Rad2Deg, rotation * Vector3.right) * rotation;
rotation = Quaternion.AngleAxis(evt.mouseDelta.x * .003f * Mathf.Rad2Deg, Vector3.up) * rotation;
m_CameraState.rotation = rotation;
m_CameraState.pivot = camPos + rotation * Vector3.forward * m_CameraState.distanceFromPivot;
evt.StopPropagation();
}
void OnMouseDragPan(MouseMoveEvent evt)
{
//[TODO: fix WorldToScreenPoint and ScreenToWorldPoint
var screenPos = m_CameraState.QuickProjectPivotInScreen(screen);
screenPos += new Vector3(evt.mouseDelta.x, -evt.mouseDelta.y, 0);
//Vector3 newWorldPos = m_CameraState.ScreenToWorldPoint(screen, screenPos);
Vector3 newWorldPos = m_CameraState.QuickReprojectionWithFixedFOVOnPivotPlane(screen, screenPos);
Vector3 worldDelta = newWorldPos - m_CameraState.pivot;
worldDelta *= EditorGUIUtility.pixelsPerPoint;
if (evt.shiftKey)
worldDelta *= 4;
m_CameraState.pivot += worldDelta;
evt.StopPropagation();
}
void OnMouseDragZoom(MouseMoveEvent evt)
{
float zoomDelta = HandleUtility.niceMouseDeltaZoom * (evt.shiftKey ? 9 : 3);
m_TotalMotion += zoomDelta;
if (m_TotalMotion < 0)
m_CameraState.viewSize = m_StartZoom * (1 + m_TotalMotion * .001f);
else
m_CameraState.viewSize = m_CameraState.viewSize + zoomDelta * m_ZoomSpeed * .003f;
evt.StopPropagation();
}
void OnKeyDownReset(KeyDownEvent evt)
{
if (evt.keyCode == KeyCode.Escape)
ResetCameraControl();
evt.StopPropagation();
}
void OnKeyDownFPS(KeyDownEvent evt)
{
if (m_BehaviorState != ViewTool.FPS)
return;
//Note: Keydown is called in loop but between first occurence of the
// loop and laters, there is a small pause. To deal with this, we
// need to register the UpdateMovement function to the Editor update
KeyCombination combination;
if (GetKeyCombinationByID("3D Viewport/Fly Mode Forward", out combination) && combination.Match(evt))
RegisterMotionChange(Vector3.forward, evt);
if (GetKeyCombinationByID("3D Viewport/Fly Mode Backward", out combination) && combination.Match(evt))
RegisterMotionChange(Vector3.back, evt);
if (GetKeyCombinationByID("3D Viewport/Fly Mode Left", out combination) && combination.Match(evt))
RegisterMotionChange(Vector3.left, evt);
if (GetKeyCombinationByID("3D Viewport/Fly Mode Right", out combination) && combination.Match(evt))
RegisterMotionChange(Vector3.right, evt);
if (GetKeyCombinationByID("3D Viewport/Fly Mode Up", out combination) && combination.Match(evt))
RegisterMotionChange(Vector3.up, evt);
if (GetKeyCombinationByID("3D Viewport/Fly Mode Down", out combination) && combination.Match(evt))
RegisterMotionChange(Vector3.down, evt);
}
void OnKeyUpFPS(KeyUpEvent evt)
{
if (m_BehaviorState != ViewTool.FPS)
return;
KeyCombination combination;
if (GetKeyCombinationByID("3D Viewport/Fly Mode Forward", out combination) && combination.Match(evt))
RegisterMotionChange(Vector3.back, evt);
if (GetKeyCombinationByID("3D Viewport/Fly Mode Backward", out combination) && combination.Match(evt))
RegisterMotionChange(Vector3.forward, evt);
if (GetKeyCombinationByID("3D Viewport/Fly Mode Left", out combination) && combination.Match(evt))
RegisterMotionChange(Vector3.right, evt);
if (GetKeyCombinationByID("3D Viewport/Fly Mode Right", out combination) && combination.Match(evt))
RegisterMotionChange(Vector3.left, evt);
if (GetKeyCombinationByID("3D Viewport/Fly Mode Up", out combination) && combination.Match(evt))
RegisterMotionChange(Vector3.down, evt);
if (GetKeyCombinationByID("3D Viewport/Fly Mode Down", out combination) && combination.Match(evt))
RegisterMotionChange(Vector3.up, evt);
}
void RegisterMotionChange<T>(Vector3 direction, KeyboardEventBase<T> evt)
where T : KeyboardEventBase<T>, new()
{
m_ShiftBoostedFly = evt.shiftKey;
m_MotionDirection = new Vector3(
Mathf.Clamp(m_MotionDirection.x + direction.x, -1, 1),
Mathf.Clamp(m_MotionDirection.y + direction.y, -1, 1),
Mathf.Clamp(m_MotionDirection.z + direction.z, -1, 1));
inFlyMotion = m_MotionDirection.sqrMagnitude != 0f;
evt.StopPropagation();
}
Vector3 GetMotionDirection()
{
var deltaTime = s_Timer.Update();
Vector3 result;
float speed = (m_ShiftBoostedFly ? 5 * flySpeed : flySpeed);
if (m_FlySpeedAccelerated == 0)
m_FlySpeedAccelerated = 9;
else
m_FlySpeedAccelerated *= Mathf.Pow(k_FlyAcceleration, deltaTime);
result = m_MotionDirection.normalized * m_FlySpeedAccelerated * speed * deltaTime;
return result;
}
void UpdateMotion()
{
if (Mathf.Approximately(m_MotionDirection.sqrMagnitude, 0f))
inFlyMotion = false;
else
m_CameraState.pivot += m_CameraState.rotation * GetMotionDirection();
}
bool GetKeyCombinationByID(string ID, out KeyCombination combination)
{
var sequence = ShortcutManager.instance.GetShortcutBinding(ID).keyCombinationSequence.GetEnumerator();
if (sequence.MoveNext()) //have a first entry
{
combination = new KeyCombination(sequence.Current);
return true;
}
else
{
combination = default;
return false;
}
}
void OnMouseUp(MouseUpEvent evt)
{
ResetCameraControl();
evt.StopPropagation();
}
void OnMouseDown(MouseDownEvent evt)
{
bool onMac = Application.platform == RuntimePlatform.OSXEditor;
if (evt.button == 2)
m_BehaviorState = ViewTool.Pan;
else if (evt.button == 0 && evt.ctrlKey && onMac || evt.button == 1 && evt.altKey)
{
m_BehaviorState = ViewTool.Zoom;
m_StartZoom = m_CameraState.viewSize;
m_ZoomSpeed = Mathf.Max(Mathf.Abs(m_StartZoom), .3f);
m_TotalMotion = 0;
}
else if (evt.button == 0)
m_BehaviorState = ViewTool.Orbit;
else if (evt.button == 1 && !evt.altKey)
m_BehaviorState = ViewTool.FPS;
// see also SceneView.HandleClickAndDragToFocus()
if (evt.button == 1 && onMac)
m_Window.Focus();
target.Focus(); //required for keyboard event
isDragging = true;
evt.StopPropagation();
m_Focused?.Invoke();
}
protected override void RegisterCallbacksOnTarget()
{
target.focusable = true; //prerequisite for being focusable and recerive keydown events
target.RegisterCallback<MouseUpEvent>(OnMouseUp);
target.RegisterCallback<MouseDownEvent>(OnMouseDown);
target.RegisterCallback<WheelEvent>(OnScrollWheel);
target.RegisterCallback<KeyDownEvent>(OnKeyDown);
target.RegisterCallback<KeyUpEvent>(OnKeyUpFPS);
}
protected override void UnregisterCallbacksFromTarget()
{
target.UnregisterCallback<MouseUpEvent>(OnMouseUp);
target.UnregisterCallback<MouseDownEvent>(OnMouseDown);
target.UnregisterCallback<WheelEvent>(OnScrollWheel);
target.UnregisterCallback<KeyDownEvent>(OnKeyDown);
target.UnregisterCallback<KeyUpEvent>(OnKeyUpFPS);
}
struct KeyCombination
{
KeyCode key;
EventModifiers modifier;
public bool shiftOnLastMatch;
public KeyCombination(UnityEditor.ShortcutManagement.KeyCombination shortcutCombination)
{
key = shortcutCombination.keyCode;
modifier = EventModifiers.None;
if ((shortcutCombination.modifiers & ShortcutModifiers.Shift) != 0)
modifier |= EventModifiers.Shift;
if ((shortcutCombination.modifiers & ShortcutModifiers.Alt) != 0)
modifier |= EventModifiers.Alt;
if ((shortcutCombination.modifiers & ShortcutModifiers.Action) != 0)
{
if (Application.platform == RuntimePlatform.OSXEditor || Application.platform == RuntimePlatform.OSXPlayer)
modifier |= EventModifiers.Command;
else
modifier |= EventModifiers.Control;
}
shiftOnLastMatch = false;
}
//atLeastModifier allow case were A is required but event provide shift+A
public bool Match(IKeyboardEvent evt, bool atLeastForModifier = true)
{
shiftOnLastMatch = evt.shiftKey;
if (atLeastForModifier)
return key == evt.keyCode && modifier == (evt.modifiers & modifier);
else
return key == evt.keyCode && modifier == evt.modifiers;
}
}
struct TimeHelper
{
long lastTime;
public void Begin() => lastTime = System.DateTime.Now.Ticks;
public float Update()
{
float deltaTime = (System.DateTime.Now.Ticks - lastTime) / 10000000.0f;
lastTime = System.DateTime.Now.Ticks;
return deltaTime;
}
}
}
class SwitchableCameraController : CameraController
{
CameraState m_FirstView;
CameraState m_SecondView;
ViewIndex m_CurrentViewIndex;
bool switchedDrag = false;
bool switchedWheel = false;
public SwitchableCameraController(CameraState cameraStateFirstView, CameraState cameraStateSecondView, DisplayWindow window, Action<ViewIndex> focused)
: base(cameraStateFirstView, window, null)
{
m_FirstView = cameraStateFirstView;
m_SecondView = cameraStateSecondView;
m_CurrentViewIndex = ViewIndex.First;
m_Focused = () => focused?.Invoke(m_CurrentViewIndex);
}
void SwitchTo(ViewIndex index)
{
CameraState stateToSwitch;
switch (index)
{
case ViewIndex.First:
stateToSwitch = m_FirstView;
break;
case ViewIndex.Second:
stateToSwitch = m_SecondView;
break;
default:
throw new ArgumentException("Unknown ViewIndex");
}
if (stateToSwitch != m_CameraState)
m_CameraState = stateToSwitch;
m_CurrentViewIndex = index;
}
public void SwitchUntilNextEndOfDrag()
{
switchedDrag = true;
SwitchTo(ViewIndex.Second);
}
override protected bool isDragging
{
get => base.isDragging;
set
{
bool switchBack = false;
if (switchedDrag && base.isDragging && !value)
switchBack = true;
base.isDragging = value;
if (switchBack)
SwitchTo(ViewIndex.First);
}
}
public void SwitchUntilNextWheelEvent()
{
switchedWheel = true;
SwitchTo(ViewIndex.Second);
}
protected override void OnScrollWheel(WheelEvent evt)
{
base.OnScrollWheel(evt);
if (switchedWheel)
SwitchTo(ViewIndex.First);
}
}
}

11
Packages/com.unity.render-pipelines.core/Editor/LookDev/CameraController.cs.meta


fileFormatVersion: 2
guid: 65d4b8eee9b19324cb60b5b126ca55f4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

117
Packages/com.unity.render-pipelines.core/Editor/LookDev/CameraState.cs


using UnityEditor.AnimatedValues;
using UnityEngine;
namespace UnityEditor.Rendering.LookDev
{
/// <summary>
/// Interface to comunicate with simple <see cref="Renderer"/>
/// </summary>
public interface ICameraUpdater
{
void UpdateCamera(Camera camera);
}
/// <summary>
/// Class containing data regarding position, rotation and viewport size of a camera
/// </summary>
[System.Serializable]
public class CameraState : ICameraUpdater
{
private static readonly Quaternion k_DefaultRotation = Quaternion.LookRotation(new Vector3(0.0f, 0.0f, 1.0f));
private const float k_DefaultViewSize = 10f;
private static readonly Vector3 k_DefaultPivot = Vector3.zero;
private const float k_DefaultFoV = 90f;
private const float k_NearFactor = 0.000005f;
private const float k_MaxFar = 1000;
/// <summary>The position of the camera pivot</summary>
[field: SerializeField]
public Vector3 pivot { get; set; } = k_DefaultPivot;
/// <summary>The rotation of the camera arround the pivot</summary>
[field: SerializeField]
public Quaternion rotation { get; set; } = k_DefaultRotation;
/// <summary>The size of the view</summary>
[field: SerializeField]
public float viewSize { get; set; } = k_DefaultViewSize;
/// <summary>The distance from pivot</summary>
public float distanceFromPivot
// distance coeficient from vertical FOV should be
// 1f / Mathf.Tan(kDefaultFoV * 0.5f * Mathf.Deg2Rad)
// but with fixed FoV of 90, this coef is always equal to 1f
=> viewSize;
/// <summary>The position of the camera</summary>
public Vector3 position
=> pivot + rotation * new Vector3(0, 0, -distanceFromPivot);
/// <summary>The field of view of the camera</summary>
public float fieldOfView => k_DefaultFoV;
/// <summary>The far clip distance from camera</summary>
public float farClip => Mathf.Max(k_MaxFar, 2 * k_MaxFar * viewSize);
/// <summary>The near clip distance from camera</summary>
public float nearClip => farClip * k_NearFactor;
/// <summary>The Forward vector in world space</summary>
public Vector3 forward => rotation * Vector3.forward;
/// <summary>The Up vector in world space</summary>
public Vector3 up => rotation * Vector3.up;
/// <summary>The Right vector in world space</summary>
public Vector3 right => rotation * Vector3.right;
internal Vector3 QuickReprojectionWithFixedFOVOnPivotPlane(Rect screen, Vector2 screenPoint)
{
if (screen.height == 0)
return Vector3.zero;
float aspect = screen.width / screen.height;
//Note: verticalDistance is same than distance from pivot with fixed FoV 90°
float verticalDistance = distanceFromPivot;
Vector2 normalizedScreenPoint = new Vector2(
screenPoint.x * 2f / screen.width - 1f,
screenPoint.y * 2f / screen.height - 1f);
return pivot
- up * verticalDistance * normalizedScreenPoint.y
- right * verticalDistance * aspect * normalizedScreenPoint.x;
}
//Pivot is always on center axis by construction
internal Vector3 QuickProjectPivotInScreen(Rect screen)
=> new Vector3(screen.width * .5f, screen.height * .5f, distanceFromPivot);
/// <summary>
/// Update a Camera component and its transform with this state values
/// </summary>
/// <param name="camera">The camera to update</param>
public void UpdateCamera(Camera camera)
{
camera.transform.rotation = rotation;
camera.transform.position = position;
camera.nearClipPlane = nearClip;
camera.farClipPlane = farClip;
camera.fieldOfView = fieldOfView;
}
/// <summary>
/// Reset the State to its default values
/// </summary>
public void Reset()
{
pivot = k_DefaultPivot;
rotation = k_DefaultRotation;
viewSize = k_DefaultViewSize;
}
internal void SynchronizeFrom(CameraState other)
{
pivot = other.pivot;
rotation = other.rotation;
viewSize = other.viewSize;
}
}
}

11
Packages/com.unity.render-pipelines.core/Editor/LookDev/CameraState.cs.meta


fileFormatVersion: 2
guid: a517d1ef992c13d4f92edc2af5b3e6c0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

240
Packages/com.unity.render-pipelines.core/Editor/LookDev/ComparisonGizmoController.cs


using System;
using UnityEngine.UIElements;
using UnityEngine;
namespace UnityEditor.Rendering.LookDev
{
//TODO: clamps to always have both node on screen
class ComparisonGizmoController : Manipulator
{
const float k_DragPadding = 0.05f;
const float k_ReferenceScale = 1080f;
ComparisonGizmoState m_State;
SwitchableCameraController m_Switcher;
enum Selected
{
None,
NodeFirstView,
NodeSecondView,
PlaneSeparator,
Fader
}
Selected m_Selected;
Vector2 m_SavedRelativePositionOnMouseDown;
bool m_IsDragging;
bool isDragging
{
get => m_IsDragging;
set
{
//As in scene view, stop dragging as first button is release in case of multiple button down
if (value ^ m_IsDragging)
{
if (value)
{
target.RegisterCallback<MouseMoveEvent>(OnMouseDrag);
target.CaptureMouse();
}
else
{
target.ReleaseMouse();
target.UnregisterCallback<MouseMoveEvent>(OnMouseDrag);
}
m_IsDragging = value;
}
}
}
public ComparisonGizmoController(ComparisonGizmoState state, SwitchableCameraController switcher)
{
m_State = state;
m_Switcher = switcher;
}
protected override void RegisterCallbacksOnTarget()
{
target.RegisterCallback<MouseDownEvent>(OnMouseDown);
target.RegisterCallback<MouseUpEvent>(OnMouseUp);
target.RegisterCallback<WheelEvent>(OnScrollWheel);
}
protected override void UnregisterCallbacksFromTarget()
{
target.UnregisterCallback<MouseDownEvent>(OnMouseDown);
target.UnregisterCallback<MouseUpEvent>(OnMouseUp);
target.UnregisterCallback<WheelEvent>(OnScrollWheel);
}
void OnScrollWheel(WheelEvent evt)
{
if (LookDev.currentContext.layout.viewLayout != Layout.CustomSplit)
return;
if (GetViewFromComposition(evt.localMousePosition) == ViewIndex.Second)
m_Switcher.SwitchUntilNextWheelEvent();
//let event be propagated to views
}
void OnMouseDown(MouseDownEvent evt)
{
if (LookDev.currentContext.layout.viewLayout != Layout.CustomSplit)
return;
Rect displayRect = target.contentRect;
SelectGizmoZone(GetNormalizedCoordinates(evt.localMousePosition, displayRect));
if (m_Selected != Selected.None)
{
m_SavedRelativePositionOnMouseDown = GetNormalizedCoordinates(evt.localMousePosition, displayRect) - m_State.center;
isDragging = true;
//We do not want to move camera and gizmo at the same time.
evt.StopImmediatePropagation();
}
else
{
//else let event be propagated to views
if (GetViewFromComposition(evt.localMousePosition) == ViewIndex.Second)
m_Switcher.SwitchUntilNextEndOfDrag();
}
}
void OnMouseUp(MouseUpEvent evt)
{
if (LookDev.currentContext.layout.viewLayout != Layout.CustomSplit
|| m_Selected == Selected.None)
return;
// deadzone in fader gizmo
if (m_Selected == Selected.Fader && Mathf.Abs(m_State.blendFactor) < ComparisonGizmoState.circleRadiusSelected / (m_State.length - ComparisonGizmoState.circleRadius))
m_State.blendFactor = 0f;
m_Selected = Selected.None;
isDragging = false;
//We do not want to move camera and gizmo at the same time.
evt.StopImmediatePropagation();
LookDev.SaveConfig();
}
void OnMouseDrag(MouseMoveEvent evt)
{
if (m_Selected == Selected.None)
return;
switch (m_Selected)
{
case Selected.PlaneSeparator: OnDragPlaneSeparator(evt); break;
case Selected.NodeFirstView:
case Selected.NodeSecondView: OnDragPlaneNodeExtremity(evt); break;
case Selected.Fader: OnDragFader(evt); break;
default: throw new ArgumentException("Unknown kind of Selected");
}
}
void OnDragPlaneSeparator(MouseMoveEvent evt)
{
//TODO: handle case when resizing window (clamping)
Vector2 newPosition = GetNormalizedCoordinates(evt.localMousePosition, target.contentRect) - m_SavedRelativePositionOnMouseDown;
// We clamp the center of the gizmo to the border of the screen in order to avoid being able to put it out of the screen.
// The safe band is here to ensure that you always see at least part of the gizmo in order to be able to grab it again.
//Vector2 extends = GetNormalizedCoordinates(new Vector2(displayRect.width, displayRect.height), displayRect);
//newPosition.x = Mathf.Clamp(newPosition.x, -extends.x + k_DragPadding, extends.x - k_DragPadding);
//newPosition.y = Mathf.Clamp(newPosition.y, -extends.y + k_DragPadding, extends.y - k_DragPadding);
m_State.Update(newPosition, m_State.length, m_State.angle);
//We do not want to move camera and gizmo at the same time.
evt.StopImmediatePropagation();
}
void OnDragPlaneNodeExtremity(MouseMoveEvent evt)
{
Vector2 normalizedCoord = GetNormalizedCoordinates(evt.localMousePosition, target.contentRect);
Vector2 basePoint, newPoint;
float angleSnapping = Mathf.Deg2Rad * 45.0f * 0.5f;
newPoint = normalizedCoord;
basePoint = m_Selected == Selected.NodeFirstView ? m_State.point2 : m_State.point1;
// Snap to a multiple of "angleSnapping"
if ((evt.modifiers & EventModifiers.Shift) != 0)
{
Vector3 verticalPlane = new Vector3(-1.0f, 0.0f, basePoint.x);
float side = Vector3.Dot(new Vector3(normalizedCoord.x, normalizedCoord.y, 1.0f), verticalPlane);
float angle = Mathf.Deg2Rad * Vector2.Angle(new Vector2(0.0f, 1.0f), normalizedCoord - basePoint);
if (side > 0.0f)
angle = 2.0f * Mathf.PI - angle;
angle = (int)(angle / angleSnapping) * angleSnapping;
Vector2 dir = normalizedCoord - basePoint;
float length = dir.magnitude;
newPoint = basePoint + new Vector2(Mathf.Sin(angle), Mathf.Cos(angle)) * length;
}
if (m_Selected == Selected.NodeFirstView)
m_State.Update(newPoint, basePoint);
else
m_State.Update(basePoint, newPoint);
//We do not want to move camera and gizmo at the same time.
evt.StopImmediatePropagation();
}
void OnDragFader(MouseMoveEvent evt)
{
Vector2 mousePosition = GetNormalizedCoordinates(evt.localMousePosition, target.contentRect);
float distanceToOrthoPlane = -Vector3.Dot(new Vector3(mousePosition.x, mousePosition.y, 1.0f), m_State.planeOrtho) / m_State.blendFactorMaxGizmoDistance;
m_State.blendFactor = Mathf.Clamp(distanceToOrthoPlane, -1.0f, 1.0f);
//We do not want to move camera and gizmo at the same time.
evt.StopImmediatePropagation();
}
void SelectGizmoZone(Vector2 normalizedMousePosition)
{
//TODO: Optimize
Vector3 normalizedMousePositionZ1 = new Vector3(normalizedMousePosition.x, normalizedMousePosition.y, 1.0f);
float distanceToPlane = Vector3.Dot(normalizedMousePositionZ1, m_State.plane);
float absDistanceToPlane = Mathf.Abs(distanceToPlane);
float distanceFromCenter = Vector2.Distance(normalizedMousePosition, m_State.center);
float distanceToOrtho = Vector3.Dot(normalizedMousePositionZ1, m_State.planeOrtho);
float side = (distanceToOrtho > 0.0f) ? 1.0f : -1.0f;
Vector2 orthoPlaneNormal = new Vector2(m_State.planeOrtho.x, m_State.planeOrtho.y);
Selected selected = Selected.None;
if (absDistanceToPlane < ComparisonGizmoState.circleRadiusSelected && (distanceFromCenter < (m_State.length + ComparisonGizmoState.circleRadiusSelected)))
{
if (absDistanceToPlane < ComparisonGizmoState.thicknessSelected)
selected = Selected.PlaneSeparator;
Vector2 circleCenter = m_State.center + side * orthoPlaneNormal * m_State.length;
float d = Vector2.Distance(normalizedMousePosition, circleCenter);
if (d <= ComparisonGizmoState.circleRadiusSelected)
selected = side > 0.0f ? Selected.NodeFirstView : Selected.NodeSecondView;
float maxBlendCircleDistanceToCenter = m_State.blendFactorMaxGizmoDistance;
float blendCircleDistanceToCenter = m_State.blendFactor * maxBlendCircleDistanceToCenter;
Vector2 blendCircleCenter = m_State.center - orthoPlaneNormal * blendCircleDistanceToCenter;
float blendCircleSelectionRadius = Mathf.Lerp(ComparisonGizmoState.blendFactorCircleRadius, ComparisonGizmoState.blendFactorCircleRadiusSelected, Mathf.Clamp((maxBlendCircleDistanceToCenter - Mathf.Abs(blendCircleDistanceToCenter)) / (ComparisonGizmoState.blendFactorCircleRadiusSelected - ComparisonGizmoState.blendFactorCircleRadius), 0.0f, 1.0f));
if ((normalizedMousePosition - blendCircleCenter).magnitude < blendCircleSelectionRadius)
selected = Selected.Fader;
}
m_Selected = selected;
}
//normalize in [-1,1]^2 for a 1080^2. Can be above 1 for higher than 1080.
internal static Vector2 GetNormalizedCoordinates(Vector2 localMousePosition, Rect rect)
=> new Vector2(
(2f * localMousePosition.x - rect.width) / k_ReferenceScale,
(-2f * localMousePosition.y + rect.height) / k_ReferenceScale);
ViewIndex GetViewFromComposition(Vector2 localCoordinate)
{
Vector2 normalizedLocalCoordinate = GetNormalizedCoordinates(localCoordinate, target.contentRect);
return Vector3.Dot(new Vector3(normalizedLocalCoordinate.x, normalizedLocalCoordinate.y, 1), m_State.plane) >= 0
? ViewIndex.First
: ViewIndex.Second;
}
}
}

11
Packages/com.unity.render-pipelines.core/Editor/LookDev/ComparisonGizmoController.cs.meta


fileFormatVersion: 2
guid: c775302533fffe2449b5b27bab30a56c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

117
Packages/com.unity.render-pipelines.core/Editor/LookDev/ComparisonGizmoState.cs


using System;
using UnityEditor.AnimatedValues;
using UnityEngine;
namespace UnityEditor.Rendering.LookDev
{
[Serializable]
public class ComparisonGizmoState
{
internal const float thickness = 0.0028f;
internal const float thicknessSelected = 0.015f;
internal const float circleRadius = 0.014f;
internal const float circleRadiusSelected = 0.03f;
internal const float blendFactorCircleRadius = 0.01f;
internal const float blendFactorCircleRadiusSelected = 0.03f;
/// <summary>Position of the first extremity</summary>
public Vector2 point1 { get; private set; }
/// <summary>Position of the second extremity</summary>
public Vector2 point2 { get; private set; }
/// <summary>Position of the center</summary>
[field: SerializeField]
public Vector2 center { get; private set; } = Vector2.zero;
/// <summary>Angle from vertical in radian</summary>
[field: SerializeField]
public float angle { get; private set; }
/// <summary>Length between extremity</summary>
[field: SerializeField]
public float length { get; private set; } = 0.2f;
internal Vector4 plane { get; private set; }
internal Vector4 planeOrtho { get; private set; }
/// <summary>
/// The position of the blending slider.
/// From value -1 on first extremity to value 1 on second extremity.
/// </summary>
[field: SerializeField]
public float blendFactor { get; set; }
internal float blendFactorMaxGizmoDistance
=> length - circleRadius - blendFactorCircleRadius;
internal float blendFactorMinGizmoDistance
=> length - circleRadius - blendFactorCircleRadiusSelected;
internal void Init()
=> Update(center, length, angle);
//TODO: optimize
private Vector4 Get2DPlane(Vector2 firstPoint, float angle)
{
Vector4 result = new Vector4();
angle = angle % (2.0f * (float)Math.PI);
Vector2 secondPoint = new Vector2(firstPoint.x + Mathf.Sin(angle), firstPoint.y + Mathf.Cos(angle));
Vector2 diff = secondPoint - firstPoint;
if (Mathf.Abs(diff.x) < 1e-5)
{
result.Set(-1.0f, 0.0f, firstPoint.x, 0.0f);
float sign = Mathf.Cos(angle) > 0.0f ? 1.0f : -1.0f;
result *= sign;
}
else
{
float slope = diff.y / diff.x;
result.Set(-slope, 1.0f, -(firstPoint.y - slope * firstPoint.x), 0.0f);
}
if (angle > Mathf.PI)
result = -result;
float length = Mathf.Sqrt(result.x * result.x + result.y * result.y);
result = result / length;
return result;
}
/// <summary>
/// Update all fields while moving one extremity
/// </summary>
/// <param name="point1">The new first extremity position</param>
/// <param name="point2">The new second extremity position</param>
public void Update(Vector2 point1, Vector2 point2)
{
this.point1 = point1;
this.point2 = point2;
center = (point1 + point2) * 0.5f;
length = (point2 - point1).magnitude * 0.5f;
Vector3 verticalPlane = Get2DPlane(center, 0.0f);
float side = Vector3.Dot(new Vector3(point1.x, point1.y, 1.0f), verticalPlane);
angle = (Mathf.Deg2Rad * Vector2.Angle(new Vector2(0.0f, 1.0f), (point1 - point2).normalized));
if (side > 0.0f)
angle = 2.0f * Mathf.PI - angle;
plane = Get2DPlane(center, angle);
planeOrtho = Get2DPlane(center, angle + 0.5f * (float)Mathf.PI);
}
/// <summary>
/// Update all fields while moving the bar
/// </summary>
/// <param name="center">The new center position</param>
/// <param name="length">Tne new length of this gizmo</param>
/// <param name="angle">The new angle of this gizmo</param>
public void Update(Vector2 center, float length, float angle)
{
this.center = center;
this.length = length;
this.angle = angle;
plane = Get2DPlane(center, angle);
planeOrtho = Get2DPlane(center, angle + 0.5f * (float)Mathf.PI);
Vector2 dir = new Vector2(planeOrtho.x, planeOrtho.y);
point1 = center + dir * length;
point2 = center - dir * length;
}
}
}

11
Packages/com.unity.render-pipelines.core/Editor/LookDev/ComparisonGizmoState.cs.meta


fileFormatVersion: 2
guid: b1b1fb8ac8cbdcb4aac5fa767e4c4330
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

401
Packages/com.unity.render-pipelines.core/Editor/LookDev/Compositor.cs


using System;
using UnityEngine;
using UnityEngine.Experimental.Rendering;
using IDataProvider = UnityEngine.Rendering.LookDev.IDataProvider;
namespace UnityEditor.Rendering.LookDev
{
enum ShadowCompositionPass
{
WithSun,
WithoutSun,
ShadowMask
}
enum CompositionFinal
{
First,
Second
}
class RenderTextureCache : IDisposable
{
//RenderTextures are packed this way:
//0: ViewIndex.First, ShadowCompositionPass.WithSun
//1: ViewIndex.First, ShadowCompositionPass.WithoutSun
//2: ViewIndex.First, ShadowCompositionPass.ShadowMask
//3: CompositionFinal.First
//4: ViewIndex.Second, ShadowCompositionPass.WithSun
//5: ViewIndex.Second, ShadowCompositionPass.WithoutSun
//6: ViewIndex.Second, ShadowCompositionPass.ShadowMask
//7: CompositionFinal.Second
RenderTexture[] m_RTs = new RenderTexture[8];
public RenderTexture this[ViewIndex index, ShadowCompositionPass passIndex]
{
get => m_RTs[computeIndex(index, passIndex)];
set => m_RTs[computeIndex(index, passIndex)] = value;
}
public RenderTexture this[CompositionFinal index]
{
get => m_RTs[computeIndex(index)];
set => m_RTs[computeIndex(index)] = value;
}
int computeIndex(ViewIndex index, ShadowCompositionPass passIndex)
=> (int)index * 4 + (int)(passIndex);
int computeIndex(CompositionFinal index)
=> 3 + (int)(index) * 4;
void UpdateSize(int index, Rect rect, bool pixelPerfect, Camera renderingCamera, string renderDocName = "LookDevRT")
{
bool nullRect = rect.IsNullOrInverted();
GraphicsFormat format = SystemInfo.IsFormatSupported(GraphicsFormat.R16G16B16A16_SFloat, FormatUsage.Render)
? GraphicsFormat.R16G16B16A16_SFloat
: SystemInfo.GetGraphicsFormat(DefaultFormat.LDR);
if (m_RTs[index] != null && (nullRect || m_RTs[index].graphicsFormat != format))
{
m_RTs[index].Release();
UnityEngine.Object.DestroyImmediate(m_RTs[index]);
m_RTs[index] = null;
}
if (nullRect)
return;
int width = (int)rect.width;
int height = (int)rect.height;
if (m_RTs[index] == null)
{
m_RTs[index] = new RenderTexture(0, 0, 24, format);
m_RTs[index].name = renderDocName;
m_RTs[index].antiAliasing = 1;
m_RTs[index].hideFlags = HideFlags.HideAndDontSave;
}
if (m_RTs[index].width != width || m_RTs[index].height != height)
{
m_RTs[index].Release();
m_RTs[index].width = width;
m_RTs[index].height = height;
m_RTs[index].Create();
}
if (renderingCamera != null)
renderingCamera.targetTexture = m_RTs[index];
}
public void UpdateSize(Rect rect, ViewIndex index, bool pixelPerfect, Camera renderingCamera)
{
UpdateSize(computeIndex(index, ShadowCompositionPass.WithSun), rect, pixelPerfect, renderingCamera, $"LookDevRT-{index}-WithSun");
UpdateSize(computeIndex(index, ShadowCompositionPass.WithoutSun), rect, pixelPerfect, renderingCamera, $"LookDevRT-{index}-WithoutSun");
UpdateSize(computeIndex(index, ShadowCompositionPass.ShadowMask), rect, pixelPerfect, renderingCamera, $"LookDevRT-{index}-ShadowMask");
}
public void UpdateSize(Rect rect, CompositionFinal index, bool pixelPerfect, Camera renderingCamera)
=> UpdateSize(computeIndex(index), rect, pixelPerfect, renderingCamera, $"LookDevRT-Final-{index}");
bool m_Disposed = false;
public void Dispose()
{
if (m_Disposed)
return;
m_Disposed = true;
for (int index = 0; index < 8; ++index)
{
if (m_RTs[index] == null || m_RTs[index].Equals(null))
continue;
UnityEngine.Object.DestroyImmediate(m_RTs[index]);
m_RTs[index] = null;
}
}
}
class Compositer : IDisposable
{
public static readonly Color firstViewGizmoColor = new Color32(0, 154, 154, 255);
public static readonly Color secondViewGizmoColor = new Color32(255, 37, 4, 255);
static Material s_Material;
static Material material
{
get
{
if (s_Material == null || s_Material.Equals(null))
s_Material = new Material(Shader.Find("Hidden/LookDev/Compositor"));
return s_Material;
}
}
IDataProvider m_DataProvider;
IViewDisplayer m_Displayer;
Context m_Contexts;
RenderTextureCache m_RenderTextures = new RenderTextureCache();
Renderer m_Renderer = new Renderer();
RenderingData[] m_RenderDataCache;
bool m_pixelPerfect;
bool m_Disposed;
public bool pixelPerfect
{
get => m_pixelPerfect;
set => m_Renderer.pixelPerfect = m_pixelPerfect = value;
}
Color m_AmbientColor = new Color(0.0f, 0.0f, 0.0f, 0.0f);
bool m_RenderDocAcquisitionRequested;
public Compositer(
IViewDisplayer displayer,
Context contexts,
IDataProvider dataProvider,
StageCache stages)
{
m_DataProvider = dataProvider;
m_Displayer = displayer;
m_Contexts = contexts;
m_RenderDataCache = new RenderingData[2]
{
new RenderingData() { stage = stages[ViewIndex.First], updater = contexts.GetViewContent(ViewIndex.First).camera },
new RenderingData() { stage = stages[ViewIndex.Second], updater = contexts.GetViewContent(ViewIndex.Second).camera }
};
m_Displayer.OnRenderDocAcquisitionTriggered += RenderDocAcquisitionRequested;
m_Displayer.OnUpdateRequested += Render;
}
void RenderDocAcquisitionRequested()
=> m_RenderDocAcquisitionRequested = true;
void CleanUp()
{
for (int index = 0; index < 2; ++index)
{
m_RenderDataCache[index]?.Dispose();
m_RenderDataCache[index] = null;
}
m_RenderTextures.Dispose();
m_Displayer.OnRenderDocAcquisitionTriggered -= RenderDocAcquisitionRequested;
m_Displayer.OnUpdateRequested -= Render;
}
public void Dispose()
{
if (m_Disposed)
return;
m_Disposed = true;
CleanUp();
GC.SuppressFinalize(this);
}
~Compositer() => CleanUp();
public void Render()
{
//TODO: make integration EditorWindow agnostic!
if (UnityEditorInternal.RenderDoc.IsLoaded() && UnityEditorInternal.RenderDoc.IsSupported() && m_RenderDocAcquisitionRequested)
UnityEditorInternal.RenderDoc.BeginCaptureRenderDoc(m_Displayer as EditorWindow);
using (new UnityEngine.Rendering.VolumeIsolationScope(true))
{
switch (m_Contexts.layout.viewLayout)
{
case Layout.FullFirstView:
RenderSingleAndOutput(ViewIndex.First);
break;
case Layout.FullSecondView:
RenderSingleAndOutput(ViewIndex.Second);
break;
case Layout.HorizontalSplit:
case Layout.VerticalSplit:
RenderSingleAndOutput(ViewIndex.First);
RenderSingleAndOutput(ViewIndex.Second);
break;
case Layout.CustomSplit:
RenderCompositeAndOutput();
break;
}
}
//TODO: make integration EditorWindow agnostic!
if (UnityEditorInternal.RenderDoc.IsLoaded() && UnityEditorInternal.RenderDoc.IsSupported() && m_RenderDocAcquisitionRequested)
UnityEditorInternal.RenderDoc.EndCaptureRenderDoc(m_Displayer as EditorWindow);
//stating that RenderDoc do not need to acquire anymore should
//allows to gather both view and composition in render doc at once
m_RenderDocAcquisitionRequested = false;
}
void AcquireDataForView(ViewIndex index, Rect viewport)
{
var renderingData = m_RenderDataCache[(int)index];
renderingData.viewPort = viewport;
Environment env = m_Contexts.GetViewContent(index).environment;
m_RenderTextures.UpdateSize(renderingData.viewPort, index, m_Renderer.pixelPerfect, renderingData.stage.camera);
renderingData.output = m_RenderTextures[index, ShadowCompositionPass.WithSun];
m_Renderer.Acquire(renderingData, RenderingPass.First);
//get shadowmask betwen first and last pass to still be isolated
RenderTexture tmp = m_RenderTextures[index, ShadowCompositionPass.ShadowMask];
env?.UpdateSunPosition(renderingData.stage.sunLight);
renderingData.stage.sunLight.intensity = 1f;
m_DataProvider.GetShadowMask(ref tmp, renderingData.stage.runtimeInterface);
renderingData.stage.sunLight.intensity = 0f;
m_RenderTextures[index, ShadowCompositionPass.ShadowMask] = tmp;
if (env != null)
m_DataProvider.UpdateSky(renderingData.stage.camera, env.shadowSky, renderingData.stage.runtimeInterface);
renderingData.output = m_RenderTextures[index, ShadowCompositionPass.WithoutSun];
m_Renderer.Acquire(renderingData, RenderingPass.Last);
if (env != null)
m_DataProvider.UpdateSky(renderingData.stage.camera, env.sky, renderingData.stage.runtimeInterface);
}
void RenderSingleAndOutput(ViewIndex index)
{
Rect viewport = m_Displayer.GetRect((ViewCompositionIndex)index);
AcquireDataForView(index, viewport);
Compositing(viewport, (int)index, (CompositionFinal)index);
m_Displayer.SetTexture((ViewCompositionIndex)index, m_RenderTextures[(CompositionFinal)index]);
}
void RenderCompositeAndOutput()
{
Rect viewport = m_Displayer.GetRect(ViewCompositionIndex.Composite);
AcquireDataForView(ViewIndex.First, viewport);
AcquireDataForView(ViewIndex.Second, viewport);
Compositing(viewport, 2 /*split*/, CompositionFinal.First);
m_Displayer.SetTexture(ViewCompositionIndex.Composite, m_RenderTextures[CompositionFinal.First]);
}
void Compositing(Rect rect, int pass, CompositionFinal finalBufferIndex)
{
if (rect.IsNullOrInverted()
|| (m_Contexts.layout.viewLayout != Layout.FullSecondView
&& (m_RenderTextures[ViewIndex.First, ShadowCompositionPass.WithSun] == null
|| m_RenderTextures[ViewIndex.First, ShadowCompositionPass.WithoutSun] == null
|| m_RenderTextures[ViewIndex.First, ShadowCompositionPass.ShadowMask] == null))
|| (m_Contexts.layout.viewLayout != Layout.FullFirstView
&& (m_RenderTextures[ViewIndex.Second, ShadowCompositionPass.WithSun] == null
|| m_RenderTextures[ViewIndex.Second, ShadowCompositionPass.WithoutSun] == null
|| m_RenderTextures[ViewIndex.Second, ShadowCompositionPass.ShadowMask] == null)))
{
m_RenderTextures[finalBufferIndex] = null;
return;
}
m_RenderTextures.UpdateSize(rect, finalBufferIndex, m_pixelPerfect, null);
ComparisonGizmoState gizmo = m_Contexts.layout.gizmoState;
Vector4 gizmoPosition = new Vector4(gizmo.center.x, gizmo.center.y, 0.0f, 0.0f);
Vector4 gizmoZoneCenter = new Vector4(gizmo.point2.x, gizmo.point2.y, 0.0f, 0.0f);
Vector4 gizmoThickness = new Vector4(ComparisonGizmoState.thickness, ComparisonGizmoState.thicknessSelected, 0.0f, 0.0f);
Vector4 gizmoCircleRadius = new Vector4(ComparisonGizmoState.circleRadius, ComparisonGizmoState.circleRadiusSelected, 0.0f, 0.0f);
Environment env0 = m_Contexts.GetViewContent(ViewIndex.First).environment;
Environment env1 = m_Contexts.GetViewContent(ViewIndex.Second).environment;
float exposureValue0 = env0?.sky.exposure ?? 0f;
float exposureValue1 = env1?.sky.exposure ?? 0f;
float dualViewBlendFactor = gizmo.blendFactor;
float isCurrentlyLeftEditting = m_Contexts.layout.lastFocusedView == ViewIndex.First ? 1f : -1f;
float dragAndDropContext = 0f; //1f left, -1f right, 0f neither
float toneMapEnabled = -1f; //1f true, -1f false
float shadowMultiplier0 = env0?.shadowIntensity ?? 0f;
float shadowMultiplier1 = env1?.shadowIntensity ?? 0f;
Color shadowColor0 = env0?.shadow.color ?? Color.white;
Color shadowColor1 = env1?.shadow.color ?? Color.white;
//TODO: handle shadow not at compositing step but in rendering
Texture texWithSun0 = m_RenderTextures[ViewIndex.First, ShadowCompositionPass.WithSun];
Texture texWithoutSun0 = m_RenderTextures[ViewIndex.First, ShadowCompositionPass.WithoutSun];
Texture texShadowsMask0 = m_RenderTextures[ViewIndex.First, ShadowCompositionPass.ShadowMask];
Texture texWithSun1 = m_RenderTextures[ViewIndex.Second, ShadowCompositionPass.WithSun];
Texture texWithoutSun1 = m_RenderTextures[ViewIndex.Second, ShadowCompositionPass.WithoutSun];
Texture texShadowsMask1 = m_RenderTextures[ViewIndex.Second, ShadowCompositionPass.ShadowMask];
Vector4 compositingParams = new Vector4(dualViewBlendFactor, exposureValue0, exposureValue1, isCurrentlyLeftEditting);
Vector4 compositingParams2 = new Vector4(dragAndDropContext, toneMapEnabled, shadowMultiplier0, shadowMultiplier1);
// Those could be tweakable for the neutral tonemapper, but in the case of the LookDev we don't need that
const float k_BlackIn = 0.02f;
const float k_WhiteIn = 10.0f;
const float k_BlackOut = 0.0f;
const float k_WhiteOut = 10.0f;
const float k_WhiteLevel = 5.3f;
const float k_WhiteClip = 10.0f;
const float k_DialUnits = 20.0f;
const float k_HalfDialUnits = k_DialUnits * 0.5f;
const float k_GizmoRenderMode = 4f; //display all
// converting from artist dial units to easy shader-lerps (0-1)
//TODO: to compute one time only
Vector4 tonemapCoeff1 = new Vector4((k_BlackIn * k_DialUnits) + 1.0f, (k_BlackOut * k_HalfDialUnits) + 1.0f, (k_WhiteIn / k_DialUnits), (1.0f - (k_WhiteOut / k_DialUnits)));
Vector4 tonemapCoeff2 = new Vector4(0.0f, 0.0f, k_WhiteLevel, k_WhiteClip / k_HalfDialUnits);
const float k_ReferenceScale = 1080.0f;
Vector4 screenRatio = new Vector4(rect.width / k_ReferenceScale, rect.height / k_ReferenceScale, rect.width, rect.height);
RenderTexture oldActive = RenderTexture.active;
RenderTexture.active = m_RenderTextures[finalBufferIndex];
material.SetTexture("_Tex0WithSun", texWithSun0);
material.SetTexture("_Tex0WithoutSun", texWithoutSun0);
material.SetTexture("_Tex0Shadows", texShadowsMask0);
material.SetColor("_ShadowColor0", shadowColor0);
material.SetTexture("_Tex1WithSun", texWithSun1);
material.SetTexture("_Tex1WithoutSun", texWithoutSun1);
material.SetTexture("_Tex1Shadows", texShadowsMask1);
material.SetColor("_ShadowColor1", shadowColor1);
material.SetVector("_CompositingParams", compositingParams);
material.SetVector("_CompositingParams2", compositingParams2);
material.SetColor("_FirstViewColor", firstViewGizmoColor);
material.SetColor("_SecondViewColor", secondViewGizmoColor);
material.SetVector("_GizmoPosition", gizmoPosition);
material.SetVector("_GizmoZoneCenter", gizmoZoneCenter);
material.SetVector("_GizmoSplitPlane", gizmo.plane);
material.SetVector("_GizmoSplitPlaneOrtho", gizmo.planeOrtho);
material.SetFloat("_GizmoLength", gizmo.length);
material.SetVector("_GizmoThickness", gizmoThickness);
material.SetVector("_GizmoCircleRadius", gizmoCircleRadius);
material.SetFloat("_BlendFactorCircleRadius", ComparisonGizmoState.blendFactorCircleRadius);
material.SetFloat("_GetBlendFactorMaxGizmoDistance", gizmo.blendFactorMaxGizmoDistance);
material.SetFloat("_GizmoRenderMode", k_GizmoRenderMode);
material.SetVector("_ScreenRatio", screenRatio);
material.SetVector("_ToneMapCoeffs1", tonemapCoeff1);
material.SetVector("_ToneMapCoeffs2", tonemapCoeff2);
material.SetPass(pass);
Renderer.DrawFullScreenQuad(new Rect(0, 0, rect.width, rect.height));
RenderTexture.active = oldActive;
}
public ViewIndex GetViewFromComposition(Vector2 localCoordinate)
{
Rect compositionRect = m_Displayer.GetRect(ViewCompositionIndex.Composite);
Vector2 normalizedLocalCoordinate = ComparisonGizmoController.GetNormalizedCoordinates(localCoordinate, compositionRect);
switch (m_Contexts.layout.viewLayout)
{
case Layout.CustomSplit:
return Vector3.Dot(new Vector3(normalizedLocalCoordinate.x, normalizedLocalCoordinate.y, 1), m_Contexts.layout.gizmoState.plane) >= 0
? ViewIndex.First
: ViewIndex.Second;
default:
throw new Exception("GetViewFromComposition call when not inside a Composition");
}
}
}
}

11
Packages/com.unity.render-pipelines.core/Editor/LookDev/Compositor.cs.meta


fileFormatVersion: 2
guid: 5471cb5dd3020f24b9bc1644dc9c9ad3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

457
Packages/com.unity.render-pipelines.core/Editor/LookDev/Compositor.shader


Shader "Hidden/LookDev/Compositor"
{
Properties
{
_Tex0WithSun("First View", 2D) = "white" {}
_Tex0WithoutSun("First View without sun", 2D) = "white" {}
_Tex0Shadows("First View shadow mask", 2D) = "white" {}
_ShadowColor0("Shadow Color for first view", Color) = (1.0, 1.0, 1.0, 1.0)
_Tex1WithSun("Second View", 2D) = "white" {}
_Tex1WithoutSun("Second View without sun", 2D) = "white" {}
_Tex1Shadows("Second View shadow mask", 2D) = "white" {}
_ShadowColor1("Shadow Color for second view", Color) = (1.0, 1.0, 1.0, 1.0)
_CompositingParams("Blend Factor, exposure for first and second view, and current selected side", Vector) = (0.0, 1.0, 1.0, 1.0)
_CompositingParams2("Drag and drop zone and shadow multipliers", Vector) = (0.0, 1.0, 1.0, 1.0) // Drag and Drop zone Left == 1.0, Right == -1.0, None == 0.0
_FirstViewColor("Gizmo Color for first view", Color) = (0.5, 0.5, 0.5, 0.5)
_SecondViewColor("Gizmo Color for second view", Color) = (0.5, 0.5, 0.5, 0.5)
_GizmoPosition("Position of split view gizmo", Vector) = (0.5, 0.5, 0.0, 0.0)
_GizmoZoneCenter("Center of Zone view gizmo", Vector) = (0.5, 0.5, 0.0, 0.0)
_GizmoSplitPlane("2D plane of the gizmo", Vector) = (1.0, 1.0, 0.0, 0.0)
_GizmoSplitPlaneOrtho("2D plane orthogonal to the gizmo", Vector) = (1.0, 1.0, 0.0, 0.0)
_GizmoLength("Gizmo Length", Float) = 0.2
_GizmoThickness("Gizmo Thickness", Vector) = (0.01, 0.08, 0.0, 0.0)
_GizmoCircleRadius("Gizmo extremities radius", Vector) = (0.05, 0.4, 0.0, 0.0)
_GizmoRenderMode("Render gizmo mode", Float) = 0.0
_GetBlendFactorMaxGizmoDistance("Distance on the gizmo where the blend circle stops", Float) = 0.2
_BlendFactorCircleRadius("Visual radius of the blend factor gizmo", Float) = 0.01
_ScreenRatio("Screen ratio", Vector) = (1.0, 1.0, 0.0, 0.0) // xy screen ratio, zw screen size
_ToneMapCoeffs1("Parameters for neutral tonemap", Vector) = (0.0, 0.0, 0.0, 0.0)
_ToneMapCoeffs2("Parameters for neutral tonemap", Vector) = (0.0, 0.0, 0.0, 0.0)
}
CGINCLUDE
#include "UnityCG.cginc"
#pragma vertex vert
// Enum matching GizmoOperationType in LookDevViews.cs
#define kNone 0.0f
#define kTranslation 1.0f
#define kRotationZone1 2.0f
#define kRotationZone2 3.0f
#define kAll 4.0f
sampler2D _Tex0WithSun;
sampler2D _Tex0WithoutSun;
sampler2D _Tex0Shadows;
float4 _ShadowColor0;
sampler2D _Tex1WithSun;
sampler2D _Tex1WithoutSun;
sampler2D _Tex1Shadows;
float4 _ShadowColor1;
float4 _CompositingParams; // x BlendFactor, yz ExposureValue (first/second view), w current selected side
float4 _CompositingParams2; // x current drag context, y apply tonemap (bool), z shadow multiplier
float4 _FirstViewColor;
float4 _SecondViewColor;
float4 _GizmoPosition;
float4 _GizmoZoneCenter;
float4 _GizmoThickness;
float4 _GizmoCircleRadius;
float4 _GizmoSplitPlane;
float4 _GizmoSplitPlaneOrtho;
float _GizmoLength;
float _GizmoRenderMode;
float _GetBlendFactorMaxGizmoDistance;
float _BlendFactorCircleRadius;
float4 _ScreenRatio;
float4 _ToneMapCoeffs1;
float4 _ToneMapCoeffs2;
float4 _Tex0WithSun_ST;
#define ShadowMultiplier0 _CompositingParams2.z
#define ShadowMultiplier1 _CompositingParams2.w
#define ExposureValue1 _CompositingParams.y
#define ExposureValue2 _CompositingParams.z
#define InBlack _ToneMapCoeffs1.x
#define OutBlack _ToneMapCoeffs1.y
#define InWhite _ToneMapCoeffs1.z
#define OutWhite _ToneMapCoeffs1.w
#define WhiteLevel _ToneMapCoeffs2.z
#define WhiteClip _ToneMapCoeffs2.w
struct appdata_t
{
float4 vertex : POSITION;
float2 texcoord : TEXCOORD0;
};
struct v2f
{
float2 texcoord : TEXCOORD0;
float4 vertex : SV_POSITION;
};
float DistanceToSplit(float2 pos, float3 splitPlane)
{
return dot(float3(pos, 1), splitPlane);
}
bool IsInsideGizmo(float2 normalizedCoord, float absDistanceToPlane, float distanceFromCenter, float side, float3 orthoPlane, float gizmoCircleRadius, float gizmoThickness, out float outSmoothing, float mode)
{
bool result = false;
outSmoothing = 0.0;
if (absDistanceToPlane < gizmoCircleRadius) // First "thick" bar, as large as the radius at extremities.
{
if (distanceFromCenter < (_GizmoLength + gizmoCircleRadius))
{
// side < 0 is cyan circle, side > 0 is orange widget
if (mode == kAll ||
(mode == kRotationZone1 && side > 0) ||
(mode == kRotationZone2 && side < 0))
{
if (distanceFromCenter >= (_GizmoLength - gizmoCircleRadius)) // Inside circle at the extremities ?
{
float2 circleCenter = _GizmoPosition.xy + side * orthoPlane.xy * _GizmoLength;
float d = length(normalizedCoord - circleCenter);
if (d <= gizmoCircleRadius)
{
outSmoothing = smoothstep(1.0, 0.8, d / gizmoCircleRadius);
result = true;
}
}
}
if (mode == kAll || mode == kTranslation)
{
if (absDistanceToPlane < gizmoThickness && distanceFromCenter < _GizmoLength)
{
outSmoothing = max(outSmoothing, smoothstep(1.0, 0.0, absDistanceToPlane / gizmoThickness));
result = true;
}
}
}
}
return result;
}
float4 GetGizmoColor(float2 normalizedCoord, float3 splitPlane, float3 orthoPlane)
{
float distanceToPlane = DistanceToSplit(normalizedCoord, splitPlane);
float absDistanceToPlane = abs(distanceToPlane);
float distanceFromCenter = length(normalizedCoord.xy - _GizmoPosition.xy);
float distanceToOrtho = DistanceToSplit(normalizedCoord, orthoPlane);
float4 result = float4(0.0, 0.0, 0.0, 0.0);
float side = 0.0;
if (distanceToOrtho > 0.0)
{
result.rgb = _FirstViewColor.rgb;
side = 1.0;
}
else
{
result.rgb = _SecondViewColor.rgb;
side = -1.0;
}
result.a = 0.0;
// "normal" gizmo
float smoothing = 1.0;
if (IsInsideGizmo(normalizedCoord, absDistanceToPlane, distanceFromCenter, side, orthoPlane, _GizmoCircleRadius.x, _GizmoThickness.x, smoothing, kAll))
{
result.a = 1.0 * smoothing;
}
// large gizmo when in translation mode
if (IsInsideGizmo(normalizedCoord, absDistanceToPlane, distanceFromCenter, side, orthoPlane, _GizmoCircleRadius.y, _GizmoThickness.y, smoothing, _GizmoRenderMode))
{
result.a = max(result.a, 0.25 * smoothing);
}
// Blend factor selection disc
float2 blendCircleCenter = _GizmoPosition.xy - _CompositingParams.x * orthoPlane.xy * _GetBlendFactorMaxGizmoDistance;
float distanceToBlendCircle = length(normalizedCoord.xy - blendCircleCenter);
if (distanceToBlendCircle < _BlendFactorCircleRadius)
{
float alpha = smoothstep(1.0, 0.6, distanceToBlendCircle / _BlendFactorCircleRadius);
result = lerp(result, float4(1.0, 1.0, 1.0, alpha), alpha);
}
// Display transparent disc if near the center where the blend factor selection disc will automatically snap back
if (abs(_CompositingParams.x) < _GizmoCircleRadius.y / _GetBlendFactorMaxGizmoDistance)
{
if (distanceFromCenter < _BlendFactorCircleRadius)
{
float alpha = smoothstep(1.0, 0.6, distanceFromCenter / _BlendFactorCircleRadius) * 0.75;
result = lerp(result, float4(1.0, 1.0, 1.0, alpha), alpha);
}
}
return result;
}
float GetZoneViewFeedbackCircleFactor(float2 normalizedCoord, float radius, float circleSize)
{
float distanceToCenter = abs(length(_GizmoZoneCenter.xy - normalizedCoord) - radius);
return saturate((circleSize - distanceToCenter) / circleSize);
}
float ComputeBorderFactor(float borderSize, float2 screenPos, bool sideBySideView)
{
float4 borderSize4 = float4(borderSize, borderSize, borderSize, borderSize);
float4 distanceToBorder = float4(screenPos.x, screenPos.y, abs(_ScreenRatio.z - screenPos.x), abs(_ScreenRatio.w - screenPos.y));
float4 factors = saturate((borderSize4 - distanceToBorder) / borderSize4); // Lerp from 1.0 to 0.0 alpha from screen border to border size
float factor = max(factors.x, max(factors.y, max(factors.z, factors.w)));
// Add middle of the screen for side by side view
if (sideBySideView)
{
float distanceToCenterLine = abs(_ScreenRatio.z * 0.5 - screenPos.x);
float factorForCenterLine = saturate((borderSize - distanceToCenterLine) / borderSize);
factor = max(factor, factorForCenterLine);
}
return factor;
}
float ComputeSelectedSideColorFactor(float side, float2 screenPos, float2 normalizedCoord, bool sideBySideView, bool zoneView)
{
float borderSize = 2.0;
bool selectedSide = side * _CompositingParams.w > 0.0;
float factor = ComputeBorderFactor(borderSize, screenPos, sideBySideView);
// Add circle for zone view
if (zoneView)
{
float selectionCircleFeedbackFactor = GetZoneViewFeedbackCircleFactor(normalizedCoord, _GizmoCircleRadius.y, 0.002);
factor = max(factor, selectionCircleFeedbackFactor);
}
// If not on the selected side, make it more transparent
if (!selectedSide)
{
factor = factor * 0.2;
}
return factor;
}
float4 ComputeDragColorFactor(float side, float2 screenPos, float2 normalizedCoord, bool sideBySideView, bool zoneView)
{
float factor = 0;
float borderSize = 40.0;
bool sideIsDragZone = (side > 0.0 && _CompositingParams2.x > 0.0) || (side < 0.0 && _CompositingParams2.x < 0.0);
if (sideIsDragZone)
{
factor = ComputeBorderFactor(borderSize, screenPos, sideBySideView);
// Add circle for zone view
if (zoneView && side < 0.0)
{
float feedbackRadius = _GizmoLength * 2.0 * 0.3; // make it proprtional to selection zone
factor = max(factor, GetZoneViewFeedbackCircleFactor(normalizedCoord, _GizmoLength * 2.0, feedbackRadius));
}
factor = pow(factor, 8) * 0.7; // Casimir magics values for optimum fadeout :)
}
return factor;
}
float4 ComputeFeedbackColor(float4 inputColor, float side, float2 screenPos, float2 normalizedCoord, bool sideBySideView, bool zoneView)
{
float factor = ComputeSelectedSideColorFactor(side, screenPos, normalizedCoord, sideBySideView, zoneView);
factor = max(factor, ComputeDragColorFactor(side, screenPos, normalizedCoord, sideBySideView, zoneView));
float4 result = float4(0.0, 0.0, 0.0, 0.0);
if (side > 0.0)
{
result = lerp(inputColor, _FirstViewColor, factor);
}
else
{
result = lerp(inputColor, _SecondViewColor, factor);
}
return result;
}
v2f vert(appdata_t IN)
{
v2f OUT;
OUT.vertex = UnityObjectToClipPos(IN.vertex);
OUT.texcoord = TRANSFORM_TEX(IN.texcoord, _Tex0WithSun);
return OUT;
}
float3 evalCurve(float3 x, float A, float B, float C, float D, float E, float F)
{
return ((x*(A*x + C*B) + D*E) / (x*(A*x + B) + D*F)) - E / F;
}
float3 applyTonemapFilmicAD(float3 linearColor)
{
float blackRatio = InBlack / OutBlack;
float whiteRatio = InWhite / OutWhite;
// blend tunable coefficients
float B = lerp(0.57, 0.37, blackRatio);
float C = lerp(0.01, 0.24, whiteRatio);
float D = lerp(0.02, 0.20, blackRatio);
// constants
float A = 0.2;
float E = 0.02;
float F = 0.30;
// eval and correct for white point
float3 whiteScale = 1.0f / evalCurve(WhiteLevel, A, B, C, D, E, F);
float3 curr = evalCurve(linearColor *whiteScale, A, B, C, D, E, F);
return curr*whiteScale;
}
float3 remapWhite(float3 inPixel, float whitePt)
{
// var breakout for readability
const float inBlack = 0;
const float outBlack = 0;
float inWhite = whitePt;
const float outWhite = 1;
// remap input range to output range
float3 outPixel = ((inPixel.rgb) - inBlack.xxx) / (inWhite.xxx - inBlack.xxx) * (outWhite.xxx - outBlack.xxx) + outBlack.xxx;
return (outPixel.rgb);
}
float3 NeutralTonemap(float3 x)
{
float3 finalColor = applyTonemapFilmicAD(x); // curve (dynamic coeffs differ per level)
finalColor = remapWhite(finalColor, WhiteClip); // post-curve white point adjustment
finalColor = saturate(finalColor);
return finalColor;
}
float3 ApplyToneMap(float3 color)
{
if (_CompositingParams2.y > 0.0)
{
return NeutralTonemap(color);
}
else
{
return saturate(color);
}
}
float3 ComputeColor(sampler2D texNormal, sampler2D texWithoutSun, sampler2D texShadowMask, float shadowMultiplier, float4 shadowColor, float2 texcoord)
{
// Explanation of how this work:
// To simulate the shadow of a directional light, we want to interpolate between two environments. One with a skybox without sun for shadowed area and the other with the sun.
// To create the lerp mask we render the scene with a white diffuse material and a single shadow casting directional light.
// This will create a mask where the shadowed area is 0 and the lit area is 1 with a smooth NDotL transition in-between.
// However, the DNotL will create an unwanted darkening of the scene (it's not actually part of the lighting equation)
// so we sqrt it in order to avoid too much darkening.
float3 color = tex2D(texNormal, texcoord).rgb;
float3 colorWithoutsun = tex2D(texWithoutSun, texcoord).rgb;
float3 shadowMask = sqrt(tex2D(texShadowMask, texcoord).rgb);
return lerp(colorWithoutsun * shadowColor.rgb * shadowMultiplier, color, saturate(shadowMask.r));
}
ENDCG
SubShader
{
Tags
{
"ForceSupported" = "True"
}
Lighting Off
Cull Off
ZTest Always
ZWrite Off
Blend One Zero
// Single view 1
Pass
{
CGPROGRAM
#pragma fragment frag
#pragma target 3.0
float4 frag(float2 texcoord : TEXCOORD0,
UNITY_VPOS_TYPE vpos : VPOS) : COLOR
{
float4 color = float4(ComputeColor(_Tex0WithSun, _Tex0WithoutSun, _Tex0Shadows, ShadowMultiplier0, _ShadowColor0, texcoord) * exp2(ExposureValue1), 1.0);
color.rgb = ApplyToneMap(color.rgb);
color = ComputeFeedbackColor(color, 1.0, vpos.xy, float2(0.0, 0.0), false, false);
return color;
}
ENDCG
}
// Single view 2
Pass
{
CGPROGRAM
#pragma fragment frag
#pragma target 3.0
float4 frag(float2 texcoord : TEXCOORD0,
UNITY_VPOS_TYPE vpos : VPOS) : COLOR
{
float4 color = float4(ComputeColor(_Tex1WithSun, _Tex1WithoutSun, _Tex1Shadows, ShadowMultiplier1, _ShadowColor1, texcoord) * exp2(ExposureValue2), 1.0);
color.rgb = ApplyToneMap(color.rgb);
color = ComputeFeedbackColor(color, -1.0, vpos.xy, float2(0.0, 0.0), false, false);
return color;
}
ENDCG
}
// split
Pass
{
CGPROGRAM
#pragma fragment frag
#pragma target 3.0
float4 frag(float2 texcoord : TEXCOORD0,
UNITY_VPOS_TYPE vpos : VPOS) : COLOR
{
float3 color1 = ComputeColor(_Tex0WithSun, _Tex0WithoutSun, _Tex0Shadows, ShadowMultiplier0, _ShadowColor0, texcoord) * exp2(ExposureValue1);
float3 color2 = ComputeColor(_Tex1WithSun, _Tex1WithoutSun, _Tex1Shadows, ShadowMultiplier1, _ShadowColor1, texcoord) * exp2(ExposureValue2);
float2 normalizedCoord = ((texcoord * 2.0 - 1.0) * _ScreenRatio.xy);
float side = DistanceToSplit(normalizedCoord, _GizmoSplitPlane) < 0.0f ? -1.0f : 1.0f;
float blendFactor = 0.0f;
if (side < 0.0)
{
blendFactor = 1.0 - saturate(side * _CompositingParams.x);
}
else
{
blendFactor = saturate(side * _CompositingParams.x);
}
float4 finalColor = float4(lerp(color1, color2, blendFactor), 1.0);
finalColor.rgb = ApplyToneMap(finalColor.rgb);
float4 gizmoColor = GetGizmoColor(normalizedCoord, _GizmoSplitPlane, _GizmoSplitPlaneOrtho);
finalColor = lerp(finalColor, gizmoColor, gizmoColor.a);
finalColor = ComputeFeedbackColor(finalColor, side, vpos.xy, float2(0.0, 0.0), false, false);
return finalColor;
}
ENDCG
}
}
}

9
Packages/com.unity.render-pipelines.core/Editor/LookDev/Compositor.shader.meta


fileFormatVersion: 2
guid: 4386e57b23a56004c93e1d57d2bbcb4f
ShaderImporter:
externalObjects: {}
defaultTextures: []
nonModifiableTextures: []
userData:
assetBundleName:
assetBundleVariant:

400
Packages/com.unity.render-pipelines.core/Editor/LookDev/Context.cs


using System;
using UnityEngine;
namespace UnityEditor.Rendering.LookDev
{
/// <summary>
/// Different working views in LookDev
/// </summary>
public enum ViewIndex
{
First,
Second
};
/// <summary>
/// Same as <see cref="ViewIndex"/> plus a compound value
/// </summary>
public enum ViewCompositionIndex
{
First = ViewIndex.First,
Second = ViewIndex.Second,
Composite
};
// /!\ WARNING: these value name are used as uss file too.
// if your rename here, rename in the uss too.
/// <summary>
/// Different layout supported in LookDev
/// </summary>
public enum Layout
{
FullFirstView,
FullSecondView,
HorizontalSplit,
VerticalSplit,
CustomSplit
}
/// <summary>
/// Statis of the side panel of the LookDev window
/// </summary>
public enum SidePanel
{
None = -1,
Environment,
Debug
}
/// <summary>
/// Class containing all data used by the LookDev Window to render
/// </summary>
[System.Serializable]
public class Context : ScriptableObject, IDisposable
{
[SerializeField]
string m_EnvironmentLibraryGUID = ""; //Empty GUID
[SerializeField]
bool m_CameraSynced = true;
/// <summary>The currently used Environment</summary>
public EnvironmentLibrary environmentLibrary { get; private set; }
/// <summary>The currently used layout</summary>
[field: SerializeField]
public LayoutContext layout { get; private set; } = new LayoutContext();
/// <summary>
/// State if both views camera movement are synced or not
/// </summary>
public bool cameraSynced
{
get => m_CameraSynced;
set
{
if (m_CameraSynced ^ value)
{
if (value)
EditorApplication.update += SynchronizeCameraStates;
else
EditorApplication.update -= SynchronizeCameraStates;
m_CameraSynced = value;
}
}
}
[SerializeField]
ViewContext[] m_Views = new ViewContext[2]
{
new ViewContext(),
new ViewContext()
};
/// <summary>
/// Get datas relative to a view
/// </summary>
/// <param name="index">The view index to look at</param>
/// <returns>Datas for the selected view</returns>
public ViewContext GetViewContent(ViewIndex index)
=> m_Views[(int)index];
internal void Init()
{
LoadEnvironmentLibraryFromGUID();
//recompute non serialized computes states
layout.gizmoState.Init();
if (cameraSynced)
EditorApplication.update += SynchronizeCameraStates;
}
/// <summary>Update the environment used.</summary>
/// <param name="environmentOrCubemapAsset">
/// The new <see cref="Environment"/> to use.
/// Or the <see cref="Cubemap"/> to use to build a new one.
/// Other types will raise an ArgumentException.
/// </param>
public void UpdateEnvironmentLibrary(EnvironmentLibrary library)
{
m_EnvironmentLibraryGUID = "";
environmentLibrary = null;
if (library == null || library.Equals(null))
return;
m_EnvironmentLibraryGUID = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(library));
environmentLibrary = library;
}
void LoadEnvironmentLibraryFromGUID()
{
environmentLibrary = null;
GUID storedGUID;
GUID.TryParse(m_EnvironmentLibraryGUID, out storedGUID);
if (storedGUID.Empty())
return;
string path = AssetDatabase.GUIDToAssetPath(m_EnvironmentLibraryGUID);
environmentLibrary = AssetDatabase.LoadAssetAtPath<EnvironmentLibrary>(path);
}
/// <summary>
/// Synchronize cameras from both view using data from the baseCameraState
/// </summary>
/// <param name="baseCameraState">The <see cref="ViewIndex"/> to be used as reference</param>
public void SynchronizeCameraStates(ViewIndex baseCameraState)
{
switch (baseCameraState)
{
case ViewIndex.First:
m_Views[1].camera.SynchronizeFrom(m_Views[0].camera);
break;
case ViewIndex.Second:
m_Views[0].camera.SynchronizeFrom(m_Views[1].camera);
break;
default:
throw new System.ArgumentException("Unknow ViewIndex given in parameter.");
}
}
void SynchronizeCameraStates()
=> SynchronizeCameraStates(layout.lastFocusedView);
/// <summary>
/// Change focused view.
/// Focused view is the base view to copy data when syncing views' cameras
/// </summary>
/// <param name="index">The index of the view</param>
public void SetFocusedCamera(ViewIndex index)
=> layout.lastFocusedView = index;
private bool disposedValue = false; // To detect redundant calls
void IDisposable.Dispose()
{
if (!disposedValue)
{
if (cameraSynced)
EditorApplication.update -= SynchronizeCameraStates;
disposedValue = true;
}
}
}
/// <summary>
/// Data regarding the layout currently used in LookDev
/// </summary>
[System.Serializable]
public class LayoutContext
{
/// <summary>The layout used</summary>
public Layout viewLayout;
/// <summary>The last focused view</summary>
public ViewIndex lastFocusedView = ViewIndex.First;
/// <summary>The state of the side panel</summary>
public SidePanel showedSidePanel;
[SerializeField]
internal ComparisonGizmoState gizmoState = new ComparisonGizmoState();
internal bool isSimpleView => viewLayout == Layout.FullFirstView || viewLayout == Layout.FullSecondView;
internal bool isMultiView => viewLayout == Layout.HorizontalSplit || viewLayout == Layout.VerticalSplit;
internal bool isCombinedView => viewLayout == Layout.CustomSplit;
}
/// <summary>
/// Data container containing content of a view
/// </summary>
[System.Serializable]
public class ViewContext
{
/// <summary>The position and rotation of the camera</summary>
[field: SerializeField]
public CameraState camera { get; private set; } = new CameraState();
/// <summary>The currently viewed debugState</summary>
[field: SerializeField]
public DebugContext debug { get; private set; } = new DebugContext();
//Environment asset, sub-asset (under a library) or cubemap
[SerializeField]
string environmentGUID = ""; //Empty GUID
/// <summary>
/// Check if an Environment is registered for this view.
/// The result will be accurate even if the Environment have not been reloaded yet.
/// </summary>
public bool hasEnvironment => !String.IsNullOrEmpty(environmentGUID);
/// <summary>The currently used Environment</summary>
public Environment environment { get; private set; }
[SerializeField]
string viewedObjectAssetGUID = ""; //Empty GUID
// Careful here: we want to keep it while reloading script.
// But from one unity editor to an other, ID are not kept.
// So, only use it when reloading from script update.
[SerializeField]
int viewedObjecHierarchytInstanceID;
/// <summary>
/// Check if an Environment is registered for this view.
/// The result will be accurate even if the object have not been reloaded yet.
/// </summary>
public bool hasViewedObject =>
!String.IsNullOrEmpty(viewedObjectAssetGUID)
|| viewedObjecHierarchytInstanceID != 0;
/// <summary>Reference to the object given for instantiation.</summary>
public GameObject viewedObjectReference { get; private set; }
/// <summary>
/// The currently displayed instance of <see cref="viewedObjectReference"/>.
/// It will be instantiated when pushing changes to renderer.
/// See <see cref="LookDev.SaveContextChangeAndApply(ViewIndex)"/>
/// </summary>
public GameObject viewedInstanceInPreview { get; internal set; }
/// <summary>Update the environment used.</summary>
/// <param name="environmentOrCubemapAsset">
/// The new <see cref="Environment"/> to use.
/// Or the <see cref="Cubemap"/> to use to build a new one.
/// Other types will raise an ArgumentException.
/// </param>
public void UpdateEnvironment(UnityEngine.Object environmentOrCubemapAsset)
{
environmentGUID = "";
environment = null;
if (environmentOrCubemapAsset == null || environmentOrCubemapAsset.Equals(null))
return;
if (!(environmentOrCubemapAsset is Environment)
&& !(environmentOrCubemapAsset is Cubemap))
throw new System.ArgumentException("Only Environment or Cubemap accepted for environmentOrCubemapAsset parameter");
string GUID;
long localIDInFile;
AssetDatabase.TryGetGUIDAndLocalFileIdentifier(environmentOrCubemapAsset, out GUID, out localIDInFile);
environmentGUID = $"{GUID},{localIDInFile}";
if (environmentOrCubemapAsset is Environment)
environment = environmentOrCubemapAsset as Environment;
else //Cubemap
{
environment = new Environment();
environment.sky.cubemap = environmentOrCubemapAsset as Cubemap;
}
}
void LoadEnvironmentFromGUID()
{
environment = null;
GUID storedGUID;
string[] GUIDAndLocalIDInFile = environmentGUID.Split(new[] { ',' });
GUID.TryParse(GUIDAndLocalIDInFile[0], out storedGUID);
if (storedGUID.Empty())
return;
long localIDInFile = GUIDAndLocalIDInFile.Length < 2 ? 0L : long.Parse(GUIDAndLocalIDInFile[1]);
string path = AssetDatabase.GUIDToAssetPath(GUIDAndLocalIDInFile[0]);
Type savedType = AssetDatabase.GetMainAssetTypeAtPath(path);
if (savedType == typeof(EnvironmentLibrary))
{
object[] loaded = AssetDatabase.LoadAllAssetsAtPath(path);
for (int i = 0; i < loaded.Length; ++i)
{
string garbage;
long testedLocalIndex;
if (AssetDatabase.TryGetGUIDAndLocalFileIdentifier((UnityEngine.Object)loaded[i], out garbage, out testedLocalIndex)
&& testedLocalIndex == localIDInFile)
{
environment = loaded[i] as Environment;
break;
}
}
}
else if (savedType == typeof(Environment))
environment = AssetDatabase.LoadAssetAtPath<Environment>(path);
else if (savedType == typeof(Cubemap))
{
Cubemap cubemap = AssetDatabase.LoadAssetAtPath<Cubemap>(path);
environment = new Environment();
environment.sky.cubemap = cubemap;
}
}
/// <summary>Update the object reference used for instantiation.</summary>
/// <param name="viewedObject">The new reference.</param>
public void UpdateViewedObject(GameObject viewedObject)
{
viewedObjectAssetGUID = "";
viewedObjecHierarchytInstanceID = 0;
viewedObjectReference = null;
if (viewedObject == null || viewedObject.Equals(null))
return;
bool fromHierarchy = viewedObject.scene.IsValid();
if (fromHierarchy)
viewedObjecHierarchytInstanceID = viewedObject.GetInstanceID();
else
viewedObjectAssetGUID = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(viewedObject));
viewedObjectReference = viewedObject;
}
//WARNING: only for script reloading
void LoadViewedObject()
{
viewedObjectReference = null;
GUID storedGUID;
GUID.TryParse(viewedObjectAssetGUID, out storedGUID);
if (!storedGUID.Empty())
{
string path = AssetDatabase.GUIDToAssetPath(viewedObjectAssetGUID);
viewedObjectReference = AssetDatabase.LoadAssetAtPath<GameObject>(path);
}
else if (viewedObjecHierarchytInstanceID != 0)
{
viewedObjectReference = EditorUtility.InstanceIDToObject(viewedObjecHierarchytInstanceID) as GameObject;
}
}
internal void LoadAll(bool reloadWithTemporaryID)
{
if (!reloadWithTemporaryID)
CleanTemporaryObjectIndexes();
LoadEnvironmentFromGUID();
LoadViewedObject();
}
internal void CleanTemporaryObjectIndexes()
=> viewedObjecHierarchytInstanceID = 0;
/// <summary>Reset the camera state to default values</summary>
public void ResetCameraState()
=> camera.Reset();
}
/// <summary>
/// Class that will contain debug value used.
/// </summary>
[System.Serializable]
public class DebugContext
{
///// <summary>Display the debug grey balls</summary>
//public bool greyBalls;
//[SerializeField]
//string colorChartGUID = ""; //Empty GUID
///// <summary>The currently used color chart</summary>
//public Texture2D colorChart { get; private set; }
}
}

11
Packages/com.unity.render-pipelines.core/Editor/LookDev/Context.cs.meta


fileFormatVersion: 2
guid: e6c442cd75783b64283e30cff5a7d377
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

133
Packages/com.unity.render-pipelines.core/Editor/LookDev/CubeToLatlong.shader


Shader "Hidden/LookDev/CubeToLatlong"
{
Properties
{
[NoScaleOffset] _MainTex ("Cubemap", Any) = "grey" {}
_CubeToLatLongParams ("Parameters", Vector) = (0.0, 0.0, 0.0, 0.0)
_WindowParams("Window params", Vector) = (0.0, 0.0, 0.0, 0.0)
}
CGINCLUDE
#include "UnityCG.cginc"
uniform float4 _MainTex_HDR;
uniform float4 _MainTex_ST;
UNITY_DECLARE_TEXCUBE(_MainTex);
uniform float4 _CubeToLatLongParams; // x angle offset, y alpha, z intensity w lod to use
uniform float4 _WindowParams; // x Editor windows height, y Environment windows posY, z margin (constant of 2), w PixelsPerPoint
uniform bool _ManualTex2SRGB;
#define OutputAlpha _CubeToLatLongParams.y
#define Intensity _CubeToLatLongParams.z
#define CurrentLOD _CubeToLatLongParams.w
struct appdata_t
{
float4 vertex : POSITION;
float2 texcoord : TEXCOORD0;
};
struct v2f
{
float2 texcoord : TEXCOORD0;
float4 vertex : SV_POSITION;
};
v2f vert(appdata_t IN)
{
v2f OUT;
OUT.vertex = UnityObjectToClipPos(IN.vertex);
OUT.texcoord = TRANSFORM_TEX(IN.texcoord, _MainTex);
return OUT;
}
float4 frag( float2 texcoord : TEXCOORD0,
UNITY_VPOS_TYPE vpos : VPOS
) : COLOR
{
float2 texCoord = texcoord.xy;
float theta = texCoord.y * UNITY_PI;
float phi = (texCoord.x * 2.f * UNITY_PI - UNITY_PI*0.5f) - _CubeToLatLongParams.x;
float cosTheta = cos(theta);
float sinTheta = sqrt(1.0f - min(1.0f, cosTheta*cosTheta));
float cosPhi = cos(phi);
float sinPhi = sin(phi);
float3 direction = float3(sinTheta*cosPhi, cosTheta, sinTheta*sinPhi);
direction.xy *= -1.0;
float4 ret = float4(DecodeHDR(UNITY_SAMPLE_TEXCUBE_LOD(_MainTex, direction, CurrentLOD), _MainTex_HDR) * Intensity, OutputAlpha);
if (_ManualTex2SRGB)
ret.rgb = LinearToGammaSpace(ret.rgb);
// Clip outside of the library window
// Editor windows is like this:
//------
// Margin (2)
// Scene - Game - Asset Store <= What we call tab size
//------
// Settings - Views <= what we call menu size
//----
// View size with Environment windows)
//
// _WindowParams.x contain the height of the editor windows
// _WindowParams.y contain the start of the windows environment in the windows editor, i.e the menu size + tab size
// _WindowParams.z contain a constant margin of 2 (don't know how to retrieve that)
// _WindowParams.w is PixelsPerPoin (To handle retina display on OSX))
// We use VPOS register to clip, VPOS is dependent on the API. It is reversed in openGL.
// There is no need to clip when y is above height because the editor windows will clip it
// vertex.y is relative to editor windows
#if UNITY_UV_STARTS_AT_TOP
if ((vpos.y / _WindowParams.w) < (_WindowParams.y + _WindowParams.z))
#else
// vertex.y is reversed (start from bottom of the editor windsows)
vpos.y = _WindowParams.x - (vpos.y / _WindowParams.w);
if (vpos.y < _WindowParams.z)
#endif
{
clip(-1);
}
return ret;
}
ENDCG
SubShader
{
Tags
{
"ForceSupported"="True"
}
Lighting Off
Cull Off
ZTest Always
ZWrite Off
Pass
{
Blend One Zero
CGPROGRAM
#pragma fragment frag
#pragma vertex vert
#pragma target 3.0
ENDCG
}
Pass
{
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma fragment frag
#pragma vertex vert
#pragma target 3.0
ENDCG
}
}
}

9
Packages/com.unity.render-pipelines.core/Editor/LookDev/CubeToLatlong.shader.meta


fileFormatVersion: 2
guid: a16365e8c873daa4c94919438490b905
ShaderImporter:
externalObjects: {}
defaultTextures: []
nonModifiableTextures: []
userData:
assetBundleName:
assetBundleVariant:

34
Packages/com.unity.render-pipelines.core/Editor/LookDev/DisplayWindow-PersonalSkin.uss


/* ******************************* */
/* override for personal skin them */
/* ******************************* */
#environmentContainer > EnvironmentElement
{
border-color: #999999;
}
#separator
{
border-color: #999999;
}
.list-environment-overlay > ToolbarButton
{
background-color: #CBCBCB;
}
#inspector-header
{
background-color: #CBCBCB;
border-color: #999999;
}
#separator-line
{
background-color: #CBCBCB;
}
Image.unity-list-view__item:selected
{
border-color: #3A72B0;
}

11
Packages/com.unity.render-pipelines.core/Editor/LookDev/DisplayWindow-PersonalSkin.uss.meta


fileFormatVersion: 2
guid: 7c95237923780ad4683f959f53629b60
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0}
disableValidation: 0

958
Packages/com.unity.render-pipelines.core/Editor/LookDev/DisplayWindow.cs


using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;
namespace UnityEditor.Rendering.LookDev
{
/// <summary>Interface that must implement the viewer to communicate with the compositor and data management</summary>
public interface IViewDisplayer
{
Rect GetRect(ViewCompositionIndex index);
void SetTexture(ViewCompositionIndex index, Texture texture);
void Repaint();
event Action<Layout, SidePanel> OnLayoutChanged;
event Action OnRenderDocAcquisitionTriggered;
event Action<IMouseEvent> OnMouseEventInView;
event Action<GameObject, ViewCompositionIndex, Vector2> OnChangingObjectInView;
event Action<UnityEngine.Object, ViewCompositionIndex, Vector2> OnChangingEnvironmentInView;
event Action OnClosed;
event Action OnUpdateRequested;
}
/// <summary>Interface that must implement the EnvironmentLibrary view to communicate with the data management</summary>
public interface IEnvironmentDisplayer
{
void Repaint();
event Action<EnvironmentLibrary> OnChangingEnvironmentLibrary;
}
class DisplayWindow : EditorWindow, IViewDisplayer, IEnvironmentDisplayer
{
static class Style
{
internal const string k_IconFolder = @"Packages/com.unity.render-pipelines.core/Editor/LookDev/Icons/";
internal const string k_uss = @"Packages/com.unity.render-pipelines.core/Editor/LookDev/DisplayWindow.uss";
internal const string k_uss_personal_overload = @"Packages/com.unity.render-pipelines.core/Editor/LookDev/DisplayWindow-PersonalSkin.uss";
public static readonly GUIContent WindowTitleAndIcon = EditorGUIUtility.TrTextContentWithIcon("Look Dev", CoreEditorUtils.LoadIcon(k_IconFolder, "LookDevMainIcon", forceLowRes: true));
}
// /!\ WARNING:
//The following const are used in the uss.
//If you change them, update the uss file too.
const string k_MainContainerName = "mainContainer";
const string k_EnvironmentContainerName = "environmentContainer";
const string k_ShowEnvironmentPanelClass = "showEnvironmentPanel";
const string k_ViewContainerName = "viewContainer";
const string k_FirstViewName = "firstView";
const string k_SecondViewName = "secondView";
const string k_ToolbarName = "toolbar";
const string k_ToolbarRadioName = "toolbarRadio";
const string k_TabsRadioName = "tabsRadio";
const string k_SideToolbarName = "sideToolbar";
const string k_SharedContainerClass = "container";
const string k_FirstViewClass = "firstView";
const string k_SecondViewsClass = "secondView";
const string k_VerticalViewsClass = "verticalSplit";
const string k_DebugContainerName = "debugContainer";
const string k_ShowDebugPanelClass = "showDebugPanel";
VisualElement m_MainContainer;
VisualElement m_ViewContainer;
VisualElement m_DebugContainer;
PopupField<string> m_DebugView;
VisualElement m_EnvironmentContainer;
ListView m_EnvironmentList;
EnvironmentElement m_EnvironmentInspector;
Label m_NoEnvironmentList;
Label m_NoObject1;
Label m_NoEnvironment1;
Label m_NoObject2;
Label m_NoEnvironment2;
Toolbar m_EnvironmentListToolbar;
Image[] m_Views = new Image[2];
Layout layout
{
get => LookDev.currentContext.layout.viewLayout;
set
{
if (LookDev.currentContext.layout.viewLayout != value)
{
OnLayoutChangedInternal?.Invoke(value, sidePanel);
ApplyLayout(value);
}
}
}
SidePanel sidePanel
{
get => LookDev.currentContext.layout.showedSidePanel;
set
{
if (LookDev.currentContext.layout.showedSidePanel != value)
{
OnLayoutChangedInternal?.Invoke(layout, value);
ApplySidePanelChange(value);
}
}
}
event Action<Layout, SidePanel> OnLayoutChangedInternal;
event Action<Layout, SidePanel> IViewDisplayer.OnLayoutChanged
{
add => OnLayoutChangedInternal += value;
remove => OnLayoutChangedInternal -= value;
}
event Action OnRenderDocAcquisitionTriggeredInternal;
event Action IViewDisplayer.OnRenderDocAcquisitionTriggered
{
add => OnRenderDocAcquisitionTriggeredInternal += value;
remove => OnRenderDocAcquisitionTriggeredInternal -= value;
}
event Action<IMouseEvent> OnMouseEventInViewPortInternal;
event Action<IMouseEvent> IViewDisplayer.OnMouseEventInView
{
add => OnMouseEventInViewPortInternal += value;
remove => OnMouseEventInViewPortInternal -= value;
}
event Action<GameObject, ViewCompositionIndex, Vector2> OnChangingObjectInViewInternal;
event Action<GameObject, ViewCompositionIndex, Vector2> IViewDisplayer.OnChangingObjectInView
{
add => OnChangingObjectInViewInternal += value;
remove => OnChangingObjectInViewInternal -= value;
}
//event Action<Material, ViewCompositionIndex, Vector2> OnChangingMaterialInViewInternal;
//event Action<Material, ViewCompositionIndex, Vector2> IViewDisplayer.OnChangingMaterialInView
//{
// add => OnChangingMaterialInViewInternal += value;
// remove => OnChangingMaterialInViewInternal -= value;
//}
event Action<UnityEngine.Object, ViewCompositionIndex, Vector2> OnChangingEnvironmentInViewInternal;
event Action<UnityEngine.Object, ViewCompositionIndex, Vector2> IViewDisplayer.OnChangingEnvironmentInView
{
add => OnChangingEnvironmentInViewInternal += value;
remove => OnChangingEnvironmentInViewInternal -= value;
}
event Action OnClosedInternal;
event Action IViewDisplayer.OnClosed
{
add => OnClosedInternal += value;
remove => OnClosedInternal -= value;
}
//event Action<UnityEngine.Object> OnAddingEnvironmentInternal;
//event Action<UnityEngine.Object> IEnvironmentDisplayer.OnAddingEnvironment
//{
// add => OnAddingEnvironmentInternal += value;
// remove => OnAddingEnvironmentInternal -= value;
//}
//event Action<int> OnRemovingEnvironmentInternal;
//event Action<int> IEnvironmentDisplayer.OnRemovingEnvironment
//{
// add => OnRemovingEnvironmentInternal += value;
// remove => OnRemovingEnvironmentInternal -= value;
//}
event Action<EnvironmentLibrary> OnChangingEnvironmentLibraryInternal;
event Action<EnvironmentLibrary> IEnvironmentDisplayer.OnChangingEnvironmentLibrary
{
add => OnChangingEnvironmentLibraryInternal += value;
remove => OnChangingEnvironmentLibraryInternal -= value;
}
event Action OnUpdateRequestedInternal;
event Action IViewDisplayer.OnUpdateRequested
{
add => OnUpdateRequestedInternal += value;
remove => OnUpdateRequestedInternal -= value;
}
void OnEnable()
{
//Call the open function to configure LookDev
// in case the window where open when last editor session finished.
// (Else it will open at start and has nothing to display).
if (!LookDev.open)
LookDev.Open();
titleContent = Style.WindowTitleAndIcon;
rootVisualElement.styleSheets.Add(
AssetDatabase.LoadAssetAtPath<StyleSheet>(Style.k_uss));
if (!EditorGUIUtility.isProSkin)
{
rootVisualElement.styleSheets.Add(
AssetDatabase.LoadAssetAtPath<StyleSheet>(Style.k_uss_personal_overload));
}
CreateToolbar();
m_MainContainer = new VisualElement() { name = k_MainContainerName };
m_MainContainer.AddToClassList(k_SharedContainerClass);
rootVisualElement.Add(m_MainContainer);
CreateViews();
CreateEnvironment();
CreateDebug();
CreateDropAreas();
ApplyLayout(layout);
ApplySidePanelChange(sidePanel);
}
void OnDisable() => OnClosedInternal?.Invoke();
void CreateToolbar()
{
// Layout swapper part
var layoutRadio = new ToolbarRadio() { name = k_ToolbarRadioName };
layoutRadio.AddRadios(new[] {
CoreEditorUtils.LoadIcon(Style.k_IconFolder, "LookDev_Layout1", forceLowRes: true),
CoreEditorUtils.LoadIcon(Style.k_IconFolder, "LookDev_Layout2", forceLowRes: true),
CoreEditorUtils.LoadIcon(Style.k_IconFolder, "LookDev_LayoutVertical", forceLowRes: true),
CoreEditorUtils.LoadIcon(Style.k_IconFolder, "LookDev_LayoutHorizontal", forceLowRes: true),
CoreEditorUtils.LoadIcon(Style.k_IconFolder, "LookDev_LayoutCustom", forceLowRes: true)
});
layoutRadio.RegisterCallback((ChangeEvent<int> evt)
=> layout = (Layout)evt.newValue);
layoutRadio.SetValueWithoutNotify((int)layout);
var cameraMenu = new ToolbarMenu() { name = "cameraMenu" };
cameraMenu.variant = ToolbarMenu.Variant.Popup;
var cameraToggle = new ToolbarToggle() { name = "cameraButton" };
cameraToggle.value = LookDev.currentContext.cameraSynced;
//Note: when having Image on top of the Toggle nested in the Menu, RegisterValueChangedCallback is not called
//cameraToggle.RegisterValueChangedCallback(evt => LookDev.currentContext.cameraSynced = evt.newValue);
cameraToggle.RegisterCallback<MouseUpEvent>(evt =>
{
LookDev.currentContext.cameraSynced ^= true;
cameraToggle.SetValueWithoutNotify(LookDev.currentContext.cameraSynced);
});
var cameraSeparator = new ToolbarToggle() { name = "cameraSeparator" };
var texCamera1 = CoreEditorUtils.LoadIcon(Style.k_IconFolder, "LookDev_Camera1", forceLowRes: true);
var texCamera2 = CoreEditorUtils.LoadIcon(Style.k_IconFolder, "LookDev_Camera2", forceLowRes: true);
var texLink = CoreEditorUtils.LoadIcon(Style.k_IconFolder, "LookDev_Link", forceLowRes: true);
var texRight = CoreEditorUtils.LoadIcon(Style.k_IconFolder, "LookDev_Right", forceLowRes: true);
var texLeft = CoreEditorUtils.LoadIcon(Style.k_IconFolder, "LookDev_Left", forceLowRes: true);
cameraToggle.Add(new Image() { image = texCamera1 });
cameraToggle.Add(new Image() { image = texLink });
cameraToggle.Add(new Image() { image = texCamera2 });
cameraMenu.Add(cameraToggle);
cameraMenu.Add(cameraSeparator);
cameraMenu.menu.AppendAction("Align Camera 1 with Camera 2",
(DropdownMenuAction a) => LookDev.currentContext.SynchronizeCameraStates(ViewIndex.Second),
DropdownMenuAction.AlwaysEnabled);
cameraMenu.menu.AppendAction("Align Camera 2 with Camera 1",
(DropdownMenuAction a) => LookDev.currentContext.SynchronizeCameraStates(ViewIndex.First),
DropdownMenuAction.AlwaysEnabled);
cameraMenu.menu.AppendAction("Reset Cameras",
(DropdownMenuAction a) =>
{
LookDev.currentContext.GetViewContent(ViewIndex.First).ResetCameraState();
LookDev.currentContext.GetViewContent(ViewIndex.Second).ResetCameraState();
},
DropdownMenuAction.AlwaysEnabled);
// Side part
var sideRadio = new ToolbarRadio(canDeselectAll: true) { name = k_TabsRadioName };
sideRadio.AddRadios(new[] {
"Environment",
"Debug"
});
sideRadio.RegisterCallback((ChangeEvent<int> evt) =>
{
sidePanel = (SidePanel)evt.newValue;
if (sidePanel == SidePanel.Debug)
RefreshDebugViews();
});
sideRadio.SetValueWithoutNotify((int)sidePanel);
var sideToolbar = new Toolbar() { name = k_SideToolbarName };
sideToolbar.Add(sideRadio);
// Aggregate parts
var toolbar = new Toolbar() { name = k_ToolbarName };
toolbar.Add(layoutRadio);
toolbar.Add(new ToolbarSpacer());
toolbar.Add(cameraMenu);
toolbar.Add(new ToolbarSpacer() { flex = true });
if (UnityEditorInternal.RenderDoc.IsInstalled() && UnityEditorInternal.RenderDoc.IsLoaded())
{
var renderDocButton = new ToolbarButton(() => OnRenderDocAcquisitionTriggeredInternal?.Invoke())
{
name = "renderdoc-content"
};
renderDocButton.Add(new Image()
{
image = CoreEditorUtils.LoadIcon(Style.k_IconFolder, "renderdoc", forceLowRes: true)
});
renderDocButton.Add(new Label() { text = " Content" });
toolbar.Add(renderDocButton);
}
toolbar.Add(sideToolbar);
rootVisualElement.Add(toolbar);
}
void CreateViews()
{
if (m_MainContainer == null || m_MainContainer.Equals(null))
throw new System.MemberAccessException("m_MainContainer should be assigned prior CreateViews()");
m_ViewContainer = new VisualElement() { name = k_ViewContainerName };
m_ViewContainer.AddToClassList(LookDev.currentContext.layout.isMultiView ? k_SecondViewsClass : k_FirstViewClass);
m_ViewContainer.AddToClassList(k_SharedContainerClass);
m_MainContainer.Add(m_ViewContainer);
m_ViewContainer.RegisterCallback<MouseDownEvent>(evt => OnMouseEventInViewPortInternal?.Invoke(evt));
m_ViewContainer.RegisterCallback<MouseUpEvent>(evt => OnMouseEventInViewPortInternal?.Invoke(evt));
m_ViewContainer.RegisterCallback<MouseMoveEvent>(evt => OnMouseEventInViewPortInternal?.Invoke(evt));
m_Views[(int)ViewIndex.First] = new Image() { name = k_FirstViewName, image = Texture2D.blackTexture };
m_ViewContainer.Add(m_Views[(int)ViewIndex.First]);
m_Views[(int)ViewIndex.Second] = new Image() { name = k_SecondViewName, image = Texture2D.blackTexture };
m_ViewContainer.Add(m_Views[(int)ViewIndex.Second]);
var firstOrCompositeManipulator = new SwitchableCameraController(
LookDev.currentContext.GetViewContent(ViewIndex.First).camera,
LookDev.currentContext.GetViewContent(ViewIndex.Second).camera,
this,
index =>
{
LookDev.currentContext.SetFocusedCamera(index);
var environment = LookDev.currentContext.GetViewContent(index).environment;
if (sidePanel == SidePanel.Environment && environment != null)
m_EnvironmentList.selectedIndex = LookDev.currentContext.environmentLibrary.IndexOf(environment);
});
var secondManipulator = new CameraController(
LookDev.currentContext.GetViewContent(ViewIndex.Second).camera,
this,
() =>
{
LookDev.currentContext.SetFocusedCamera(ViewIndex.Second);
var environment = LookDev.currentContext.GetViewContent(ViewIndex.Second).environment;
if (sidePanel == SidePanel.Environment && environment != null)
m_EnvironmentList.selectedIndex = LookDev.currentContext.environmentLibrary.IndexOf(environment);
});
var gizmoManipulator = new ComparisonGizmoController(LookDev.currentContext.layout.gizmoState, firstOrCompositeManipulator);
m_Views[(int)ViewIndex.First].AddManipulator(gizmoManipulator); //must take event first to switch the firstOrCompositeManipulator
m_Views[(int)ViewIndex.First].AddManipulator(firstOrCompositeManipulator);
m_Views[(int)ViewIndex.Second].AddManipulator(secondManipulator);
m_NoObject1 = new Label("Drag'n'drop object here");
m_NoObject1.style.flexGrow = 1;
m_NoObject1.style.unityTextAlign = TextAnchor.MiddleCenter;
m_NoObject2 = new Label("Drag'n'drop object here");
m_NoObject2.style.flexGrow = 1;
m_NoObject2.style.unityTextAlign = TextAnchor.MiddleCenter;
m_NoEnvironment1 = new Label("Drag'n'drop environment from side panel here");
m_NoEnvironment1.style.flexGrow = 1;
m_NoEnvironment1.style.unityTextAlign = TextAnchor.MiddleCenter;
m_NoEnvironment2 = new Label("Drag'n'drop environment from side panel here");
m_NoEnvironment2.style.flexGrow = 1;
m_NoEnvironment2.style.unityTextAlign = TextAnchor.MiddleCenter;
m_Views[(int)ViewIndex.First].Add(m_NoObject1);
m_Views[(int)ViewIndex.First].Add(m_NoEnvironment1);
m_Views[(int)ViewIndex.Second].Add(m_NoObject2);
m_Views[(int)ViewIndex.Second].Add(m_NoEnvironment2);
}
void CreateDropAreas()
{
// GameObject or Prefab in view
new DropArea(new[] { typeof(GameObject) }, m_Views[(int)ViewIndex.First], (obj, localPos) =>
{
if (layout == Layout.CustomSplit)
OnChangingObjectInViewInternal?.Invoke(obj as GameObject, ViewCompositionIndex.Composite, localPos);
else
OnChangingObjectInViewInternal?.Invoke(obj as GameObject, ViewCompositionIndex.First, localPos);
m_NoObject1.style.visibility = obj == null || obj.Equals(null) ? Visibility.Visible : Visibility.Hidden;
});
new DropArea(new[] { typeof(GameObject) }, m_Views[(int)ViewIndex.Second], (obj, localPos) =>
{
OnChangingObjectInViewInternal?.Invoke(obj as GameObject, ViewCompositionIndex.Second, localPos);
m_NoObject2.style.visibility = obj == null || obj.Equals(null) ? Visibility.Visible : Visibility.Hidden;
});
// Material in view
//new DropArea(new[] { typeof(GameObject) }, m_Views[(int)ViewIndex.First], (obj, localPos) =>
//{
// if (layout == Layout.CustomSplit || layout == Layout.CustomCircular)
// OnChangingMaterialInViewInternal?.Invoke(obj as Material, ViewCompositionIndex.Composite, localPos);
// else
// OnChangingMaterialInViewInternal?.Invoke(obj as Material, ViewCompositionIndex.First, localPos);
//});
//new DropArea(new[] { typeof(Material) }, m_Views[(int)ViewIndex.Second], (obj, localPos)
// => OnChangingMaterialInViewInternal?.Invoke(obj as Material, ViewCompositionIndex.Second, localPos));
// Environment in view
new DropArea(new[] { typeof(Environment), typeof(Cubemap) }, m_Views[(int)ViewIndex.First], (obj, localPos) =>
{
if (layout == Layout.CustomSplit)
OnChangingEnvironmentInViewInternal?.Invoke(obj, ViewCompositionIndex.Composite, localPos);
else
OnChangingEnvironmentInViewInternal?.Invoke(obj, ViewCompositionIndex.First, localPos);
m_NoEnvironment1.style.visibility = obj == null || obj.Equals(null) ? Visibility.Visible : Visibility.Hidden;
});
new DropArea(new[] { typeof(Environment), typeof(Cubemap) }, m_Views[(int)ViewIndex.Second], (obj, localPos) =>
{
OnChangingEnvironmentInViewInternal?.Invoke(obj, ViewCompositionIndex.Second, localPos);
m_NoEnvironment2.style.visibility = obj == null || obj.Equals(null) ? Visibility.Visible : Visibility.Hidden;
});
// Environment in library
//new DropArea(new[] { typeof(Environment), typeof(Cubemap) }, m_EnvironmentContainer, (obj, localPos) =>
//{
// //[TODO: check if this come from outside of library]
// OnAddingEnvironmentInternal?.Invoke(obj);
//});
new DropArea(new[] { typeof(EnvironmentLibrary) }, m_EnvironmentContainer, (obj, localPos) =>
{
OnChangingEnvironmentLibraryInternal?.Invoke(obj as EnvironmentLibrary);
RefreshLibraryDisplay();
});
}
void CreateDebug()
{
if (m_MainContainer == null || m_MainContainer.Equals(null))
throw new System.MemberAccessException("m_MainContainer should be assigned prior CreateEnvironment()");
m_DebugContainer = new VisualElement() { name = k_DebugContainerName };
m_MainContainer.Add(m_DebugContainer);
if (sidePanel == SidePanel.Debug)
m_MainContainer.AddToClassList(k_ShowDebugPanelClass);
//[TODO: finish]
//Toggle greyBalls = new Toggle("Grey balls");
//greyBalls.SetValueWithoutNotify(LookDev.currentContext.GetViewContent(LookDev.currentContext.layout.lastFocusedView).debug.greyBalls);
//greyBalls.RegisterValueChangedCallback(evt =>
//{
// LookDev.currentContext.GetViewContent(LookDev.currentContext.layout.lastFocusedView).debug.greyBalls = evt.newValue;
//});
//m_DebugContainer.Add(greyBalls);
//[TODO: debug why list sometimes empty on resource reloading]
//[TODO: display only per view]
RefreshDebugViews();
}
void RefreshDebugViews()
{
if (m_DebugView != null && m_DebugContainer.Contains(m_DebugView))
m_DebugContainer.Remove(m_DebugView);
List<string> list = new List<string>(LookDev.dataProvider?.supportedDebugModes ?? Enumerable.Empty<string>());
list.Insert(0, "None");
m_DebugView = new PopupField<string>("Debug view mode", list, 0);
m_DebugView.RegisterValueChangedCallback(evt
=> LookDev.dataProvider.UpdateDebugMode(list.IndexOf(evt.newValue) - 1));
m_DebugContainer.Add(m_DebugView);
}
static int FirstVisibleIndex(ListView listView)
=> (int)(listView.Q<ScrollView>().scrollOffset.y / listView.itemHeight);
void CreateEnvironment()
{
if (m_MainContainer == null || m_MainContainer.Equals(null))
throw new System.MemberAccessException("m_MainContainer should be assigned prior CreateEnvironment()");
m_EnvironmentContainer = new VisualElement() { name = k_EnvironmentContainerName };
m_MainContainer.Add(m_EnvironmentContainer);
if (sidePanel == SidePanel.Environment)
m_MainContainer.AddToClassList(k_ShowEnvironmentPanelClass);
m_EnvironmentInspector = new EnvironmentElement(withPreview: false, () =>
{
LookDev.SaveContextChangeAndApply(ViewIndex.First);
LookDev.SaveContextChangeAndApply(ViewIndex.Second);
});
m_EnvironmentList = new ListView();
m_EnvironmentList.AddToClassList("list-environment");
m_EnvironmentList.selectionType = SelectionType.Single;
m_EnvironmentList.itemHeight = EnvironmentElement.k_SkyThumbnailHeight;
m_EnvironmentList.makeItem = () =>
{
var preview = new Image();
preview.AddManipulator(new EnvironmentPreviewDragger(this, m_ViewContainer));
return preview;
};
m_EnvironmentList.bindItem = (e, i) =>
{
(e as Image).image = EnvironmentElement.GetLatLongThumbnailTexture(
LookDev.currentContext.environmentLibrary[i],
EnvironmentElement.k_SkyThumbnailWidth);
};
m_EnvironmentList.onSelectionChanged += objects =>
{
if (objects.Count == 0 || (LookDev.currentContext.environmentLibrary?.Count ?? 0) == 0)
{
m_EnvironmentInspector.style.visibility = Visibility.Hidden;
m_EnvironmentInspector.style.height = 0;
}
else
{
m_EnvironmentInspector.style.visibility = Visibility.Visible;
m_EnvironmentInspector.style.height = new StyleLength(StyleKeyword.Auto);
int firstVisibleIndex = FirstVisibleIndex(m_EnvironmentList);
Environment environment = LookDev.currentContext.environmentLibrary[m_EnvironmentList.selectedIndex];
var container = m_EnvironmentList.Q("unity-content-container");
if (m_EnvironmentList.selectedIndex - firstVisibleIndex >= container.childCount || m_EnvironmentList.selectedIndex < firstVisibleIndex)
{
m_EnvironmentList.ScrollToItem(m_EnvironmentList.selectedIndex);
firstVisibleIndex = FirstVisibleIndex(m_EnvironmentList);
}
Image deportedLatLong = container[m_EnvironmentList.selectedIndex - firstVisibleIndex] as Image;
m_EnvironmentInspector.Bind(environment, deportedLatLong);
}
};
m_EnvironmentList.onItemChosen += obj =>
EditorGUIUtility.PingObject(LookDev.currentContext.environmentLibrary[(int)obj]);
m_NoEnvironmentList = new Label("Drag'n'drop EnvironmentLibrary here");
m_NoEnvironmentList.style.flexGrow = 1;
m_NoEnvironmentList.style.unityTextAlign = TextAnchor.MiddleCenter;
m_EnvironmentContainer.Add(m_EnvironmentInspector);
m_EnvironmentListToolbar = new Toolbar();
ToolbarButton addEnvironment = new ToolbarButton(() =>
{
LookDev.currentContext.environmentLibrary.Add();
RefreshLibraryDisplay();
m_EnvironmentList.ScrollToItem(-1); //-1: scroll to end
m_EnvironmentList.selectedIndex = LookDev.currentContext.environmentLibrary.Count - 1;
ScrollToEnd();
})
{
name = "add",
tooltip = "Add new empty environment"
};
addEnvironment.Add(new Image() { image = CoreEditorUtils.LoadIcon(Style.k_IconFolder, "LookDev_EnvironmentAdd", forceLowRes: true) });
ToolbarButton removeEnvironment = new ToolbarButton(() =>
{
if (m_EnvironmentList.selectedIndex == -1)
return;
LookDev.currentContext.environmentLibrary.Remove(m_EnvironmentList.selectedIndex);
RefreshLibraryDisplay();
m_EnvironmentList.selectedIndex = -1;
})
{
name = "remove",
tooltip = "Remove environment currently selected"
};
removeEnvironment.Add(new Image() { image = CoreEditorUtils.LoadIcon(Style.k_IconFolder, "LookDev_EnvironmentDelete", forceLowRes: true) });
ToolbarButton duplicateEnvironment = new ToolbarButton(() =>
{
if (m_EnvironmentList.selectedIndex == -1)
return;
LookDev.currentContext.environmentLibrary.Duplicate(m_EnvironmentList.selectedIndex);
RefreshLibraryDisplay();
m_EnvironmentList.ScrollToItem(-1); //-1: scroll to end
m_EnvironmentList.selectedIndex = LookDev.currentContext.environmentLibrary.Count - 1;
ScrollToEnd();
})
{
name = "duplicate",
tooltip = "Duplicate environment currently selected"
};
duplicateEnvironment.Add(new Image() { image = CoreEditorUtils.LoadIcon(Style.k_IconFolder, "LookDev_EnvironmentDuplicate", forceLowRes: true) });
m_EnvironmentListToolbar.Add(addEnvironment);
m_EnvironmentListToolbar.Add(removeEnvironment);
m_EnvironmentListToolbar.Add(duplicateEnvironment);
m_EnvironmentListToolbar.AddToClassList("list-environment-overlay");
var m_EnvironmentInspectorSeparator = new VisualElement() { name = "separator-line" };
m_EnvironmentInspectorSeparator.Add(new VisualElement() { name = "separator" });
m_EnvironmentContainer.Add(m_EnvironmentInspectorSeparator);
VisualElement listContainer = new VisualElement();
listContainer.AddToClassList("list-environment");
listContainer.Add(m_EnvironmentList);
listContainer.Add(m_EnvironmentListToolbar);
var environmentListCreationToolbar = new Toolbar()
{
name = "environmentListCreationToolbar"
};
environmentListCreationToolbar.Add(new ToolbarButton(()
=> EnvironmentLibraryCreator.Create())
{
text = "New Library",
tooltip = "Create a new EnvironmentLibrary"
});
environmentListCreationToolbar.Add(new ToolbarButton(()
=> EnvironmentLibraryLoader.Load(RefreshLibraryDisplay))
{
text = "Load Library",
tooltip = "Load an existing EnvironmentLibrary"
});
m_EnvironmentContainer.Add(listContainer);
m_EnvironmentContainer.Add(m_NoEnvironmentList);
m_EnvironmentContainer.Add(environmentListCreationToolbar);
//add ability to unselect
m_EnvironmentList.RegisterCallback<MouseDownEvent>(evt =>
{
var clickedIndex = (int)(evt.localMousePosition.y / m_EnvironmentList.itemHeight);
if (clickedIndex >= m_EnvironmentList.itemsSource.Count)
{
m_EnvironmentList.selectedIndex = -1;
evt.StopPropagation();
}
});
RefreshLibraryDisplay();
}
//necessary as the scrollview need to be updated which take some editor frames.
void ScrollToEnd(int attemptRemaining = 5)
{
m_EnvironmentList.ScrollToItem(-1); //-1: scroll to end
if (attemptRemaining > 0)
EditorApplication.delayCall += () => ScrollToEnd(--attemptRemaining);
}
void RefreshLibraryDisplay()
{
int itemMax = LookDev.currentContext.environmentLibrary?.Count ?? 0;
if (itemMax == 0 || m_EnvironmentList.selectedIndex == -1)
{
m_EnvironmentInspector.style.visibility = Visibility.Hidden;
m_EnvironmentInspector.style.height = 0;
}
else
{
m_EnvironmentInspector.style.visibility = Visibility.Visible;
m_EnvironmentInspector.style.height = new StyleLength(StyleKeyword.Auto);
}
var items = new List<int>(itemMax);
for (int i = 0; i < itemMax; i++)
items.Add(i);
m_EnvironmentList.itemsSource = items;
if (LookDev.currentContext.environmentLibrary == null)
{
m_EnvironmentList
.Q(className: "unity-scroll-view__vertical-scroller")
.Q("unity-dragger")
.style.visibility = Visibility.Hidden;
m_EnvironmentListToolbar.style.visibility = Visibility.Hidden;
m_NoEnvironmentList.style.display = DisplayStyle.Flex;
}
else
{
m_EnvironmentList
.Q(className: "unity-scroll-view__vertical-scroller")
.Q("unity-dragger")
.style.visibility = itemMax == 0
? Visibility.Hidden
: Visibility.Visible;
m_EnvironmentListToolbar.style.visibility = Visibility.Visible;
m_NoEnvironmentList.style.display = DisplayStyle.None;
}
}
DraggingContext StartDragging(VisualElement item, Vector2 worldPosition)
=> new DraggingContext(
rootVisualElement,
item as Image,
//note: this even can come before the selection event of the
//ListView. Reconstruct index by looking at target of the event.
(int)item.layout.y / m_EnvironmentList.itemHeight,
worldPosition);
void EndDragging(DraggingContext context, Vector2 mouseWorldPosition)
{
Environment environment = LookDev.currentContext.environmentLibrary[context.draggedIndex];
if (m_Views[(int)ViewIndex.First].ContainsPoint(mouseWorldPosition))
{
if (layout == Layout.CustomSplit)
OnChangingEnvironmentInViewInternal?.Invoke(environment, ViewCompositionIndex.Composite, mouseWorldPosition);
else
OnChangingEnvironmentInViewInternal?.Invoke(environment, ViewCompositionIndex.First, mouseWorldPosition);
}
else
OnChangingEnvironmentInViewInternal?.Invoke(environment, ViewCompositionIndex.Second, mouseWorldPosition);
}
class DraggingContext : IDisposable
{
const string k_CursorFollowerName = "cursorFollower";
public readonly int draggedIndex;
readonly Image cursorFollower;
readonly Vector2 cursorOffset;
readonly VisualElement windowContent;
public DraggingContext(VisualElement windowContent, Image draggedElement, int draggedIndex, Vector2 worldPosition)
{
this.windowContent = windowContent;
this.draggedIndex = draggedIndex;
cursorFollower = new Image()
{
name = k_CursorFollowerName,
image = draggedElement.image
};
cursorFollower.tintColor = new Color(1f, 1f, 1f, .5f);
windowContent.Add(cursorFollower);
cursorOffset = draggedElement.WorldToLocal(worldPosition);
cursorFollower.style.position = Position.Absolute;
UpdateCursorFollower(worldPosition);
}
public void UpdateCursorFollower(Vector2 mouseWorldPosition)
{
Vector2 windowLocalPosition = windowContent.WorldToLocal(mouseWorldPosition);
cursorFollower.style.left = windowLocalPosition.x - cursorOffset.x;
cursorFollower.style.top = windowLocalPosition.y - cursorOffset.y;
}
public void Dispose()
{
if (windowContent.Contains(cursorFollower))
windowContent.Remove(cursorFollower);
}
}
class EnvironmentPreviewDragger : Manipulator
{
VisualElement m_DropArea;
DisplayWindow m_Window;
//Note: static as only one drag'n'drop at a time
static DraggingContext s_Context;
public EnvironmentPreviewDragger(DisplayWindow window, VisualElement dropArea)
{
m_Window = window;
m_DropArea = dropArea;
}
protected override void RegisterCallbacksOnTarget()
{
target.RegisterCallback<MouseDownEvent>(OnMouseDown);
target.RegisterCallback<MouseUpEvent>(OnMouseUp);
}
protected override void UnregisterCallbacksFromTarget()
{
target.UnregisterCallback<MouseDownEvent>(OnMouseDown);
target.UnregisterCallback<MouseUpEvent>(OnMouseUp);
}
void Release()
{
target.UnregisterCallback<MouseMoveEvent>(OnMouseMove);
s_Context.Dispose();
target.ReleaseMouse();
s_Context = null;
}
void OnMouseDown(MouseDownEvent evt)
{
if (evt.button == 0)
{
target.CaptureMouse();
target.RegisterCallback<MouseMoveEvent>(OnMouseMove);
s_Context = m_Window.StartDragging(target, evt.mousePosition);
//do not stop event as we still need to propagate it to the ListView for selection
}
}
void OnMouseUp(MouseUpEvent evt)
{
if (evt.button != 0)
return;
if (m_DropArea.ContainsPoint(m_DropArea.WorldToLocal(Event.current.mousePosition)))
{
m_Window.EndDragging(s_Context, evt.mousePosition);
evt.StopPropagation();
}
Release();
}
void OnMouseMove(MouseMoveEvent evt)
{
evt.StopPropagation();
s_Context.UpdateCursorFollower(evt.mousePosition);
}
}
Rect IViewDisplayer.GetRect(ViewCompositionIndex index)
{
switch (index)
{
case ViewCompositionIndex.First:
case ViewCompositionIndex.Composite: //display composition on first rect
return m_Views[(int)ViewIndex.First].contentRect;
case ViewCompositionIndex.Second:
return m_Views[(int)ViewIndex.Second].contentRect;
default:
throw new ArgumentException("Unknown ViewCompositionIndex: " + index);
}
}
Vector2 m_LastFirstViewSize = new Vector2();
Vector2 m_LastSecondViewSize = new Vector2();
void IViewDisplayer.SetTexture(ViewCompositionIndex index, Texture texture)
{
bool updated = false;
switch (index)
{
case ViewCompositionIndex.First:
case ViewCompositionIndex.Composite: //display composition on first rect
if (updated |= m_Views[(int)ViewIndex.First].image != texture)
m_Views[(int)ViewIndex.First].image = texture;
else if (updated |= (m_LastFirstViewSize.x != texture.width
|| m_LastFirstViewSize.y != texture.height))
{
m_Views[(int)ViewIndex.First].image = null; //force refresh else it will appear zoomed
m_Views[(int)ViewIndex.First].image = texture;
}
if (updated)
{
m_LastFirstViewSize.x = texture?.width ?? 0;
m_LastFirstViewSize.y = texture?.height ?? 0;
}
break;
case ViewCompositionIndex.Second:
if (m_Views[(int)ViewIndex.Second].image != texture)
m_Views[(int)ViewIndex.Second].image = texture;
else if (updated |= (m_LastSecondViewSize.x != texture.width
|| m_LastSecondViewSize.y != texture.height))
{
m_Views[(int)ViewIndex.Second].image = null; //force refresh else it will appear zoomed
m_Views[(int)ViewIndex.Second].image = texture;
}
if (updated)
{
m_LastSecondViewSize.x = texture?.width ?? 0;
m_LastSecondViewSize.y = texture?.height ?? 0;
}
break;
default:
throw new ArgumentException("Unknown ViewCompositionIndex: " + index);
}
}
void IViewDisplayer.Repaint() => Repaint();
void IEnvironmentDisplayer.Repaint()
=> RefreshLibraryDisplay();
void ApplyLayout(Layout value)
{
m_NoObject1.style.visibility = LookDev.currentContext.GetViewContent(ViewIndex.First).hasViewedObject ? Visibility.Hidden : Visibility.Visible;
m_NoObject2.style.visibility = LookDev.currentContext.GetViewContent(ViewIndex.Second).hasViewedObject ? Visibility.Hidden : Visibility.Visible;
m_NoEnvironment1.style.visibility = LookDev.currentContext.GetViewContent(ViewIndex.First).hasEnvironment ? Visibility.Hidden : Visibility.Visible;
m_NoEnvironment2.style.visibility = LookDev.currentContext.GetViewContent(ViewIndex.Second).hasEnvironment ? Visibility.Hidden : Visibility.Visible;
switch (value)
{
case Layout.HorizontalSplit:
case Layout.VerticalSplit:
if (!m_ViewContainer.ClassListContains(k_FirstViewClass))
m_ViewContainer.AddToClassList(k_FirstViewClass);
if (!m_ViewContainer.ClassListContains(k_SecondViewsClass))
m_ViewContainer.AddToClassList(k_SecondViewsClass);
if (value == Layout.VerticalSplit)
{
m_ViewContainer.AddToClassList(k_VerticalViewsClass);
if (!m_ViewContainer.ClassListContains(k_VerticalViewsClass))
m_ViewContainer.AddToClassList(k_FirstViewClass);
}
for (int i = 0; i < 2; ++i)
m_Views[i].style.display = DisplayStyle.Flex;
break;
case Layout.FullFirstView:
case Layout.CustomSplit: //display composition on first rect
if (!m_ViewContainer.ClassListContains(k_FirstViewClass))
m_ViewContainer.AddToClassList(k_FirstViewClass);
if (m_ViewContainer.ClassListContains(k_SecondViewsClass))
m_ViewContainer.RemoveFromClassList(k_SecondViewsClass);
m_Views[0].style.display = DisplayStyle.Flex;
m_Views[1].style.display = DisplayStyle.None;
break;
case Layout.FullSecondView:
if (m_ViewContainer.ClassListContains(k_FirstViewClass))
m_ViewContainer.RemoveFromClassList(k_FirstViewClass);
if (!m_ViewContainer.ClassListContains(k_SecondViewsClass))
m_ViewContainer.AddToClassList(k_SecondViewsClass);
m_Views[0].style.display = DisplayStyle.None;
m_Views[1].style.display = DisplayStyle.Flex;
break;
default:
throw new ArgumentException("Unknown Layout");
}
//Add flex direction here
if (value == Layout.VerticalSplit)
m_ViewContainer.AddToClassList(k_VerticalViewsClass);
else if (m_ViewContainer.ClassListContains(k_VerticalViewsClass))
m_ViewContainer.RemoveFromClassList(k_VerticalViewsClass);
}
void ApplySidePanelChange(SidePanel sidePanel)
{
switch (sidePanel)
{
case SidePanel.None:
if (m_MainContainer.ClassListContains(k_ShowEnvironmentPanelClass))
m_MainContainer.RemoveFromClassList(k_ShowEnvironmentPanelClass);
if (m_MainContainer.ClassListContains(k_ShowDebugPanelClass))
m_MainContainer.RemoveFromClassList(k_ShowDebugPanelClass);
m_EnvironmentContainer.Q<EnvironmentElement>().style.visibility = Visibility.Hidden;
m_EnvironmentContainer.Q(className: "unity-base-slider--vertical").Q("unity-dragger").style.display = DisplayStyle.None;
m_EnvironmentContainer.style.display = DisplayStyle.None;
break;
case SidePanel.Environment:
if (!m_MainContainer.ClassListContains(k_ShowEnvironmentPanelClass))
m_MainContainer.AddToClassList(k_ShowEnvironmentPanelClass);
if (m_MainContainer.ClassListContains(k_ShowDebugPanelClass))
m_MainContainer.RemoveFromClassList(k_ShowDebugPanelClass);
if (m_EnvironmentList.selectedIndex != -1)
m_EnvironmentContainer.Q<EnvironmentElement>().style.visibility = Visibility.Visible;
m_EnvironmentContainer.Q(className: "unity-base-slider--vertical").Q("unity-dragger").style.display = DisplayStyle.Flex;
m_EnvironmentContainer.style.display = DisplayStyle.Flex;
break;
case SidePanel.Debug:
if (m_MainContainer.ClassListContains(k_ShowEnvironmentPanelClass))
m_MainContainer.RemoveFromClassList(k_ShowEnvironmentPanelClass);
if (!m_MainContainer.ClassListContains(k_ShowDebugPanelClass))
m_MainContainer.AddToClassList(k_ShowDebugPanelClass);
m_EnvironmentContainer.Q<EnvironmentElement>().style.visibility = Visibility.Hidden;
m_EnvironmentContainer.Q(className: "unity-base-slider--vertical").Q("unity-dragger").style.display = DisplayStyle.None;
m_EnvironmentContainer.style.display = DisplayStyle.None;
break;
default:
throw new ArgumentException("Unknown SidePanel");
}
}
void OnGUI() => OnUpdateRequestedInternal?.Invoke();
}
}

11
Packages/com.unity.render-pipelines.core/Editor/LookDev/DisplayWindow.cs.meta


fileFormatVersion: 2
guid: 3c06d176f822d384683da5237d832c67
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

325
Packages/com.unity.render-pipelines.core/Editor/LookDev/DisplayWindow.uss


.container
{
margin: 0px;
flex: 1;
flex-direction: row;
}
/* ///// */
/* VIEWS */
/* ///// */
/* override as later in document */
.verticalSplit
{
flex-direction: column;
}
#viewContainer
{
flex-shrink: 0;
}
#firstView,
#secondView
{
flex: 0;
}
/* override as later in document */
.firstView > #firstView,
.secondView > #secondView
{
flex: 1;
}
/* /////////// */
/* ENVIRONMENT */
/* /////////// */
#environmentContainer
{
width: 0px;
visibility: hidden;
flex-direction: column-reverse;
}
#debugContainer
{
width: 0px;
visibility: hidden;
}
#environmentContainer > EnvironmentElement
{
border-color: #232323;
flex-shrink: 0;
}
.showEnvironmentPanel > #environmentContainer
{
width: 250px; /*219px;*/
visibility: visible;
}
.showDebugPanel > #debugContainer
{
width: 250px; /*219px;*/
visibility: visible;
}
.unity-label
{
min-width: 100px;
}
.unity-composite-field__field > .unity-base-field__label
{
min-width: 10px;
}
#unity-text-input
{
min-width: 40px;
}
.list-environment
{
flex: 1;
}
.list-environment-overlay
{
position: absolute;
bottom: 0px;
padding-left: 1px;
border-bottom-width: 0px;
background-color: rgba(0,0,0,0);
}
#environmentListCreationToolbar
{
padding-left: 1px;
}
#environmentListCreationToolbar > *
{
flex: 1;
-unity-text-align: middle-center;
}
ToolbarButton
{
border-left-width: 0px;
}
.list-environment-overlay > ToolbarButton
{
border-width: 0px;
border-right-width: 1px;
width: 20px;
min-width: 20px;
-unity-text-align: middle-center;
background-color: #3c3c3c;
}
.list-environment-overlay > #duplicate
{
border-right-width: 0px;
}
Image.unity-list-view__item
{
width: 210px;
margin: 15px;
padding: 5px;
}
Image.unity-list-view__item:selected
{
border-width: 2px;
padding: 3px;
border-color: #3d6091;
background-color: rgba(0,0,0,0);
}
.sun-to-brightest-button
{
padding-left: 4px;
}
#inspector-header
{
flex-direction: row;
border-bottom-width: 1px;
border-color: #232323;
padding-top: 5px;
padding-bottom: 5px;
padding-left: 3px;
background-color: #3C3C3C;
}
#inspector-header > Image
{
margin-top: 2px;
margin-bottom: 2px;
}
#inspector-header > TextField
{
flex: 1;
}
#inspector
{
padding-bottom: 5px;
}
#separator-line
{
background-color: #3C3C3C;
}
#separator
{
flex: 1;
width: 190px;
border-bottom-width: 1px;
border-color: #232323;
height: 0;
align-self: flex-end;
}
#sunToBrightestButton
{
background-color: rgba(0,0,0,0);
border-radius: 0px;
border-width: 0px;
padding: 0px;
margin-right: 1px;
}
/* /////// */
/* TOOLBAR */
/* /////// */
#toolbar
{
flex-direction: row;
}
#toolbar > #toolbarRadio
{
flex-direction: row;
}
.unity-toggle__input > .unity-image
{
padding: 2px;
}
#toolbar > * > .unity-label
{
padding-top: 3px;
padding-bottom: 0px;
min-width: 30px;
}
#sideToolbar
{
width: 249px;
}
#sideToolbar > #tabsRadio
{
min-width: auto;
flex: 1;
flex-direction: row;
-unity-text-align: middle-center;
}
.unity-toolbar-toggle
{
padding-top: 0px;
padding-right: 0px;
padding-bottom: 0px;
padding-left: 0px;
margin-left: 0px;
margin-right: 0px;
border-left-width: 0px;
}
#renderdoc-content
{
flex: 1;
flex-direction: row;
max-width: 80px;
flex-shrink: 0;
min-width: 24px;
}
#renderdoc-content > Label
{
-unity-text-align: middle-left;
min-width: 0px;
}
#renderdoc-content > Image
{
flex-shrink: 0;
min-width: 16px;
}
#cameraMenu
{
flex-direction: row;
padding: 0px;
padding-right: 16px;
}
#cameraButton
{
border-radius: 0px;
border-width: 0px;
border-left-width: 1px;
padding: 0px;
padding-right: 4px;
margin: 0px;
}
#cameraSeparator
{
margin-top: 4px;
margin-bottom: 4px;
border-right-width: 1px;
}
#cameraButton > *
{
margin: 2px;
}
/* /////////// */
/* DRAG'N'DROP */
/* /////////// */
#cursorFollower
{
position: absolute;
}
/* TEST UNDER THIS LINE */
#firstView
{
background-color: rgb(255, 0, 0);
}
#secondView
{
background-color: rgb(0, 255, 0);
}

11
Packages/com.unity.render-pipelines.core/Editor/LookDev/DisplayWindow.uss.meta


fileFormatVersion: 2
guid: e78887bb3654fde48bc3bc31beb7c7e4
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0}
disableValidation: 0

103
Packages/com.unity.render-pipelines.core/Editor/LookDev/DropArea.cs


using System.Collections;
using System;
using UnityEngine;
using UnityEngine.UIElements;
namespace UnityEditor.Rendering.LookDev
{
class DropArea
{
readonly Type[] k_AcceptedTypes;
bool droppable;
public DropArea(Type[] acceptedTypes, VisualElement area, Action<UnityEngine.Object, Vector2> OnDrop)
{
k_AcceptedTypes = acceptedTypes;
area.RegisterCallback<DragPerformEvent>(evt => Drop(evt, OnDrop));
area.RegisterCallback<DragEnterEvent>(evt => DragEnter(evt));
area.RegisterCallback<DragLeaveEvent>(evt => DragLeave(evt));
area.RegisterCallback<DragExitedEvent>(evt => DragExit(evt));
area.RegisterCallback<DragUpdatedEvent>(evt => DragUpdate(evt));
}
void DragEnter(DragEnterEvent evt)
{
droppable = false;
foreach (UnityEngine.Object obj in DragAndDrop.objectReferences)
{
if (!IsInAcceptedTypes(obj.GetType()))
continue;
droppable = true;
evt.StopPropagation();
return;
}
}
void DragLeave(DragLeaveEvent evt)
{
foreach (UnityEngine.Object obj in DragAndDrop.objectReferences)
{
if (!IsInAcceptedTypes(obj.GetType()))
continue;
DragAndDrop.visualMode = DragAndDropVisualMode.Rejected;
evt.StopPropagation();
return;
}
}
void DragExit(DragExitedEvent evt)
{
foreach (UnityEngine.Object obj in DragAndDrop.objectReferences)
{
if (!IsInAcceptedTypes(obj.GetType()))
continue;
DragAndDrop.visualMode = DragAndDropVisualMode.Rejected;
evt.StopPropagation();
return;
}
}
void DragUpdate(DragUpdatedEvent evt)
{
foreach (UnityEngine.Object obj in DragAndDrop.objectReferences)
{
if (!IsInAcceptedTypes(obj.GetType()))
continue;
DragAndDrop.visualMode = droppable ? DragAndDropVisualMode.Link : DragAndDropVisualMode.Rejected;
evt.StopPropagation();
}
}
void Drop(DragPerformEvent evt, Action<UnityEngine.Object, Vector2> OnDrop)
{
bool atLeastOneAccepted = false;
foreach (UnityEngine.Object obj in DragAndDrop.objectReferences)
{
if (!IsInAcceptedTypes(obj.GetType()))
continue;
OnDrop.Invoke(obj, evt.localMousePosition);
atLeastOneAccepted = true;
}
if (atLeastOneAccepted)
{
DragAndDrop.AcceptDrag();
evt.StopPropagation();
}
}
bool IsInAcceptedTypes(Type testedType)
{
foreach (Type type in k_AcceptedTypes)
{
if (testedType.IsAssignableFrom(type))
return true;
}
return false;
}
}
}

11
Packages/com.unity.render-pipelines.core/Editor/LookDev/DropArea.cs.meta


fileFormatVersion: 2
guid: 775ab08d64fdb694d89087d33fce3216
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

542
Packages/com.unity.render-pipelines.core/Editor/LookDev/Environment.cs


using UnityEngine;
using System;
using UnityEngine.UIElements;
using UnityEditor.UIElements;
using System.Linq;
namespace UnityEditor.Rendering.LookDev
{
/// <summary>
/// Lighting environment used in LookDev
/// </summary>
public class Environment : ScriptableObject
{
[Serializable]
public abstract class BaseEnvironmentCubemapHandler
{
[SerializeField]
string m_CubemapGUID;
Cubemap m_Cubemap;
/// <summary>
/// The cubemap used for this part of the lighting environment
/// </summary>
public Cubemap cubemap
{
get
{
if (m_Cubemap == null || m_Cubemap.Equals(null))
LoadCubemap();
return m_Cubemap;
}
set
{
m_Cubemap = value;
m_CubemapGUID = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(m_Cubemap));
}
}
void LoadCubemap()
{
m_Cubemap = null;
GUID storedGUID;
GUID.TryParse(m_CubemapGUID, out storedGUID);
if (!storedGUID.Empty())
{
string path = AssetDatabase.GUIDToAssetPath(m_CubemapGUID);
m_Cubemap = AssetDatabase.LoadAssetAtPath<Cubemap>(path);
}
}
}
/// <summary>
/// Class containing editor data for shadow part of the lighting environment
/// </summary>
[Serializable]
public class Shadow : BaseEnvironmentCubemapHandler
{
// Setup default position to be on the sun in the default HDRI.
// This is important as the defaultHDRI don't call the set brightest spot function on first call.
[SerializeField]
float m_Latitude = 60.0f; // [-90..90]
[SerializeField]
float m_Longitude = 299.0f; // [0..360[
/// <summary>
/// The shading tint to used when computing shadow from sun
/// </summary>
public Color color = Color.white;
/// <summary>
/// The Latitude position of the sun casting shadows
/// </summary>
public float sunLatitude
{
get => m_Latitude;
set => m_Latitude = ClampLatitude(value);
}
internal static float ClampLatitude(float value) => Mathf.Clamp(value, -90, 90);
/// <summary>
/// The Longitude position of the sun casting shadows
/// </summary>
public float sunLongitude
{
get => m_Longitude;
set => m_Longitude = ClampLongitude(value);
}
internal static float ClampLongitude(float value)
{
value = value % 360f;
if (value < 0.0)
value += 360f;
return value;
}
}
/// <summary>
/// Class containing editor data for sky part of the lighting environment
/// </summary>
[Serializable]
public class Sky : BaseEnvironmentCubemapHandler
{
/// <summary>
/// Offset on the longitude. Affect both sky and sun position in Shadow part
/// </summary>
public float rotation = 0.0f;
/// <summary>
/// Exposure to use with this Sky
/// </summary>
public float exposure = 1f;
/// <summary>
/// Implicit conversion operator to runtime version of sky datas
/// </summary>
/// <param name="sky">Editor version of the datas</param>
public static implicit operator UnityEngine.Rendering.LookDev.Sky(Sky sky)
=> sky == null
? default
: new UnityEngine.Rendering.LookDev.Sky()
{
cubemap = sky.cubemap,
longitudeOffset = sky.rotation,
exposure = sky.exposure
};
}
/// <summary>
/// The sky part of the lighting environment
/// </summary>
public Sky sky = new Sky();
/// <summary>
/// The shadow part of the lighting environment
/// </summary>
public Shadow shadow = new Shadow();
/// <summary>
/// Compute the shadow runtime data with editor datas
/// </summary>
public UnityEngine.Rendering.LookDev.Sky shadowSky
=> new UnityEngine.Rendering.LookDev.Sky()
{
cubemap = shadow.cubemap ?? sky.cubemap,
longitudeOffset = sky.rotation,
exposure = sky.exposure
};
internal float shadowIntensity
=> shadow.cubemap == null ? 0.3f : 1f;
internal void UpdateSunPosition(Light sun)
=> sun.transform.rotation = Quaternion.Euler(shadow.sunLatitude, sky.rotation + shadow.sunLongitude, 0f);
internal void CopyTo(Environment other)
{
other.sky.cubemap = sky.cubemap;
other.sky.exposure = sky.exposure;
other.sky.rotation = sky.rotation;
other.shadow.cubemap = shadow.cubemap;
other.shadow.sunLatitude = shadow.sunLatitude;
other.shadow.sunLongitude = shadow.sunLongitude;
other.shadow.color = shadow.color;
other.name = name + " (copy)";
}
/// <summary>
/// Compute sun position to be brightest spot of the sky
/// </summary>
public void ResetToBrightestSpot()
=> EnvironmentElement.ResetToBrightestSpot(this);
}
[CustomEditor(typeof(Environment))]
class EnvironmentEditor : Editor
{
//display nothing
public sealed override VisualElement CreateInspectorGUI() => null;
// Don't use ImGUI
public sealed override void OnInspectorGUI() { }
//but make preview in Project window
override public Texture2D RenderStaticPreview(string assetPath, UnityEngine.Object[] subAssets, int width, int height)
=> EnvironmentElement.GetLatLongThumbnailTexture(target as Environment, width);
}
interface IBendable<T>
{
void Bind(T data);
}
class EnvironmentElement : VisualElement, IBendable<Environment>
{
internal const int k_SkyThumbnailWidth = 200;
internal const int k_SkyThumbnailHeight = 100;
const int k_SkadowThumbnailWidth = 60;
const int k_SkadowThumbnailHeight = 30;
const int k_SkadowThumbnailXPosition = 130;
const int k_SkadowThumbnailYPosition = 10;
static Material s_cubeToLatlongMaterial;
static Material cubeToLatlongMaterial
{
get
{
if (s_cubeToLatlongMaterial == null || s_cubeToLatlongMaterial.Equals(null))
{
s_cubeToLatlongMaterial = new Material(Shader.Find("Hidden/LookDev/CubeToLatlong"));
}
return s_cubeToLatlongMaterial;
}
}
VisualElement environmentParams;
Environment environment;
Image latlong;
ObjectField skyCubemapField;
FloatField skyRotationOffset;
FloatField skyExposureField;
ObjectField shadowCubemapField;
Vector2Field sunPosition;
ColorField shadowColor;
TextField environmentName;
Action OnChangeCallback;
public Environment target => environment;
public EnvironmentElement() => Create(withPreview: true);
public EnvironmentElement(bool withPreview, Action OnChangeCallback = null)
{
this.OnChangeCallback = OnChangeCallback;
Create(withPreview);
}
public EnvironmentElement(Environment environment)
{
Create(withPreview: true);
Bind(environment);
}
void Create(bool withPreview)
{
if (withPreview)
{
latlong = new Image();
latlong.style.width = k_SkyThumbnailWidth;
latlong.style.height = k_SkyThumbnailHeight;
Add(latlong);
}
environmentParams = GetDefaultInspector();
Add(environmentParams);
}
public void Bind(Environment environment)
{
this.environment = environment;
if (environment == null || environment.Equals(null))
return;
if (latlong != null && !latlong.Equals(null))
latlong.image = GetLatLongThumbnailTexture();
skyCubemapField.SetValueWithoutNotify(environment.sky.cubemap);
skyRotationOffset.SetValueWithoutNotify(environment.sky.rotation);
skyExposureField.SetValueWithoutNotify(environment.sky.exposure);
shadowCubemapField.SetValueWithoutNotify(environment.shadow.cubemap);
sunPosition.SetValueWithoutNotify(new Vector2(environment.shadow.sunLongitude, environment.shadow.sunLatitude));
shadowColor.SetValueWithoutNotify(environment.shadow.color);
environmentName.SetValueWithoutNotify(environment.name);
}
public void Bind(Environment environment, Image deportedLatlong)
{
latlong = deportedLatlong;
Bind(environment);
}
static public Vector2 PositionToLatLong(Vector2 position)
{
Vector2 result = new Vector2();
result.x = position.y * Mathf.PI * 0.5f * Mathf.Rad2Deg;
result.y = (position.x * 0.5f + 0.5f) * 2f * Mathf.PI * Mathf.Rad2Deg;
if (result.x < -90.0f) result.x = -90f;
if (result.x > 90.0f) result.x = 90f;
return result;
}
public static void ResetToBrightestSpot(Environment environment)
{
cubeToLatlongMaterial.SetTexture("_MainTex", environment.sky.cubemap);
cubeToLatlongMaterial.SetVector("_WindowParams", new Vector4(10000, -1000.0f, 2, 0.0f)); // Neutral value to not clip
cubeToLatlongMaterial.SetVector("_CubeToLatLongParams", new Vector4(Mathf.Deg2Rad * environment.sky.rotation, 0.5f, 1.0f, 3.0f)); // We use LOD 3 to take a region rather than a single pixel in the map
cubeToLatlongMaterial.SetPass(0);
int width = k_SkyThumbnailWidth;
int height = width >> 1;
RenderTexture temporaryRT = new RenderTexture(width, height, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.sRGB);
Texture2D brightestPointTexture = new Texture2D(width, height, TextureFormat.RGBAHalf, false);
// Convert cubemap to a 2D LatLong to read on CPU
Graphics.Blit(environment.sky.cubemap, temporaryRT, cubeToLatlongMaterial);
brightestPointTexture.ReadPixels(new Rect(0, 0, width, height), 0, 0, false);
brightestPointTexture.Apply();
// CPU read back
// From Doc: The returned array is a flattened 2D array, where pixels are laid out left to right, bottom to top (i.e. row after row)
Color[] color = brightestPointTexture.GetPixels();
RenderTexture.active = null;
temporaryRT.Release();
float maxLuminance = 0.0f;
int maxIndex = 0;
for (int index = height * width - 1; index >= 0; --index)
{
Color pixel = color[index];
float luminance = pixel.r * 0.2126729f + pixel.g * 0.7151522f + pixel.b * 0.0721750f;
if (maxLuminance < luminance)
{
maxLuminance = luminance;
maxIndex = index;
}
}
Vector2 sunPosition = PositionToLatLong(new Vector2(((maxIndex % width) / (float)(width - 1)) * 2f - 1f, ((maxIndex / width) / (float)(height - 1)) * 2f - 1f));
environment.shadow.sunLatitude = sunPosition.x;
environment.shadow.sunLongitude = sunPosition.y - environment.sky.rotation;
}
public Texture2D GetLatLongThumbnailTexture()
=> GetLatLongThumbnailTexture(environment, k_SkyThumbnailWidth);
public static Texture2D GetLatLongThumbnailTexture(Environment environment, int width)
{
int height = width >> 1;
RenderTexture oldActive = RenderTexture.active;
RenderTexture temporaryRT = new RenderTexture(width, height, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.sRGB);
RenderTexture.active = temporaryRT;
cubeToLatlongMaterial.SetTexture("_MainTex", environment.sky.cubemap);
cubeToLatlongMaterial.SetVector("_WindowParams",
new Vector4(
height, //height
-1000f, //y position, -1000f to be sure to not have clipping issue (we should not clip normally but don't want to create a new shader)
2f, //margin value
1f)); //Pixel per Point
cubeToLatlongMaterial.SetVector("_CubeToLatLongParams",
new Vector4(
Mathf.Deg2Rad * environment.sky.rotation, //rotation of the environment in radian
1f, //alpha
1f, //intensity
0f)); //LOD
cubeToLatlongMaterial.SetPass(0);
GL.LoadPixelMatrix(0, width, height, 0);
GL.Clear(true, true, Color.black);
Rect skyRect = new Rect(0, 0, width, height);
Renderer.DrawFullScreenQuad(skyRect);
if (environment.shadow.cubemap != null)
{
cubeToLatlongMaterial.SetTexture("_MainTex", environment.shadow.cubemap);
cubeToLatlongMaterial.SetVector("_WindowParams",
new Vector4(
height, //height
-1000f, //y position, -1000f to be sure to not have clipping issue (we should not clip normally but don't want to create a new shader)
2f, //margin value
1f)); //Pixel per Point
cubeToLatlongMaterial.SetVector("_CubeToLatLongParams",
new Vector4(
Mathf.Deg2Rad * environment.sky.rotation, //rotation of the environment in radian
1f, //alpha
0.3f, //intensity
0f)); //LOD
cubeToLatlongMaterial.SetPass(0);
int shadowWidth = (int)(width * (k_SkadowThumbnailWidth / (float)k_SkyThumbnailWidth));
int shadowXPosition = (int)(width * (k_SkadowThumbnailXPosition / (float)k_SkyThumbnailWidth));
int shadowYPosition = (int)(width * (k_SkadowThumbnailYPosition / (float)k_SkyThumbnailWidth));
Rect shadowRect = new Rect(
shadowXPosition,
shadowYPosition,
shadowWidth,
shadowWidth >> 1);
Renderer.DrawFullScreenQuad(shadowRect);
}
Texture2D result = new Texture2D(width, height, TextureFormat.ARGB32, false);
result.ReadPixels(new Rect(0, 0, width, height), 0, 0, false);
result.Apply(false);
RenderTexture.active = oldActive;
UnityEngine.Object.DestroyImmediate(temporaryRT);
return result;
}
public VisualElement GetDefaultInspector()
{
VisualElement inspector = new VisualElement() { name = "inspector" };
VisualElement header = new VisualElement() { name = "inspector-header" };
header.Add(new Image()
{
image = CoreEditorUtils.LoadIcon(@"Packages/com.unity.render-pipelines.core/Editor/LookDev/Icons/", "LookDev_EnvironmentHDR", forceLowRes: true)
});
environmentName = new TextField();
environmentName.isDelayed = true;
environmentName.RegisterValueChangedCallback(evt =>
{
string path = AssetDatabase.GetAssetPath(environment);
environment.name = evt.newValue;
AssetDatabase.SetLabels(environment, new string[] { evt.newValue });
EditorUtility.SetDirty(environment);
AssetDatabase.ImportAsset(path);
environmentName.name = environment.name;
});
header.Add(environmentName);
inspector.Add(header);
Foldout foldout = new Foldout()
{
text = "Environment Settings"
};
skyCubemapField = new ObjectField("Sky with Sun")
{
tooltip = "A cubemap that will be used as the sky."
};
skyCubemapField.allowSceneObjects = false;
skyCubemapField.objectType = typeof(Cubemap);
skyCubemapField.RegisterValueChangedCallback(evt =>
{
var tmp = environment.sky.cubemap;
RegisterChange(ref tmp, evt.newValue as Cubemap);
environment.sky.cubemap = tmp;
latlong.image = GetLatLongThumbnailTexture(environment, k_SkyThumbnailWidth);
});
foldout.Add(skyCubemapField);
shadowCubemapField = new ObjectField("Sky without Sun")
{
tooltip = "[Optional] A cubemap that will be used to compute self shadowing.\nIt should be the same sky without the sun.\nIf nothing is provided, the sky with sun will be used with lower intensity."
};
shadowCubemapField.allowSceneObjects = false;
shadowCubemapField.objectType = typeof(Cubemap);
shadowCubemapField.RegisterValueChangedCallback(evt =>
{
var tmp = environment.shadow.cubemap;
RegisterChange(ref tmp, evt.newValue as Cubemap);
environment.shadow.cubemap = tmp;
latlong.image = GetLatLongThumbnailTexture(environment, k_SkyThumbnailWidth);
});
foldout.Add(shadowCubemapField);
skyRotationOffset = new FloatField("Rotation")
{
tooltip = "Rotation offset on the longitude of the sky."
};
skyRotationOffset.RegisterValueChangedCallback(evt
=> RegisterChange(ref environment.sky.rotation, Environment.Shadow.ClampLongitude(evt.newValue), skyRotationOffset, updatePreview: true));
foldout.Add(skyRotationOffset);
skyExposureField = new FloatField("Exposure")
{
tooltip = "The exposure to apply with this sky."
};
skyExposureField.RegisterValueChangedCallback(evt
=> RegisterChange(ref environment.sky.exposure, evt.newValue));
foldout.Add(skyExposureField);
var style = foldout.Q<Toggle>().style;
style.marginLeft = 3;
style.unityFontStyleAndWeight = FontStyle.Bold;
inspector.Add(foldout);
sunPosition = new Vector2Field("Sun Position")
{
tooltip = "The sun position as (Longitude, Latitude)\nThe button compute brightest position in the sky with sun."
};
sunPosition.Q("unity-x-input").Q<FloatField>().formatString = "n1";
sunPosition.Q("unity-y-input").Q<FloatField>().formatString = "n1";
sunPosition.RegisterValueChangedCallback(evt =>
{
var tmpContainer = new Vector2(
environment.shadow.sunLongitude,
environment.shadow.sunLatitude);
var tmpNewValue = new Vector2(
Environment.Shadow.ClampLongitude(evt.newValue.x),
Environment.Shadow.ClampLatitude(evt.newValue.y));
RegisterChange(ref tmpContainer, tmpNewValue, sunPosition);
environment.shadow.sunLongitude = tmpContainer.x;
environment.shadow.sunLatitude = tmpContainer.y;
});
foldout.Add(sunPosition);
Button sunToBrightess = new Button(() =>
{
ResetToBrightestSpot(environment);
sunPosition.SetValueWithoutNotify(new Vector2(
Environment.Shadow.ClampLongitude(environment.shadow.sunLongitude),
Environment.Shadow.ClampLatitude(environment.shadow.sunLatitude)));
})
{
name = "sunToBrightestButton"
};
sunToBrightess.Add(new Image()
{
image = CoreEditorUtils.LoadIcon(@"Packages/com.unity.render-pipelines.core/Editor/LookDev/Icons/", "LookDev_SunPosition", forceLowRes: true)
});
sunToBrightess.AddToClassList("sun-to-brightest-button");
var vector2Input = sunPosition.Q(className: "unity-vector2-field__input");
vector2Input.Remove(sunPosition.Q(className: "unity-composite-field__field-spacer"));
vector2Input.Add(sunToBrightess);
shadowColor = new ColorField("Shadow Tint")
{
tooltip = "The wanted shadow tint to be used when computing shadow."
};
shadowColor.RegisterValueChangedCallback(evt
=> RegisterChange(ref environment.shadow.color, evt.newValue));
foldout.Add(shadowColor);
style = foldout.Q<Toggle>().style;
style.marginLeft = 3;
style.unityFontStyleAndWeight = FontStyle.Bold;
inspector.Add(foldout);
return inspector;
}
void RegisterChange<TValueType>(ref TValueType reflectedVariable, TValueType newValue, BaseField<TValueType> resyncField = null, bool updatePreview = false)
{
if (environment == null || environment.Equals(null))
return;
reflectedVariable = newValue;
resyncField?.SetValueWithoutNotify(newValue);
if (updatePreview && latlong != null && !latlong.Equals(null))
latlong.image = GetLatLongThumbnailTexture(environment, k_SkyThumbnailWidth);
EditorUtility.SetDirty(environment);
OnChangeCallback?.Invoke();
}
}
}

11
Packages/com.unity.render-pipelines.core/Editor/LookDev/Environment.cs.meta


fileFormatVersion: 2
guid: 612aa45d38dd94a479185b83f080aa22
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

198
Packages/com.unity.render-pipelines.core/Editor/LookDev/EnvironmentLibrary.cs


using UnityEngine;
using UnityEngine.Rendering;
using System;
using System.Collections.Generic;
using UnityEngine.UIElements;
using System.Linq;
using System.IO;
using System.Linq.Expressions;
using System.Reflection;
namespace UnityEditor.Rendering.LookDev
{
/// <summary>
/// Class containing a collection of Environment
/// </summary>
public class EnvironmentLibrary : BaseEnvironmentLibrary
{
[field: SerializeField]
List<Environment> environments { get; set; } = new List<Environment>();
/// <summary>
/// Number of elements in the collection
/// </summary>
public int Count => environments.Count;
/// <summary>
/// Indexer giving access to contained Environment
/// </summary>
public Environment this[int index] => environments[index];
/// <summary>
/// Create a new empty Environment at the end of the collection
/// </summary>
/// <returns>The created Environment</returns>
public Environment Add()
{
Environment environment = ScriptableObject.CreateInstance<Environment>();
environment.name = "New Environment";
Undo.RegisterCreatedObjectUndo(environment, "Add Environment");
environments.Add(environment);
// Store this new environment as a subasset so we can reference it safely afterwards.
AssetDatabase.AddObjectToAsset(environment, this);
// Force save / refresh. Important to do this last because SaveAssets can cause effect to become null!
EditorUtility.SetDirty(this);
AssetDatabase.SaveAssets();
return environment;
}
/// <summary>
/// Remove Environment of the collection at given index
/// </summary>
/// <param name="index">Index where to remove Environment</param>
public void Remove(int index)
{
Environment environment = environments[index];
Undo.RecordObject(this, "Remove Environment");
environments.RemoveAt(index);
Undo.DestroyObjectImmediate(environment);
// Force save / refresh
EditorUtility.SetDirty(this);
AssetDatabase.SaveAssets();
}
/// <summary>
/// Duplicate the Environment at given index and add it at the end of the Collection
/// </summary>
/// <param name="fromIndex">Index where to take data for duplication</param>
/// <returns>The created Environment</returns>
public Environment Duplicate(int fromIndex)
{
Environment environment = ScriptableObject.CreateInstance<Environment>();
Environment environmentToCopy = environments[fromIndex];
environmentToCopy.CopyTo(environment);
Undo.RegisterCreatedObjectUndo(environment, "Duplicate Environment");
environments.Add(environment);
// Store this new environment as a subasset so we can reference it safely afterwards.
AssetDatabase.AddObjectToAsset(environment, this);
// Force save / refresh. Important to do this last because SaveAssets can cause effect to become null!
EditorUtility.SetDirty(this);
AssetDatabase.SaveAssets();
return environment;
}
/// <summary>
/// Compute position of given Environment in the collection
/// </summary>
/// <param name="environment">Environment to look at</param>
/// <returns>Index of the searched environment. If not found, -1.</returns>
public int IndexOf(Environment environment)
=> environments.IndexOf(environment);
}
[CustomEditor(typeof(EnvironmentLibrary))]
class EnvironmentLibraryEditor : Editor
{
VisualElement root;
public sealed override VisualElement CreateInspectorGUI()
{
var library = target as EnvironmentLibrary;
root = new VisualElement();
Button open = new Button(() =>
{
if (!LookDev.open)
LookDev.Open();
LookDev.currentContext.UpdateEnvironmentLibrary(library);
LookDev.currentEnvironmentDisplayer.Repaint();
})
{
text = "Open in LookDev window"
};
root.Add(open);
return root;
}
// Don't use ImGUI
public sealed override void OnInspectorGUI() { }
}
class EnvironmentLibraryCreator : ProjectWindowCallback.EndNameEditAction
{
public override void Action(int instanceId, string pathName, string resourceFile)
{
var newAsset = CreateInstance<EnvironmentLibrary>();
newAsset.name = Path.GetFileName(pathName);
AssetDatabase.CreateAsset(newAsset, pathName);
ProjectWindowUtil.ShowCreatedAsset(newAsset);
}
[MenuItem("Assets/Create/LookDev/Environment Library", priority = 2000)]
public static void Create()
{
var icon = EditorGUIUtility.FindTexture("ScriptableObject Icon");
ProjectWindowUtil.StartNameEditingIfProjectWindowExists(0, ScriptableObject.CreateInstance<EnvironmentLibraryCreator>(), "EnvironmentLibrary.asset", icon, null);
}
}
static class EnvironmentLibraryLoader
{
public static void Load(Action onInspectorRedrawRequested)
{
UnityEngine.Object target = LookDev.currentContext.environmentLibrary;
UIElementObjectSelectorWorkaround.Show(target, typeof(EnvironmentLibrary), LoadCallback(onInspectorRedrawRequested));
}
static Action<UnityEngine.Object> LoadCallback(Action onUpdate)
{
return (UnityEngine.Object newLibrary) =>
{
LookDev.currentContext.UpdateEnvironmentLibrary(newLibrary as EnvironmentLibrary);
onUpdate?.Invoke();
};
}
// As in UIElement.ObjectField we cannot support cancel when closing window
static class UIElementObjectSelectorWorkaround
{
static Action<UnityEngine.Object, Type, Action<UnityEngine.Object>> ShowObjectSelector;
static UIElementObjectSelectorWorkaround()
{
Type playerSettingsType = typeof(PlayerSettings);
Type objectSelectorType = playerSettingsType.Assembly.GetType("UnityEditor.ObjectSelector");
var instanceObjectSelectorInfo = objectSelectorType.GetProperty("get", BindingFlags.Static | BindingFlags.Public);
var showInfo = objectSelectorType.GetMethod("Show", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof(UnityEngine.Object), typeof(Type), typeof(SerializedProperty), typeof(bool), typeof(List<int>), typeof(Action<UnityEngine.Object>), typeof(Action<UnityEngine.Object>) }, null);
var objectSelectorVariable = Expression.Variable(objectSelectorType, "objectSelector");
var objectParameter = Expression.Parameter(typeof(UnityEngine.Object), "unityObject");
var typeParameter = Expression.Parameter(typeof(Type), "type");
var onChangedObjectParameter = Expression.Parameter(typeof(Action<UnityEngine.Object>), "onChangedObject");
var showObjectSelectorBlock = Expression.Block(
new[] { objectSelectorVariable },
Expression.Assign(objectSelectorVariable, Expression.Call(null, instanceObjectSelectorInfo.GetGetMethod())),
Expression.Call(objectSelectorVariable, showInfo, objectParameter, typeParameter, Expression.Constant(null, typeof(SerializedProperty)), Expression.Constant(false), Expression.Constant(null, typeof(List<int>)), Expression.Constant(null, typeof(Action<UnityEngine.Object>)), onChangedObjectParameter)
);
var showObjectSelectorLambda = Expression.Lambda<Action<UnityEngine.Object, Type, Action<UnityEngine.Object>>>(showObjectSelectorBlock, objectParameter, typeParameter, onChangedObjectParameter);
ShowObjectSelector = showObjectSelectorLambda.Compile();
}
public static void Show(UnityEngine.Object obj, Type type, Action<UnityEngine.Object> onObjectChanged)
{
ShowObjectSelector(obj, type, onObjectChanged);
}
}
}
}

11
Packages/com.unity.render-pipelines.core/Editor/LookDev/EnvironmentLibrary.cs.meta


fileFormatVersion: 2
guid: 985c12cb5f71c6440825869433552460
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

8
Packages/com.unity.render-pipelines.core/Editor/LookDev/Icons.meta


fileFormatVersion: 2
guid: 9d38fc3327537ec418b043933457c849
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

6
Packages/com.unity.render-pipelines.core/Editor/LookDev/Icons/LookDevCenterLight.png

之前 之后
宽度: 16  |  高度: 12  |  大小: 330 B

77
Packages/com.unity.render-pipelines.core/Editor/LookDev/Icons/LookDevCenterLight.png.meta


fileFormatVersion: 2
guid: 3092b2985cafe0b43b1f02ce1d6829d5
timeCreated: 1470993508
licenseType: Pro
TextureImporter:
fileIDToRecycleName: {}
externalObjects: {}
serializedVersion: 5
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 0
aniso: -1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spritePixelsToUnits: 100
alphaUsage: 1
alphaIsTransparency: 0
spriteTessellationDetail: -1
textureType: 0
textureShape: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
platformSettings:
- buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 0
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
spritePackingTag:
userData:
assetBundleName:
assetBundleVariant:

4
Packages/com.unity.render-pipelines.core/Editor/LookDev/Icons/LookDevCenterLightl@2x.png

之前 之后
宽度: 32  |  高度: 24  |  大小: 692 B

77
Packages/com.unity.render-pipelines.core/Editor/LookDev/Icons/LookDevCenterLightl@2x.png.meta


fileFormatVersion: 2
guid: 38e497c7c3ba97b42aee0fc8cec4178b
timeCreated: 1470993508
licenseType: Pro
TextureImporter:
fileIDToRecycleName: {}
externalObjects: {}
serializedVersion: 5
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 0
aniso: -1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spritePixelsToUnits: 100
alphaUsage: 1
alphaIsTransparency: 0
spriteTessellationDetail: -1
textureType: 0
textureShape: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
platformSettings:
- buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 0
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
spritePackingTag:
userData:
assetBundleName:
assetBundleVariant:

3
Packages/com.unity.render-pipelines.core/Editor/LookDev/Icons/LookDevClose.png

之前 之后
宽度: 12  |  高度: 12  |  大小: 328 B

77
Packages/com.unity.render-pipelines.core/Editor/LookDev/Icons/LookDevClose.png.meta


fileFormatVersion: 2
guid: de30227a4b9409e499c278d82bfa458a
timeCreated: 1470994216
licenseType: Pro
TextureImporter:
fileIDToRecycleName: {}
externalObjects: {}
serializedVersion: 5
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 0
aniso: -1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spritePixelsToUnits: 100
alphaUsage: 1
alphaIsTransparency: 0
spriteTessellationDetail: -1
textureType: 0
textureShape: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
platformSettings:
- buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 0
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
spritePackingTag:
userData:
assetBundleName:
assetBundleVariant:

4
Packages/com.unity.render-pipelines.core/Editor/LookDev/Icons/LookDevClose@2x.png

之前 之后
宽度: 24  |  高度: 24  |  大小: 490 B

77
Packages/com.unity.render-pipelines.core/Editor/LookDev/Icons/LookDevClose@2x.png.meta


fileFormatVersion: 2
guid: 6b9dda8129e93a040989f49b03f6f65a
timeCreated: 1470994216
licenseType: Pro
TextureImporter:
fileIDToRecycleName: {}
externalObjects: {}
serializedVersion: 5
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 0
aniso: -1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spritePixelsToUnits: 100
alphaUsage: 1
alphaIsTransparency: 0
spriteTessellationDetail: -1
textureType: 0
textureShape: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
platformSettings:
- buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 0
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
spritePackingTag:
userData:
assetBundleName:
assetBundleVariant:

6
Packages/com.unity.render-pipelines.core/Editor/LookDev/Icons/LookDevEnvRotation.png

之前 之后
宽度: 16  |  高度: 12  |  大小: 459 B

77
Packages/com.unity.render-pipelines.core/Editor/LookDev/Icons/LookDevEnvRotation.png.meta


fileFormatVersion: 2
guid: 2ee66e81975377745aaa59fe11ad374d
timeCreated: 1471001145
licenseType: Pro
TextureImporter:
fileIDToRecycleName: {}
externalObjects: {}
serializedVersion: 5
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 1
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 5
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 0
aniso: -1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spritePixelsToUnits: 100
alphaUsage: 1
alphaIsTransparency: 0
spriteTessellationDetail: -1
textureType: 0
textureShape: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
platformSettings:
- buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 0
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
spritePackingTag:
userData:
assetBundleName:
assetBundleVariant:

部分文件因为文件数量过多而无法显示

正在加载...
取消
保存