浏览代码
Merge pull request #631 from Unity-Technologies/volume-framework
Merge pull request #631 from Unity-Technologies/volume-framework
Generic volume framework/main
GitHub
7 年前
当前提交
d8ba0411
共有 37 个文件被更改,包括 2341 次插入 和 0 次删除
-
88ScriptableRenderPipeline/Core/Editor/CoreEditorUtils.cs
-
3ScriptableRenderPipeline/HDRenderPipeline/HDCustomSamplerId.cs
-
6ScriptableRenderPipeline/HDRenderPipeline/HDRenderPipeline.cs
-
42ScriptableRenderPipeline/Core/Editor/CoreEditorStyles.cs
-
11ScriptableRenderPipeline/Core/Editor/CoreEditorStyles.cs.meta
-
8ScriptableRenderPipeline/Core/Volume.meta
-
8ScriptableRenderPipeline/Core/Volume/Editor.meta
-
8ScriptableRenderPipeline/Core/Volume/Editor/Drawers.meta
-
77ScriptableRenderPipeline/Core/Volume/Editor/Drawers/ClampedFloatParameterDrawer.cs
-
11ScriptableRenderPipeline/Core/Volume/Editor/Drawers/ClampedFloatParameterDrawer.cs.meta
-
77ScriptableRenderPipeline/Core/Volume/Editor/Drawers/ClampedIntParameterDrawer.cs
-
11ScriptableRenderPipeline/Core/Volume/Editor/Drawers/ClampedIntParameterDrawer.cs.meta
-
21ScriptableRenderPipeline/Core/Volume/Editor/Drawers/ColorParameterDrawer.cs
-
11ScriptableRenderPipeline/Core/Volume/Editor/Drawers/ColorParameterDrawer.cs.meta
-
42ScriptableRenderPipeline/Core/Volume/Editor/Drawers/RangeParameterDrawer.cs
-
11ScriptableRenderPipeline/Core/Volume/Editor/Drawers/RangeParameterDrawer.cs.meta
-
20ScriptableRenderPipeline/Core/Volume/Editor/Drawers/Vector4ParameterDrawer.cs
-
11ScriptableRenderPipeline/Core/Volume/Editor/Drawers/Vector4ParameterDrawer.cs.meta
-
57ScriptableRenderPipeline/Core/Volume/Editor/SerializedDataParameter.cs
-
11ScriptableRenderPipeline/Core/Volume/Editor/SerializedDataParameter.cs.meta
-
266ScriptableRenderPipeline/Core/Volume/Editor/VolumeComponentEditor.cs
-
11ScriptableRenderPipeline/Core/Volume/Editor/VolumeComponentEditor.cs.meta
-
388ScriptableRenderPipeline/Core/Volume/Editor/VolumeEditor.cs
-
11ScriptableRenderPipeline/Core/Volume/Editor/VolumeEditor.cs.meta
-
31ScriptableRenderPipeline/Core/Volume/Editor/VolumeParameterDrawer.cs
-
11ScriptableRenderPipeline/Core/Volume/Editor/VolumeParameterDrawer.cs.meta
-
11ScriptableRenderPipeline/Core/Volume/Volume.cs.meta
-
73ScriptableRenderPipeline/Core/Volume/VolumeComponent.cs
-
11ScriptableRenderPipeline/Core/Volume/VolumeComponent.cs.meta
-
336ScriptableRenderPipeline/Core/Volume/VolumeManager.cs
-
11ScriptableRenderPipeline/Core/Volume/VolumeManager.cs.meta
-
424ScriptableRenderPipeline/Core/Volume/VolumeParameter.cs
-
11ScriptableRenderPipeline/Core/Volume/VolumeParameter.cs.meta
-
212ScriptableRenderPipeline/Core/Volume/Volume.cs
|
|||
using UnityEngine; |
|||
|
|||
namespace UnityEditor.Experimental.Rendering |
|||
{ |
|||
public static class CoreEditorStyles |
|||
{ |
|||
public static readonly GUIStyle smallTickbox; |
|||
public static readonly GUIStyle miniLabelButton; |
|||
|
|||
public static readonly Texture2D paneOptionsIconDark; |
|||
public static readonly Texture2D paneOptionsIconLight; |
|||
|
|||
static CoreEditorStyles() |
|||
{ |
|||
smallTickbox = new GUIStyle("ShurikenCheckMark"); |
|||
|
|||
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"); |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 106fd77ef6b30234597e56c849578bad |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
fileFormatVersion: 2 |
|||
guid: 1189cbf559a12c64e92584c52d17a4a0 |
|||
folderAsset: yes |
|||
DefaultImporter: |
|||
externalObjects: {} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
fileFormatVersion: 2 |
|||
guid: 138c0b127ef07fd428612196694d782f |
|||
folderAsset: yes |
|||
DefaultImporter: |
|||
externalObjects: {} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
fileFormatVersion: 2 |
|||
guid: 8c667ca46d23aa3468b6c4243b135b0d |
|||
folderAsset: yes |
|||
DefaultImporter: |
|||
externalObjects: {} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using UnityEngine; |
|||
using UnityEngine.Experimental.Rendering; |
|||
|
|||
namespace UnityEditor.Experimental.Rendering |
|||
{ |
|||
[VolumeParameterDrawer(typeof(ClampedFloatParameter))] |
|||
sealed class ClampedFloatParameterDrawer : VolumeParameterDrawer |
|||
{ |
|||
public override bool OnGUI(SerializedDataParameter parameter, GUIContent title) |
|||
{ |
|||
var value = parameter.value; |
|||
|
|||
if (value.propertyType != SerializedPropertyType.Float) |
|||
return false; |
|||
|
|||
var o = parameter.GetObjectRef<ClampedFloatParameter>(); |
|||
|
|||
if (o.clampMode == ParameterClampMode.MinMax) |
|||
{ |
|||
EditorGUILayout.Slider(value, o.min, o.max, title); |
|||
value.floatValue = Mathf.Clamp(value.floatValue, o.min, o.max); |
|||
} |
|||
else if (o.clampMode == ParameterClampMode.Min) |
|||
{ |
|||
float v = EditorGUILayout.FloatField(title, value.floatValue); |
|||
value.floatValue = Mathf.Max(v, o.min); |
|||
} |
|||
else if (o.clampMode == ParameterClampMode.Max) |
|||
{ |
|||
float v = EditorGUILayout.FloatField(title, value.floatValue); |
|||
value.floatValue = Mathf.Min(v, o.max); |
|||
} |
|||
else |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
} |
|||
|
|||
[VolumeParameterDrawer(typeof(InstantClampedFloatParameter))] |
|||
sealed class InstantClampedFloatParameterDrawer : VolumeParameterDrawer |
|||
{ |
|||
public override bool OnGUI(SerializedDataParameter parameter, GUIContent title) |
|||
{ |
|||
var value = parameter.value; |
|||
|
|||
if (value.propertyType != SerializedPropertyType.Float) |
|||
return false; |
|||
|
|||
var o = parameter.GetObjectRef<InstantClampedFloatParameter>(); |
|||
|
|||
if (o.clampMode == ParameterClampMode.MinMax) |
|||
{ |
|||
EditorGUILayout.Slider(value, o.min, o.max, title); |
|||
value.floatValue = Mathf.Clamp(value.floatValue, o.min, o.max); |
|||
} |
|||
else if (o.clampMode == ParameterClampMode.Min) |
|||
{ |
|||
float v = EditorGUILayout.FloatField(title, value.floatValue); |
|||
value.floatValue = Mathf.Max(v, o.min); |
|||
} |
|||
else if (o.clampMode == ParameterClampMode.Max) |
|||
{ |
|||
float v = EditorGUILayout.FloatField(title, value.floatValue); |
|||
value.floatValue = Mathf.Min(v, o.max); |
|||
} |
|||
else |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: f9816364c4bdf314db38db52fc67a3f2 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using UnityEngine; |
|||
using UnityEngine.Experimental.Rendering; |
|||
|
|||
namespace UnityEditor.Experimental.Rendering |
|||
{ |
|||
[VolumeParameterDrawer(typeof(ClampedIntParameter))] |
|||
sealed class ClampedIntParameterDrawer : VolumeParameterDrawer |
|||
{ |
|||
public override bool OnGUI(SerializedDataParameter parameter, GUIContent title) |
|||
{ |
|||
var value = parameter.value; |
|||
|
|||
if (value.propertyType != SerializedPropertyType.Integer) |
|||
return false; |
|||
|
|||
var o = parameter.GetObjectRef<ClampedIntParameter>(); |
|||
|
|||
if (o.clampMode == ParameterClampMode.MinMax) |
|||
{ |
|||
EditorGUILayout.IntSlider(value, o.min, o.max, title); |
|||
value.intValue = Mathf.Clamp(value.intValue, o.min, o.max); |
|||
} |
|||
else if (o.clampMode == ParameterClampMode.Min) |
|||
{ |
|||
int v = EditorGUILayout.IntField(title, value.intValue); |
|||
value.intValue = Mathf.Max(v, o.min); |
|||
} |
|||
else if (o.clampMode == ParameterClampMode.Max) |
|||
{ |
|||
int v = EditorGUILayout.IntField(title, value.intValue); |
|||
value.intValue = Mathf.Min(v, o.max); |
|||
} |
|||
else |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
} |
|||
|
|||
[VolumeParameterDrawer(typeof(InstantClampedIntParameter))] |
|||
sealed class InstantClampedIntParameterDrawer : VolumeParameterDrawer |
|||
{ |
|||
public override bool OnGUI(SerializedDataParameter parameter, GUIContent title) |
|||
{ |
|||
var value = parameter.value; |
|||
|
|||
if (value.propertyType != SerializedPropertyType.Integer) |
|||
return false; |
|||
|
|||
var o = parameter.GetObjectRef<InstantClampedIntParameter>(); |
|||
|
|||
if (o.clampMode == ParameterClampMode.MinMax) |
|||
{ |
|||
EditorGUILayout.IntSlider(value, o.min, o.max, title); |
|||
value.intValue = Mathf.Clamp(value.intValue, o.min, o.max); |
|||
} |
|||
else if (o.clampMode == ParameterClampMode.Min) |
|||
{ |
|||
int v = EditorGUILayout.IntField(title, value.intValue); |
|||
value.intValue = Mathf.Max(v, o.min); |
|||
} |
|||
else if (o.clampMode == ParameterClampMode.Max) |
|||
{ |
|||
int v = EditorGUILayout.IntField(title, value.intValue); |
|||
value.intValue = Mathf.Min(v, o.max); |
|||
} |
|||
else |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 6d2f589334fbc8444b30925a405106d8 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using UnityEngine; |
|||
using UnityEngine.Experimental.Rendering; |
|||
|
|||
namespace UnityEditor.Experimental.Rendering |
|||
{ |
|||
[VolumeParameterDrawer(typeof(ColorParameter))] |
|||
sealed class ColorParameterDrawer : VolumeParameterDrawer |
|||
{ |
|||
public override bool OnGUI(SerializedDataParameter parameter, GUIContent title) |
|||
{ |
|||
var value = parameter.value; |
|||
|
|||
if (value.propertyType != SerializedPropertyType.Color) |
|||
return false; |
|||
|
|||
var o = parameter.GetObjectRef<ColorParameter>(); |
|||
value.colorValue = EditorGUILayout.ColorField(title, value.colorValue, o.showEyeDropper, o.showAlpha, o.showAlpha); |
|||
return true; |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 34ad702e46732ff438113b62a10ae80e |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using UnityEngine; |
|||
using UnityEngine.Experimental.Rendering; |
|||
|
|||
namespace UnityEditor.Experimental.Rendering |
|||
{ |
|||
[VolumeParameterDrawer(typeof(RangeParameter))] |
|||
sealed class RangeParameterDrawer : VolumeParameterDrawer |
|||
{ |
|||
public override bool OnGUI(SerializedDataParameter parameter, GUIContent title) |
|||
{ |
|||
var value = parameter.value; |
|||
|
|||
if (value.propertyType != SerializedPropertyType.Vector2) |
|||
return false; |
|||
|
|||
var o = parameter.GetObjectRef<RangeParameter>(); |
|||
var v = value.vector2Value; |
|||
|
|||
// The layout system breaks alignement when mixing inspector fields with custom layouted
|
|||
// fields as soon as a scrollbar is needed in the inspector, so we'll do the layout
|
|||
// manually instead
|
|||
const int kFloatFieldWidth = 50; |
|||
const int kSeparatorWidth = 5; |
|||
float indentOffset = EditorGUI.indentLevel * 15f; |
|||
var lineRect = GUILayoutUtility.GetRect(1, EditorGUIUtility.singleLineHeight); |
|||
lineRect.xMin += 4f; |
|||
lineRect.y += 2f; |
|||
var labelRect = new Rect(lineRect.x, lineRect.y, EditorGUIUtility.labelWidth - indentOffset, lineRect.height); |
|||
var floatFieldLeft = new Rect(labelRect.xMax, lineRect.y, kFloatFieldWidth + indentOffset, lineRect.height); |
|||
var sliderRect = new Rect(floatFieldLeft.xMax + kSeparatorWidth - indentOffset, lineRect.y, lineRect.width - labelRect.width - kFloatFieldWidth * 2 - kSeparatorWidth * 2, lineRect.height); |
|||
var floatFieldRight = new Rect(sliderRect.xMax + kSeparatorWidth - indentOffset, lineRect.y, kFloatFieldWidth + indentOffset, lineRect.height); |
|||
|
|||
EditorGUI.PrefixLabel(labelRect, title); |
|||
v.x = EditorGUI.FloatField(floatFieldLeft, v.x); |
|||
EditorGUI.MinMaxSlider(sliderRect, ref v.x, ref v.y, o.min, o.max); |
|||
v.y = EditorGUI.FloatField(floatFieldRight, v.y); |
|||
|
|||
value.vector2Value = v; |
|||
return true; |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 54ad01bd6d555e0438e364d8b0f17e06 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using UnityEngine; |
|||
using UnityEngine.Experimental.Rendering; |
|||
|
|||
namespace UnityEditor.Experimental.Rendering |
|||
{ |
|||
[VolumeParameterDrawer(typeof(Vector4Parameter))] |
|||
sealed class Vector4ParametrDrawer : VolumeParameterDrawer |
|||
{ |
|||
public override bool OnGUI(SerializedDataParameter parameter, GUIContent title) |
|||
{ |
|||
var value = parameter.value; |
|||
|
|||
if (value.propertyType != SerializedPropertyType.Vector4) |
|||
return false; |
|||
|
|||
value.vector4Value = EditorGUILayout.Vector4Field(title, value.vector4Value); |
|||
return true; |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 9c51e2ea627a393478d0b5c2a086136d |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System; |
|||
using System.Linq; |
|||
using System.Reflection; |
|||
using UnityEngine.Assertions; |
|||
|
|||
namespace UnityEditor.Experimental.Rendering |
|||
{ |
|||
public sealed class SerializedDataParameter |
|||
{ |
|||
public SerializedProperty overrideState { get; private set; } |
|||
public SerializedProperty value { get; private set; } |
|||
public Attribute[] attributes { get; private set; } |
|||
public Type referenceType { get; private set; } |
|||
|
|||
SerializedProperty m_BaseProperty; |
|||
object m_ReferenceValue; |
|||
|
|||
public string displayName |
|||
{ |
|||
get { return m_BaseProperty.displayName; } |
|||
} |
|||
|
|||
internal SerializedDataParameter(SerializedProperty property) |
|||
{ |
|||
// Find the actual property type, optional attributes & reference
|
|||
var path = property.propertyPath.Split('.'); |
|||
object obj = property.serializedObject.targetObject; |
|||
FieldInfo field = null; |
|||
|
|||
foreach (var p in path) |
|||
{ |
|||
field = obj.GetType().GetField(p, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); |
|||
obj = field.GetValue(obj); |
|||
} |
|||
|
|||
Assert.IsNotNull(field); |
|||
|
|||
m_BaseProperty = property.Copy(); |
|||
overrideState = m_BaseProperty.FindPropertyRelative("m_OverrideState"); |
|||
value = m_BaseProperty.FindPropertyRelative("m_Value"); |
|||
attributes = field.GetCustomAttributes(false).Cast<Attribute>().ToArray(); |
|||
referenceType = obj.GetType(); |
|||
m_ReferenceValue = obj; |
|||
} |
|||
|
|||
public T GetAttribute<T>() |
|||
where T : Attribute |
|||
{ |
|||
return (T)attributes.FirstOrDefault(x => x is T); |
|||
} |
|||
|
|||
public T GetObjectRef<T>() |
|||
{ |
|||
return (T)m_ReferenceValue; |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: abf61146103c4444290de1048016607b |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Reflection; |
|||
using UnityEngine; |
|||
using UnityEngine.Assertions; |
|||
using UnityEngine.Experimental.Rendering; |
|||
|
|||
namespace UnityEditor.Experimental.Rendering |
|||
{ |
|||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] |
|||
public sealed class VolumeComponentEditorAttribute : Attribute |
|||
{ |
|||
public readonly Type componentType; |
|||
|
|||
public VolumeComponentEditorAttribute(Type componentType) |
|||
{ |
|||
this.componentType = componentType; |
|||
} |
|||
} |
|||
|
|||
public class VolumeComponentEditor |
|||
{ |
|||
internal VolumeComponent target { get; private set; } |
|||
internal SerializedObject serializedObject { get; private set; } |
|||
|
|||
internal SerializedProperty baseProperty; |
|||
internal SerializedProperty activeProperty; |
|||
|
|||
Editor m_Inspector; |
|||
List<SerializedDataParameter> m_Parameters; |
|||
|
|||
static Dictionary<Type, VolumeParameterDrawer> s_ParameterDrawers; |
|||
|
|||
static VolumeComponentEditor() |
|||
{ |
|||
s_ParameterDrawers = new Dictionary<Type, VolumeParameterDrawer>(); |
|||
ReloadDecoratorTypes(); |
|||
} |
|||
|
|||
[Callbacks.DidReloadScripts] |
|||
static void OnEditorReload() |
|||
{ |
|||
ReloadDecoratorTypes(); |
|||
} |
|||
|
|||
static void ReloadDecoratorTypes() |
|||
{ |
|||
s_ParameterDrawers.Clear(); |
|||
|
|||
// Look for all the valid parameter drawers
|
|||
var types = AppDomain.CurrentDomain.GetAssemblies() |
|||
.SelectMany( |
|||
a => a.GetTypes() |
|||
.Where( |
|||
t => t.IsSubclassOf(typeof(VolumeParameterDrawer)) |
|||
&& t.IsDefined(typeof(VolumeParameterDrawerAttribute), false) |
|||
) |
|||
); |
|||
|
|||
// Store them
|
|||
foreach (var type in types) |
|||
{ |
|||
var attr = (VolumeParameterDrawerAttribute)type.GetCustomAttributes(typeof(VolumeParameterDrawerAttribute), false)[0]; |
|||
var decorator = (VolumeParameterDrawer)Activator.CreateInstance(type); |
|||
s_ParameterDrawers.Add(attr.parameterType, decorator); |
|||
} |
|||
} |
|||
|
|||
public void Repaint() |
|||
{ |
|||
m_Inspector.Repaint(); |
|||
} |
|||
|
|||
internal void Init(VolumeComponent target, Editor inspector) |
|||
{ |
|||
this.target = target; |
|||
m_Inspector = inspector; |
|||
serializedObject = new SerializedObject(target); |
|||
activeProperty = serializedObject.FindProperty("active"); |
|||
OnEnable(); |
|||
} |
|||
|
|||
public virtual void OnEnable() |
|||
{ |
|||
m_Parameters = new List<SerializedDataParameter>(); |
|||
|
|||
// Grab all valid serializable field on the VolumeComponent
|
|||
var fields = target.GetType() |
|||
.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) |
|||
.Where(t => t.FieldType.IsSubclassOf(typeof(VolumeParameter))) |
|||
.Where(t => |
|||
(t.IsPublic && t.GetCustomAttributes(typeof(NonSerializedAttribute), false).Length == 0) || |
|||
(t.GetCustomAttributes(typeof(SerializeField), false).Length > 0) |
|||
) |
|||
.Where(t => t.GetCustomAttributes(typeof(HideInInspector), false).Length == 0) |
|||
.ToList(); |
|||
|
|||
// Prepare all serialized objects for this editor
|
|||
foreach (var field in fields) |
|||
{ |
|||
var property = serializedObject.FindProperty(field.Name); |
|||
var parameter = new SerializedDataParameter(property); |
|||
m_Parameters.Add(parameter); |
|||
} |
|||
} |
|||
|
|||
public virtual void OnDisable() |
|||
{ |
|||
} |
|||
|
|||
internal void OnInternalInspectorGUI() |
|||
{ |
|||
serializedObject.Update(); |
|||
TopRowFields(); |
|||
OnInspectorGUI(); |
|||
EditorGUILayout.Space(); |
|||
serializedObject.ApplyModifiedProperties(); |
|||
} |
|||
|
|||
public virtual void OnInspectorGUI() |
|||
{ |
|||
// Display every field as-is
|
|||
foreach (var parameter in m_Parameters) |
|||
PropertyField(parameter); |
|||
} |
|||
|
|||
public virtual string GetDisplayTitle() |
|||
{ |
|||
return ObjectNames.NicifyVariableName(target.GetType().Name); |
|||
} |
|||
|
|||
void TopRowFields() |
|||
{ |
|||
using (new EditorGUILayout.HorizontalScope()) |
|||
{ |
|||
if (GUILayout.Button(CoreEditorUtils.GetContent("All|Toggle all overrides on. To maximize performances you should only toggle overrides that you actually need."), CoreEditorStyles.miniLabelButton, GUILayout.Width(17f), GUILayout.ExpandWidth(false))) |
|||
SetAllOverridesTo(true); |
|||
|
|||
if (GUILayout.Button(CoreEditorUtils.GetContent("None|Toggle all overrides off."), CoreEditorStyles.miniLabelButton, GUILayout.Width(32f), GUILayout.ExpandWidth(false))) |
|||
SetAllOverridesTo(false); |
|||
|
|||
GUILayout.FlexibleSpace(); |
|||
} |
|||
} |
|||
|
|||
internal void SetAllOverridesTo(bool state) |
|||
{ |
|||
Undo.RecordObject(target, "Toggle All"); |
|||
target.SetAllOverridesTo(state); |
|||
serializedObject.Update(); |
|||
} |
|||
|
|||
// Takes a serialized VolumeParameter<T> as input
|
|||
protected SerializedDataParameter Unpack(SerializedProperty property) |
|||
{ |
|||
Assert.IsNotNull(property); |
|||
return new SerializedDataParameter(property); |
|||
} |
|||
|
|||
protected void PropertyField(SerializedDataParameter property) |
|||
{ |
|||
var title = CoreEditorUtils.GetContent(property.displayName); |
|||
PropertyField(property, title); |
|||
} |
|||
|
|||
protected void PropertyField(SerializedDataParameter property, GUIContent title) |
|||
{ |
|||
// Handle unity built-in decorators (Space, Header, Tooltip etc)
|
|||
foreach (var attr in property.attributes) |
|||
{ |
|||
if (attr is PropertyAttribute) |
|||
{ |
|||
if (attr is SpaceAttribute) |
|||
{ |
|||
EditorGUILayout.GetControlRect(false, (attr as SpaceAttribute).height); |
|||
} |
|||
else if (attr is HeaderAttribute) |
|||
{ |
|||
var rect = EditorGUILayout.GetControlRect(false, EditorGUIUtility.singleLineHeight); |
|||
rect.y += 0f; |
|||
rect = EditorGUI.IndentedRect(rect); |
|||
EditorGUI.LabelField(rect, (attr as HeaderAttribute).header, EditorStyles.miniLabel); |
|||
} |
|||
else if (attr is TooltipAttribute) |
|||
{ |
|||
if (string.IsNullOrEmpty(title.tooltip)) |
|||
title.tooltip = (attr as TooltipAttribute).tooltip; |
|||
} |
|||
} |
|||
} |
|||
|
|||
// Custom parameter drawer
|
|||
VolumeParameterDrawer drawer; |
|||
s_ParameterDrawers.TryGetValue(property.referenceType, out drawer); |
|||
|
|||
bool invalidProp = false; |
|||
|
|||
if (drawer != null && !drawer.IsAutoProperty()) |
|||
{ |
|||
if (drawer.OnGUI(property, title)) |
|||
return; |
|||
|
|||
invalidProp = true; |
|||
} |
|||
|
|||
// ObjectParameter<T> is a special case
|
|||
if (VolumeParameter.IsObjectParameter(property.referenceType)) |
|||
{ |
|||
bool expanded = property.value.isExpanded; |
|||
expanded = EditorGUILayout.Foldout(expanded, title, true); |
|||
|
|||
if (expanded) |
|||
{ |
|||
EditorGUI.indentLevel++; |
|||
|
|||
// Not the fastest way to do it but that'll do just fine for now
|
|||
var it = property.value.Copy(); |
|||
var end = it.GetEndProperty(); |
|||
bool first = true; |
|||
|
|||
while (it.Next(first) && !SerializedProperty.EqualContents(it, end)) |
|||
{ |
|||
PropertyField(Unpack(it)); |
|||
first = false; |
|||
} |
|||
|
|||
EditorGUI.indentLevel--; |
|||
} |
|||
|
|||
property.value.isExpanded = expanded; |
|||
return; |
|||
} |
|||
|
|||
using (new EditorGUILayout.HorizontalScope()) |
|||
{ |
|||
// Override checkbox
|
|||
var overrideRect = GUILayoutUtility.GetRect(17f, 17f, GUILayout.ExpandWidth(false)); |
|||
overrideRect.yMin += 4f; |
|||
overrideRect.xMin += EditorGUI.indentLevel * 15f; |
|||
DrawOverrideCheckbox(overrideRect, property.overrideState); |
|||
|
|||
// Property
|
|||
using (new EditorGUI.DisabledScope(!property.overrideState.boolValue)) |
|||
{ |
|||
if (drawer != null && !invalidProp) |
|||
{ |
|||
if (drawer.OnGUI(property, title)) |
|||
return; |
|||
} |
|||
|
|||
// Default unity field
|
|||
EditorGUILayout.PropertyField(property.value, title); |
|||
} |
|||
} |
|||
} |
|||
|
|||
void DrawOverrideCheckbox(Rect rect, SerializedProperty property) |
|||
{ |
|||
var oldColor = GUI.color; |
|||
GUI.color = new Color(0.6f, 0.6f, 0.6f, 0.75f); |
|||
property.boolValue = GUI.Toggle(rect, property.boolValue, CoreEditorUtils.GetContent("|Override this setting for this volume."), CoreEditorStyles.smallTickbox); |
|||
GUI.color = oldColor; |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: d6bb22372ebe2bc42a227c8f8c1795ac |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using UnityEngine; |
|||
using UnityEngine.Assertions; |
|||
using UnityEngine.Experimental.Rendering; |
|||
|
|||
namespace UnityEditor.Experimental.Rendering |
|||
{ |
|||
[CustomEditor(typeof(Volume))] |
|||
public sealed class VolumeEditor : Editor |
|||
{ |
|||
SerializedProperty m_IsGlobal; |
|||
SerializedProperty m_BlendRadius; |
|||
SerializedProperty m_Weight; |
|||
SerializedProperty m_Priority; |
|||
SerializedProperty m_Components; |
|||
|
|||
Dictionary<Type, Type> m_EditorTypes; // Component type => Editor type
|
|||
List<VolumeComponentEditor> m_Editors; |
|||
|
|||
static VolumeComponent s_ClipboardContent; |
|||
|
|||
Volume actualTarget |
|||
{ |
|||
get { return target as Volume; } |
|||
} |
|||
|
|||
void OnEnable() |
|||
{ |
|||
var o = new PropertyFetcher<Volume>(serializedObject); |
|||
m_IsGlobal = o.Find(x => x.isGlobal); |
|||
m_BlendRadius = o.Find(x => x.blendDistance); |
|||
m_Weight = o.Find(x => x.weight); |
|||
m_Priority = o.Find(x => x.priority); |
|||
m_Components = o.Find(x => x.components); |
|||
|
|||
m_EditorTypes = new Dictionary<Type, Type>(); |
|||
m_Editors = new List<VolumeComponentEditor>(); |
|||
|
|||
// Gets the list of all available component editors
|
|||
var editorTypes = AppDomain.CurrentDomain.GetAssemblies() |
|||
.SelectMany( |
|||
a => a.GetTypes() |
|||
.Where( |
|||
t => t.IsSubclassOf(typeof(VolumeComponentEditor)) |
|||
&& t.IsDefined(typeof(VolumeComponentEditorAttribute), false) |
|||
) |
|||
).ToList(); |
|||
|
|||
// Map them to their corresponding component type
|
|||
foreach (var editorType in editorTypes) |
|||
{ |
|||
var attribute = (VolumeComponentEditorAttribute)editorType.GetCustomAttributes(typeof(VolumeComponentEditorAttribute), false)[0]; |
|||
m_EditorTypes.Add(attribute.componentType, editorType); |
|||
} |
|||
|
|||
// Create editors for existing components
|
|||
var components = actualTarget.components; |
|||
for (int i = 0; i < components.Count; i++) |
|||
CreateEditor(components[i], m_Components.GetArrayElementAtIndex(i)); |
|||
|
|||
// Keep track of undo/redo to redraw the inspector when that happens
|
|||
Undo.undoRedoPerformed += OnUndoRedoPerformed; |
|||
} |
|||
|
|||
void OnDisable() |
|||
{ |
|||
Undo.undoRedoPerformed -= OnUndoRedoPerformed; |
|||
|
|||
if (m_Editors == null) |
|||
return; // Hasn't been inited yet
|
|||
|
|||
foreach (var editor in m_Editors) |
|||
editor.OnDisable(); |
|||
|
|||
m_Editors.Clear(); |
|||
m_EditorTypes.Clear(); |
|||
} |
|||
|
|||
public override void OnInspectorGUI() |
|||
{ |
|||
serializedObject.Update(); |
|||
|
|||
if (actualTarget.isDirty) |
|||
{ |
|||
RefreshEditors(); |
|||
actualTarget.isDirty = false; |
|||
} |
|||
|
|||
using (var scope = new EditorGUILayout.VerticalScope()) |
|||
{ |
|||
EditorGUILayout.PropertyField(m_IsGlobal); |
|||
|
|||
if (!m_IsGlobal.boolValue) // Blend radius is not needed for global volumes
|
|||
{ |
|||
EditorGUILayout.PropertyField(m_BlendRadius); |
|||
m_BlendRadius.floatValue = Mathf.Max(m_BlendRadius.floatValue, 0f); |
|||
} |
|||
|
|||
EditorGUILayout.PropertyField(m_Weight); |
|||
EditorGUILayout.PropertyField(m_Priority); |
|||
|
|||
EditorGUILayout.Space(); |
|||
|
|||
// Component list
|
|||
for (int i = 0; i < m_Editors.Count; i++) |
|||
{ |
|||
var editor = m_Editors[i]; |
|||
string title = editor.GetDisplayTitle(); |
|||
int id = i; // Needed for closure capture below
|
|||
|
|||
CoreEditorUtils.DrawSplitter(); |
|||
bool displayContent = CoreEditorUtils.DrawHeaderToggle( |
|||
title, |
|||
editor.baseProperty, |
|||
editor.activeProperty, |
|||
pos => OnContextClick(pos, editor.target, id) |
|||
); |
|||
|
|||
if (displayContent) |
|||
{ |
|||
using (new EditorGUI.DisabledScope(!editor.activeProperty.boolValue)) |
|||
editor.OnInternalInspectorGUI(); |
|||
} |
|||
} |
|||
|
|||
if (m_Editors.Count > 0) |
|||
CoreEditorUtils.DrawSplitter(); |
|||
else |
|||
EditorGUILayout.HelpBox("No override set on this volume.", MessageType.Info); |
|||
|
|||
using (new EditorGUILayout.HorizontalScope()) |
|||
{ |
|||
GUILayout.FlexibleSpace(); |
|||
|
|||
//if (GUILayout.Button(CoreEditorUtils.GetContent("Add Component"), GUILayout.Width(230f), GUILayout.Height(24f)))
|
|||
//{
|
|||
|
|||
//}
|
|||
|
|||
GUILayout.FlexibleSpace(); |
|||
} |
|||
|
|||
// Handle components drag'n'drop
|
|||
var e = Event.current; |
|||
if (e.type == EventType.DragUpdated) |
|||
{ |
|||
if (IsDragValid(scope.rect, e.mousePosition)) |
|||
{ |
|||
DragAndDrop.visualMode = DragAndDropVisualMode.Link; |
|||
e.Use(); |
|||
} |
|||
else |
|||
{ |
|||
DragAndDrop.visualMode = DragAndDropVisualMode.Rejected; |
|||
} |
|||
} |
|||
else if (e.type == EventType.DragPerform) |
|||
{ |
|||
if (IsDragValid(scope.rect, e.mousePosition)) |
|||
{ |
|||
DragAndDrop.AcceptDrag(); |
|||
|
|||
var objs = DragAndDrop.objectReferences; |
|||
foreach (var o in objs) |
|||
{ |
|||
var compType = ((MonoScript)o).GetClass(); |
|||
AddComponent(compType); |
|||
} |
|||
|
|||
e.Use(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
serializedObject.ApplyModifiedProperties(); |
|||
} |
|||
|
|||
bool IsDragValid(Rect rect, Vector2 mousePos) |
|||
{ |
|||
if (!rect.Contains(mousePos)) |
|||
return false; |
|||
|
|||
var objs = DragAndDrop.objectReferences; |
|||
foreach (var o in objs) |
|||
{ |
|||
if (o.GetType() != typeof(MonoScript)) |
|||
return false; |
|||
|
|||
var script = (MonoScript)o; |
|||
var scriptType = script.GetClass(); |
|||
|
|||
if (!scriptType.IsSubclassOf(typeof(VolumeComponent))) |
|||
return false; |
|||
|
|||
if (actualTarget.components.Exists(t => t.GetType() == scriptType)) |
|||
return false; |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
void RefreshEditors() |
|||
{ |
|||
// Disable all editors first
|
|||
foreach (var editor in m_Editors) |
|||
editor.OnDisable(); |
|||
|
|||
// Remove them
|
|||
m_Editors.Clear(); |
|||
|
|||
// Recreate editors for existing settings, if any
|
|||
for (int i = 0; i < actualTarget.components.Count; i++) |
|||
CreateEditor(actualTarget.components[i], m_Components.GetArrayElementAtIndex(i)); |
|||
} |
|||
|
|||
// index is only used when we need to re-create a component in a specific spot (e.g. reset)
|
|||
void CreateEditor(VolumeComponent settings, SerializedProperty property, int index = -1) |
|||
{ |
|||
var settingsType = settings.GetType(); |
|||
Type editorType; |
|||
|
|||
if (!m_EditorTypes.TryGetValue(settingsType, out editorType)) |
|||
editorType = typeof(VolumeComponentEditor); |
|||
|
|||
var editor = (VolumeComponentEditor)Activator.CreateInstance(editorType); |
|||
editor.Init(settings, this); |
|||
editor.baseProperty = property.Copy(); |
|||
|
|||
if (index < 0) |
|||
m_Editors.Add(editor); |
|||
else |
|||
m_Editors[index] = editor; |
|||
} |
|||
|
|||
void AddComponent(Type type) |
|||
{ |
|||
serializedObject.Update(); |
|||
|
|||
var component = (VolumeComponent)CreateInstance(type); |
|||
Undo.RegisterCreatedObjectUndo(component, "Add Volume Component"); |
|||
|
|||
// Grow the list first, then add - that's how serialized lists work in Unity
|
|||
m_Components.arraySize++; |
|||
var effectProp = m_Components.GetArrayElementAtIndex(m_Components.arraySize - 1); |
|||
effectProp.objectReferenceValue = component; |
|||
|
|||
// Create & store the internal editor object for this effect
|
|||
CreateEditor(component, effectProp); |
|||
|
|||
serializedObject.ApplyModifiedProperties(); |
|||
} |
|||
|
|||
void RemoveComponent(int id) |
|||
{ |
|||
// Huh. Hack to keep foldout state on the next element...
|
|||
bool nextFoldoutState = false; |
|||
if (id < m_Editors.Count - 1) |
|||
nextFoldoutState = m_Editors[id + 1].baseProperty.isExpanded; |
|||
|
|||
// Remove from the cached editors list
|
|||
m_Editors[id].OnDisable(); |
|||
m_Editors.RemoveAt(id); |
|||
|
|||
serializedObject.Update(); |
|||
|
|||
var property = m_Components.GetArrayElementAtIndex(id); |
|||
var effect = property.objectReferenceValue; |
|||
|
|||
// Unassign it (should be null already but serialization does funky things
|
|||
property.objectReferenceValue = null; |
|||
|
|||
// ...and remove the array index itself from the list
|
|||
m_Components.DeleteArrayElementAtIndex(id); |
|||
|
|||
// Finally refresh editor reference to the serialized settings list
|
|||
for (int i = 0; i < m_Editors.Count; i++) |
|||
m_Editors[i].baseProperty = m_Components.GetArrayElementAtIndex(i).Copy(); |
|||
|
|||
// Set the proper foldout state if needed
|
|||
if (id < m_Editors.Count) |
|||
m_Editors[id].baseProperty.isExpanded = nextFoldoutState; |
|||
|
|||
serializedObject.ApplyModifiedProperties(); |
|||
|
|||
// Destroy the setting object after ApplyModifiedProperties(). If we do it before, redo
|
|||
// actions will be in the wrong order and the reference to the setting object in the
|
|||
// list will be lost.
|
|||
Undo.DestroyObjectImmediate(effect); |
|||
} |
|||
|
|||
// Reset is done by deleting and removing the object from the list and adding a new one in
|
|||
// the same spot as it was before
|
|||
void ResetComponent(Type type, int id) |
|||
{ |
|||
// Remove from the cached editors list
|
|||
m_Editors[id].OnDisable(); |
|||
m_Editors[id] = null; |
|||
|
|||
serializedObject.Update(); |
|||
|
|||
var property = m_Components.GetArrayElementAtIndex(id); |
|||
var prevSettings = property.objectReferenceValue; |
|||
|
|||
// Unassign it but down remove it from the array to keep the index available
|
|||
property.objectReferenceValue = null; |
|||
|
|||
// Create a new object
|
|||
var newEffect = (VolumeComponent)CreateInstance(type); |
|||
Undo.RegisterCreatedObjectUndo(newEffect, "Reset Volume Component"); |
|||
|
|||
// Put it in the reserved space
|
|||
property.objectReferenceValue = newEffect; |
|||
|
|||
// Create & store the internal editor object for this effect
|
|||
CreateEditor(newEffect, property, id); |
|||
|
|||
serializedObject.ApplyModifiedProperties(); |
|||
|
|||
// Same as RemoveComponent, destroy at the end so it's recreated first on Undo to make
|
|||
// sure the GUID exists before undoing the list state
|
|||
Undo.DestroyObjectImmediate(prevSettings); |
|||
} |
|||
|
|||
void OnContextClick(Vector2 position, VolumeComponent targetComponent, int id) |
|||
{ |
|||
var menu = new GenericMenu(); |
|||
menu.AddItem(CoreEditorUtils.GetContent("Reset"), false, () => ResetComponent(targetComponent.GetType(), id)); |
|||
menu.AddItem(CoreEditorUtils.GetContent("Remove"), false, () => RemoveComponent(id)); |
|||
menu.AddSeparator(string.Empty); |
|||
menu.AddItem(CoreEditorUtils.GetContent("Copy Settings"), false, () => CopySettings(targetComponent)); |
|||
|
|||
if (CanPaste(targetComponent)) |
|||
menu.AddItem(CoreEditorUtils.GetContent("Paste Settings"), false, () => PasteSettings(targetComponent)); |
|||
else |
|||
menu.AddDisabledItem(CoreEditorUtils.GetContent("Paste Settings")); |
|||
|
|||
menu.AddSeparator(string.Empty); |
|||
menu.AddItem(CoreEditorUtils.GetContent("Toggle All"), false, () => m_Editors[id].SetAllOverridesTo(true)); |
|||
menu.AddItem(CoreEditorUtils.GetContent("Toggle None"), false, () => m_Editors[id].SetAllOverridesTo(false)); |
|||
|
|||
menu.DropDown(new Rect(position, Vector2.zero)); |
|||
} |
|||
|
|||
// Copy/pasting is simply done by creating an in memory copy of the selected component and
|
|||
// copying over the serialized data to another; it doesn't use nor affect the OS clipboard
|
|||
bool CanPaste(VolumeComponent targetComponent) |
|||
{ |
|||
return s_ClipboardContent != null |
|||
&& s_ClipboardContent.GetType() == targetComponent.GetType(); |
|||
} |
|||
|
|||
void CopySettings(VolumeComponent targetComponent) |
|||
{ |
|||
if (s_ClipboardContent != null) |
|||
{ |
|||
CoreUtils.Destroy(s_ClipboardContent); |
|||
s_ClipboardContent = null; |
|||
} |
|||
|
|||
s_ClipboardContent = (VolumeComponent)CreateInstance(targetComponent.GetType()); |
|||
EditorUtility.CopySerializedIfDifferent(targetComponent, s_ClipboardContent); |
|||
} |
|||
|
|||
void PasteSettings(VolumeComponent targetComponent) |
|||
{ |
|||
Assert.IsNotNull(s_ClipboardContent); |
|||
Assert.AreEqual(s_ClipboardContent.GetType(), targetComponent.GetType()); |
|||
|
|||
Undo.RecordObject(targetComponent, "Paste Settings"); |
|||
EditorUtility.CopySerializedIfDifferent(s_ClipboardContent, targetComponent); |
|||
} |
|||
|
|||
void OnUndoRedoPerformed() |
|||
{ |
|||
actualTarget.isDirty = true; |
|||
|
|||
// Dumb hack to make sure the serialized object is up to date on undo
|
|||
serializedObject.Update(); |
|||
serializedObject.ApplyModifiedProperties(); |
|||
|
|||
// Seems like there's an issue with the inspector not repainting after some undo events
|
|||
// This will take care of that
|
|||
Repaint(); |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 9a033a261241d6e48af8ca011ab7c5c1 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System; |
|||
using UnityEngine; |
|||
|
|||
namespace UnityEditor.Experimental.Rendering |
|||
{ |
|||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] |
|||
public sealed class VolumeParameterDrawerAttribute : Attribute |
|||
{ |
|||
public readonly Type parameterType; |
|||
|
|||
public VolumeParameterDrawerAttribute(Type parameterType) |
|||
{ |
|||
this.parameterType = parameterType; |
|||
} |
|||
} |
|||
|
|||
// Default parameter drawer - simply displays the serialized property as Unity would
|
|||
public abstract class VolumeParameterDrawer |
|||
{ |
|||
// Override this and return false if you want to customize the override checkbox position,
|
|||
// else it'll automatically draw it and put the property content in a horizontal scope.
|
|||
public virtual bool IsAutoProperty() |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
// Return false is the input parameter is invalid - unity will display the default editor
|
|||
// for this control then
|
|||
public abstract bool OnGUI(SerializedDataParameter parameter, GUIContent title); |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: d8ce10b4215bd81438b65f7e23ac0946 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
fileFormatVersion: 2 |
|||
guid: 172515602e62fb746b5d573b38a5fe58 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Collections.ObjectModel; |
|||
using System.Reflection; |
|||
using System.Linq; |
|||
|
|||
namespace UnityEngine.Experimental.Rendering |
|||
{ |
|||
[Serializable] |
|||
public class VolumeComponent : ScriptableObject |
|||
{ |
|||
// Used to control the state of this override - handy to quickly turn a volume override
|
|||
// on & off in the editor
|
|||
public bool active = true; |
|||
|
|||
internal ReadOnlyCollection<VolumeParameter> parameters { get; private set; } |
|||
|
|||
void OnEnable() |
|||
{ |
|||
// Automatically grab all fields of type VolumeParameter for this instance
|
|||
parameters = this.GetType() |
|||
.GetFields(BindingFlags.Public | BindingFlags.Instance) |
|||
.Where(t => t.FieldType.IsSubclassOf(typeof(VolumeParameter))) |
|||
.OrderBy(t => t.MetadataToken) // Guaranteed order
|
|||
.Select(t => (VolumeParameter)t.GetValue(this)) |
|||
.ToList() |
|||
.AsReadOnly(); |
|||
} |
|||
|
|||
public void SetAllOverridesTo(bool state) |
|||
{ |
|||
SetAllOverridesTo(parameters, state); |
|||
} |
|||
|
|||
void SetAllOverridesTo(IEnumerable<VolumeParameter> enumerable, bool state) |
|||
{ |
|||
foreach (var prop in enumerable) |
|||
{ |
|||
prop.overrideState = state; |
|||
var t = prop.GetType(); |
|||
|
|||
if (VolumeParameter.IsObjectParameter(t)) |
|||
{ |
|||
// This method won't be called a lot but this is sub-optimal, fix me
|
|||
var innerParams = (ReadOnlyCollection<VolumeParameter>) |
|||
t.GetProperty("parameters", BindingFlags.NonPublic | BindingFlags.Instance) |
|||
.GetValue(prop, null); |
|||
|
|||
if (innerParams != null) |
|||
SetAllOverridesTo(innerParams, state); |
|||
} |
|||
} |
|||
} |
|||
|
|||
// Custom hashing function used to compare the state of settings (it's not meant to be
|
|||
// unique but to be a quick way to check if two setting sets have the same state or not).
|
|||
// Hash collision rate should be pretty low.
|
|||
public override int GetHashCode() |
|||
{ |
|||
unchecked |
|||
{ |
|||
//return parameters.Aggregate(17, (i, p) => i * 23 + p.GetHash());
|
|||
|
|||
int hash = 17; |
|||
|
|||
foreach (var p in parameters) |
|||
hash = hash * 23 + p.GetHashCode(); |
|||
|
|||
return hash; |
|||
} |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 556096baecf4aeb4e9f0d3efce77e08f |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using UnityEngine.Assertions; |
|||
|
|||
namespace UnityEngine.Experimental.Rendering |
|||
{ |
|||
public sealed class VolumeManager |
|||
{ |
|||
//>>> System.Lazy<T> is broken in Unity (legacy runtime) so we'll have to do it ourselves :|
|
|||
static volatile VolumeManager s_Instance; |
|||
static object s_LockObj = new Object(); |
|||
|
|||
public static VolumeManager instance |
|||
{ |
|||
get |
|||
{ |
|||
// Double-lock checking
|
|||
if (s_Instance == null) |
|||
{ |
|||
lock (s_LockObj) // Lock on a separate object to avoid deadlocks
|
|||
{ |
|||
if (s_Instance == null) |
|||
s_Instance = new VolumeManager(); |
|||
} |
|||
} |
|||
|
|||
return s_Instance; |
|||
} |
|||
} |
|||
//<<<
|
|||
|
|||
// Max amount of layers available in Unity
|
|||
const int k_MaxLayerCount = 32; |
|||
|
|||
// List of all volumes (sorted by priority) per layer
|
|||
readonly List<Volume>[] m_Volumes; |
|||
|
|||
// Keep track of sorting states for all layers
|
|||
readonly bool[] m_SortNeeded; |
|||
|
|||
// Internal state of all component types
|
|||
readonly Dictionary<Type, VolumeComponent> m_Components; |
|||
|
|||
// Internal list of default state for each component type - this is used to reset component
|
|||
// states on update instead of having to implement a Reset method on all components (which
|
|||
// would be error-prone)
|
|||
readonly List<VolumeComponent> m_ComponentsDefaultState; |
|||
|
|||
// Recycled list used for volume traversal
|
|||
readonly List<Collider> m_TempColliders; |
|||
|
|||
// In the editor, when entering play-mode, it will call the constructor and OnEditorReload()
|
|||
// which in turn will call ReloadBaseTypes() twice, so we need to keep track of the reloads
|
|||
// to avoid wasting any more CPU than required
|
|||
static bool s_StopReloads = false; |
|||
|
|||
VolumeManager() |
|||
{ |
|||
m_Volumes = new List<Volume>[k_MaxLayerCount]; |
|||
m_SortNeeded = new bool[k_MaxLayerCount]; |
|||
m_TempColliders = new List<Collider>(8); |
|||
m_Components = new Dictionary<Type, VolumeComponent>(); |
|||
m_ComponentsDefaultState = new List<VolumeComponent>(); |
|||
|
|||
ReloadBaseTypes(); |
|||
} |
|||
|
|||
#if UNITY_EDITOR
|
|||
// Called every time Unity recompiles scripts in the editor. We need this to keep track of
|
|||
// any new custom component the user might add to the project.
|
|||
[UnityEditor.Callbacks.DidReloadScripts] |
|||
static void OnEditorReload() |
|||
{ |
|||
if (!s_StopReloads) |
|||
instance.ReloadBaseTypes(); |
|||
|
|||
s_StopReloads = false; |
|||
} |
|||
#endif
|
|||
|
|||
// This will be called only once at runtime and everytime script reload kicks-in in the
|
|||
// editor as we need to keep track of any compatible component in the project
|
|||
void ReloadBaseTypes() |
|||
{ |
|||
// Clean component map & default states
|
|||
foreach (var component in m_Components) |
|||
CoreUtils.Destroy(component.Value); |
|||
|
|||
foreach (var component in m_ComponentsDefaultState) |
|||
CoreUtils.Destroy(component); |
|||
|
|||
m_Components.Clear(); |
|||
m_ComponentsDefaultState.Clear(); |
|||
|
|||
// Rebuild it from scratch
|
|||
var types = AppDomain.CurrentDomain.GetAssemblies() |
|||
.SelectMany( |
|||
a => a.GetTypes() |
|||
.Where( |
|||
t => t.IsSubclassOf(typeof(VolumeComponent)) |
|||
) |
|||
); |
|||
|
|||
foreach (var type in types) |
|||
{ |
|||
// We need two instances, one for global state tracking and another one to keep a
|
|||
// default state that will act as the lowest priority global volume (so that we have
|
|||
// a state to fallback to when exiting volumes)
|
|||
var inst = (VolumeComponent)ScriptableObject.CreateInstance(type); |
|||
m_Components.Add(type, inst); |
|||
|
|||
inst = (VolumeComponent)ScriptableObject.CreateInstance(type); |
|||
inst.SetAllOverridesTo(true); |
|||
m_ComponentsDefaultState.Add(inst); |
|||
} |
|||
|
|||
s_StopReloads = true; |
|||
} |
|||
|
|||
public T GetComponent<T>() |
|||
where T : VolumeComponent |
|||
{ |
|||
var comp = GetComponent(typeof(T)); |
|||
return (T)comp; |
|||
} |
|||
|
|||
public VolumeComponent GetComponent(Type type) |
|||
{ |
|||
VolumeComponent comp; |
|||
m_Components.TryGetValue(type, out comp); |
|||
Assert.IsNotNull(comp, "Component map is corrupted, \"" + type + "\" not found"); |
|||
return comp; |
|||
} |
|||
|
|||
public void Register(Volume volume, int layer) |
|||
{ |
|||
var volumes = m_Volumes[layer]; |
|||
|
|||
if (volumes == null) |
|||
{ |
|||
volumes = new List<Volume>(); |
|||
m_Volumes[layer] = volumes; |
|||
} |
|||
|
|||
Assert.IsFalse(volumes.Contains(volume), "Volume has already been registered"); |
|||
volumes.Add(volume); |
|||
SetLayerDirty(layer); |
|||
} |
|||
|
|||
public void Unregister(Volume volume, int layer) |
|||
{ |
|||
var volumes = m_Volumes[layer]; |
|||
|
|||
if (volumes == null) |
|||
return; |
|||
|
|||
volumes.Remove(volume); |
|||
} |
|||
|
|||
internal void SetLayerDirty(int layer) |
|||
{ |
|||
Assert.IsTrue(layer >= 0 && layer <= k_MaxLayerCount, "Invalid layer bit"); |
|||
m_SortNeeded[layer] = true; |
|||
} |
|||
|
|||
internal void UpdateVolumeLayer(Volume volume, int prevLayer, int newLayer) |
|||
{ |
|||
Assert.IsTrue(prevLayer >= 0 && prevLayer <= k_MaxLayerCount, "Invalid layer bit"); |
|||
Unregister(volume, prevLayer); |
|||
Register(volume, newLayer); |
|||
} |
|||
|
|||
// Go through all listed components and lerp overriden values in the global state
|
|||
void OverrideData(List<VolumeComponent> components, float interpFactor) |
|||
{ |
|||
foreach (var component in components) |
|||
{ |
|||
if (!component.active) |
|||
continue; |
|||
|
|||
var target = GetComponent(component.GetType()); |
|||
int count = component.parameters.Count; |
|||
|
|||
for (int i = 0; i < count; i++) |
|||
{ |
|||
var toParam = component.parameters[i]; |
|||
if (toParam.overrideState) |
|||
{ |
|||
var fromParam = target.parameters[i]; |
|||
fromParam.Interp(fromParam, toParam, interpFactor); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
// Faster version of OverrideData to force replace values in the global state
|
|||
void ReplaceData(List<VolumeComponent> components) |
|||
{ |
|||
foreach (var component in components) |
|||
{ |
|||
var target = GetComponent(component.GetType()); |
|||
int count = component.parameters.Count; |
|||
|
|||
for (int i = 0; i < count; i++) |
|||
target.parameters[i].SetValue(component.parameters[i]); |
|||
} |
|||
} |
|||
|
|||
// Update the global state - should be called once per frame in the update loop before
|
|||
// anything else
|
|||
public void Update(Transform trigger, LayerMask layerMask) |
|||
{ |
|||
#if UNITY_EDITOR
|
|||
// Editor specific hack to work around serialization doing funky things when exiting
|
|||
// play mode -> re-create the world when bad things happen
|
|||
if (m_ComponentsDefaultState == null |
|||
|| (m_ComponentsDefaultState.Count > 0 && m_ComponentsDefaultState[0] == null)) |
|||
{ |
|||
ReloadBaseTypes(); |
|||
} |
|||
else |
|||
#endif
|
|||
{ |
|||
// Start by resetting the global state to default values
|
|||
ReplaceData(m_ComponentsDefaultState); |
|||
} |
|||
|
|||
// Do magic
|
|||
bool onlyGlobal = trigger == null; |
|||
int mask = layerMask.value; |
|||
var triggerPos = onlyGlobal ? Vector3.zero : trigger.position; |
|||
|
|||
for (int i = 0; i < k_MaxLayerCount; i++) |
|||
{ |
|||
// Skip layers not in the mask
|
|||
if ((mask & (1 << i)) == 0) |
|||
continue; |
|||
|
|||
// Skip empty layers
|
|||
var volumes = m_Volumes[i]; |
|||
|
|||
if (volumes == null) |
|||
continue; |
|||
|
|||
// Sort the volume list if needed
|
|||
if (m_SortNeeded[i]) |
|||
{ |
|||
SortByPriority(volumes); |
|||
m_SortNeeded[i] = false; |
|||
} |
|||
|
|||
// Traverse all volumes
|
|||
foreach (var volume in volumes) |
|||
{ |
|||
// Skip disabled volumes and volumes without any data or weight
|
|||
if (!volume.enabled || volume.weight <= 0f) |
|||
continue; |
|||
|
|||
var components = volume.components; |
|||
|
|||
// Global volumes always have influence
|
|||
if (volume.isGlobal) |
|||
{ |
|||
OverrideData(components, Mathf.Clamp01(volume.weight)); |
|||
continue; |
|||
} |
|||
|
|||
if (onlyGlobal) |
|||
continue; |
|||
|
|||
// If volume isn't global and has no collider, skip it as it's useless
|
|||
var colliders = m_TempColliders; |
|||
volume.GetComponents(colliders); |
|||
if (colliders.Count == 0) |
|||
continue; |
|||
|
|||
// Find closest distance to volume, 0 means it's inside it
|
|||
float closestDistanceSqr = float.PositiveInfinity; |
|||
|
|||
foreach (var collider in colliders) |
|||
{ |
|||
if (!collider.enabled) |
|||
continue; |
|||
|
|||
var closestPoint = collider.ClosestPoint(triggerPos); |
|||
var d = (closestPoint - triggerPos).sqrMagnitude; |
|||
|
|||
if (d < closestDistanceSqr) |
|||
closestDistanceSqr = d; |
|||
} |
|||
|
|||
colliders.Clear(); |
|||
float blendDistSqr = volume.blendDistance * volume.blendDistance; |
|||
|
|||
// Volume has no influence, ignore it
|
|||
// Note: Volume doesn't do anything when `closestDistanceSqr = blendDistSqr` but
|
|||
// we can't use a >= comparison as blendDistSqr could be set to 0 in which
|
|||
// case volume would have total influence
|
|||
if (closestDistanceSqr > blendDistSqr) |
|||
continue; |
|||
|
|||
// Volume has influence
|
|||
float interpFactor = 1f; |
|||
|
|||
if (blendDistSqr > 0f) |
|||
interpFactor = 1f - (closestDistanceSqr / blendDistSqr); |
|||
|
|||
// No need to clamp01 the interpolation factor as it'll always be in [0;1[ range
|
|||
OverrideData(components, interpFactor * Mathf.Clamp01(volume.weight)); |
|||
} |
|||
} |
|||
} |
|||
|
|||
// Stable insertion sort. Faster than List<T>.Sort() for our needs.
|
|||
static void SortByPriority(List<Volume> volumes) |
|||
{ |
|||
Assert.IsNotNull(volumes, "Trying to sort volumes of non-initialized layer"); |
|||
|
|||
for (int i = 1; i < volumes.Count; i++) |
|||
{ |
|||
var temp = volumes[i]; |
|||
int j = i - 1; |
|||
|
|||
// Sort order is ascending
|
|||
while (j >= 0 && volumes[j].priority > temp.priority) |
|||
{ |
|||
volumes[j + 1] = volumes[j]; |
|||
j--; |
|||
} |
|||
|
|||
volumes[j + 1] = temp; |
|||
} |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: f709aacbf8d0b0048889f52315d67d58 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System; |
|||
using System.Collections.ObjectModel; |
|||
using System.Linq; |
|||
using System.Reflection; |
|||
|
|||
namespace UnityEngine.Experimental.Rendering |
|||
{ |
|||
// We need this base class to be able to store a list of VolumeParameter in collections as we
|
|||
// can't store VolumeParameter<T> with variable T types in the same collection. As a result some
|
|||
// of the following is a bit hacky...
|
|||
public abstract class VolumeParameter |
|||
{ |
|||
[SerializeField] |
|||
protected bool m_OverrideState; |
|||
|
|||
public virtual bool overrideState |
|||
{ |
|||
get { return m_OverrideState; } |
|||
set { m_OverrideState = value; } |
|||
} |
|||
|
|||
internal abstract void Interp(VolumeParameter from, VolumeParameter to, float t); |
|||
|
|||
public T GetValue<T>() |
|||
{ |
|||
return ((VolumeParameter<T>)this).value; |
|||
} |
|||
|
|||
internal abstract void SetValue(VolumeParameter parameter); |
|||
|
|||
public static bool IsObjectParameter(Type type) |
|||
{ |
|||
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(ObjectParameter<>)) |
|||
return true; |
|||
|
|||
return type.BaseType != null |
|||
&& IsObjectParameter(type.BaseType); |
|||
} |
|||
} |
|||
|
|||
[Serializable] |
|||
public class VolumeParameter<T> : VolumeParameter |
|||
{ |
|||
[SerializeField] |
|||
protected T m_Value; |
|||
|
|||
public virtual T value |
|||
{ |
|||
get { return m_Value; } |
|||
set { m_Value = value; } |
|||
} |
|||
|
|||
public VolumeParameter() |
|||
: this(default(T), false) |
|||
{ |
|||
} |
|||
|
|||
protected VolumeParameter(T value, bool overrideState) |
|||
{ |
|||
m_Value = value; |
|||
this.overrideState = overrideState; |
|||
} |
|||
|
|||
internal override void Interp(VolumeParameter from, VolumeParameter to, float t) |
|||
{ |
|||
// Note: this is relatively unsafe (assumes that from and to are both holding type T)
|
|||
Interp(from.GetValue<T>(), to.GetValue<T>(), t); |
|||
} |
|||
|
|||
public virtual void Interp(T from, T to, float t) |
|||
{ |
|||
// Returns `b` if `dt > 0` by default so we don't have to write overrides for bools and
|
|||
// enumerations.
|
|||
m_Value = t > 0f ? to : from; |
|||
} |
|||
|
|||
public void Override(T x) |
|||
{ |
|||
overrideState = true; |
|||
m_Value = x; |
|||
} |
|||
|
|||
internal override void SetValue(VolumeParameter parameter) |
|||
{ |
|||
m_Value = parameter.GetValue<T>(); |
|||
} |
|||
|
|||
public override int GetHashCode() |
|||
{ |
|||
unchecked |
|||
{ |
|||
int hash = 17; |
|||
hash = hash * 23 + overrideState.GetHashCode(); |
|||
hash = hash * 23 + value.GetHashCode(); |
|||
return hash; |
|||
} |
|||
} |
|||
|
|||
//
|
|||
// Implicit conversion; assuming the following:
|
|||
//
|
|||
// var myFloatProperty = new ParameterOverride<float> { value = 42f; };
|
|||
//
|
|||
// It allows for implicit casts:
|
|||
//
|
|||
// float myFloat = myFloatProperty.value; // No implicit cast
|
|||
// float myFloat = myFloatProperty; // Implicit cast
|
|||
//
|
|||
// For safety reason this is one-way only.
|
|||
//
|
|||
public static implicit operator T(VolumeParameter<T> prop) |
|||
{ |
|||
return prop.m_Value; |
|||
} |
|||
} |
|||
|
|||
public enum ParameterClampMode |
|||
{ |
|||
Min, |
|||
Max, |
|||
MinMax |
|||
} |
|||
|
|||
//
|
|||
// The serialization system in Unity can't serialize generic types, the workaround is to extend
|
|||
// and flatten pre-defined generic types.
|
|||
// For enums it's recommended to make your own types on the spot, like so:
|
|||
//
|
|||
// [Serializable]
|
|||
// public sealed class MyEnumParameter : VolumeParameter<MyEnum> { }
|
|||
// public enum MyEnum { One, Two }
|
|||
//
|
|||
|
|||
[Serializable] |
|||
public sealed class BoolParameter : VolumeParameter<bool> { } |
|||
|
|||
[Serializable] |
|||
public sealed class IntParameter : VolumeParameter<int> |
|||
{ |
|||
public override void Interp(int from, int to, float t) |
|||
{ |
|||
// Int snapping interpolation. Don't use this for enums as they don't necessarily have
|
|||
// contiguous values. Use the default interpolator instead (same as bool).
|
|||
m_Value = (int)(from + (to - from) * t); |
|||
} |
|||
} |
|||
|
|||
[Serializable] |
|||
public sealed class InstantIntParameter : VolumeParameter<int> { } |
|||
|
|||
[Serializable] |
|||
public sealed class ClampedIntParameter : VolumeParameter<int> |
|||
{ |
|||
public ParameterClampMode clampMode = ParameterClampMode.MinMax; |
|||
public int min = 0; |
|||
public int max = 10; |
|||
|
|||
public override int value |
|||
{ |
|||
get { return m_Value; } |
|||
set |
|||
{ |
|||
switch (clampMode) |
|||
{ |
|||
case ParameterClampMode.Min: m_Value = Mathf.Max(min, value); break; |
|||
case ParameterClampMode.Max: m_Value = Mathf.Min(max, value); break; |
|||
case ParameterClampMode.MinMax: m_Value = Mathf.Clamp(value, min, max); break; |
|||
} |
|||
} |
|||
} |
|||
|
|||
public override void Interp(int from, int to, float t) |
|||
{ |
|||
m_Value = (int)(from + (to - from) * t); |
|||
} |
|||
} |
|||
|
|||
[Serializable] |
|||
public sealed class InstantClampedIntParameter : VolumeParameter<int> |
|||
{ |
|||
public ParameterClampMode clampMode = ParameterClampMode.MinMax; |
|||
public int min = 0; |
|||
public int max = 10; |
|||
|
|||
public override int value |
|||
{ |
|||
get { return m_Value; } |
|||
set |
|||
{ |
|||
switch (clampMode) |
|||
{ |
|||
case ParameterClampMode.Min: m_Value = Mathf.Max(min, value); break; |
|||
case ParameterClampMode.Max: m_Value = Mathf.Min(max, value); break; |
|||
case ParameterClampMode.MinMax: m_Value = Mathf.Clamp(value, min, max); break; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
[Serializable] |
|||
public sealed class FloatParameter : VolumeParameter<float> |
|||
{ |
|||
public override void Interp(float from, float to, float t) |
|||
{ |
|||
m_Value = from + (to - from) * t; |
|||
} |
|||
} |
|||
|
|||
[Serializable] |
|||
public sealed class InstantFloatParameter : VolumeParameter<float> { } |
|||
|
|||
[Serializable] |
|||
public sealed class ClampedFloatParameter : VolumeParameter<float> |
|||
{ |
|||
public ParameterClampMode clampMode = ParameterClampMode.MinMax; |
|||
public float min = 0f; |
|||
public float max = 1f; |
|||
|
|||
public override float value |
|||
{ |
|||
get { return m_Value; } |
|||
set |
|||
{ |
|||
switch (clampMode) |
|||
{ |
|||
case ParameterClampMode.Min: m_Value = Mathf.Max(min, value); break; |
|||
case ParameterClampMode.Max: m_Value = Mathf.Min(max, value); break; |
|||
case ParameterClampMode.MinMax: m_Value = Mathf.Clamp(value, min, max); break; |
|||
} |
|||
} |
|||
} |
|||
|
|||
// We could override FloatParameter here but that would require making it not-sealed which
|
|||
// will stop the compiler from doing specific optimizations on virtual methods - considering
|
|||
// how often float is used, duplicating the code in this case is a definite win
|
|||
public override void Interp(float from, float to, float t) |
|||
{ |
|||
m_Value = from + (to - from) * t; |
|||
} |
|||
} |
|||
|
|||
[Serializable] |
|||
public sealed class InstantClampedFloatParameter : VolumeParameter<float> |
|||
{ |
|||
public ParameterClampMode clampMode = ParameterClampMode.MinMax; |
|||
public float min = 0f; |
|||
public float max = 1f; |
|||
|
|||
public override float value |
|||
{ |
|||
get { return m_Value; } |
|||
set |
|||
{ |
|||
switch (clampMode) |
|||
{ |
|||
case ParameterClampMode.Min: m_Value = Mathf.Max(min, value); break; |
|||
case ParameterClampMode.Max: m_Value = Mathf.Min(max, value); break; |
|||
case ParameterClampMode.MinMax: m_Value = Mathf.Clamp(value, min, max); break; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
// Holds a min & a max values clamped in a range (MinMaxSlider in the editor)
|
|||
[Serializable] |
|||
public sealed class RangeParameter : VolumeParameter<Vector2> |
|||
{ |
|||
public float min = 0; |
|||
public float max = 1; |
|||
|
|||
public override Vector2 value |
|||
{ |
|||
get { return m_Value; } |
|||
set |
|||
{ |
|||
m_Value.x = Mathf.Max(value.x, min); |
|||
m_Value.y = Mathf.Min(value.y, max); |
|||
} |
|||
} |
|||
|
|||
public override void Interp(Vector2 from, Vector2 to, float t) |
|||
{ |
|||
m_Value.x = from.x + (to.x - from.x) * t; |
|||
m_Value.y = from.y + (to.y - from.y) * t; |
|||
} |
|||
} |
|||
|
|||
[Serializable] |
|||
public sealed class InstantRangeParameter : VolumeParameter<Vector2> |
|||
{ |
|||
public float min = 0; |
|||
public float max = 1; |
|||
|
|||
public override Vector2 value |
|||
{ |
|||
get { return m_Value; } |
|||
set |
|||
{ |
|||
m_Value.x = Mathf.Max(value.x, min); |
|||
m_Value.y = Mathf.Min(value.y, max); |
|||
} |
|||
} |
|||
} |
|||
|
|||
// 32-bit RGBA
|
|||
[Serializable] |
|||
public sealed class ColorParameter : VolumeParameter<Color> |
|||
{ |
|||
public bool hdr = false; |
|||
public bool showAlpha = true; |
|||
public bool showEyeDropper = true; |
|||
|
|||
public override void Interp(Color from, Color to, float t) |
|||
{ |
|||
// Lerping color values is a sensitive subject... We looked into lerping colors using
|
|||
// HSV and LCH but they have some downsides that make them not work correctly in all
|
|||
// situations, so we stick with RGB lerping for now, at least its behavior is
|
|||
// predictable despite looking desaturated when `t ~= 0.5` and it's faster anyway.
|
|||
m_Value.r = from.r + (to.r - from.r) * t; |
|||
m_Value.g = from.g + (to.g - from.g) * t; |
|||
m_Value.b = from.b + (to.b - from.b) * t; |
|||
m_Value.a = from.a + (to.a - from.a) * t; |
|||
} |
|||
} |
|||
|
|||
[Serializable] |
|||
public sealed class InstantColorParameter : VolumeParameter<Color> { } |
|||
|
|||
[Serializable] |
|||
public sealed class Vector2Parameter : VolumeParameter<Vector2> |
|||
{ |
|||
public override void Interp(Vector2 from, Vector2 to, float t) |
|||
{ |
|||
m_Value.x = from.x + (to.x - from.x) * t; |
|||
m_Value.y = from.y + (to.y - from.y) * t; |
|||
} |
|||
} |
|||
|
|||
[Serializable] |
|||
public sealed class InstantVector2Parameter : VolumeParameter<Vector2> { } |
|||
|
|||
[Serializable] |
|||
public sealed class Vector3Parameter : VolumeParameter<Vector3> |
|||
{ |
|||
public override void Interp(Vector3 from, Vector3 to, float t) |
|||
{ |
|||
m_Value.x = from.x + (to.x - from.x) * t; |
|||
m_Value.y = from.y + (to.y - from.y) * t; |
|||
m_Value.z = from.z + (to.z - from.z) * t; |
|||
} |
|||
} |
|||
|
|||
[Serializable] |
|||
public sealed class InstantVector3Parameter : VolumeParameter<Vector3> { } |
|||
|
|||
[Serializable] |
|||
public sealed class Vector4Parameter : VolumeParameter<Vector4> |
|||
{ |
|||
public override void Interp(Vector4 from, Vector4 to, float t) |
|||
{ |
|||
m_Value.x = from.x + (to.x - from.x) * t; |
|||
m_Value.y = from.y + (to.y - from.y) * t; |
|||
m_Value.z = from.z + (to.z - from.z) * t; |
|||
m_Value.w = from.w + (to.w - from.w) * t; |
|||
} |
|||
} |
|||
|
|||
[Serializable] |
|||
public sealed class InstantVector4Parameter : VolumeParameter<Vector4> { } |
|||
|
|||
// Used as a container to store custom serialized classes/structs inside volume components
|
|||
[Serializable] |
|||
public class ObjectParameter<T> : VolumeParameter<T> |
|||
{ |
|||
internal ReadOnlyCollection<VolumeParameter> parameters { get; private set; } |
|||
|
|||
// Force override state to true for container objects
|
|||
public override bool overrideState |
|||
{ |
|||
get { return true; } |
|||
set { m_OverrideState = true; } |
|||
} |
|||
|
|||
public override T value |
|||
{ |
|||
get { return m_Value; } |
|||
set |
|||
{ |
|||
m_Value = value; |
|||
|
|||
if (m_Value == null) |
|||
{ |
|||
parameters = null; |
|||
return; |
|||
} |
|||
|
|||
// Automatically grab all fields of type VolumeParameter contained in this instance
|
|||
parameters = m_Value.GetType() |
|||
.GetFields(BindingFlags.Public | BindingFlags.Instance) |
|||
.Where(t => t.FieldType.IsSubclassOf(typeof(VolumeParameter))) |
|||
.OrderBy(t => t.MetadataToken) // Guaranteed order
|
|||
.Select(t => (VolumeParameter)t.GetValue(m_Value)) |
|||
.ToList() |
|||
.AsReadOnly(); |
|||
} |
|||
} |
|||
|
|||
internal override void Interp(VolumeParameter from, VolumeParameter to, float t) |
|||
{ |
|||
if (m_Value == null) |
|||
return; |
|||
|
|||
var paramOrigin = parameters; |
|||
var paramFrom = ((ObjectParameter<T>)from).parameters; |
|||
var paramTo = ((ObjectParameter<T>)to).parameters; |
|||
|
|||
for (int i = 0; i < paramFrom.Count; i++) |
|||
{ |
|||
if (paramOrigin[i].overrideState) |
|||
paramOrigin[i].Interp(paramFrom[i], paramTo[i], t); |
|||
} |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: d53242bc2649d524aa42f2a84eb077b2 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
|
|||
namespace UnityEngine.Experimental.Rendering |
|||
{ |
|||
[ExecuteInEditMode] |
|||
public class Volume : MonoBehaviour |
|||
{ |
|||
[Tooltip("A global volume is applied to the whole scene.")] |
|||
public bool isGlobal = false; |
|||
|
|||
[Tooltip("Volume priority in the stack. Higher number means higher priority. Negative values are supported.")] |
|||
public float priority = 0f; |
|||
|
|||
[Tooltip("Outer distance to start blending from. A value of 0 means no blending and the volume overrides will be applied immediatly upon entry.")] |
|||
public float blendDistance = 0f; |
|||
|
|||
[Range(0f, 1f), Tooltip("Total weight of this volume in the scene. 0 means it won't do anything, 1 means full effect.")] |
|||
public float weight = 1f; |
|||
|
|||
public List<VolumeComponent> components = new List<VolumeComponent>(); |
|||
|
|||
// Editor-only
|
|||
[NonSerialized] |
|||
public bool isDirty; |
|||
|
|||
// Needed for state tracking (see the comments in Update)
|
|||
int m_PreviousLayer; |
|||
float m_PreviousPriority; |
|||
|
|||
void OnEnable() |
|||
{ |
|||
// Make sure every setting is valid. If a profile holds a script that doesn't exist
|
|||
// anymore, nuke it to keep the volume clean. Note that if you delete a script that is
|
|||
// currently in use in a volume you'll still get a one-time error in the console, it's
|
|||
// harmless and happens because Unity does a redraw of the editor (and thus the current
|
|||
// frame) before the recompilation step.
|
|||
components.RemoveAll(x => x == null); |
|||
|
|||
m_PreviousLayer = gameObject.layer; |
|||
VolumeManager.instance.Register(this, m_PreviousLayer); |
|||
} |
|||
|
|||
void OnDisable() |
|||
{ |
|||
VolumeManager.instance.Unregister(this, gameObject.layer); |
|||
} |
|||
|
|||
void Reset() |
|||
{ |
|||
isDirty = true; |
|||
} |
|||
|
|||
void Update() |
|||
{ |
|||
// Unfortunately we need to track the current layer to update the volume manager in
|
|||
// real-time as the user could change it at any time in the editor or at runtime.
|
|||
// Because no event is raised when the layer changes, we have to track it on every
|
|||
// frame :/
|
|||
int layer = gameObject.layer; |
|||
if (layer != m_PreviousLayer) |
|||
{ |
|||
VolumeManager.instance.UpdateVolumeLayer(this, m_PreviousLayer, layer); |
|||
m_PreviousLayer = layer; |
|||
} |
|||
|
|||
// Same for priority. We could use a property instead, but it doesn't play nice with the
|
|||
// serialization system. Using a custom Attribute/PropertyDrawer for a property is
|
|||
// possible but it doesn't work with Undo/Redo in the editor, which makes it useless for
|
|||
// our case.
|
|||
if (priority != m_PreviousPriority) |
|||
{ |
|||
VolumeManager.instance.SetLayerDirty(layer); |
|||
m_PreviousPriority = priority; |
|||
} |
|||
} |
|||
|
|||
public T Add<T>(bool overrides = false) |
|||
where T : VolumeComponent |
|||
{ |
|||
if (Has<T>()) |
|||
throw new InvalidOperationException("Component already exists in the volume"); |
|||
|
|||
var component = ScriptableObject.CreateInstance<T>(); |
|||
component.SetAllOverridesTo(overrides); |
|||
isDirty = true; |
|||
return component; |
|||
} |
|||
|
|||
public void Remove<T>() |
|||
where T : VolumeComponent |
|||
{ |
|||
int toRemove = -1; |
|||
var type = typeof(T); |
|||
|
|||
for (int i = 0; i < components.Count; i++) |
|||
{ |
|||
if (components[i].GetType() == type) |
|||
{ |
|||
toRemove = i; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
if (toRemove >= 0) |
|||
{ |
|||
components.RemoveAt(toRemove); |
|||
isDirty = true; |
|||
} |
|||
} |
|||
|
|||
public bool Has<T>() |
|||
where T : VolumeComponent |
|||
{ |
|||
var type = typeof(T); |
|||
|
|||
foreach (var component in components) |
|||
{ |
|||
if (component.GetType() == type) |
|||
return true; |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
public bool TryGet<T>(out T component) |
|||
where T : VolumeComponent |
|||
{ |
|||
var type = typeof(T); |
|||
component = null; |
|||
|
|||
foreach (var comp in components) |
|||
{ |
|||
if (comp.GetType() == type) |
|||
{ |
|||
component = (T)comp; |
|||
return true; |
|||
} |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
#if UNITY_EDITOR
|
|||
// TODO: Look into a better volume previsualization system
|
|||
List<Collider> m_TempColliders; |
|||
|
|||
void OnDrawGizmos() |
|||
{ |
|||
if (m_TempColliders == null) |
|||
m_TempColliders = new List<Collider>(); |
|||
|
|||
var colliders = m_TempColliders; |
|||
GetComponents(colliders); |
|||
|
|||
if (isGlobal || colliders == null) |
|||
return; |
|||
|
|||
var scale = transform.localScale; |
|||
var invScale = new Vector3(1f / scale.x, 1f / scale.y, 1f / scale.z); |
|||
Gizmos.matrix = Matrix4x4.TRS(transform.position, transform.rotation, scale); |
|||
Gizmos.color = new Color(0f, 1f, 0.1f, 0.6f); |
|||
|
|||
// Draw a separate gizmo for each collider
|
|||
foreach (var collider in colliders) |
|||
{ |
|||
if (!collider.enabled) |
|||
continue; |
|||
|
|||
// We'll just use scaling as an approximation for volume skin. It's far from being
|
|||
// correct (and is completely wrong in some cases). Ultimately we'd use a distance
|
|||
// field or at least a tesselate + push modifier on the collider's mesh to get a
|
|||
// better approximation, but the current Gizmo system is a bit limited and because
|
|||
// everything is dynamic in Unity and can be changed at anytime, it's hard to keep
|
|||
// track of changes in an elegant way (which we'd need to implement a nice cache
|
|||
// system for generated volume meshes).
|
|||
var type = collider.GetType(); |
|||
|
|||
if (type == typeof(BoxCollider)) |
|||
{ |
|||
var c = (BoxCollider)collider; |
|||
Gizmos.DrawCube(c.center, c.size); |
|||
Gizmos.DrawWireCube(c.center, c.size + invScale * blendDistance * 2f); |
|||
} |
|||
else if (type == typeof(SphereCollider)) |
|||
{ |
|||
var c = (SphereCollider)collider; |
|||
Gizmos.DrawSphere(c.center, c.radius); |
|||
Gizmos.DrawWireSphere(c.center, c.radius + invScale.x * blendDistance); |
|||
} |
|||
else if (type == typeof(MeshCollider)) |
|||
{ |
|||
var c = (MeshCollider)collider; |
|||
|
|||
// Only convex mesh colliders are allowed
|
|||
if (!c.convex) |
|||
c.convex = true; |
|||
|
|||
// Mesh pivot should be centered or this won't work
|
|||
Gizmos.DrawMesh(c.sharedMesh); |
|||
Gizmos.DrawWireMesh(c.sharedMesh, Vector3.zero, Quaternion.identity, Vector3.one + invScale * blendDistance * 2f); |
|||
} |
|||
|
|||
// Nothing for capsule (DrawCapsule isn't exposed in Gizmo), terrain, wheel and
|
|||
// other colliders...
|
|||
} |
|||
|
|||
colliders.Clear(); |
|||
} |
|||
#endif
|
|||
} |
|||
} |
撰写
预览
正在加载...
取消
保存
Reference in new issue