MaterialChecks.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. /******************************************************************************
  2. * Spine Runtimes License Agreement
  3. * Last updated July 28, 2023. Replaces all prior versions.
  4. *
  5. * Copyright (c) 2013-2023, 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 or
  13. * 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 THE
  27. * SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  28. *****************************************************************************/
  29. using System.Collections.Generic;
  30. using UnityEngine;
  31. #if UNITY_EDITOR
  32. namespace Spine.Unity {
  33. /// <summary>Utility class providing methods to check material settings for incorrect combinations.</summary>
  34. public class MaterialChecks {
  35. static readonly int STRAIGHT_ALPHA_PARAM_ID = Shader.PropertyToID("_StraightAlphaInput");
  36. static readonly string ALPHAPREMULTIPLY_ON_KEYWORD = "_ALPHAPREMULTIPLY_ON";
  37. static readonly string ALPHAPREMULTIPLY_VERTEX_ONLY_ON_KEYWORD = "_ALPHAPREMULTIPLY_VERTEX_ONLY";
  38. static readonly string ALPHABLEND_ON_KEYWORD = "_ALPHABLEND_ON";
  39. static readonly string STRAIGHT_ALPHA_KEYWORD = "_STRAIGHT_ALPHA_INPUT";
  40. static readonly string[] FIXED_NORMALS_KEYWORDS = {
  41. "_FIXED_NORMALS_VIEWSPACE",
  42. "_FIXED_NORMALS_VIEWSPACE_BACKFACE",
  43. "_FIXED_NORMALS_MODELSPACE",
  44. "_FIXED_NORMALS_MODELSPACE_BACKFACE",
  45. "_FIXED_NORMALS_WORLDSPACE"
  46. };
  47. static readonly string NORMALMAP_KEYWORD = "_NORMALMAP";
  48. static readonly string CANVAS_GROUP_COMPATIBLE_KEYWORD = "_CANVAS_GROUP_COMPATIBLE";
  49. public static readonly string kPMANotSupportedLinearMessage =
  50. "\nWarning: Premultiply-alpha atlas textures not supported in Linear color space!"
  51. + "You can use a straight alpha texture with 'PMA Vertex Color' by choosing blend mode 'PMA Vertex, Straight Texture'.\n\n"
  52. + "If you have a PMA Texture, please\n"
  53. + "a) re-export atlas as straight alpha texture with 'premultiply alpha' unchecked\n"
  54. + " (if you have already done this, please set the 'Straight Alpha Texture' Material parameter to 'true') or\n"
  55. + "b) switch to Gamma color space via\nProject Settings - Player - Other Settings - Color Space.\n";
  56. public static readonly string kZSpacingRequiredMessage =
  57. "\nWarning: Z Spacing required on selected shader! Otherwise you will receive incorrect results.\n\nPlease\n"
  58. + "1) make sure at least minimal 'Z Spacing' is set at the SkeletonRenderer/SkeletonAnimation component under 'Advanced' and\n"
  59. + "2) ensure that the skeleton has overlapping parts on different Z depth. You can adjust this in Spine via draw order.\n";
  60. public static readonly string kZSpacingRecommendedMessage =
  61. "\nWarning: Z Spacing recommended on selected shader configuration!\n\nPlease\n"
  62. + "1) make sure at least minimal 'Z Spacing' is set at the SkeletonRenderer/SkeletonAnimation component under 'Advanced' and\n"
  63. + "2) ensure that the skeleton has overlapping parts on different Z depth. You can adjust this in Spine via draw order.\n";
  64. public static readonly string kAddNormalsMessage =
  65. "\nWarning: 'Add Normals' required when not using 'Fixed Normals'!\n\nPlease\n"
  66. + "a) enable 'Add Normals' at the SkeletonRenderer/SkeletonAnimation component under 'Advanced' or\n"
  67. + "b) enable 'Fixed Normals' at the Material.\n";
  68. public static readonly string kSolveTangentsMessage =
  69. "\nWarning: 'Solve Tangents' required when using a Normal Map!\n\nPlease\n"
  70. + "a) enable 'Solve Tangents' at the SkeletonRenderer/SkeletonAnimation component under 'Advanced' or\n"
  71. + "b) clear the 'Normal Map' parameter at the Material.\n";
  72. public static readonly string kNoSkeletonGraphicMaterialMessage =
  73. "\nWarning: Normal non-UI shaders other than 'Spine/SkeletonGraphic *' are not compatible with 'SkeletonGraphic' components! "
  74. + "This will lead to incorrect rendering on some devices.\n\n"
  75. + "Please change the assigned Material to e.g. 'SkeletonGraphicDefault' or change the used shader to one of the 'Spine/SkeletonGraphic *' shaders.\n\n"
  76. + "Note that 'Spine/SkeletonGraphic *' shall still be used when using URP.\n";
  77. public static readonly string kSkeletonGraphicTintBlackMaterialRequiredMessage =
  78. "\nWarning: Only enable 'Tint Black' when using a 'SkeletonGraphic Tint Black' shader!\n"
  79. + "Otherwise this will lead to incorrect rendering.\n\nPlease\n"
  80. + "a) disable 'Tint Black' under 'Advanced' or\n"
  81. + "b) use a 'SkeletonGraphic Tint Black' Material if you need Tint Black on a CanvasGroup.\n";
  82. public static readonly string kTintBlackRequiredMessage =
  83. "\nWarning: 'Advanced - Tint Black' required when using any 'Tint Black' shader!\n\nPlease\n"
  84. + "a) enable 'Tint Black' at the SkeletonRenderer/SkeletonGraphic component under 'Advanced' or\n"
  85. + "b) use a different shader at the Material.\n";
  86. public static readonly string kCanvasTintBlackMessage =
  87. "\nWarning: Canvas 'Additional Shader Channels' 'uv1' and 'uv2' are required when 'Advanced - Tint Black' is enabled!\n\n"
  88. + "Please enable both 'uv1' and 'uv2' channels at the parent Canvas component parameter 'Additional Shader Channels'.\n";
  89. public static readonly string kCanvasGroupCompatibleMaterialRequiredMessage =
  90. "\nWarning: 'CanvasGroup Compatible' is enabled at SkeletonGraphic but not at the Material!\n\nPlease\n"
  91. + "a) use a Material with 'CanvasGroup Compatible' enabled or\n"
  92. + "b) disable 'CanvasGroup Compatible' at the SkeletonGraphic component under 'Advanced'.\n"
  93. + "You can find CanvasGroup Compatible 'SkeletonGraphicTintBlack' materials in the\n"
  94. + "'CanvasGroupCompatible' subfolder of 'SkeletonGraphic-PMATexture' or 'SkeletonGraphic-StraightAlphaTexture'.";
  95. public static readonly string kCanvasGroupRequiredMessage =
  96. "\nWarning: 'CanvasGroup Compatible' is enabled at the Material but disabled at SkeletonGraphic!\n\nPlease\n"
  97. + "a) disable 'CanvasGroup Compatible' at the Material or\n"
  98. + "b) enable 'CanvasGroup Compatible' at the SkeletonGraphic component under 'Advanced'.\n"
  99. + "You can find CanvasGroup Compatible 'SkeletonGraphicTintBlack' materials in the\n"
  100. + "'CanvasGroupCompatible' subfolder of 'SkeletonGraphic-PMATexture' or 'SkeletonGraphic-StraightAlphaTexture'.";
  101. public static readonly string kCanvasGroupCompatiblePMAVertexMessage =
  102. "\nWarning: 'CanvasGroup Compatible' is enabled at the Material and 'PMA Vertex Colors' is enabled at SkeletonGraphic!\n\nPlease\n"
  103. + "a) disable 'CanvasGroup Compatible' at the Material or\n"
  104. + "b) disable 'PMA Vertex Colors' at the SkeletonGraphic component under 'Advanced'.";
  105. public static bool IsMaterialSetupProblematic (SkeletonRenderer renderer, ref string errorMessage) {
  106. Material[] materials = renderer.GetComponent<Renderer>().sharedMaterials;
  107. bool isProblematic = false;
  108. foreach (Material material in materials) {
  109. if (material == null) continue;
  110. isProblematic |= IsMaterialSetupProblematic(material, ref errorMessage);
  111. if (renderer.zSpacing == 0) {
  112. isProblematic |= IsZSpacingRequired(material, ref errorMessage);
  113. }
  114. if (renderer.addNormals == false && RequiresMeshNormals(material)) {
  115. isProblematic = true;
  116. errorMessage += kAddNormalsMessage;
  117. }
  118. if (renderer.calculateTangents == false && RequiresTangents(material)) {
  119. isProblematic = true;
  120. errorMessage += kSolveTangentsMessage;
  121. }
  122. if (renderer.tintBlack == false && RequiresTintBlack(material)) {
  123. isProblematic = true;
  124. errorMessage += kTintBlackRequiredMessage;
  125. }
  126. }
  127. return isProblematic;
  128. }
  129. public static bool IsMaterialSetupProblematic (SkeletonGraphic skeletonGraphic, ref string errorMessage) {
  130. Material material = skeletonGraphic.material;
  131. bool isProblematic = false;
  132. if (material) {
  133. isProblematic |= IsMaterialSetupProblematic(material, ref errorMessage);
  134. MeshGenerator.Settings settings = skeletonGraphic.MeshGenerator.settings;
  135. if (settings.zSpacing == 0) {
  136. isProblematic |= IsZSpacingRequired(material, ref errorMessage);
  137. }
  138. if (IsSpineNonSkeletonGraphicMaterial(material)) {
  139. isProblematic = true;
  140. errorMessage += kNoSkeletonGraphicMaterialMessage;
  141. }
  142. bool isTintBlackMaterial = IsSkeletonGraphicTintBlackMaterial(material);
  143. if (settings.tintBlack != isTintBlackMaterial) {
  144. isProblematic = true;
  145. errorMessage += (settings.tintBlack == false) ?
  146. kTintBlackRequiredMessage : kSkeletonGraphicTintBlackMaterialRequiredMessage;
  147. }
  148. if (settings.tintBlack == true && CanvasNotSetupForTintBlack(skeletonGraphic)) {
  149. isProblematic = true;
  150. errorMessage += kCanvasTintBlackMessage;
  151. }
  152. bool isCanvasGroupCompatible = IsCanvasGroupCompatible(material);
  153. if (settings.canvasGroupCompatible != isCanvasGroupCompatible) {
  154. isProblematic = true;
  155. errorMessage += (settings.canvasGroupCompatible == false) ?
  156. kCanvasGroupRequiredMessage : kCanvasGroupCompatibleMaterialRequiredMessage;
  157. }
  158. if (settings.pmaVertexColors == true && settings.canvasGroupCompatible == true && settings.tintBlack == false) {
  159. isProblematic = true;
  160. errorMessage += kCanvasGroupCompatiblePMAVertexMessage;
  161. }
  162. }
  163. return isProblematic;
  164. }
  165. public static bool IsMaterialSetupProblematic (Material material, ref string errorMessage) {
  166. return !IsColorSpaceSupported(material, ref errorMessage);
  167. }
  168. public static bool IsZSpacingRequired (Material material, ref string errorMessage) {
  169. bool hasForwardAddPass = material.FindPass("FORWARD_DELTA") >= 0;
  170. if (hasForwardAddPass) {
  171. errorMessage += kZSpacingRequiredMessage;
  172. return true;
  173. }
  174. bool zWrite = material.HasProperty("_ZWrite") && material.GetFloat("_ZWrite") > 0.0f;
  175. if (zWrite) {
  176. errorMessage += kZSpacingRecommendedMessage;
  177. return true;
  178. }
  179. return false;
  180. }
  181. public static bool IsColorSpaceSupported (Material material, ref string errorMessage) {
  182. if (QualitySettings.activeColorSpace == ColorSpace.Linear) {
  183. if (IsPMATextureMaterial(material)) {
  184. errorMessage += kPMANotSupportedLinearMessage;
  185. return false;
  186. }
  187. }
  188. return true;
  189. }
  190. public static bool UsesSpineShader (Material material) {
  191. return material.shader.name.Contains("Spine/");
  192. }
  193. public static bool IsTextureSetupProblematic (Material material, ColorSpace colorSpace,
  194. bool sRGBTexture, bool mipmapEnabled, bool alphaIsTransparency,
  195. string texturePath, string materialPath,
  196. ref string errorMessage) {
  197. if (material == null || !UsesSpineShader(material)) {
  198. return false;
  199. }
  200. bool isProblematic = false;
  201. if (IsPMATextureMaterial(material)) {
  202. // 'sRGBTexture = true' generates incorrectly weighted mipmaps at PMA textures,
  203. // causing white borders due to undesired custom weighting.
  204. if (sRGBTexture && mipmapEnabled && colorSpace == ColorSpace.Gamma) {
  205. errorMessage += string.Format("`{0}` : Problematic Texture Settings found: " +
  206. "When enabling `Generate Mip Maps` in Gamma color space, it is recommended " +
  207. "to disable `sRGB (Color Texture)` on `Premultiply alpha` textures. Otherwise " +
  208. "you will receive white border artifacts on an atlas exported with default " +
  209. "`Premultiply alpha` settings.\n" +
  210. "(You can disable this warning in `Edit - Preferences - Spine`)\n", texturePath);
  211. isProblematic = true;
  212. }
  213. if (alphaIsTransparency) {
  214. string materialName = System.IO.Path.GetFileName(materialPath);
  215. errorMessage += string.Format("`{0}` and material `{1}` : Problematic " +
  216. "Texture / Material Settings found: It is recommended to disable " +
  217. "`Alpha Is Transparency` on `Premultiply alpha` textures.\n" +
  218. "Assuming `Premultiply alpha` texture because `Straight Alpha Texture` " +
  219. "is disabled at material). " +
  220. "(You can disable this warning in `Edit - Preferences - Spine`)\n", texturePath, materialName);
  221. isProblematic = true;
  222. }
  223. } else { // straight alpha texture
  224. if (!alphaIsTransparency) {
  225. string materialName = System.IO.Path.GetFileName(materialPath);
  226. errorMessage += string.Format("`{0}` and material `{1}` : Incorrect" +
  227. "Texture / Material Settings found: It is strongly recommended " +
  228. "to enable `Alpha Is Transparency` on `Straight alpha` textures.\n" +
  229. "Assuming `Straight alpha` texture because `Straight Alpha Texture` " +
  230. "is enabled at material). " +
  231. "(You can disable this warning in `Edit - Preferences - Spine`)\n", texturePath, materialName);
  232. isProblematic = true;
  233. }
  234. }
  235. return isProblematic;
  236. }
  237. public static void EnablePMATextureAtMaterial (Material material, bool enablePMATexture) {
  238. if (material.HasProperty(STRAIGHT_ALPHA_PARAM_ID)) {
  239. material.SetInt(STRAIGHT_ALPHA_PARAM_ID, enablePMATexture ? 0 : 1);
  240. if (enablePMATexture)
  241. material.DisableKeyword(STRAIGHT_ALPHA_KEYWORD);
  242. else
  243. material.EnableKeyword(STRAIGHT_ALPHA_KEYWORD);
  244. } else {
  245. if (enablePMATexture) {
  246. material.DisableKeyword(ALPHAPREMULTIPLY_ON_KEYWORD);
  247. material.DisableKeyword(ALPHABLEND_ON_KEYWORD);
  248. material.EnableKeyword(ALPHAPREMULTIPLY_VERTEX_ONLY_ON_KEYWORD);
  249. } else {
  250. material.DisableKeyword(ALPHAPREMULTIPLY_ON_KEYWORD);
  251. material.DisableKeyword(ALPHAPREMULTIPLY_VERTEX_ONLY_ON_KEYWORD);
  252. material.EnableKeyword(ALPHABLEND_ON_KEYWORD);
  253. }
  254. }
  255. }
  256. public static bool IsPMATextureMaterial (Material material) {
  257. bool usesAlphaPremultiplyKeyword = IsSpriteShader(material);
  258. if (usesAlphaPremultiplyKeyword)
  259. return material.IsKeywordEnabled(ALPHAPREMULTIPLY_ON_KEYWORD);
  260. else
  261. return material.HasProperty(STRAIGHT_ALPHA_PARAM_ID) && material.GetInt(STRAIGHT_ALPHA_PARAM_ID) == 0;
  262. }
  263. static bool IsURP3DMaterial (Material material) {
  264. return material.shader.name.Contains("Universal Render Pipeline/Spine");
  265. }
  266. static bool IsSpineNonSkeletonGraphicMaterial (Material material) {
  267. return material.shader.name.Contains("Spine") && !material.shader.name.Contains("SkeletonGraphic");
  268. }
  269. static bool IsSkeletonGraphicTintBlackMaterial (Material material) {
  270. return material.shader.name.Contains("Spine") && material.shader.name.Contains("SkeletonGraphic")
  271. && material.shader.name.Contains("Black");
  272. }
  273. static bool AreShadowsDisabled (Material material) {
  274. return material.IsKeywordEnabled("_RECEIVE_SHADOWS_OFF");
  275. }
  276. static bool RequiresMeshNormals (Material material) {
  277. bool anyFixedNormalSet = false;
  278. foreach (string fixedNormalKeyword in FIXED_NORMALS_KEYWORDS) {
  279. if (material.IsKeywordEnabled(fixedNormalKeyword)) {
  280. anyFixedNormalSet = true;
  281. break;
  282. }
  283. }
  284. bool isShaderWithMeshNormals = IsLitSpriteShader(material);
  285. return isShaderWithMeshNormals && !anyFixedNormalSet;
  286. }
  287. static bool IsLitSpriteShader (Material material) {
  288. string shaderName = material.shader.name;
  289. return shaderName.Contains("Spine/Sprite/Pixel Lit") ||
  290. shaderName.Contains("Spine/Sprite/Vertex Lit") ||
  291. shaderName.Contains("2D/Spine/Sprite") || // covers both URP and LWRP
  292. shaderName.Contains("Pipeline/Spine/Sprite"); // covers both URP and LWRP
  293. }
  294. static bool IsSpriteShader (Material material) {
  295. if (IsLitSpriteShader(material))
  296. return true;
  297. string shaderName = material.shader.name;
  298. return shaderName.Contains("Spine/Sprite/Unlit");
  299. }
  300. static bool RequiresTintBlack (Material material) {
  301. bool isTintBlackShader =
  302. material.shader.name.Contains("Spine") &&
  303. material.shader.name.Contains("Tint Black");
  304. return isTintBlackShader;
  305. }
  306. static bool RequiresTangents (Material material) {
  307. return material.IsKeywordEnabled(NORMALMAP_KEYWORD);
  308. }
  309. static bool IsCanvasGroupCompatible (Material material) {
  310. return material.IsKeywordEnabled(CANVAS_GROUP_COMPATIBLE_KEYWORD);
  311. }
  312. static bool CanvasNotSetupForTintBlack (SkeletonGraphic skeletonGraphic) {
  313. Canvas canvas = skeletonGraphic.canvas;
  314. if (!canvas)
  315. return false;
  316. var requiredChannels =
  317. AdditionalCanvasShaderChannels.TexCoord1 |
  318. AdditionalCanvasShaderChannels.TexCoord2;
  319. return (canvas.additionalShaderChannels & requiredChannels) != requiredChannels;
  320. }
  321. }
  322. }
  323. #endif // UNITY_EDITOR