977 lines
41 KiB
C#
977 lines
41 KiB
C#
using System;
|
||
using System.IO;
|
||
using System.Linq;
|
||
using System.Collections.Generic;
|
||
using System.ComponentModel;
|
||
using UnityEditorInternal;
|
||
using UnityEditor;
|
||
using UnityEngine;
|
||
using UnityEngine.VFX;
|
||
using UnityEditor.Callbacks;
|
||
using UnityEditor.VFX;
|
||
using UnityEditor.VFX.UI;
|
||
using UnityEngine.UIElements;
|
||
using UnityObject = UnityEngine.Object;
|
||
|
||
|
||
class VFXExternalShaderProcessor : AssetPostprocessor
|
||
{
|
||
public const string k_ShaderDirectory = "Shaders";
|
||
public const string k_ShaderExt = ".vfxshader";
|
||
public static bool allowExternalization { get { return EditorPrefs.GetBool(VFXViewPreference.allowShaderExternalizationKey, false); } }
|
||
|
||
void OnPreprocessAsset()
|
||
{
|
||
if (!allowExternalization)
|
||
return;
|
||
bool isVFX = assetPath.EndsWith(VisualEffectResource.Extension);
|
||
if (isVFX)
|
||
{
|
||
string vfxName = Path.GetFileNameWithoutExtension(assetPath);
|
||
string vfxDirectory = Path.GetDirectoryName(assetPath);
|
||
|
||
string shaderDirectory = vfxDirectory + "/" + k_ShaderDirectory + "/" + vfxName;
|
||
|
||
if (!Directory.Exists(shaderDirectory))
|
||
{
|
||
return;
|
||
}
|
||
VisualEffectAsset asset = AssetDatabase.LoadAssetAtPath<VisualEffectAsset>(assetPath);
|
||
if (asset == null)
|
||
return;
|
||
|
||
bool oneFound = false;
|
||
VisualEffectResource resource = asset.GetResource();
|
||
if (resource == null)
|
||
return;
|
||
VFXShaderSourceDesc[] descs = resource.shaderSources;
|
||
|
||
foreach (var shaderPath in Directory.GetFiles(shaderDirectory))
|
||
{
|
||
if (shaderPath.EndsWith(k_ShaderExt))
|
||
{
|
||
System.IO.StreamReader file = new System.IO.StreamReader(shaderPath);
|
||
|
||
string shaderLine = file.ReadLine();
|
||
file.Close();
|
||
if (shaderLine == null || !shaderLine.StartsWith("//"))
|
||
continue;
|
||
|
||
string[] shaderParams = shaderLine.Split(',');
|
||
|
||
string shaderName = shaderParams[0].Substring(2);
|
||
|
||
int index;
|
||
if (!int.TryParse(shaderParams[1], out index))
|
||
continue;
|
||
|
||
if (index < 0 || index >= descs.Length)
|
||
continue;
|
||
if (descs[index].name != shaderName)
|
||
continue;
|
||
|
||
string shaderSource = File.ReadAllText(shaderPath);
|
||
//remove the first two lines that where added when externalized
|
||
shaderSource = shaderSource.Substring(shaderSource.IndexOf("\n", shaderSource.IndexOf("\n") + 1) + 1);
|
||
|
||
descs[index].source = shaderSource;
|
||
oneFound = true;
|
||
}
|
||
}
|
||
if (oneFound)
|
||
{
|
||
resource.shaderSources = descs;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
[CustomEditor(typeof(VisualEffectAsset))]
|
||
[CanEditMultipleObjects]
|
||
class VisualEffectAssetEditor : UnityEditor.Editor
|
||
{
|
||
#if UNITY_2021_1_OR_NEWER
|
||
[OnOpenAsset(OnOpenAssetAttributeMode.Validate)]
|
||
public static bool WillOpenInUnity(int instanceID)
|
||
{
|
||
var obj = EditorUtility.InstanceIDToObject(instanceID);
|
||
if (obj is VFXGraph || obj is VFXModel || obj is VFXUI)
|
||
return true;
|
||
else if (obj is VisualEffectAsset)
|
||
return true;
|
||
else if (obj is VisualEffectSubgraph)
|
||
return true;
|
||
return false;
|
||
}
|
||
|
||
#endif
|
||
[OnOpenAsset(1)]
|
||
public static bool OnOpenVFX(int instanceID, int line)
|
||
{
|
||
var obj = EditorUtility.InstanceIDToObject(instanceID);
|
||
if (obj is VFXGraph || obj is VFXModel || obj is VFXUI)
|
||
{
|
||
// for visual effect graph editor ScriptableObject select them when double clicking on them.
|
||
//Since .vfx importer is a copyasset, the default is to open it with an external editor.
|
||
Selection.activeInstanceID = instanceID;
|
||
return true;
|
||
}
|
||
else if (obj is VisualEffectAsset vfxAsset)
|
||
{
|
||
var window = VFXViewWindow.GetWindow(vfxAsset, false);
|
||
if (window == null)
|
||
{
|
||
window = VFXViewWindow.GetWindow(vfxAsset, true);
|
||
}
|
||
|
||
window.LoadAsset(vfxAsset, null);
|
||
window.Focus();
|
||
return true;
|
||
}
|
||
else if (obj is VisualEffectSubgraph)
|
||
{
|
||
VisualEffectResource resource = VisualEffectResource.GetResourceAtPath(AssetDatabase.GetAssetPath(obj));
|
||
var window = VFXViewWindow.GetWindow(resource, false);
|
||
if (window == null)
|
||
{
|
||
window = VFXViewWindow.GetWindow(resource, true);
|
||
window.LoadResource(resource, null);
|
||
}
|
||
window.Focus();
|
||
return true;
|
||
}
|
||
else if (obj is Material || obj is ComputeShader)
|
||
{
|
||
string path = AssetDatabase.GetAssetPath(instanceID);
|
||
|
||
if (path.EndsWith(VisualEffectResource.Extension))
|
||
{
|
||
var resource = VisualEffectResource.GetResourceAtPath(path);
|
||
if (resource != null)
|
||
{
|
||
int index = resource.GetShaderIndex(obj);
|
||
resource.ShowGeneratedShaderFile(index, line);
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
ReorderableList m_ReorderableList;
|
||
List<IVFXSubRenderer> m_OutputContexts = new List<IVFXSubRenderer>();
|
||
VFXGraph m_CurrentGraph;
|
||
|
||
void OnReorder(ReorderableList list)
|
||
{
|
||
for (int i = 0; i < m_OutputContexts.Count(); ++i)
|
||
{
|
||
m_OutputContexts[i].vfxSystemSortPriority = i;
|
||
}
|
||
|
||
if (VFXViewWindow.GetAllWindows().All(x => x.graphView?.controller?.graph.visualEffectResource.GetInstanceID() != m_CurrentGraph.visualEffectResource.GetInstanceID() || !x.hasFocus))
|
||
{
|
||
// Do we need a compileReporter here?
|
||
AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(m_CurrentGraph.visualEffectResource));
|
||
}
|
||
}
|
||
|
||
private void DrawOutputContextItem(Rect rect, int index, bool isActive, bool isFocused)
|
||
{
|
||
var context = m_OutputContexts[index] as VFXContext;
|
||
|
||
var systemName = context.GetGraph().systemNames.GetUniqueSystemName(context.GetData());
|
||
var contextLetter = context.letter;
|
||
var contextName = string.IsNullOrEmpty(context.label) ? context.name.Replace('\n', ' ') : context.label;
|
||
var fullName = string.Format("{0}{1}/{2}", systemName, contextLetter != '\0' ? "/" + contextLetter : string.Empty, contextName.Replace('\n', ' '));
|
||
|
||
EditorGUI.LabelField(rect, EditorGUIUtility.TempContent(fullName));
|
||
}
|
||
|
||
private void DrawHeader(Rect rect)
|
||
{
|
||
EditorGUI.LabelField(rect, EditorGUIUtility.TrTextContent("Output Render Order"));
|
||
}
|
||
|
||
static Mesh s_CubeWireFrame;
|
||
void OnEnable()
|
||
{
|
||
m_OutputContexts.Clear();
|
||
VisualEffectAsset vfxTarget = target as VisualEffectAsset;
|
||
var resource = vfxTarget.GetResource();
|
||
if (resource != null) //Can be null if VisualEffectAsset is in Asset Bundle
|
||
{
|
||
m_CurrentGraph = resource.GetOrCreateGraph();
|
||
m_CurrentGraph.systemNames.Sync(m_CurrentGraph);
|
||
m_OutputContexts.AddRange(m_CurrentGraph.children.OfType<IVFXSubRenderer>().OrderBy(t => t.vfxSystemSortPriority));
|
||
}
|
||
|
||
if (s_PlayPauseIcons == null)
|
||
{
|
||
s_PlayPauseIcons = new[]
|
||
{
|
||
EditorGUIUtility.TrIconContent("PlayButton", "Animate preview"),
|
||
EditorGUIUtility.TrIconContent("PauseButton", "Pause preview animation"),
|
||
};
|
||
}
|
||
|
||
m_ReorderableList = new ReorderableList(m_OutputContexts, typeof(IVFXSubRenderer));
|
||
m_ReorderableList.displayRemove = false;
|
||
m_ReorderableList.displayAdd = false;
|
||
m_ReorderableList.onReorderCallback = OnReorder;
|
||
m_ReorderableList.drawHeaderCallback = DrawHeader;
|
||
|
||
m_ReorderableList.drawElementCallback = DrawOutputContextItem;
|
||
|
||
var targetResources = targets.Cast<VisualEffectAsset>().Select(t => t.GetResource()).Where(t => t != null).ToArray();
|
||
if (targetResources.Any())
|
||
{
|
||
resourceObject = new SerializedObject(targetResources);
|
||
resourceUpdateModeProperty = resourceObject.FindProperty("m_Infos.m_UpdateMode");
|
||
cullingFlagsProperty = resourceObject.FindProperty("m_Infos.m_CullingFlags");
|
||
motionVectorRenderModeProperty = resourceObject.FindProperty("m_Infos.m_RendererSettings.motionVectorGenerationMode");
|
||
prewarmDeltaTime = resourceObject.FindProperty("m_Infos.m_PreWarmDeltaTime");
|
||
prewarmStepCount = resourceObject.FindProperty("m_Infos.m_PreWarmStepCount");
|
||
initialEventName = resourceObject.FindProperty("m_Infos.m_InitialEventName");
|
||
instancingModeProperty = resourceObject.FindProperty("m_Infos.m_InstancingMode");
|
||
instancingCapacityProperty = resourceObject.FindProperty("m_Infos.m_InstancingCapacity");
|
||
}
|
||
if (targets?.Length > 0)
|
||
{
|
||
targetObject = new SerializedObject(targets);
|
||
instancingDisabledReasonProperty = targetObject.FindProperty("m_Infos.m_InstancingDisabledReason");
|
||
}
|
||
}
|
||
|
||
private void CreateVisualEffect()
|
||
{
|
||
Debug.Assert(m_VisualEffectGO == null);
|
||
|
||
m_PreviewUtility?.Cleanup();
|
||
|
||
m_PreviewUtility = new PreviewRenderUtility();
|
||
m_PreviewUtility.camera.fieldOfView = 60.0f;
|
||
m_PreviewUtility.camera.allowHDR = true;
|
||
m_PreviewUtility.camera.allowMSAA = false;
|
||
m_PreviewUtility.camera.farClipPlane = 10000.0f;
|
||
m_PreviewUtility.camera.clearFlags = CameraClearFlags.SolidColor;
|
||
m_PreviewUtility.ambientColor = new Color(.1f, .1f, .1f, 1.0f);
|
||
m_PreviewUtility.lights[0].intensity = 1.4f;
|
||
m_PreviewUtility.lights[0].transform.rotation = Quaternion.Euler(40f, 40f, 0);
|
||
m_PreviewUtility.lights[1].intensity = 1.4f;
|
||
|
||
m_VisualEffectGO = new GameObject("VisualEffect (Preview)");
|
||
|
||
m_VisualEffectGO.hideFlags = HideFlags.DontSave;
|
||
m_VisualEffect = m_VisualEffectGO.AddComponent<VisualEffect>();
|
||
m_VisualEffect.pause = true;
|
||
m_RemainingFramesToRender = 1;
|
||
m_PreviewUtility.AddManagedGO(m_VisualEffectGO);
|
||
|
||
m_VisualEffectGO.transform.localPosition = Vector3.zero;
|
||
m_VisualEffectGO.transform.localRotation = Quaternion.identity;
|
||
m_VisualEffectGO.transform.localScale = Vector3.one;
|
||
|
||
VisualEffectAsset vfxTarget = target as VisualEffectAsset;
|
||
m_VisualEffect.visualEffectAsset = vfxTarget;
|
||
|
||
m_CurrentBounds = new Bounds(Vector3.zero, Vector3.one);
|
||
m_Distance = null;
|
||
m_Angles = Vector2.zero;
|
||
|
||
if (s_CubeWireFrame == null)
|
||
{
|
||
s_CubeWireFrame = new Mesh();
|
||
|
||
var vertices = new Vector3[]
|
||
{
|
||
new Vector3(-0.5f, -0.5f, -0.5f),
|
||
new Vector3(-0.5f, -0.5f, 0.5f),
|
||
new Vector3(-0.5f, 0.5f, 0.5f),
|
||
new Vector3(-0.5f, 0.5f, -0.5f),
|
||
|
||
new Vector3(0.5f, -0.5f, -0.5f),
|
||
new Vector3(0.5f, -0.5f, 0.5f),
|
||
new Vector3(0.5f, 0.5f, 0.5f),
|
||
new Vector3(0.5f, 0.5f, -0.5f)
|
||
};
|
||
|
||
|
||
var indices = new int[]
|
||
{
|
||
0, 1,
|
||
0, 3,
|
||
0, 4,
|
||
|
||
6, 2,
|
||
6, 5,
|
||
6, 7,
|
||
|
||
1, 2,
|
||
1, 5,
|
||
|
||
3, 7,
|
||
3, 2,
|
||
|
||
4, 5,
|
||
4, 7
|
||
};
|
||
s_CubeWireFrame.vertices = vertices;
|
||
s_CubeWireFrame.SetIndices(indices, MeshTopology.Lines, 0);
|
||
}
|
||
}
|
||
|
||
PreviewRenderUtility m_PreviewUtility;
|
||
|
||
GameObject m_VisualEffectGO;
|
||
VisualEffect m_VisualEffect;
|
||
Vector2 m_Angles;
|
||
float? m_Distance;
|
||
int m_RemainingFramesToRender;
|
||
Bounds m_CurrentBounds;
|
||
|
||
const int kSafeFrame = 2;
|
||
|
||
public override bool HasPreviewGUI()
|
||
{
|
||
return !serializedObject.isEditingMultipleObjects;
|
||
}
|
||
|
||
void ComputeFarNear(float distance)
|
||
{
|
||
if (m_CurrentBounds.size != Vector3.zero)
|
||
{
|
||
float maxBounds = Mathf.Sqrt(m_CurrentBounds.size.x * m_CurrentBounds.size.x + m_CurrentBounds.size.y * m_CurrentBounds.size.y + m_CurrentBounds.size.z * m_CurrentBounds.size.z);
|
||
m_PreviewUtility.camera.farClipPlane = distance + maxBounds * 1.1f;
|
||
m_PreviewUtility.camera.nearClipPlane = Mathf.Max(0.0001f, (distance - maxBounds));
|
||
m_PreviewUtility.camera.nearClipPlane = Mathf.Max(0.0001f, (distance - maxBounds));
|
||
}
|
||
}
|
||
|
||
public override void OnPreviewSettings()
|
||
{
|
||
EditorGUI.BeginChangeCheck();
|
||
int isAnimatedState = m_IsAnimated ? 1 : 0;
|
||
m_IsAnimated = PreviewGUI.CycleButton(isAnimatedState, s_PlayPauseIcons) == 1;
|
||
if (EditorGUI.EndChangeCheck())
|
||
{
|
||
m_VisualEffect.pause = !m_IsAnimated;
|
||
|
||
if (!m_IsAnimated)
|
||
{
|
||
StopRendering();
|
||
}
|
||
}
|
||
|
||
GUI.enabled = m_IsAnimated;
|
||
// Random id=10012 because when set to 0 the button get highlighted by default !?
|
||
if (EditorGUILayout.IconButton(10012, EditorGUIUtility.TrIconContent("Refresh", "Restart VFX"), EditorStyles.toolbarButton, null))
|
||
{
|
||
m_VisualEffect.Reinit();
|
||
}
|
||
GUI.enabled = true;
|
||
}
|
||
|
||
private static GUIContent[] s_PlayPauseIcons;
|
||
private bool m_IsAnimated;
|
||
private Rect m_LastArea;
|
||
|
||
public override void OnInteractivePreviewGUI(Rect r, GUIStyle background)
|
||
{
|
||
if (m_VisualEffectGO == null)
|
||
CreateVisualEffect();
|
||
|
||
bool isRepaint = Event.current.type == EventType.Repaint;
|
||
|
||
Renderer renderer = m_VisualEffectGO.GetComponent<Renderer>();
|
||
if (renderer == null)
|
||
return;
|
||
|
||
if (isRepaint && r != m_LastArea)
|
||
RequestSingleFrame();
|
||
|
||
if (VFXPreviewGUI.TryDrag2D(ref m_Angles, m_LastArea))
|
||
RequestSingleFrame();
|
||
|
||
if (renderer.bounds.size != Vector3.zero)
|
||
{
|
||
m_CurrentBounds = renderer.bounds;
|
||
|
||
//make sure that none of the bounds values are 0
|
||
if (m_CurrentBounds.size.x == 0)
|
||
{
|
||
Vector3 size = m_CurrentBounds.size;
|
||
size.x = (m_CurrentBounds.size.y + m_CurrentBounds.size.z) * 0.1f;
|
||
m_CurrentBounds.size = size;
|
||
}
|
||
|
||
if (m_CurrentBounds.size.y == 0)
|
||
{
|
||
Vector3 size = m_CurrentBounds.size;
|
||
size.y = (m_CurrentBounds.size.x + m_CurrentBounds.size.z) * 0.1f;
|
||
m_CurrentBounds.size = size;
|
||
}
|
||
|
||
if (m_CurrentBounds.size.z == 0)
|
||
{
|
||
Vector3 size = m_CurrentBounds.size;
|
||
size.z = (m_CurrentBounds.size.x + m_CurrentBounds.size.y) * 0.1f;
|
||
m_CurrentBounds.size = size;
|
||
}
|
||
}
|
||
|
||
if (!m_Distance.HasValue && m_RemainingFramesToRender == 1)
|
||
{
|
||
float maxBounds = Mathf.Sqrt(m_CurrentBounds.size.x * m_CurrentBounds.size.x +
|
||
m_CurrentBounds.size.y * m_CurrentBounds.size.y +
|
||
m_CurrentBounds.size.z * m_CurrentBounds.size.z);
|
||
m_Distance = Mathf.Max(0.01f, maxBounds * 1.25f);
|
||
ComputeFarNear(0f);
|
||
}
|
||
else
|
||
{
|
||
ComputeFarNear(m_Distance.GetValueOrDefault(0f));
|
||
}
|
||
|
||
if (Event.current.isScrollWheel)
|
||
{
|
||
m_Distance *= 1 + Event.current.delta.y * .015f;
|
||
RequestSingleFrame();
|
||
}
|
||
|
||
if (m_Mat == null)
|
||
m_Mat = (Material)EditorGUIUtility.LoadRequired("SceneView/HandleLines.mat");
|
||
|
||
if (!isRepaint)
|
||
{
|
||
if (m_RemainingFramesToRender > 0)
|
||
Repaint();
|
||
return;
|
||
}
|
||
|
||
if (r.width > 50 && r.height > 50)
|
||
m_LastArea = r;
|
||
|
||
bool needsRender = m_IsAnimated || m_RemainingFramesToRender > 0;
|
||
|
||
if (needsRender)
|
||
{
|
||
m_RemainingFramesToRender--;
|
||
m_PreviewUtility.BeginPreview(m_LastArea, background);
|
||
|
||
Quaternion rot = Quaternion.Euler(0, m_Angles.x, 0) * Quaternion.Euler(m_Angles.y, 0, 0);
|
||
m_PreviewUtility.camera.transform.position = m_CurrentBounds.center + rot * new Vector3(0, 0, -m_Distance.GetValueOrDefault(0));
|
||
m_PreviewUtility.camera.transform.localRotation = rot;
|
||
if (m_Distance.HasValue)
|
||
m_PreviewUtility.DrawMesh(s_CubeWireFrame, Matrix4x4.TRS(m_CurrentBounds.center, Quaternion.identity, m_CurrentBounds.size), m_Mat, 0);
|
||
m_PreviewUtility.Render(true);
|
||
m_PreviewUtility.EndAndDrawPreview(m_LastArea);
|
||
}
|
||
|
||
if (!m_IsAnimated && m_RemainingFramesToRender == 0)
|
||
StopRendering();
|
||
|
||
if (m_IsAnimated)
|
||
Repaint();
|
||
else
|
||
EditorGUI.DrawPreviewTexture(m_LastArea, m_PreviewUtility.renderTexture);
|
||
}
|
||
|
||
void RequestSingleFrame()
|
||
{
|
||
if (m_RemainingFramesToRender < 0)
|
||
m_RemainingFramesToRender = 1;
|
||
}
|
||
|
||
void StopRendering()
|
||
{
|
||
m_RemainingFramesToRender = -1;
|
||
}
|
||
|
||
Material m_Mat;
|
||
|
||
void OnDisable()
|
||
{
|
||
if (!UnityObject.ReferenceEquals(m_VisualEffectGO, null))
|
||
{
|
||
UnityObject.DestroyImmediate(m_VisualEffectGO);
|
||
}
|
||
if (m_PreviewUtility != null)
|
||
{
|
||
m_PreviewUtility.Cleanup();
|
||
}
|
||
}
|
||
|
||
private static readonly GUIContent[] k_CullingOptionsContents = new GUIContent[]
|
||
{
|
||
EditorGUIUtility.TrTextContent("Recompute bounds and simulate when visible"),
|
||
EditorGUIUtility.TrTextContent("Always recompute bounds, simulate only when visible"),
|
||
EditorGUIUtility.TrTextContent("Always recompute bounds and simulate")
|
||
};
|
||
static readonly VFXCullingFlags[] k_CullingOptionsValue = new VFXCullingFlags[]
|
||
{
|
||
VFXCullingFlags.CullSimulation | VFXCullingFlags.CullBoundsUpdate,
|
||
VFXCullingFlags.CullSimulation,
|
||
VFXCullingFlags.CullNone,
|
||
};
|
||
|
||
private static readonly GUIContent k_InstancingContent = EditorGUIUtility.TrTextContent("Instancing");
|
||
private static readonly GUIContent k_InstancingModeContent = EditorGUIUtility.TrTextContent("Instancing Mode", "Selects how the visual effect will be handled regarding instancing.");
|
||
private static readonly GUIContent k_InstancingCapacityContent = EditorGUIUtility.TrTextContent("Max Batch Capacity", "Max number of instances that can be grouped together in a single batch.");
|
||
|
||
SerializedObject resourceObject;
|
||
SerializedProperty resourceUpdateModeProperty;
|
||
SerializedProperty cullingFlagsProperty;
|
||
SerializedProperty motionVectorRenderModeProperty;
|
||
SerializedProperty prewarmDeltaTime;
|
||
SerializedProperty prewarmStepCount;
|
||
SerializedProperty initialEventName;
|
||
SerializedProperty instancingModeProperty;
|
||
SerializedProperty instancingCapacityProperty;
|
||
SerializedObject targetObject;
|
||
SerializedProperty instancingDisabledReasonProperty;
|
||
|
||
private static readonly float k_MinimalCommonDeltaTime = 1.0f / 800.0f;
|
||
|
||
public static void DisplayPrewarmInspectorGUI(SerializedObject resourceObject, SerializedProperty prewarmDeltaTime, SerializedProperty prewarmStepCount)
|
||
{
|
||
if (!prewarmDeltaTime.hasMultipleDifferentValues && !prewarmStepCount.hasMultipleDifferentValues)
|
||
{
|
||
var currentDeltaTime = prewarmDeltaTime.floatValue;
|
||
var currentStepCount = prewarmStepCount.uintValue;
|
||
var currentTotalTime = currentDeltaTime * currentStepCount;
|
||
EditorGUI.BeginChangeCheck();
|
||
currentTotalTime = EditorGUILayout.FloatField(EditorGUIUtility.TrTextContent("PreWarm Total Time", "Sets the time in seconds to advance the current effect to when it is initially played. "), currentTotalTime);
|
||
if (EditorGUI.EndChangeCheck())
|
||
{
|
||
if (currentStepCount <= 0)
|
||
{
|
||
prewarmStepCount.uintValue = currentStepCount = 1u;
|
||
}
|
||
|
||
currentDeltaTime = currentTotalTime / currentStepCount;
|
||
prewarmDeltaTime.floatValue = currentDeltaTime;
|
||
resourceObject.ApplyModifiedProperties();
|
||
}
|
||
|
||
EditorGUI.BeginChangeCheck();
|
||
currentStepCount = (uint)EditorGUILayout.IntField(EditorGUIUtility.TrTextContent("PreWarm Step Count", "Sets the number of simulation steps the prewarm should be broken down to. "), (int)currentStepCount);
|
||
if (EditorGUI.EndChangeCheck())
|
||
{
|
||
if (currentStepCount <= 0 && currentTotalTime != 0.0f)
|
||
{
|
||
prewarmStepCount.uintValue = currentStepCount = 1;
|
||
}
|
||
|
||
currentDeltaTime = currentTotalTime == 0.0f ? 0.0f : currentTotalTime / currentStepCount;
|
||
prewarmDeltaTime.floatValue = currentDeltaTime;
|
||
prewarmStepCount.uintValue = currentStepCount;
|
||
resourceObject.ApplyModifiedProperties();
|
||
}
|
||
|
||
EditorGUI.BeginChangeCheck();
|
||
currentDeltaTime = EditorGUILayout.FloatField(EditorGUIUtility.TrTextContent("PreWarm Delta Time", "Sets the time in seconds for each step to achieve the desired total prewarm time."), currentDeltaTime);
|
||
if (EditorGUI.EndChangeCheck())
|
||
{
|
||
if (currentDeltaTime < k_MinimalCommonDeltaTime)
|
||
{
|
||
prewarmDeltaTime.floatValue = currentDeltaTime = k_MinimalCommonDeltaTime;
|
||
}
|
||
|
||
if (currentDeltaTime > currentTotalTime)
|
||
{
|
||
currentTotalTime = currentDeltaTime;
|
||
}
|
||
|
||
if (currentTotalTime != 0.0f)
|
||
{
|
||
var candidateStepCount_A = (uint)Mathf.FloorToInt(currentTotalTime / currentDeltaTime);
|
||
var candidateStepCount_B = (uint)Mathf.RoundToInt(currentTotalTime / currentDeltaTime);
|
||
|
||
var totalTime_A = currentDeltaTime * candidateStepCount_A;
|
||
var totalTime_B = currentDeltaTime * candidateStepCount_B;
|
||
|
||
if (Mathf.Abs(totalTime_A - currentTotalTime) < Mathf.Abs(totalTime_B - currentTotalTime))
|
||
{
|
||
currentStepCount = candidateStepCount_A;
|
||
}
|
||
else
|
||
{
|
||
currentStepCount = candidateStepCount_B;
|
||
}
|
||
|
||
prewarmStepCount.uintValue = currentStepCount;
|
||
}
|
||
prewarmDeltaTime.floatValue = currentDeltaTime;
|
||
resourceObject.ApplyModifiedProperties();
|
||
}
|
||
}
|
||
else
|
||
{
|
||
//Multi selection case, can't resolve total time easily
|
||
EditorGUI.BeginChangeCheck();
|
||
EditorGUI.showMixedValue = prewarmStepCount.hasMultipleDifferentValues;
|
||
EditorGUILayout.PropertyField(prewarmStepCount, EditorGUIUtility.TrTextContent("PreWarm Step Count", "Sets the number of simulation steps the prewarm should be broken down to."));
|
||
EditorGUI.showMixedValue = prewarmDeltaTime.hasMultipleDifferentValues;
|
||
EditorGUILayout.PropertyField(prewarmDeltaTime, EditorGUIUtility.TrTextContent("PreWarm Delta Time", "Sets the time in seconds for each step to achieve the desired total prewarm time."));
|
||
if (EditorGUI.EndChangeCheck())
|
||
{
|
||
if (prewarmDeltaTime.floatValue < k_MinimalCommonDeltaTime)
|
||
prewarmDeltaTime.floatValue = k_MinimalCommonDeltaTime;
|
||
resourceObject.ApplyModifiedProperties();
|
||
}
|
||
}
|
||
}
|
||
|
||
public override void OnInspectorGUI()
|
||
{
|
||
resourceObject.Update();
|
||
|
||
GUI.enabled = AssetDatabase.IsOpenForEdit(this.target, StatusQueryOptions.UseCachedIfPossible);
|
||
|
||
VFXUpdateMode initialUpdateMode = (VFXUpdateMode)0;
|
||
bool? initialFixedDeltaTime = null;
|
||
bool? initialProcessEveryFrame = null;
|
||
bool? initialIgnoreGameTimeScale = null;
|
||
if (resourceUpdateModeProperty.hasMultipleDifferentValues)
|
||
{
|
||
var resourceUpdateModeProperties = resourceUpdateModeProperty.serializedObject.targetObjects
|
||
.Select(o => new SerializedObject(o)
|
||
.FindProperty(resourceUpdateModeProperty.propertyPath))
|
||
.ToArray(); //N.B.: This will create garbage
|
||
var allDeltaTime = resourceUpdateModeProperties.Select(o => ((VFXUpdateMode)o.intValue & VFXUpdateMode.DeltaTime) == VFXUpdateMode.DeltaTime)
|
||
.Distinct();
|
||
var allProcessEveryFrame = resourceUpdateModeProperties.Select(o => ((VFXUpdateMode)o.intValue & VFXUpdateMode.ExactFixedTimeStep) == VFXUpdateMode.ExactFixedTimeStep)
|
||
.Distinct();
|
||
var allIgnoreScale = resourceUpdateModeProperties.Select(o => ((VFXUpdateMode)o.intValue & VFXUpdateMode.IgnoreTimeScale) == VFXUpdateMode.IgnoreTimeScale)
|
||
.Distinct();
|
||
if (allDeltaTime.Count() == 1)
|
||
initialFixedDeltaTime = !allDeltaTime.First();
|
||
if (allProcessEveryFrame.Count() == 1)
|
||
initialProcessEveryFrame = allProcessEveryFrame.First();
|
||
if (allIgnoreScale.Count() == 1)
|
||
initialIgnoreGameTimeScale = allIgnoreScale.First();
|
||
}
|
||
else
|
||
{
|
||
initialUpdateMode = (VFXUpdateMode)resourceUpdateModeProperty.intValue;
|
||
initialFixedDeltaTime = !((initialUpdateMode & VFXUpdateMode.DeltaTime) == VFXUpdateMode.DeltaTime);
|
||
initialProcessEveryFrame = (initialUpdateMode & VFXUpdateMode.ExactFixedTimeStep) == VFXUpdateMode.ExactFixedTimeStep;
|
||
initialIgnoreGameTimeScale = (initialUpdateMode & VFXUpdateMode.IgnoreTimeScale) == VFXUpdateMode.IgnoreTimeScale;
|
||
}
|
||
|
||
EditorGUI.showMixedValue = !initialFixedDeltaTime.HasValue;
|
||
var deltaTimeContent = EditorGUIUtility.TrTextContent("Fixed Delta Time", "If enabled, use visual effect manager fixed delta time mode, otherwise, use the default Time.deltaTime.");
|
||
var processEveryFrameContent = EditorGUIUtility.TrTextContent("Exact Fixed Time", "Only relevant when using Fixed Delta Time. When enabled, several updates can be processed per frame (e.g.: if a frame is 10ms and the fixed frame rate is set to 5 ms, the effect will update twice with a 5ms deltaTime instead of once with a 10ms deltaTime). This method is expensive and should only be used for high-end scenarios.");
|
||
var ignoreTimeScaleContent = EditorGUIUtility.TrTextContent("Ignore Time Scale", "When enabled, the computed visual effect delta time ignores the game Time Scale value (Play Rate is still applied).");
|
||
|
||
EditorGUI.BeginChangeCheck();
|
||
|
||
VisualEffectEditor.ShowHeader(EditorGUIUtility.TrTextContent("Update mode"), false, false);
|
||
bool newFixedDeltaTime = EditorGUILayout.Toggle(deltaTimeContent, initialFixedDeltaTime ?? false);
|
||
bool newExactFixedTimeStep = false;
|
||
EditorGUI.showMixedValue = !initialProcessEveryFrame.HasValue;
|
||
EditorGUI.BeginDisabledGroup((!initialFixedDeltaTime.HasValue || !initialFixedDeltaTime.Value) && !resourceUpdateModeProperty.hasMultipleDifferentValues);
|
||
newExactFixedTimeStep = EditorGUILayout.Toggle(processEveryFrameContent, initialProcessEveryFrame ?? false);
|
||
EditorGUI.EndDisabledGroup();
|
||
EditorGUI.showMixedValue = !initialIgnoreGameTimeScale.HasValue;
|
||
bool newIgnoreTimeScale = EditorGUILayout.Toggle(ignoreTimeScaleContent, initialIgnoreGameTimeScale ?? false);
|
||
|
||
if (EditorGUI.EndChangeCheck())
|
||
{
|
||
if (!resourceUpdateModeProperty.hasMultipleDifferentValues)
|
||
{
|
||
var newUpdateMode = (VFXUpdateMode)0;
|
||
if (!newFixedDeltaTime)
|
||
newUpdateMode = newUpdateMode | VFXUpdateMode.DeltaTime;
|
||
if (newExactFixedTimeStep)
|
||
newUpdateMode = newUpdateMode | VFXUpdateMode.ExactFixedTimeStep;
|
||
if (newIgnoreTimeScale)
|
||
newUpdateMode = newUpdateMode | VFXUpdateMode.IgnoreTimeScale;
|
||
|
||
resourceUpdateModeProperty.intValue = (int)newUpdateMode;
|
||
resourceObject.ApplyModifiedProperties();
|
||
}
|
||
else
|
||
{
|
||
var resourceUpdateModeProperties = resourceUpdateModeProperty.serializedObject.targetObjects.Select(o => new SerializedObject(o).FindProperty(resourceUpdateModeProperty.propertyPath));
|
||
foreach (var property in resourceUpdateModeProperties)
|
||
{
|
||
var updateMode = (VFXUpdateMode)property.intValue;
|
||
|
||
if (initialFixedDeltaTime.HasValue)
|
||
{
|
||
if (!newFixedDeltaTime)
|
||
updateMode = updateMode | VFXUpdateMode.DeltaTime;
|
||
else
|
||
updateMode = updateMode & ~VFXUpdateMode.DeltaTime;
|
||
}
|
||
else
|
||
{
|
||
if (newFixedDeltaTime)
|
||
updateMode = updateMode & ~VFXUpdateMode.DeltaTime;
|
||
}
|
||
|
||
if (newExactFixedTimeStep)
|
||
updateMode = updateMode | VFXUpdateMode.ExactFixedTimeStep;
|
||
else if (initialProcessEveryFrame.HasValue)
|
||
updateMode = updateMode & ~VFXUpdateMode.ExactFixedTimeStep;
|
||
|
||
if (newIgnoreTimeScale)
|
||
updateMode = updateMode | VFXUpdateMode.IgnoreTimeScale;
|
||
else if (initialIgnoreGameTimeScale.HasValue)
|
||
updateMode = updateMode & ~VFXUpdateMode.IgnoreTimeScale;
|
||
|
||
property.intValue = (int)updateMode;
|
||
property.serializedObject.ApplyModifiedProperties();
|
||
}
|
||
}
|
||
}
|
||
VisualEffectAsset asset = (VisualEffectAsset)target;
|
||
VisualEffectResource resource = asset.GetResource();
|
||
|
||
//The following should be working, and works for newly created systems, but fails for old systems,
|
||
//due probably to incorrectly pasting the VFXData when creating them.
|
||
// bool hasAutomaticBoundsSystems = resource.GetOrCreateGraph().children
|
||
// .OfType<VFXDataParticle>().Any(d => d.boundsMode == BoundsSettingMode.Automatic);
|
||
|
||
bool hasAutomaticBoundsSystems = resource.GetOrCreateGraph().children
|
||
.OfType<VFXBasicInitialize>()
|
||
.Select(x => x.GetData())
|
||
.OfType<VFXDataParticle>()
|
||
.Any(x => x.boundsMode == BoundsSettingMode.Automatic);
|
||
|
||
using (new EditorGUI.DisabledScope(hasAutomaticBoundsSystems))
|
||
{
|
||
EditorGUILayout.BeginHorizontal();
|
||
EditorGUI.showMixedValue = cullingFlagsProperty.hasMultipleDifferentValues;
|
||
string forceSimulateTooltip = hasAutomaticBoundsSystems
|
||
? " When using systems with Bounds Mode set to Automatic, this has to be set to Always recompute bounds and simulate."
|
||
: "";
|
||
EditorGUILayout.PrefixLabel(EditorGUIUtility.TrTextContent("Culling Flags", "Specifies how the system recomputes its bounds and simulates when off-screen." + forceSimulateTooltip));
|
||
EditorGUI.BeginChangeCheck();
|
||
|
||
int newOption =
|
||
EditorGUILayout.Popup(
|
||
Array.IndexOf(k_CullingOptionsValue, (VFXCullingFlags)cullingFlagsProperty.intValue),
|
||
k_CullingOptionsContents);
|
||
if (EditorGUI.EndChangeCheck())
|
||
{
|
||
cullingFlagsProperty.intValue = (int)k_CullingOptionsValue[newOption];
|
||
resourceObject.ApplyModifiedProperties();
|
||
}
|
||
}
|
||
|
||
EditorGUILayout.EndHorizontal();
|
||
|
||
DrawInstancingGUI();
|
||
|
||
VisualEffectEditor.ShowHeader(EditorGUIUtility.TrTextContent("Initial state"), false, false);
|
||
if (prewarmDeltaTime != null && prewarmStepCount != null)
|
||
{
|
||
DisplayPrewarmInspectorGUI(resourceObject, prewarmDeltaTime, prewarmStepCount);
|
||
}
|
||
|
||
if (initialEventName != null)
|
||
{
|
||
EditorGUI.BeginChangeCheck();
|
||
EditorGUI.showMixedValue = initialEventName.hasMultipleDifferentValues;
|
||
EditorGUILayout.PropertyField(initialEventName, new GUIContent("Initial Event Name", "Sets the name of the event which triggers once the system is activated. Default: ‘OnPlay’."));
|
||
if (EditorGUI.EndChangeCheck())
|
||
{
|
||
resourceObject.ApplyModifiedProperties();
|
||
}
|
||
}
|
||
|
||
if (!serializedObject.isEditingMultipleObjects)
|
||
{
|
||
asset = (VisualEffectAsset)target;
|
||
resource = asset.GetResource();
|
||
|
||
m_OutputContexts.Clear();
|
||
m_OutputContexts.AddRange(resource.GetOrCreateGraph().children.OfType<IVFXSubRenderer>().OrderBy(t => t.vfxSystemSortPriority));
|
||
|
||
m_ReorderableList.DoLayoutList();
|
||
|
||
VisualEffectEditor.ShowHeader(EditorGUIUtility.TrTextContent("Shaders"), false, false);
|
||
|
||
string assetPath = AssetDatabase.GetAssetPath(asset);
|
||
UnityObject[] objects = AssetDatabase.LoadAllAssetsAtPath(assetPath);
|
||
string directory = Path.GetDirectoryName(assetPath) + "/" + VFXExternalShaderProcessor.k_ShaderDirectory + "/" + asset.name + "/";
|
||
|
||
foreach (var obj in objects)
|
||
{
|
||
if (obj is Material || obj is ComputeShader)
|
||
{
|
||
GUILayout.BeginHorizontal();
|
||
Rect r = GUILayoutUtility.GetRect(0, 18, GUILayout.ExpandWidth(true));
|
||
|
||
int buttonsWidth = VFXExternalShaderProcessor.allowExternalization ? 240 : 160;
|
||
|
||
int index = resource.GetShaderIndex(obj);
|
||
|
||
var shader = obj;
|
||
if (obj is Material) // Retrieve the shader from the material
|
||
shader = ((Material)(obj)).shader;
|
||
if (shader == null)
|
||
continue;
|
||
|
||
Rect labelR = r;
|
||
labelR.width -= buttonsWidth;
|
||
GUI.Label(labelR, shader.name.Replace('\n', ' '));
|
||
|
||
if (index >= 0)
|
||
{
|
||
if (VFXExternalShaderProcessor.allowExternalization && index < resource.GetShaderSourceCount())
|
||
{
|
||
string shaderSourceName = resource.GetShaderSourceName(index);
|
||
string externalPath = directory + shaderSourceName;
|
||
|
||
externalPath = directory + shaderSourceName.Replace('/', '_') + VFXExternalShaderProcessor.k_ShaderExt;
|
||
|
||
Rect buttonRect = r;
|
||
buttonRect.xMin = labelR.xMax;
|
||
buttonRect.width = 80;
|
||
labelR.width += 80;
|
||
if (System.IO.File.Exists(externalPath))
|
||
{
|
||
if (GUI.Button(buttonRect, "Reveal External"))
|
||
{
|
||
EditorUtility.RevealInFinder(externalPath);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if (GUI.Button(buttonRect, "Externalize"))
|
||
{
|
||
Directory.CreateDirectory(directory);
|
||
|
||
File.WriteAllText(externalPath, "//" + shaderSourceName + "," + index.ToString() + "\n//Don't delete the previous line or this one\n" + resource.GetShaderSource(index));
|
||
}
|
||
}
|
||
}
|
||
|
||
Rect buttonR = r;
|
||
buttonR.xMin = labelR.xMax;
|
||
buttonR.width = 110;
|
||
labelR.width += 110;
|
||
if (GUI.Button(buttonR, "Show Generated"))
|
||
{
|
||
resource.ShowGeneratedShaderFile(index);
|
||
}
|
||
}
|
||
|
||
Rect selectButtonR = r;
|
||
selectButtonR.xMin = labelR.xMax;
|
||
selectButtonR.width = 50;
|
||
if (GUI.Button(selectButtonR, "Select"))
|
||
{
|
||
Selection.activeObject = shader;
|
||
}
|
||
GUILayout.EndHorizontal();
|
||
}
|
||
}
|
||
}
|
||
GUI.enabled = false;
|
||
}
|
||
|
||
private void DrawInstancingGUI()
|
||
{
|
||
VisualEffectEditor.ShowHeader(k_InstancingContent, false, false);
|
||
|
||
EditorGUI.BeginChangeCheck();
|
||
|
||
VFXInstancingDisabledReason disabledReason = (VFXInstancingDisabledReason)instancingDisabledReasonProperty.intValue;
|
||
bool forceDisabled = disabledReason != VFXInstancingDisabledReason.None;
|
||
if (forceDisabled)
|
||
{
|
||
System.Text.StringBuilder reasonString = new System.Text.StringBuilder("Instancing not available:");
|
||
GetInstancingDisabledReasons(reasonString, disabledReason);
|
||
EditorGUILayout.HelpBox(reasonString.ToString(), MessageType.Info);
|
||
}
|
||
|
||
VFXInstancingMode instancingMode = forceDisabled ? VFXInstancingMode.Disabled : (VFXInstancingMode)instancingModeProperty.intValue;
|
||
EditorGUI.BeginDisabled(forceDisabled);
|
||
instancingMode = (VFXInstancingMode)EditorGUILayout.EnumPopup(k_InstancingModeContent, instancingMode);
|
||
EditorGUI.EndDisabled();
|
||
|
||
int instancingCapacity = instancingCapacityProperty.intValue;
|
||
if (instancingMode == VFXInstancingMode.Custom)
|
||
{
|
||
instancingCapacity = EditorGUILayout.DelayedIntField(k_InstancingCapacityContent, instancingCapacity);
|
||
}
|
||
|
||
if (EditorGUI.EndChangeCheck())
|
||
{
|
||
instancingModeProperty.intValue = (int)instancingMode;
|
||
instancingCapacityProperty.intValue = System.Math.Max(instancingCapacity, 1);
|
||
resourceObject.ApplyModifiedProperties();
|
||
}
|
||
}
|
||
|
||
void GetInstancingDisabledReasons(System.Text.StringBuilder reasonString, VFXInstancingDisabledReason disabledReasonMask)
|
||
{
|
||
if (disabledReasonMask == VFXInstancingDisabledReason.Unknown)
|
||
{
|
||
GetInstancingDisabledReason(reasonString, VFXInstancingDisabledReason.Unknown);
|
||
}
|
||
else
|
||
{
|
||
Enum.GetValues(typeof(VFXInstancingDisabledReason))
|
||
.Cast<VFXInstancingDisabledReason>()
|
||
.Where(x => x != VFXInstancingDisabledReason.None && disabledReasonMask.HasFlag(x))
|
||
.ToList()
|
||
.ForEach(x => GetInstancingDisabledReason(reasonString, x));
|
||
}
|
||
}
|
||
|
||
void GetInstancingDisabledReason(System.Text.StringBuilder reasonString, VFXInstancingDisabledReason disabledReasonFlag)
|
||
{
|
||
reasonString.AppendLine();
|
||
reasonString.Append("- ");
|
||
|
||
Type type = disabledReasonFlag.GetType();
|
||
var memberInfo = type.GetMember(type.GetEnumName(disabledReasonFlag));
|
||
var descriptionAttribute = memberInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false).FirstOrDefault() as DescriptionAttribute;
|
||
reasonString.Append(descriptionAttribute?.Description ?? disabledReasonFlag.ToString());
|
||
}
|
||
}
|
||
|
||
|
||
static class VFXPreviewGUI
|
||
{
|
||
static int sliderHash = "Slider".GetHashCode();
|
||
public static bool TryDrag2D(ref Vector2 scrollPosition, Rect position)
|
||
{
|
||
int id = GUIUtility.GetControlID(sliderHash, FocusType.Passive);
|
||
Event evt = Event.current;
|
||
switch (evt.GetTypeForControl(id))
|
||
{
|
||
case EventType.MouseDown:
|
||
if (position.Contains(evt.mousePosition) && position.width > 50)
|
||
{
|
||
GUIUtility.hotControl = id;
|
||
evt.Use();
|
||
EditorGUIUtility.SetWantsMouseJumping(1);
|
||
}
|
||
return false;
|
||
case EventType.MouseDrag:
|
||
if (GUIUtility.hotControl == id)
|
||
{
|
||
scrollPosition -= -evt.delta * (evt.shift ? 3 : 1) / Mathf.Min(position.width, position.height) * 140.0f;
|
||
scrollPosition.y = Mathf.Clamp(scrollPosition.y, -90, 90);
|
||
evt.Use();
|
||
GUI.changed = true;
|
||
}
|
||
return true;
|
||
case EventType.MouseUp:
|
||
if (GUIUtility.hotControl == id)
|
||
GUIUtility.hotControl = 0;
|
||
EditorGUIUtility.SetWantsMouseJumping(0);
|
||
return false;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
}
|