feat: mesh sharing group

Particle simulation results are shared within the same group.
A large number of the same effects can be displayed with a small load.
pull/225/head
mob-sakai 2022-06-11 23:10:17 +09:00
parent d8e96e69a6
commit 9afeebf672
4 changed files with 186 additions and 23 deletions

View File

@ -58,6 +58,8 @@ namespace Coffee.UIExtensions
#endif #endif
private SerializedProperty m_Scale3D; private SerializedProperty m_Scale3D;
private SerializedProperty m_AnimatableProperties; private SerializedProperty m_AnimatableProperties;
private SerializedProperty m_MeshSharing;
private SerializedProperty m_GroupId;
private ReorderableList _ro; private ReorderableList _ro;
static private bool _xyzMode; static private bool _xyzMode;
@ -133,6 +135,8 @@ namespace Coffee.UIExtensions
m_Maskable = serializedObject.FindProperty("m_Maskable"); m_Maskable = serializedObject.FindProperty("m_Maskable");
m_Scale3D = serializedObject.FindProperty("m_Scale3D"); m_Scale3D = serializedObject.FindProperty("m_Scale3D");
m_AnimatableProperties = serializedObject.FindProperty("m_AnimatableProperties"); m_AnimatableProperties = serializedObject.FindProperty("m_AnimatableProperties");
m_MeshSharing = serializedObject.FindProperty("m_MeshSharing");
m_GroupId = serializedObject.FindProperty("m_GroupId");
var sp = serializedObject.FindProperty("m_Particles"); var sp = serializedObject.FindProperty("m_Particles");
_ro = new ReorderableList(sp.serializedObject, sp, true, true, true, true); _ro = new ReorderableList(sp.serializedObject, sp, true, true, true, true);
@ -213,7 +217,9 @@ namespace Coffee.UIExtensions
EditorGUILayout.PropertyField(m_Maskable); EditorGUILayout.PropertyField(m_Maskable);
// Scale // Scale
EditorGUI.BeginDisabledGroup(!m_MeshSharing.hasMultipleDifferentValues && m_MeshSharing.intValue == 4);
_xyzMode = DrawFloatOrVector3Field(m_Scale3D, _xyzMode); _xyzMode = DrawFloatOrVector3Field(m_Scale3D, _xyzMode);
EditorGUI.EndDisabledGroup();
// AnimatableProperties // AnimatableProperties
var mats = current.particles var mats = current.particles
@ -225,6 +231,9 @@ namespace Coffee.UIExtensions
// Animated properties // Animated properties
AnimatedPropertiesEditor.DrawAnimatableProperties(m_AnimatableProperties, mats); AnimatedPropertiesEditor.DrawAnimatableProperties(m_AnimatableProperties, mats);
// Mesh sharing
DrawMeshSharing();
// Target ParticleSystems. // Target ParticleSystems.
_ro.DoLayoutList(); _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) private static void WindowFunction(UnityEngine.Object target, SceneView sceneView)
{ {
try try

View File

@ -20,6 +20,15 @@ namespace Coffee.UIExtensions
[RequireComponent(typeof(CanvasRenderer))] [RequireComponent(typeof(CanvasRenderer))]
public class UIParticle : MaskableGraphic public class UIParticle : MaskableGraphic
{ {
public enum MeshSharing
{
None,
Auto,
Primary,
PrimarySimulator,
Reprica,
}
[HideInInspector][SerializeField] internal bool m_IsTrail = false; [HideInInspector][SerializeField] internal bool m_IsTrail = false;
[Tooltip("Particle effect scale")] [Tooltip("Particle effect scale")]
@ -34,6 +43,14 @@ namespace Coffee.UIExtensions
[SerializeField] [SerializeField]
private List<ParticleSystem> m_Particles = new List<ParticleSystem>(); private List<ParticleSystem> m_Particles = new List<ParticleSystem>();
[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<UIParticleRenderer> m_Renderers = new List<UIParticleRenderer>(); private List<UIParticleRenderer> m_Renderers = new List<UIParticleRenderer>();
#if !SERIALIZE_FIELD_MASKABLE #if !SERIALIZE_FIELD_MASKABLE
@ -52,6 +69,48 @@ namespace Coffee.UIExtensions
set { } set { }
} }
/// <summary>
/// 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.
/// </summary>
public MeshSharing meshSharing
{
get { return m_MeshSharing; }
set { m_MeshSharing = value; }
}
/// <summary>
/// Mesh sharing group ID. If non-zero is specified, particle simulation results are shared within the group.
/// </summary>
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; }
}
/// <summary> /// <summary>
/// Particle effect scale. /// Particle effect scale.
/// </summary> /// </summary>

View File

@ -17,6 +17,7 @@ namespace Coffee.UIExtensions
private static ParticleSystem.Particle[] s_Particles = new ParticleSystem.Particle[2048]; private static ParticleSystem.Particle[] s_Particles = new ParticleSystem.Particle[2048];
private static readonly List<Material> s_Materials = new List<Material>(2); private static readonly List<Material> s_Materials = new List<Material>(2);
private static MaterialPropertyBlock s_Mpb; private static MaterialPropertyBlock s_Mpb;
private static readonly List<UIParticleRenderer> s_Renderers = new List<UIParticleRenderer>();
private ParticleSystemRenderer _renderer; private ParticleSystemRenderer _renderer;
private ParticleSystem _particleSystem; private ParticleSystem _particleSystem;
@ -30,6 +31,7 @@ namespace Coffee.UIExtensions
private Vector2Int _prevScreenSize; private Vector2Int _prevScreenSize;
private bool _delay = false; private bool _delay = false;
private bool _prewarm = false; private bool _prewarm = false;
private Material _currentMaterialForRendering;
public override Texture mainTexture public override Texture mainTexture
{ {
@ -76,6 +78,8 @@ namespace Coffee.UIExtensions
/// </summary> /// </summary>
public override Material GetModifiedMaterial(Material baseMaterial) public override Material GetModifiedMaterial(Material baseMaterial)
{ {
_currentMaterialForRendering = null;
if (!IsActive()) return baseMaterial; if (!IsActive()) return baseMaterial;
var modifiedMaterial = base.GetModifiedMaterial(baseMaterial); var modifiedMaterial = base.GetModifiedMaterial(baseMaterial);
@ -164,6 +168,7 @@ namespace Coffee.UIExtensions
// No particle to render: Clear mesh. // No particle to render: Clear mesh.
if ( if (
!enabled || !_particleSystem || !_parent || !canvasRenderer || !canvas || !bakeCamera !enabled || !_particleSystem || !_parent || !canvasRenderer || !canvas || !bakeCamera
|| _parent.meshSharing == UIParticle.MeshSharing.Reprica
|| !transform.lossyScale.GetScaled(_parent.scale3D).IsVisible() // Scale is not visible. || !transform.lossyScale.GetScaled(_parent.scale3D).IsVisible() // Scale is not visible.
|| (!_particleSystem.IsAlive() && !_particleSystem.isPlaying) // No particle. || (!_particleSystem.IsAlive() && !_particleSystem.isPlaying) // No particle.
|| (_isTrail && !_particleSystem.trails.enabled) // Trail, but it is not enabled. || (_isTrail && !_particleSystem.trails.enabled) // Trail, but it is not enabled.
@ -185,9 +190,9 @@ namespace Coffee.UIExtensions
var psPos = _particleSystem.transform.position; var psPos = _particleSystem.transform.position;
// Simulate particles. // 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 UNITY_EDITOR
if (!Application.isPlaying) if (!Application.isPlaying)
{ {
@ -210,32 +215,31 @@ namespace Coffee.UIExtensions
_particleSystem.Stop(false); _particleSystem.Stop(false);
} }
} }
Profiler.EndSample();
_prevScale = scale; _prevScale = scale;
_prevPsPos = psPos; _prevPsPos = psPos;
_delay = false; _delay = false;
} }
Profiler.EndSample();
// Bake mesh. // Bake mesh.
Profiler.BeginSample("[UIParticleRenderer] Bake Mesh"); Profiler.BeginSample("[UIParticleRenderer] Bake Mesh");
if (_isTrail && _parent.canSimulate)
{ {
if (_isTrail) _renderer.BakeTrailsMesh(s_CombineInstances[0].mesh, bakeCamera, true);
{ }
_renderer.BakeTrailsMesh(s_CombineInstances[0].mesh, bakeCamera, true); else if (_renderer.CanBakeMesh())
} {
else if (_renderer.CanBakeMesh()) _renderer.BakeMesh(s_CombineInstances[0].mesh, bakeCamera, true);
{ }
_renderer.BakeMesh(s_CombineInstances[0].mesh, bakeCamera, true); else
} {
else s_CombineInstances[0].mesh.Clear();
{
s_CombineInstances[0].mesh.Clear();
}
} }
Profiler.EndSample(); Profiler.EndSample();
// Combine mesh to transform. ([ParticleSystem local ->] world -> renderer local) // Combine mesh to transform. ([ParticleSystem local ->] world -> renderer local)
Profiler.BeginSample("[UIParticleRenderer] Combine Mesh"); Profiler.BeginSample("[UIParticleRenderer] Combine Mesh");
if (_parent.canSimulate)
{ {
s_CombineInstances[0].transform = canvasRenderer.transform.worldToLocalMatrix * GetWorldMatrix(psPos, scale); s_CombineInstances[0].transform = canvasRenderer.transform.worldToLocalMatrix * GetWorldMatrix(psPos, scale);
workerMesh.CombineMeshes(s_CombineInstances, true, true); workerMesh.CombineMeshes(s_CombineInstances, true, true);
@ -252,15 +256,49 @@ namespace Coffee.UIExtensions
} }
Profiler.EndSample(); Profiler.EndSample();
// Get grouped renderers.
s_Renderers.Clear();
if (_parent.useMeshSharing)
{
UIParticleUpdater.GetGroupedRenderers(_parent.groupId, _index, s_Renderers);
}
// Set mesh to the CanvasRenderer. // Set mesh to the CanvasRenderer.
Profiler.BeginSample("[UIParticleRenderer] Set Mesh"); 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); canvasRenderer.SetMesh(workerMesh);
Profiler.EndSample(); Profiler.EndSample();
// Update animatable material properties. // Update animatable material properties.
Profiler.BeginSample("[UIParticleRenderer] Update Animatable Material Properties"); Profiler.BeginSample("[UIParticleRenderer] Update Animatable Material Properties");
UpdateMaterialProperties(); 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(); Profiler.EndSample();
s_Renderers.Clear();
} }
protected override void OnEnable() protected override void OnEnable()
@ -275,6 +313,7 @@ namespace Coffee.UIExtensions
hideFlags = HideFlags.HideAndDontSave, hideFlags = HideFlags.HideAndDontSave,
}; };
} }
_currentMaterialForRendering = null;
} }
protected override void OnDisable() protected override void OnDisable()
@ -283,6 +322,7 @@ namespace Coffee.UIExtensions
ModifiedMaterial.Remove(_modifiedMaterial); ModifiedMaterial.Remove(_modifiedMaterial);
_modifiedMaterial = null; _modifiedMaterial = null;
_currentMaterialForRendering = null;
} }
/// <summary> /// <summary>

View File

@ -1,13 +1,12 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using UnityEngine; using UnityEngine;
using UnityEngine.Profiling;
namespace Coffee.UIExtensions namespace Coffee.UIExtensions
{ {
internal static class UIParticleUpdater internal static class UIParticleUpdater
{ {
static readonly List<UIParticle> s_ActiveParticles = new List<UIParticle>(); static readonly List<UIParticle> s_ActiveParticles = new List<UIParticle>();
static readonly HashSet<int> s_UpdatedGroupIds = new HashSet<int>();
private static int frameCount = 0; private static int frameCount = 0;
public static int uiParticleCount public static int uiParticleCount
@ -46,21 +45,61 @@ namespace Coffee.UIExtensions
if (frameCount == Time.frameCount) return; if (frameCount == Time.frameCount) return;
frameCount = Time.frameCount; frameCount = Time.frameCount;
Profiler.BeginSample("[UIParticle] Refresh"); // Simulate -> Primary
for (var i = 0; i < s_ActiveParticles.Count; i++) for (var i = 0; i < s_ActiveParticles.Count; i++)
{ {
var uip = s_ActiveParticles[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(); 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<UIParticleRenderer> 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;
} }
} }
} }