diff --git a/Scripts/Editor/UIParticleEditor.cs b/Scripts/Editor/UIParticleEditor.cs index c753dea..d410ea0 100644 --- a/Scripts/Editor/UIParticleEditor.cs +++ b/Scripts/Editor/UIParticleEditor.cs @@ -58,6 +58,8 @@ namespace Coffee.UIExtensions #endif private SerializedProperty m_Scale3D; private SerializedProperty m_AnimatableProperties; + private SerializedProperty m_MeshSharing; + private SerializedProperty m_GroupId; private ReorderableList _ro; static private bool _xyzMode; @@ -133,6 +135,8 @@ namespace Coffee.UIExtensions m_Maskable = serializedObject.FindProperty("m_Maskable"); m_Scale3D = serializedObject.FindProperty("m_Scale3D"); m_AnimatableProperties = serializedObject.FindProperty("m_AnimatableProperties"); + m_MeshSharing = serializedObject.FindProperty("m_MeshSharing"); + m_GroupId = serializedObject.FindProperty("m_GroupId"); var sp = serializedObject.FindProperty("m_Particles"); _ro = new ReorderableList(sp.serializedObject, sp, true, true, true, true); @@ -213,7 +217,9 @@ namespace Coffee.UIExtensions EditorGUILayout.PropertyField(m_Maskable); // Scale + EditorGUI.BeginDisabledGroup(!m_MeshSharing.hasMultipleDifferentValues && m_MeshSharing.intValue == 4); _xyzMode = DrawFloatOrVector3Field(m_Scale3D, _xyzMode); + EditorGUI.EndDisabledGroup(); // AnimatableProperties var mats = current.particles @@ -225,6 +231,9 @@ namespace Coffee.UIExtensions // Animated properties AnimatedPropertiesEditor.DrawAnimatableProperties(m_AnimatableProperties, mats); + // Mesh sharing + DrawMeshSharing(); + // Target ParticleSystems. _ro.DoLayoutList(); @@ -272,6 +281,22 @@ namespace Coffee.UIExtensions } } + private void DrawMeshSharing() + { + EditorGUILayout.PropertyField(m_MeshSharing); + EditorGUI.BeginDisabledGroup(m_MeshSharing.intValue == 0); + EditorGUI.indentLevel++; + EditorGUILayout.PropertyField(m_GroupId); + if (m_MeshSharing.intValue == 1 || m_MeshSharing.intValue == 4) + { + EditorGUI.BeginDisabledGroup(true); + EditorGUILayout.ObjectField("Primary", UIParticleUpdater.GetPrimary(m_GroupId.intValue), typeof(UIParticle), false); + EditorGUI.EndDisabledGroup(); + } + EditorGUI.indentLevel--; + EditorGUI.EndDisabledGroup(); + } + private static void WindowFunction(UnityEngine.Object target, SceneView sceneView) { try diff --git a/Scripts/UIParticle.cs b/Scripts/UIParticle.cs index 38a3adb..30ff9e5 100644 --- a/Scripts/UIParticle.cs +++ b/Scripts/UIParticle.cs @@ -20,6 +20,15 @@ namespace Coffee.UIExtensions [RequireComponent(typeof(CanvasRenderer))] public class UIParticle : MaskableGraphic { + public enum MeshSharing + { + None, + Auto, + Primary, + PrimarySimulator, + Reprica, + } + [HideInInspector][SerializeField] internal bool m_IsTrail = false; [Tooltip("Particle effect scale")] @@ -34,6 +43,14 @@ namespace Coffee.UIExtensions [SerializeField] private List m_Particles = new List(); + [Tooltip("Mesh sharing.None: disable mesh sharing.\nAuto: automatically select Primary/Reprica.\nPrimary: provides particle simulation results to the same group.\nPrimary Simulator: Primary, but do not render the particle (simulation only).\nReprica: render simulation results provided by the primary.")] + [SerializeField] + private MeshSharing m_MeshSharing = MeshSharing.None; + + [Tooltip("Mesh sharing group ID. If non-zero is specified, particle simulation results are shared within the group.")] + [SerializeField] + private int m_GroupId = 0; + private List m_Renderers = new List(); #if !SERIALIZE_FIELD_MASKABLE @@ -52,6 +69,48 @@ namespace Coffee.UIExtensions set { } } + /// + /// Mesh sharing.None: disable mesh sharing. + /// Auto: automatically select Primary/Reprica. + /// Primary: provides particle simulation results to the same group. + /// Primary Simulator: Primary, but do not render the particle (simulation only). + /// Reprica: render simulation results provided by the primary. + /// + public MeshSharing meshSharing + { + get { return m_MeshSharing; } + set { m_MeshSharing = value; } + } + + /// + /// Mesh sharing group ID. If non-zero is specified, particle simulation results are shared within the group. + /// + public int groupId + { + get { return m_GroupId; } + set { m_GroupId = value; } + } + + internal bool useMeshSharing + { + get { return m_MeshSharing != MeshSharing.None; } + } + + internal bool isPrimary + { + get { return m_MeshSharing == MeshSharing.Primary || m_MeshSharing == MeshSharing.PrimarySimulator; } + } + + internal bool canSimulate + { + get { return m_MeshSharing == MeshSharing.None || m_MeshSharing == MeshSharing.Auto || m_MeshSharing == MeshSharing.Primary || m_MeshSharing == MeshSharing.PrimarySimulator; } + } + + internal bool canRender + { + get { return m_MeshSharing == MeshSharing.None || m_MeshSharing == MeshSharing.Auto || m_MeshSharing == MeshSharing.Primary || m_MeshSharing == MeshSharing.Reprica; } + } + /// /// Particle effect scale. /// diff --git a/Scripts/UIParticleRenderer.cs b/Scripts/UIParticleRenderer.cs index 1ef0831..e2d5f47 100644 --- a/Scripts/UIParticleRenderer.cs +++ b/Scripts/UIParticleRenderer.cs @@ -17,6 +17,7 @@ namespace Coffee.UIExtensions private static ParticleSystem.Particle[] s_Particles = new ParticleSystem.Particle[2048]; private static readonly List s_Materials = new List(2); private static MaterialPropertyBlock s_Mpb; + private static readonly List s_Renderers = new List(); private ParticleSystemRenderer _renderer; private ParticleSystem _particleSystem; @@ -30,6 +31,7 @@ namespace Coffee.UIExtensions private Vector2Int _prevScreenSize; private bool _delay = false; private bool _prewarm = false; + private Material _currentMaterialForRendering; public override Texture mainTexture { @@ -76,6 +78,8 @@ namespace Coffee.UIExtensions /// public override Material GetModifiedMaterial(Material baseMaterial) { + _currentMaterialForRendering = null; + if (!IsActive()) return baseMaterial; var modifiedMaterial = base.GetModifiedMaterial(baseMaterial); @@ -164,6 +168,7 @@ namespace Coffee.UIExtensions // No particle to render: Clear mesh. if ( !enabled || !_particleSystem || !_parent || !canvasRenderer || !canvas || !bakeCamera + || _parent.meshSharing == UIParticle.MeshSharing.Reprica || !transform.lossyScale.GetScaled(_parent.scale3D).IsVisible() // Scale is not visible. || (!_particleSystem.IsAlive() && !_particleSystem.isPlaying) // No particle. || (_isTrail && !_particleSystem.trails.enabled) // Trail, but it is not enabled. @@ -185,9 +190,9 @@ namespace Coffee.UIExtensions var psPos = _particleSystem.transform.position; // Simulate particles. - if (!_isTrail) + Profiler.BeginSample("[UIParticle] Bake Mesh > Simulate Particles"); + if (!_isTrail && _parent.canSimulate) { - Profiler.BeginSample("[UIParticle] Bake Mesh > Simulate Particles"); #if UNITY_EDITOR if (!Application.isPlaying) { @@ -210,32 +215,31 @@ namespace Coffee.UIExtensions _particleSystem.Stop(false); } } - Profiler.EndSample(); _prevScale = scale; _prevPsPos = psPos; _delay = false; } + Profiler.EndSample(); // Bake mesh. Profiler.BeginSample("[UIParticleRenderer] Bake Mesh"); + if (_isTrail && _parent.canSimulate) { - if (_isTrail) - { - _renderer.BakeTrailsMesh(s_CombineInstances[0].mesh, bakeCamera, true); - } - else if (_renderer.CanBakeMesh()) - { - _renderer.BakeMesh(s_CombineInstances[0].mesh, bakeCamera, true); - } - else - { - s_CombineInstances[0].mesh.Clear(); - } + _renderer.BakeTrailsMesh(s_CombineInstances[0].mesh, bakeCamera, true); + } + else if (_renderer.CanBakeMesh()) + { + _renderer.BakeMesh(s_CombineInstances[0].mesh, bakeCamera, true); + } + else + { + s_CombineInstances[0].mesh.Clear(); } Profiler.EndSample(); // Combine mesh to transform. ([ParticleSystem local ->] world -> renderer local) Profiler.BeginSample("[UIParticleRenderer] Combine Mesh"); + if (_parent.canSimulate) { s_CombineInstances[0].transform = canvasRenderer.transform.worldToLocalMatrix * GetWorldMatrix(psPos, scale); workerMesh.CombineMeshes(s_CombineInstances, true, true); @@ -252,15 +256,49 @@ namespace Coffee.UIExtensions } Profiler.EndSample(); + + // Get grouped renderers. + s_Renderers.Clear(); + if (_parent.useMeshSharing) + { + UIParticleUpdater.GetGroupedRenderers(_parent.groupId, _index, s_Renderers); + } + // Set mesh to the CanvasRenderer. Profiler.BeginSample("[UIParticleRenderer] Set Mesh"); + for (int i = 0; i < s_Renderers.Count; i++) + { + if (s_Renderers[i] == this) continue; + s_Renderers[i].canvasRenderer.SetMesh(workerMesh); + } + + if (!_parent.canRender) + { + workerMesh.Clear(); + } canvasRenderer.SetMesh(workerMesh); Profiler.EndSample(); // Update animatable material properties. Profiler.BeginSample("[UIParticleRenderer] Update Animatable Material Properties"); UpdateMaterialProperties(); + if (!_parent.useMeshSharing) + { + if (!_currentMaterialForRendering) + { + _currentMaterialForRendering = materialForRendering; + } + for (int i = 0; i < s_Renderers.Count; i++) + { + if (s_Renderers[i] == this) continue; + + s_Renderers[i].canvasRenderer.materialCount = 1; + s_Renderers[i].canvasRenderer.SetMaterial(_currentMaterialForRendering, 0); + } + } Profiler.EndSample(); + + s_Renderers.Clear(); } protected override void OnEnable() @@ -275,6 +313,7 @@ namespace Coffee.UIExtensions hideFlags = HideFlags.HideAndDontSave, }; } + _currentMaterialForRendering = null; } protected override void OnDisable() @@ -283,6 +322,7 @@ namespace Coffee.UIExtensions ModifiedMaterial.Remove(_modifiedMaterial); _modifiedMaterial = null; + _currentMaterialForRendering = null; } /// diff --git a/Scripts/UIParticleUpdater.cs b/Scripts/UIParticleUpdater.cs index 306df07..8472d69 100644 --- a/Scripts/UIParticleUpdater.cs +++ b/Scripts/UIParticleUpdater.cs @@ -1,13 +1,12 @@ -using System; using System.Collections.Generic; using UnityEngine; -using UnityEngine.Profiling; namespace Coffee.UIExtensions { internal static class UIParticleUpdater { static readonly List s_ActiveParticles = new List(); + static readonly HashSet s_UpdatedGroupIds = new HashSet(); private static int frameCount = 0; public static int uiParticleCount @@ -46,21 +45,61 @@ namespace Coffee.UIExtensions if (frameCount == Time.frameCount) return; frameCount = Time.frameCount; - Profiler.BeginSample("[UIParticle] Refresh"); + // Simulate -> Primary for (var i = 0; i < s_ActiveParticles.Count; i++) { var uip = s_ActiveParticles[i]; - try + if (!uip.isPrimary || s_UpdatedGroupIds.Contains(uip.groupId)) continue; + + s_UpdatedGroupIds.Add(uip.groupId); + uip.UpdateTransformScale(); + uip.UpdateRenderers(); + } + + // Simulate -> Others + for (var i = 0; i < s_ActiveParticles.Count; i++) + { + var uip = s_ActiveParticles[i]; + uip.UpdateTransformScale(); + + if (!uip.useMeshSharing) { - uip.UpdateTransformScale(); uip.UpdateRenderers(); } - catch (Exception e) + else if (!s_UpdatedGroupIds.Contains(uip.groupId)) { - Debug.LogException(e); + s_UpdatedGroupIds.Add(uip.groupId); + uip.UpdateRenderers(); } } - Profiler.EndSample(); + + s_UpdatedGroupIds.Clear(); + } + + public static void GetGroupedRenderers(int groupId, int index, List results) + { + results.Clear(); + for (var i = 0; i < s_ActiveParticles.Count; i++) + { + var uip = s_ActiveParticles[i]; + if (uip.useMeshSharing && uip.groupId == groupId) + { + results.Add(uip.GetRenderer(index)); + } + } + } + + internal static UIParticle GetPrimary(int groupId) + { + UIParticle primary = null; + for (var i = 0; i < s_ActiveParticles.Count; i++) + { + var uip = s_ActiveParticles[i]; + if (!uip.useMeshSharing || uip.groupId != groupId) continue; + if (uip.isPrimary) return uip; + if (!primary && uip.canSimulate) primary = uip; + } + return primary; } } }