[unity] Added frustum culling / update mode parameters Update When Invisible and UpdateMode to all Skeleton components. Closes #1595.

This commit is contained in:
Harald Csaszar 2020-08-05 19:24:49 +02:00
parent a67094d642
commit 978b305717
8 changed files with 105 additions and 17 deletions

View File

@ -235,6 +235,7 @@
* `SkeletonMecanim` now provides an additional `Custom MixMode` parameter under `Mecanim Translator`. It is enabled by default in version 3.8 to maintain current behaviour, using the set `Mix Mode` for each Mecanim layer. When disabled, `SkeletonMecanim` will use the recommended `MixMode` according to the layer blend mode. Additional information can be found in the [Mecanim Translator section](http://esotericsoftware.com/spine-unity#Parameters-for-animation-blending-control) on the spine-unity documentation pages.
* Added **SkeletonGraphic Timeline support**. Added supprot for multi-track Timeline preview in the Editor outside of play mode (multi-track scrubbing). See the [Timeline-Extension-UPM-Package](http://esotericsoftware.com/spine-unity#Timeline-Extension-UPM-Package) section of the spine-unity documentation for more information.
* Added support for double-sided lighting at all `SkeletonLit` shaders (including URP and LWRP packages).
* Added frustum culling update mode parameters `Update When Invisible` (Inspector parameter) and `UpdateMode` (available via code) to all Skeleton components. This provides a simple way to disable certain updates when the `Renderer` is no longer visible (outside all cameras, culled in frustum culling). The new `UpdateMode` property allows disabling updates at a finer granularity level than disabling the whole component. Available modes are: `Nothing`, `OnlyAnimationStatus`, `EverythingExceptMesh` and `FullUpdate`.
* **Changes of default values**
* `SkeletonMecanim`'s `Layer Mix Mode` now defaults to `MixMode.MixNext` instead of `MixMode.MixAlways`.

View File

@ -37,7 +37,6 @@ using UnityEditor;
namespace Spine.Unity.Editor {
using Icons = SpineEditorUtilities.Icons;
[InitializeOnLoad]
[CustomEditor(typeof(SkeletonGraphic))]
[CanEditMultipleObjects]
public class SkeletonGraphicInspector : UnityEditor.Editor {
@ -51,7 +50,7 @@ namespace Spine.Unity.Editor {
SerializedProperty material, color;
SerializedProperty skeletonDataAsset, initialSkinName;
SerializedProperty startingAnimation, startingLoop, timeScale, freeze, unscaledTime, tintBlack;
SerializedProperty startingAnimation, startingLoop, timeScale, freeze, updateWhenInvisible, unscaledTime, tintBlack;
SerializedProperty initialFlipX, initialFlipY;
SerializedProperty meshGeneratorSettings;
SerializedProperty allowMultipleCanvasRenderers, separatorSlotNames, enableSeparatorSlots, updateSeparatorPartLocation;
@ -111,6 +110,7 @@ namespace Spine.Unity.Editor {
timeScale = so.FindProperty("timeScale");
unscaledTime = so.FindProperty("unscaledTime");
freeze = so.FindProperty("freeze");
updateWhenInvisible = so.FindProperty("updateWhenInvisible");
meshGeneratorSettings = so.FindProperty("meshGenerator").FindPropertyRelative("settings");
meshGeneratorSettings.isExpanded = SkeletonRendererInspector.advancedFoldout;
@ -233,6 +233,7 @@ namespace Spine.Unity.Editor {
EditorGUILayout.PropertyField(unscaledTime, SpineInspectorUtility.TempContent(unscaledTime.displayName, tooltip: "If checked, this will use Time.unscaledDeltaTime to make this update independent of game Time.timeScale. Instance SkeletonGraphic.timeScale will still be applied."));
EditorGUILayout.Space();
EditorGUILayout.PropertyField(freeze);
EditorGUILayout.PropertyField(updateWhenInvisible);
EditorGUILayout.Space();
SkeletonRendererInspector.SkeletonRootMotionParameter(targets);
EditorGUILayout.Space();

View File

@ -59,7 +59,7 @@ namespace Spine.Unity.Editor {
protected SerializedProperty skeletonDataAsset, initialSkinName;
protected SerializedProperty initialFlipX, initialFlipY;
protected SerializedProperty singleSubmesh, separatorSlotNames, clearStateOnDisable, immutableTriangles, fixDrawOrder;
protected SerializedProperty updateWhenInvisible, singleSubmesh, separatorSlotNames, clearStateOnDisable, immutableTriangles, fixDrawOrder;
protected SerializedProperty normals, tangents, zSpacing, pmaVertexColors, tintBlack; // MeshGenerator settings
protected SerializedProperty maskInteraction;
protected SerializedProperty maskMaterialsNone, maskMaterialsInside, maskMaterialsOutside;
@ -74,7 +74,7 @@ namespace Spine.Unity.Editor {
protected bool deleteOutsideMaskMaterialsQueued = false;
protected GUIContent SkeletonDataAssetLabel, SkeletonUtilityButtonContent;
protected GUIContent PMAVertexColorsLabel, ClearStateOnDisableLabel, ZSpacingLabel, ImmubleTrianglesLabel, TintBlackLabel, SingleSubmeshLabel, FixDrawOrderLabel;
protected GUIContent PMAVertexColorsLabel, ClearStateOnDisableLabel, ZSpacingLabel, ImmubleTrianglesLabel, TintBlackLabel, UpdateWhenInvisibleLabel, SingleSubmeshLabel, FixDrawOrderLabel;
protected GUIContent NormalsLabel, TangentsLabel, MaskInteractionLabel;
protected GUIContent MaskMaterialsHeadingLabel, MaskMaterialsNoneLabel, MaskMaterialsInsideLabel, MaskMaterialsOutsideLabel;
protected GUIContent SetMaterialButtonLabel, ClearMaterialButtonLabel, DeleteMaterialButtonLabel;
@ -119,6 +119,7 @@ namespace Spine.Unity.Editor {
TangentsLabel = new GUIContent("Solve Tangents", "Calculates the tangents per frame. Use this if you are using lit shaders (usually with normal maps) that require vertex tangents.");
TintBlackLabel = new GUIContent("Tint Black (!)", "Adds black tint vertex data to the mesh as UV2 and UV3. Black tinting requires that the shader interpret UV2 and UV3 as black tint colors for this effect to work. You may also use the default [Spine/Skeleton Tint Black] shader.\n\nIf you only need to tint the whole skeleton and not individual parts, the [Spine/Skeleton Tint] shader is recommended for better efficiency and changing/animating the _Black material property via MaterialPropertyBlock.");
SingleSubmeshLabel = new GUIContent("Use Single Submesh", "Simplifies submesh generation by assuming you are only using one Material and need only one submesh. This is will disable multiple materials, render separation, and custom slot materials.");
UpdateWhenInvisibleLabel = new GUIContent("Update When Invisible", "Update mode used when the MeshRenderer becomes invisible. Update mode is automatically reset to UpdateMode.FullUpdate when the mesh becomes visible again.");
FixDrawOrderLabel = new GUIContent("Fix Draw Order", "Applies only when 3+ submeshes are used (2+ materials with alternating order, e.g. \"A B A\"). If true, GPU instancing will be disabled at all materials and MaterialPropertyBlocks are assigned at each material to prevent aggressive batching of submeshes by e.g. the LWRP renderer, leading to incorrect draw order (e.g. \"A1 B A2\" changed to \"A1A2 B\"). You can disable this parameter when everything is drawn correctly to save the additional performance cost. Note: the GPU instancing setting will remain disabled at affected material assets after exiting play mode, you have to enable it manually if you accidentally enabled this parameter.");
MaskInteractionLabel = new GUIContent("Mask Interaction", "SkeletonRenderer's interaction with a Sprite Mask.");
MaskMaterialsHeadingLabel = new GUIContent("Mask Interaction Materials", "Materials used for different interaction with sprite masks.");
@ -140,6 +141,7 @@ namespace Spine.Unity.Editor {
pmaVertexColors = so.FindProperty("pmaVertexColors");
clearStateOnDisable = so.FindProperty("clearStateOnDisable");
tintBlack = so.FindProperty("tintBlack");
updateWhenInvisible = so.FindProperty("updateWhenInvisible");
singleSubmesh = so.FindProperty("singleSubmesh");
fixDrawOrder = so.FindProperty("fixDrawOrder");
maskInteraction = so.FindProperty("maskInteraction");
@ -319,6 +321,8 @@ namespace Spine.Unity.Editor {
EditorGUILayout.LabelField("Renderer Settings", EditorStyles.boldLabel);
using (new SpineInspectorUtility.LabelWidthScope()) {
// Optimization options
if (updateWhenInvisible != null) EditorGUILayout.PropertyField(updateWhenInvisible, UpdateWhenInvisibleLabel);
if (singleSubmesh != null) EditorGUILayout.PropertyField(singleSubmesh, SingleSubmeshLabel);
#if PER_MATERIAL_PROPERTY_BLOCKS
if (fixDrawOrder != null) EditorGUILayout.PropertyField(fixDrawOrder, FixDrawOrderLabel);

View File

@ -185,9 +185,23 @@ namespace Spine.Unity {
if (!valid || state == null)
return;
wasUpdatedAfterInit = true;
if (updateMode < UpdateMode.OnlyAnimationStatus)
return;
UpdateAnimationStatus(deltaTime);
if (updateMode == UpdateMode.OnlyAnimationStatus)
return;
ApplyAnimation();
}
protected void UpdateAnimationStatus (float deltaTime) {
deltaTime *= timeScale;
skeleton.Update(deltaTime);
state.Update(deltaTime);
}
protected void ApplyAnimation () {
state.Apply(skeleton);
if (_UpdateLocal != null)
@ -203,7 +217,6 @@ namespace Spine.Unity {
if (_UpdateComplete != null) {
_UpdateComplete(this);
}
wasUpdatedAfterInit = true;
}
public override void LateUpdate () {

View File

@ -58,6 +58,16 @@ namespace Spine.Unity {
public bool startingLoop;
public float timeScale = 1f;
public bool freeze;
/// <summary>Update mode to optionally limit updates to e.g. only apply animations but not update the mesh.</summary>
public UpdateMode UpdateMode { get { return updateMode; } set { updateMode = value; } }
[SerializeField] protected UpdateMode updateMode = UpdateMode.FullUpdate;
/// <summary>Update mode used when the MeshRenderer becomes invisible
/// (when <c>OnBecameInvisible()</c> is called). Update mode is automatically
/// reset to <c>UpdateMode.FullUpdate</c> when the mesh becomes visible again.</summary>
public UpdateMode updateWhenInvisible = UpdateMode.FullUpdate;
public bool unscaledTime;
public bool allowMultipleCanvasRenderers = false;
public List<CanvasRenderer> canvasRenderers = new List<CanvasRenderer>();
@ -232,12 +242,27 @@ namespace Spine.Unity {
public virtual void Update (float deltaTime) {
if (!this.IsValid) return;
wasUpdatedAfterInit = true;
if (updateMode < UpdateMode.OnlyAnimationStatus)
return;
UpdateAnimationStatus(deltaTime);
if (updateMode == UpdateMode.OnlyAnimationStatus)
return;
ApplyAnimation();
}
protected void UpdateAnimationStatus (float deltaTime) {
deltaTime *= timeScale;
skeleton.Update(deltaTime);
state.Update(deltaTime);
}
protected void ApplyAnimation () {
state.Apply(skeleton);
if (UpdateLocal != null) UpdateLocal(this);
if (UpdateLocal != null)
UpdateLocal(this);
skeleton.UpdateWorldTransform();
@ -246,18 +271,27 @@ namespace Spine.Unity {
skeleton.UpdateWorldTransform();
}
if (UpdateComplete != null) UpdateComplete(this);
wasUpdatedAfterInit = true;
if (UpdateComplete != null)
UpdateComplete(this);
}
public void LateUpdate () {
// instantiation can happen from Update() after this component, leading to a missing Update() call.
if (!wasUpdatedAfterInit) Update(0);
if (freeze) return;
//this.SetVerticesDirty(); // Which is better?
if (updateMode <= UpdateMode.EverythingExceptMesh) return;
UpdateMesh();
}
public void OnBecameVisible () {
updateMode = UpdateMode.FullUpdate;
}
public void OnBecameInvisible () {
updateMode = updateWhenInvisible;
}
public void ReapplySeparatorSlotNames () {
if (!IsValid)
return;

View File

@ -78,14 +78,23 @@ namespace Spine.Unity {
public void Update () {
if (!valid) return;
#if UNITY_EDITOR
wasUpdatedAfterInit = true;
// animation status is kept by Mecanim Animator component
if (updateMode <= UpdateMode.OnlyAnimationStatus)
return;
ApplyAnimation();
}
protected void ApplyAnimation () {
#if UNITY_EDITOR
var translatorAnimator = translator.Animator;
if (translatorAnimator != null && !translatorAnimator.isInitialized)
translatorAnimator.Rebind();
if (Application.isPlaying) {
translator.Apply(skeleton);
} else {
}
else {
if (translatorAnimator != null && translatorAnimator.isInitialized &&
translatorAnimator.isActiveAndEnabled && translatorAnimator.runtimeAnimatorController != null) {
// Note: Rebind is required to prevent warning "Animator is not playing an AnimatorController" with prefabs
@ -93,9 +102,9 @@ namespace Spine.Unity {
translator.Apply(skeleton);
}
}
#else
#else
translator.Apply(skeleton);
#endif
#endif
// UpdateWorldTransform and Bone Callbacks
{
@ -112,7 +121,6 @@ namespace Spine.Unity {
if (_UpdateComplete != null)
_UpdateComplete(this);
}
wasUpdatedAfterInit = true;
}
public override void LateUpdate () {

View File

@ -55,11 +55,11 @@ namespace Spine.Unity {
[RequireComponent(typeof(MeshFilter), typeof(MeshRenderer)), DisallowMultipleComponent]
[HelpURL("http://esotericsoftware.com/spine-unity-rendering")]
public class SkeletonRenderer : MonoBehaviour, ISkeletonComponent, IHasSkeletonDataAsset {
[SerializeField] public SkeletonDataAsset skeletonDataAsset;
public SkeletonDataAsset skeletonDataAsset;
#region Initialization settings
/// <summary>Skin name to use when the Skeleton is initialized.</summary>
[SerializeField] [SpineSkin(defaultAsEmptyString:true)] public string initialSkinName;
[SpineSkin(defaultAsEmptyString:true)] public string initialSkinName;
/// <summary>Enable this parameter when overwriting the Skeleton's skin from an editor script.
/// Otherwise any changes will be overwritten by the next inspector update.</summary>
@ -71,10 +71,20 @@ namespace Spine.Unity {
protected bool editorSkipSkinSync = false;
#endif
/// <summary>Flip X and Y to use when the Skeleton is initialized.</summary>
[SerializeField] public bool initialFlipX, initialFlipY;
public bool initialFlipX, initialFlipY;
#endregion
#region Advanced Render Settings
/// <summary>Update mode to optionally limit updates to e.g. only apply animations but not update the mesh.</summary>
public UpdateMode UpdateMode { get { return updateMode; } set { updateMode = value; } }
[SerializeField] protected UpdateMode updateMode = UpdateMode.FullUpdate;
/// <summary>Update mode used when the MeshRenderer becomes invisible
/// (when <c>OnBecameInvisible()</c> is called). Update mode is automatically
/// reset to <c>UpdateMode.FullUpdate</c> when the mesh becomes visible again.</summary>
public UpdateMode updateWhenInvisible = UpdateMode.FullUpdate;
// Submesh Separation
/// <summary>Slot names used to populate separatorSlots list when the Skeleton is initialized. Changing this after initialization does nothing.</summary>
[UnityEngine.Serialization.FormerlySerializedAs("submeshSeparators")][SerializeField][SpineSlot] protected string[] separatorSlotNames = new string[0];
@ -361,6 +371,8 @@ namespace Spine.Unity {
}
#endif
if (updateMode <= UpdateMode.EverythingExceptMesh) return;
#if SPINE_OPTIONAL_RENDEROVERRIDE
bool doMeshOverride = generateMeshOverride != null;
if ((!meshRenderer.enabled) && !doMeshOverride) return;
@ -477,6 +489,14 @@ namespace Spine.Unity {
OnMeshAndMaterialsUpdated(this);
}
public void OnBecameVisible () {
updateMode = UpdateMode.FullUpdate;
}
public void OnBecameInvisible () {
updateMode = updateWhenInvisible;
}
public void FindAndApplySeparatorSlots (string startsWith, bool clearExistingSeparators = true, bool updateStringArray = false) {
if (string.IsNullOrEmpty(startsWith)) return;

View File

@ -28,6 +28,13 @@
*****************************************************************************/
namespace Spine.Unity {
public enum UpdateMode {
Nothing = 0,
OnlyAnimationStatus,
EverythingExceptMesh,
FullUpdate
};
public delegate void UpdateBonesDelegate (ISkeletonAnimation animated);
/// <summary>A Spine-Unity Component that animates a Skeleton but not necessarily with a Spine.AnimationState.</summary>