SpineShaderWithOutlineGUI.cs 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. /******************************************************************************
  2. * Spine Runtimes License Agreement
  3. * Last updated January 1, 2020. Replaces all prior versions.
  4. *
  5. * Copyright (c) 2013-2020, Esoteric Software LLC
  6. *
  7. * Integration of the Spine Runtimes into software or otherwise creating
  8. * derivative works of the Spine Runtimes is permitted under the terms and
  9. * conditions of Section 2 of the Spine Editor License Agreement:
  10. * http://esotericsoftware.com/spine-editor-license
  11. *
  12. * Otherwise, it is permitted to integrate the Spine Runtimes into software
  13. * or otherwise create derivative works of the Spine Runtimes (collectively,
  14. * "Products"), provided that each user of the Products must obtain their own
  15. * Spine Editor license and redistribution of the Products in any form must
  16. * include this license and copyright notice.
  17. *
  18. * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
  19. * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  20. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  21. * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
  22. * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  23. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
  24. * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
  25. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  26. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  27. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  28. *****************************************************************************/
  29. using Spine.Unity;
  30. using UnityEditor;
  31. using UnityEngine;
  32. using SpineInspectorUtility = Spine.Unity.Editor.SpineInspectorUtility;
  33. public class SpineShaderWithOutlineGUI : ShaderGUI {
  34. protected MaterialEditor _materialEditor;
  35. bool _showAdvancedOutlineSettings = false;
  36. bool _showStencilSettings = false;
  37. MaterialProperty _OutlineWidth = null;
  38. MaterialProperty _OutlineColor = null;
  39. MaterialProperty _OutlineReferenceTexWidth = null;
  40. MaterialProperty _ThresholdEnd = null;
  41. MaterialProperty _OutlineSmoothness = null;
  42. MaterialProperty _Use8Neighbourhood = null;
  43. MaterialProperty _OutlineMipLevel = null;
  44. MaterialProperty _StencilComp = null;
  45. MaterialProperty _StencilRef = null;
  46. static GUIContent _EnableOutlineText = new GUIContent("Outline", "Enable outline rendering. Draws an outline by sampling 4 or 8 neighbourhood pixels at a given distance specified via 'Outline Width'.");
  47. static GUIContent _OutlineWidthText = new GUIContent("Outline Width", "");
  48. static GUIContent _OutlineColorText = new GUIContent("Outline Color", "");
  49. static GUIContent _OutlineReferenceTexWidthText = new GUIContent("Reference Texture Width", "");
  50. static GUIContent _ThresholdEndText = new GUIContent("Outline Threshold", "");
  51. static GUIContent _OutlineSmoothnessText = new GUIContent("Outline Smoothness", "");
  52. static GUIContent _Use8NeighbourhoodText = new GUIContent("Sample 8 Neighbours", "");
  53. static GUIContent _OutlineMipLevelText = new GUIContent("Outline Mip Level", "");
  54. static GUIContent _StencilCompText = new GUIContent("Stencil Comparison", "");
  55. static GUIContent _StencilRefText = new GUIContent("Stencil Reference", "");
  56. static GUIContent _OutlineAdvancedText = new GUIContent("Advanced", "");
  57. static GUIContent _ShowStencilText = new GUIContent("Stencil", "Stencil parameters for mask interaction.");
  58. protected const string ShaderOutlineNamePrefix = "Spine/Outline/";
  59. protected const string ShaderNormalNamePrefix = "Spine/";
  60. protected const string ShaderWithoutStandardVariantSuffix = "OutlineOnly";
  61. #region ShaderGUI
  62. public override void OnGUI (MaterialEditor materialEditor, MaterialProperty[] properties) {
  63. FindProperties(properties); // MaterialProperties can be animated so we do not cache them but fetch them every event to ensure animated values are updated correctly
  64. _materialEditor = materialEditor;
  65. base.OnGUI(materialEditor, properties);
  66. EditorGUILayout.Space();
  67. RenderStencilProperties();
  68. EditorGUILayout.Space();
  69. RenderOutlineProperties();
  70. }
  71. #endregion
  72. #region Virtual Interface
  73. protected virtual void FindProperties (MaterialProperty[] props) {
  74. _OutlineWidth = FindProperty("_OutlineWidth", props, false);
  75. _OutlineReferenceTexWidth = FindProperty("_OutlineReferenceTexWidth", props, false);
  76. _OutlineColor = FindProperty("_OutlineColor", props, false);
  77. _ThresholdEnd = FindProperty("_ThresholdEnd", props, false);
  78. _OutlineSmoothness = FindProperty("_OutlineSmoothness", props, false);
  79. _Use8Neighbourhood = FindProperty("_Use8Neighbourhood", props, false);
  80. _OutlineMipLevel = FindProperty("_OutlineMipLevel", props, false);
  81. _StencilComp = FindProperty("_StencilComp", props, false);
  82. _StencilRef = FindProperty("_StencilRef", props, false);
  83. if (_StencilRef == null)
  84. _StencilRef = FindProperty("_Stencil", props, false);
  85. }
  86. protected virtual void RenderStencilProperties () {
  87. if (_StencilComp == null)
  88. return; // not a shader supporting custom stencil operations
  89. // Use default labelWidth
  90. EditorGUIUtility.labelWidth = 0f;
  91. _showStencilSettings = EditorGUILayout.Foldout(_showStencilSettings, _ShowStencilText);
  92. if (_showStencilSettings) {
  93. using (new SpineInspectorUtility.IndentScope()) {
  94. _materialEditor.ShaderProperty(_StencilComp, _StencilCompText);
  95. _materialEditor.ShaderProperty(_StencilRef, _StencilRefText);
  96. }
  97. }
  98. }
  99. protected virtual void RenderOutlineProperties () {
  100. if (_OutlineWidth == null)
  101. return; // not an outline shader
  102. // Use default labelWidth
  103. EditorGUIUtility.labelWidth = 0f;
  104. bool mixedValue;
  105. bool hasOutlineVariant = !IsShaderWithoutStandardVariantShader(_materialEditor, out mixedValue);
  106. bool isOutlineEnabled = true;
  107. if (hasOutlineVariant) {
  108. isOutlineEnabled = IsOutlineEnabled(_materialEditor, out mixedValue);
  109. EditorGUI.showMixedValue = mixedValue;
  110. EditorGUI.BeginChangeCheck();
  111. var origFontStyle = EditorStyles.label.fontStyle;
  112. EditorStyles.label.fontStyle = FontStyle.Bold;
  113. isOutlineEnabled = EditorGUILayout.Toggle(_EnableOutlineText, isOutlineEnabled);
  114. EditorStyles.label.fontStyle = origFontStyle;
  115. EditorGUI.showMixedValue = false;
  116. if (EditorGUI.EndChangeCheck()) {
  117. foreach (Material material in _materialEditor.targets) {
  118. SwitchShaderToOutlineSettings(material, isOutlineEnabled);
  119. }
  120. }
  121. } else {
  122. var origFontStyle = EditorStyles.label.fontStyle;
  123. EditorStyles.label.fontStyle = FontStyle.Bold;
  124. EditorGUILayout.LabelField(_EnableOutlineText);
  125. EditorStyles.label.fontStyle = origFontStyle;
  126. }
  127. if (isOutlineEnabled) {
  128. _materialEditor.ShaderProperty(_OutlineWidth, _OutlineWidthText);
  129. _materialEditor.ShaderProperty(_OutlineColor, _OutlineColorText);
  130. _showAdvancedOutlineSettings = EditorGUILayout.Foldout(_showAdvancedOutlineSettings, _OutlineAdvancedText);
  131. if (_showAdvancedOutlineSettings) {
  132. using (new SpineInspectorUtility.IndentScope()) {
  133. _materialEditor.ShaderProperty(_OutlineReferenceTexWidth, _OutlineReferenceTexWidthText);
  134. _materialEditor.ShaderProperty(_ThresholdEnd, _ThresholdEndText);
  135. _materialEditor.ShaderProperty(_OutlineSmoothness, _OutlineSmoothnessText);
  136. _materialEditor.ShaderProperty(_Use8Neighbourhood, _Use8NeighbourhoodText);
  137. _materialEditor.ShaderProperty(_OutlineMipLevel, _OutlineMipLevelText);
  138. }
  139. }
  140. }
  141. }
  142. #endregion
  143. #region Private Functions
  144. void SwitchShaderToOutlineSettings (Material material, bool enableOutline) {
  145. var shaderName = material.shader.name;
  146. bool isSetToOutlineShader = shaderName.Contains(ShaderOutlineNamePrefix);
  147. if (isSetToOutlineShader && !enableOutline) {
  148. shaderName = shaderName.Replace(ShaderOutlineNamePrefix, ShaderNormalNamePrefix);
  149. _materialEditor.SetShader(Shader.Find(shaderName), false);
  150. return;
  151. } else if (!isSetToOutlineShader && enableOutline) {
  152. shaderName = shaderName.Replace(ShaderNormalNamePrefix, ShaderOutlineNamePrefix);
  153. _materialEditor.SetShader(Shader.Find(shaderName), false);
  154. return;
  155. }
  156. }
  157. static bool IsOutlineEnabled (MaterialEditor editor, out bool mixedValue) {
  158. mixedValue = false;
  159. bool isAnyEnabled = false;
  160. foreach (Material material in editor.targets) {
  161. if (material.shader.name.Contains(ShaderOutlineNamePrefix)) {
  162. isAnyEnabled = true;
  163. } else if (isAnyEnabled) {
  164. mixedValue = true;
  165. }
  166. }
  167. return isAnyEnabled;
  168. }
  169. static bool IsShaderWithoutStandardVariantShader (MaterialEditor editor, out bool mixedValue) {
  170. mixedValue = false;
  171. bool isAnyShaderWithoutVariant = false;
  172. foreach (Material material in editor.targets) {
  173. if (material.shader.name.Contains(ShaderWithoutStandardVariantSuffix)) {
  174. isAnyShaderWithoutVariant = true;
  175. } else if (isAnyShaderWithoutVariant) {
  176. mixedValue = true;
  177. }
  178. }
  179. return isAnyShaderWithoutVariant;
  180. }
  181. static bool BoldToggleField (GUIContent label, bool value) {
  182. FontStyle origFontStyle = EditorStyles.label.fontStyle;
  183. EditorStyles.label.fontStyle = FontStyle.Bold;
  184. value = EditorGUILayout.Toggle(label, value, EditorStyles.toggle);
  185. EditorStyles.label.fontStyle = origFontStyle;
  186. return value;
  187. }
  188. #endregion
  189. }