SoftMask.cs 49 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using UnityEngine;
  5. using UnityEngine.Assertions;
  6. using UnityEngine.EventSystems;
  7. using UnityEngine.UI;
  8. using UnityEngine.Sprites;
  9. using SoftMasking.Extensions;
  10. namespace SoftMasking {
  11. /// <summary>
  12. /// Contains some predefined combinations of mask channel weights.
  13. /// </summary>
  14. public static class MaskChannel {
  15. public static Color alpha = new Color(0, 0, 0, 1);
  16. public static Color red = new Color(1, 0, 0, 0);
  17. public static Color green = new Color(0, 1, 0, 0);
  18. public static Color blue = new Color(0, 0, 1, 0);
  19. public static Color gray = new Color(1, 1, 1, 0) / 3.0f;
  20. }
  21. /// <summary>
  22. /// SoftMask is a component that can be added to UI elements for masking the children. It works
  23. /// like a standard Unity's <see cref="Mask"/> but supports alpha.
  24. /// </summary>
  25. [ExecuteInEditMode]
  26. [DisallowMultipleComponent]
  27. [AddComponentMenu("UI/Soft Mask", 14)]
  28. [RequireComponent(typeof(RectTransform))]
  29. [HelpURL("https://docs.google.com/document/d/1xFZQGn_odhTCokMFR0LyCPXWtqWXN-bBGVS9GETglx8")]
  30. public class SoftMask : UIBehaviour, ISoftMask, ICanvasRaycastFilter {
  31. //
  32. // How it works:
  33. //
  34. // SoftMask overrides Shader used by child elements. To do it, SoftMask spawns invisible
  35. // SoftMaskable components on them on the fly. SoftMaskable implements IMaterialOverride,
  36. // which allows it to override the shader that performs actual rendering. Use of
  37. // IMaterialOverride is transparent to the user: a material assigned to Graphic in the
  38. // inspector is left untouched.
  39. //
  40. // Management of SoftMaskables is fully automated. SoftMaskables are kept on the child
  41. // objects while any SoftMask parent present. When something changes and SoftMask parent
  42. // no longer exists, SoftMaskable is destroyed automatically. So, a user of SoftMask
  43. // doesn't have to worry about any component changes in the hierarchy.
  44. //
  45. // The replacement shader samples the mask texture and multiply the resulted color
  46. // accordingly. SoftMask has the predefined replacement for Unity's default UI shader
  47. // (and its ETC1-version in Unity 5.4+). So, when SoftMask 'sees' a material that uses a
  48. // known shader, it overrides shader by the predefined one. If SoftMask encounters a
  49. // material with an unknown shader, it can't do anything reasonable (because it doesn't know
  50. // what that shader should do). In such a case, SoftMask will not work and a warning will
  51. // be displayed in Console. If you want SoftMask to work with a custom shader, you can
  52. // manually add support to this shader. For reference how to do it, see
  53. // CustomWithSoftMask.shader from included samples.
  54. //
  55. // All replacements are cached in SoftMask instances. By default Unity draws UI with a
  56. // very small number of material instances (they are spawned one per masking/clipping layer),
  57. // so, SoftMask creates a relatively small number of overrides.
  58. //
  59. [SerializeField] Shader _defaultShader = null;
  60. [SerializeField] Shader _defaultETC1Shader = null;
  61. [SerializeField] MaskSource _source = MaskSource.Graphic;
  62. [SerializeField] RectTransform _separateMask = null;
  63. [SerializeField] Sprite _sprite = null;
  64. [SerializeField] BorderMode _spriteBorderMode = BorderMode.Simple;
  65. [SerializeField] float _spritePixelsPerUnitMultiplier = 1f;
  66. [SerializeField] Texture _texture = null;
  67. [SerializeField] Rect _textureUVRect = DefaultUVRect;
  68. [SerializeField] Color _channelWeights = MaskChannel.alpha;
  69. [SerializeField] float _raycastThreshold = 0f;
  70. [SerializeField] bool _invertMask = false;
  71. [SerializeField] bool _invertOutsides = false;
  72. MaterialReplacements _materials;
  73. MaterialParameters _parameters;
  74. WarningReporter _warningReporter;
  75. Rect _lastMaskRect;
  76. bool _maskingWasEnabled;
  77. bool _destroyed;
  78. bool _dirty;
  79. // Cached components
  80. RectTransform _maskTransform;
  81. Graphic _graphic;
  82. Canvas _canvas;
  83. public SoftMask() {
  84. var materialReplacer =
  85. new MaterialReplacerChain(
  86. MaterialReplacer.globalReplacers,
  87. new MaterialReplacerImpl(this));
  88. _materials = new MaterialReplacements(materialReplacer, m => _parameters.Apply(m));
  89. _warningReporter = new WarningReporter(this);
  90. }
  91. /// <summary>
  92. /// Source of the mask's image.
  93. /// </summary>
  94. [Serializable]
  95. public enum MaskSource {
  96. /// <summary>
  97. /// The mask image should be taken from the Graphic component of the containing
  98. /// GameObject. Only Image and RawImage components are supported. If there is no
  99. /// appropriate Graphic on the GameObject, a solid rectangle of the RectTransform
  100. /// dimensions will be used.
  101. /// </summary>
  102. Graphic,
  103. /// <summary>
  104. /// The mask image should be taken from an explicitly specified Sprite. When this mode
  105. /// is used, spriteBorderMode can also be set to determine how to process Sprite's
  106. /// borders. If the sprite isn't set, a solid rectangle of the RectTransform dimensions
  107. /// will be used. This mode is analogous to using an Image with according sprite and
  108. /// type set.
  109. /// </summary>
  110. Sprite,
  111. /// <summary>
  112. /// The mask image should be taken from an explicitly specified Texture2D or
  113. /// RenderTexture. When this mode is used, textureUVRect can also be set to determine
  114. /// which part of the texture should be used. If the texture isn't set, a solid rectangle
  115. /// of the RectTransform dimensions will be used. This mode is analogous to using a
  116. /// RawImage with according texture and uvRect set.
  117. /// </summary>
  118. Texture
  119. }
  120. /// <summary>
  121. /// How Sprite's borders should be processed. It is a reduced set of Image.Type values.
  122. /// </summary>
  123. [Serializable]
  124. public enum BorderMode {
  125. /// <summary>
  126. /// Sprite should be drawn as a whole, ignoring any borders set. It works the
  127. /// same way as Unity's Image.Type.Simple.
  128. /// </summary>
  129. Simple,
  130. /// <summary>
  131. /// Sprite borders should be stretched when the drawn image is larger that the
  132. /// source. It works the same way as Unity's Image.Type.Sliced.
  133. /// </summary>
  134. Sliced,
  135. /// <summary>
  136. /// The same as Sliced, but border fragments will be repeated instead of
  137. /// stretched. It works the same way as Unity's Image.Type.Tiled.
  138. /// </summary>
  139. Tiled
  140. }
  141. /// <summary>
  142. /// Errors encountered during SoftMask diagnostics. Used by SoftMaskEditor to display
  143. /// hints relevant to the current state.
  144. /// </summary>
  145. [Flags]
  146. [Serializable]
  147. public enum Errors {
  148. NoError = 0,
  149. UnsupportedShaders = 1 << 0,
  150. NestedMasks = 1 << 1,
  151. TightPackedSprite = 1 << 2,
  152. AlphaSplitSprite = 1 << 3,
  153. UnsupportedImageType = 1 << 4,
  154. UnreadableTexture = 1 << 5,
  155. UnreadableRenderTexture = 1 << 6
  156. }
  157. /// <summary>
  158. /// Specifies a Shader that should be used as a replacement of the Unity's default UI
  159. /// shader. If you add SoftMask in play-time by AddComponent(), you should set
  160. /// this property manually.
  161. /// </summary>
  162. public Shader defaultShader {
  163. get { return _defaultShader; }
  164. set { SetShader(ref _defaultShader, value); }
  165. }
  166. /// <summary>
  167. /// Specifies a Shader that should be used as a replacement of the Unity's default UI
  168. /// shader with ETC1 (alpha-split) support. If you use ETC1 textures in UI and
  169. /// add SoftMask in play-time by AddComponent(), you should set this property manually.
  170. /// </summary>
  171. public Shader defaultETC1Shader {
  172. get { return _defaultETC1Shader; }
  173. set { SetShader(ref _defaultETC1Shader, value, warnIfNotSet: false); }
  174. }
  175. /// <summary>
  176. /// Determines from where the mask image should be taken.
  177. /// </summary>
  178. public MaskSource source {
  179. get { return _source; }
  180. set { if (_source != value) Set(ref _source, value); }
  181. }
  182. /// <summary>
  183. /// Specifies a RectTransform that defines the bounds of the mask. Use of a separate
  184. /// RectTransform allows moving or resizing the mask bounds without affecting children.
  185. /// When null, the RectTransform of this GameObject is used.
  186. /// Default value is null.
  187. /// </summary>
  188. public RectTransform separateMask {
  189. get { return _separateMask; }
  190. set {
  191. if (_separateMask != value) {
  192. Set(ref _separateMask, value);
  193. // We should search them again
  194. _graphic = null;
  195. _maskTransform = null;
  196. }
  197. }
  198. }
  199. /// <summary>
  200. /// Specifies a Sprite that should be used as the mask image. This property takes
  201. /// effect only when source is MaskSource.Sprite.
  202. /// </summary>
  203. /// <seealso cref="source"/>
  204. public Sprite sprite {
  205. get { return _sprite; }
  206. set { if (_sprite != value) Set(ref _sprite, value); }
  207. }
  208. /// <summary>
  209. /// Specifies how to draw sprite borders. This property takes effect only when
  210. /// source is MaskSource.Sprite.
  211. /// </summary>
  212. /// <seealso cref="source"/>
  213. /// <seealso cref="sprite"/>
  214. public BorderMode spriteBorderMode {
  215. get { return _spriteBorderMode; }
  216. set { if (_spriteBorderMode != value) Set(ref _spriteBorderMode, value); }
  217. }
  218. /// <summary>
  219. /// A multiplier that is applied to the pixelsPerUnit property of the selected sprite.
  220. /// Default value is 1. This property takes effect only when source is MaskSource.Sprite.
  221. /// </summary>
  222. /// <seealso cref="source"/>
  223. /// <seealso cref="sprite"/>
  224. public float spritePixelsPerUnitMultiplier {
  225. get { return _spritePixelsPerUnitMultiplier; }
  226. set {
  227. if (_spritePixelsPerUnitMultiplier != value)
  228. Set(ref _spritePixelsPerUnitMultiplier, ClampPixelsPerUnitMultiplier(value));
  229. }
  230. }
  231. /// <summary>
  232. /// Specifies a Texture2D that should be used as the mask image. This property takes
  233. /// effect only when the source is MaskSource.Texture. This and <see cref="renderTexture"/>
  234. /// properties are mutually exclusive.
  235. /// </summary>
  236. /// <seealso cref="renderTexture"/>
  237. public Texture2D texture {
  238. get { return _texture as Texture2D; }
  239. set { if (_texture != value) Set(ref _texture, value); }
  240. }
  241. /// <summary>
  242. /// Specifies a RenderTexture that should be used as the mask image. This property takes
  243. /// effect only when the source is MaskSource.Texture. This and <see cref="texture"/>
  244. /// properties are mutually exclusive.
  245. /// </summary>
  246. /// <seealso cref="texture"/>
  247. public RenderTexture renderTexture {
  248. get { return _texture as RenderTexture; }
  249. set { if (_texture != value) Set(ref _texture, value); }
  250. }
  251. /// <summary>
  252. /// Specifies a normalized UV-space rectangle defining the image part that should be used as
  253. /// the mask image. This property takes effect only when the source is MaskSource.Texture.
  254. /// A value is set in normalized coordinates. The default value is (0, 0, 1, 1), which means
  255. /// that the whole texture is used.
  256. /// </summary>
  257. public Rect textureUVRect {
  258. get { return _textureUVRect; }
  259. set { if (_textureUVRect != value) Set(ref _textureUVRect, value); }
  260. }
  261. /// <summary>
  262. /// Specifies weights of the color channels of the mask. The color sampled from the mask
  263. /// texture is multiplied by this value, after what all components are summed up together.
  264. /// That is, the final mask value is calculated as:
  265. /// color = `pixel-from-mask` * channelWeights
  266. /// value = color.r + color.g + color.b + color.a
  267. /// The `value` is a number by which the resulting pixel's alpha is multiplied. As you
  268. /// can see, the result value isn't normalized, so, you should account it while defining
  269. /// custom values for this property.
  270. /// Static class MaskChannel contains some useful predefined values. You can use they
  271. /// as example of how mask calculation works.
  272. /// The default value is MaskChannel.alpha.
  273. /// </summary>
  274. public Color channelWeights {
  275. get { return _channelWeights; }
  276. set { if (_channelWeights != value) Set(ref _channelWeights, value); }
  277. }
  278. /// <summary>
  279. /// Specifies the minimum mask value that the point should have for an input event to pass.
  280. /// If the value sampled from the mask is greater or equal this value, the input event
  281. /// is considered 'hit'. The mask value is compared with raycastThreshold after
  282. /// channelWeights applied.
  283. /// The default value is 0, which means that any pixel belonging to RectTransform is
  284. /// considered in input events. If you specify the value greater than 0, the mask's
  285. /// texture should be readable and it should be not a RenderTexture.
  286. /// Accepts values in range [0..1].
  287. /// </summary>
  288. public float raycastThreshold {
  289. get { return _raycastThreshold; }
  290. set { _raycastThreshold = value; }
  291. }
  292. /// <summary>
  293. /// If set, mask values inside the mask rectangle will be inverted. In this case mask's
  294. /// zero value (taking <see cref="channelWeights"/> into account) will be treated as one
  295. /// and vice versa. The mask rectangle is the RectTransform of the GameObject this
  296. /// component is attached to or <see cref="separateMask"/> if it's not null.
  297. /// The default value is false.
  298. /// </summary>
  299. /// <seealso cref="invertOutsides"/>
  300. public bool invertMask {
  301. get { return _invertMask; }
  302. set { if (_invertMask != value) Set(ref _invertMask, value); }
  303. }
  304. /// <summary>
  305. /// If set, mask values outside the mask rectangle will be inverted. By default, everything
  306. /// outside the mask rectangle has zero mask value. When this property is set, the mask
  307. /// outsides will have value one, which means that everything outside the mask will be
  308. /// visible. The mask rectangle is the RectTransform of the GameObject this component
  309. /// is attached to or <see cref="separateMask"/> if it's not null.
  310. /// The default value is false.
  311. /// </summary>
  312. /// <seealso cref="invertMask"/>
  313. public bool invertOutsides {
  314. get { return _invertOutsides; }
  315. set { if (_invertOutsides != value) Set(ref _invertOutsides, value); }
  316. }
  317. /// <summary>
  318. /// Returns true if Soft Mask does raycast filtering, that is if the masked areas are
  319. /// transparent to input.
  320. /// </summary>
  321. public bool isUsingRaycastFiltering {
  322. get { return _raycastThreshold > 0f; }
  323. }
  324. /// <summary>
  325. /// Returns true if masking is currently active.
  326. /// </summary>
  327. public bool isMaskingEnabled {
  328. get { return isActiveAndEnabled && canvas; }
  329. }
  330. /// <summary>
  331. /// Checks for errors and returns them as flags. It is used in the editor to determine
  332. /// which warnings should be displayed.
  333. /// </summary>
  334. public Errors PollErrors() { return new Diagnostics(this).PollErrors(); }
  335. // ICanvasRaycastFilter
  336. public bool IsRaycastLocationValid(Vector2 sp, Camera cam) {
  337. Vector2 localPos;
  338. if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(maskTransform, sp, cam, out localPos)) return false;
  339. if (!Mathr.Inside(localPos, LocalMaskRect(Vector4.zero))) return _invertOutsides;
  340. if (!_parameters.texture) return true;
  341. if (!isUsingRaycastFiltering) return true;
  342. float mask;
  343. var sampleResult = _parameters.SampleMask(localPos, out mask);
  344. _warningReporter.TextureRead(_parameters.texture, sampleResult);
  345. if (sampleResult != MaterialParameters.SampleMaskResult.Success)
  346. return true;
  347. if (_invertMask)
  348. mask = 1 - mask;
  349. return mask >= _raycastThreshold;
  350. }
  351. protected override void Start() {
  352. base.Start();
  353. WarnIfDefaultShaderIsNotSet();
  354. }
  355. protected override void OnEnable() {
  356. base.OnEnable();
  357. SubscribeOnWillRenderCanvases();
  358. SpawnMaskablesInChildren(transform);
  359. FindGraphic();
  360. if (isMaskingEnabled)
  361. UpdateMaskParameters();
  362. NotifyChildrenThatMaskMightChanged();
  363. }
  364. protected override void OnDisable() {
  365. base.OnDisable();
  366. UnsubscribeFromWillRenderCanvases();
  367. if (_graphic) {
  368. _graphic.UnregisterDirtyVerticesCallback(OnGraphicDirty);
  369. _graphic.UnregisterDirtyMaterialCallback(OnGraphicDirty);
  370. _graphic = null;
  371. }
  372. NotifyChildrenThatMaskMightChanged();
  373. DestroyMaterials();
  374. }
  375. protected override void OnDestroy() {
  376. base.OnDestroy();
  377. _destroyed = true;
  378. NotifyChildrenThatMaskMightChanged();
  379. }
  380. protected virtual void LateUpdate() {
  381. var maskingEnabled = isMaskingEnabled;
  382. if (maskingEnabled) {
  383. if (_maskingWasEnabled != maskingEnabled)
  384. SpawnMaskablesInChildren(transform);
  385. var prevGraphic = _graphic;
  386. FindGraphic();
  387. if (_lastMaskRect != maskTransform.rect
  388. || !ReferenceEquals(_graphic, prevGraphic))
  389. _dirty = true;
  390. }
  391. _maskingWasEnabled = maskingEnabled;
  392. }
  393. protected override void OnRectTransformDimensionsChange() {
  394. base.OnRectTransformDimensionsChange();
  395. _dirty = true;
  396. }
  397. protected override void OnDidApplyAnimationProperties() {
  398. base.OnDidApplyAnimationProperties();
  399. _dirty = true;
  400. }
  401. #if UNITY_EDITOR
  402. protected override void OnValidate() {
  403. base.OnValidate();
  404. _spritePixelsPerUnitMultiplier = ClampPixelsPerUnitMultiplier(_spritePixelsPerUnitMultiplier);
  405. _dirty = true;
  406. _maskTransform = null;
  407. _graphic = null;
  408. }
  409. #endif
  410. static float ClampPixelsPerUnitMultiplier(float value) {
  411. return Mathf.Max(value, 0.01f);
  412. }
  413. protected override void OnTransformParentChanged() {
  414. base.OnTransformParentChanged();
  415. _canvas = null;
  416. _dirty = true;
  417. }
  418. protected override void OnCanvasHierarchyChanged() {
  419. base.OnCanvasHierarchyChanged();
  420. _canvas = null;
  421. _dirty = true;
  422. NotifyChildrenThatMaskMightChanged();
  423. }
  424. void OnTransformChildrenChanged() {
  425. SpawnMaskablesInChildren(transform);
  426. }
  427. void SubscribeOnWillRenderCanvases() {
  428. // To get called when layout and graphics update is finished we should
  429. // subscribe after CanvasUpdateRegistry. CanvasUpdateRegistry subscribes
  430. // in his constructor, so we force its execution.
  431. Touch(CanvasUpdateRegistry.instance);
  432. Canvas.willRenderCanvases += OnWillRenderCanvases;
  433. }
  434. void UnsubscribeFromWillRenderCanvases() {
  435. Canvas.willRenderCanvases -= OnWillRenderCanvases;
  436. }
  437. void OnWillRenderCanvases() {
  438. // To be sure that mask will match the state of another drawn UI elements,
  439. // we update material parameters when layout and graphic update is done,
  440. // just before actual rendering.
  441. if (isMaskingEnabled)
  442. UpdateMaskParameters();
  443. }
  444. static T Touch<T>(T obj) { return obj; }
  445. static readonly Rect DefaultUVRect = new Rect(0, 0, 1, 1);
  446. RectTransform maskTransform {
  447. get {
  448. return
  449. _maskTransform
  450. ? _maskTransform
  451. : (_maskTransform = _separateMask ? _separateMask : GetComponent<RectTransform>());
  452. }
  453. }
  454. Canvas canvas {
  455. get { return _canvas ? _canvas : (_canvas = NearestEnabledCanvas()); }
  456. }
  457. bool isBasedOnGraphic { get { return _source == MaskSource.Graphic; } }
  458. bool ISoftMask.isAlive { get { return this && !_destroyed; } }
  459. Material ISoftMask.GetReplacement(Material original) {
  460. Assert.IsTrue(isActiveAndEnabled);
  461. return _materials.Get(original);
  462. }
  463. void ISoftMask.ReleaseReplacement(Material replacement) {
  464. _materials.Release(replacement);
  465. }
  466. void ISoftMask.UpdateTransformChildren(Transform transform) {
  467. SpawnMaskablesInChildren(transform);
  468. }
  469. void OnGraphicDirty() {
  470. if (isBasedOnGraphic) // TODO is this check neccessary?
  471. _dirty = true;
  472. }
  473. void FindGraphic() {
  474. if (!_graphic && isBasedOnGraphic) {
  475. _graphic = maskTransform.GetComponent<Graphic>();
  476. if (_graphic) {
  477. _graphic.RegisterDirtyVerticesCallback(OnGraphicDirty);
  478. _graphic.RegisterDirtyMaterialCallback(OnGraphicDirty);
  479. }
  480. }
  481. }
  482. Canvas NearestEnabledCanvas() {
  483. // It's a rare operation, so I do not optimize it with static lists
  484. var canvases = GetComponentsInParent<Canvas>(false);
  485. for (int i = 0; i < canvases.Length; ++i)
  486. if (canvases[i].isActiveAndEnabled)
  487. return canvases[i];
  488. return null;
  489. }
  490. void UpdateMaskParameters() {
  491. Assert.IsTrue(isMaskingEnabled);
  492. if (_dirty || maskTransform.hasChanged) {
  493. CalculateMaskParameters();
  494. maskTransform.hasChanged = false;
  495. _lastMaskRect = maskTransform.rect;
  496. _dirty = false;
  497. }
  498. _materials.ApplyAll();
  499. }
  500. void SpawnMaskablesInChildren(Transform root) {
  501. using (new ClearListAtExit<SoftMaskable>(s_maskables))
  502. for (int i = 0; i < root.childCount; ++i) {
  503. var child = root.GetChild(i);
  504. child.GetComponents(s_maskables);
  505. Assert.IsTrue(s_maskables.Count <= 1);
  506. if (s_maskables.Count == 0)
  507. child.gameObject.AddComponent<SoftMaskable>();
  508. }
  509. }
  510. void InvalidateChildren() {
  511. ForEachChildMaskable(x => x.Invalidate());
  512. }
  513. void NotifyChildrenThatMaskMightChanged() {
  514. ForEachChildMaskable(x => x.MaskMightChanged());
  515. }
  516. void ForEachChildMaskable(Action<SoftMaskable> f) {
  517. transform.GetComponentsInChildren(s_maskables);
  518. using (new ClearListAtExit<SoftMaskable>(s_maskables))
  519. for (int i = 0; i < s_maskables.Count; ++i) {
  520. var maskable = s_maskables[i];
  521. if (maskable && maskable.gameObject != gameObject)
  522. f(maskable);
  523. }
  524. }
  525. void DestroyMaterials() {
  526. _materials.DestroyAllAndClear();
  527. }
  528. struct SourceParameters {
  529. public Image image;
  530. public Sprite sprite;
  531. public BorderMode spriteBorderMode;
  532. public float spritePixelsPerUnit;
  533. public Texture texture;
  534. public Rect textureUVRect;
  535. }
  536. const float DefaultPixelsPerUnit = 100f;
  537. SourceParameters DeduceSourceParameters() {
  538. var result = new SourceParameters();
  539. switch (_source) {
  540. case MaskSource.Graphic:
  541. if (_graphic is Image) {
  542. var image = (Image)_graphic;
  543. var sprite = image.sprite;
  544. result.image = image;
  545. result.sprite = sprite;
  546. result.spriteBorderMode = ImageTypeToBorderMode(image.type);
  547. if (sprite) {
  548. #if UNITY_2019_2_OR_NEWER
  549. result.spritePixelsPerUnit = sprite.pixelsPerUnit * image.pixelsPerUnitMultiplier;
  550. #else
  551. result.spritePixelsPerUnit = sprite.pixelsPerUnit;
  552. #endif
  553. result.texture = sprite.texture;
  554. } else
  555. result.spritePixelsPerUnit = DefaultPixelsPerUnit;
  556. } else if (_graphic is RawImage) {
  557. var rawImage = (RawImage)_graphic;
  558. result.texture = rawImage.texture;
  559. result.textureUVRect = rawImage.uvRect;
  560. }
  561. break;
  562. case MaskSource.Sprite:
  563. result.sprite = _sprite;
  564. result.spriteBorderMode = _spriteBorderMode;
  565. if (_sprite) {
  566. result.spritePixelsPerUnit = _sprite.pixelsPerUnit * _spritePixelsPerUnitMultiplier;
  567. result.texture = _sprite.texture;
  568. } else
  569. result.spritePixelsPerUnit = DefaultPixelsPerUnit;
  570. break;
  571. case MaskSource.Texture:
  572. result.texture = _texture;
  573. result.textureUVRect = _textureUVRect;
  574. break;
  575. default:
  576. Debug.LogAssertionFormat(this, "Unknown MaskSource: {0}", _source);
  577. break;
  578. }
  579. return result;
  580. }
  581. public static BorderMode ImageTypeToBorderMode(Image.Type type) {
  582. switch (type) {
  583. case Image.Type.Simple: return BorderMode.Simple;
  584. case Image.Type.Sliced: return BorderMode.Sliced;
  585. case Image.Type.Tiled: return BorderMode.Tiled;
  586. default:
  587. return BorderMode.Simple;
  588. }
  589. }
  590. public static bool IsImageTypeSupported(Image.Type type) {
  591. return type == Image.Type.Simple
  592. || type == Image.Type.Sliced
  593. || type == Image.Type.Tiled;
  594. }
  595. void CalculateMaskParameters() {
  596. var sourceParams = DeduceSourceParameters();
  597. _warningReporter.ImageUsed(sourceParams.image);
  598. var spriteErrors = Diagnostics.CheckSprite(sourceParams.sprite);
  599. _warningReporter.SpriteUsed(sourceParams.sprite, spriteErrors);
  600. if (sourceParams.sprite) {
  601. if (spriteErrors == Errors.NoError)
  602. CalculateSpriteBased(sourceParams.sprite, sourceParams.spriteBorderMode, sourceParams.spritePixelsPerUnit);
  603. else
  604. CalculateSolidFill();
  605. } else if (sourceParams.texture)
  606. CalculateTextureBased(sourceParams.texture, sourceParams.textureUVRect);
  607. else
  608. CalculateSolidFill();
  609. }
  610. void CalculateSpriteBased(Sprite sprite, BorderMode borderMode, float spritePixelsPerUnit) {
  611. FillCommonParameters();
  612. var inner = DataUtility.GetInnerUV(sprite);
  613. var outer = DataUtility.GetOuterUV(sprite);
  614. var padding = DataUtility.GetPadding(sprite);
  615. var fullMaskRect = LocalMaskRect(Vector4.zero);
  616. _parameters.maskRectUV = outer;
  617. if (borderMode == BorderMode.Simple) {
  618. var normalizedPadding = Mathr.Div(padding, sprite.rect.size);
  619. _parameters.maskRect = Mathr.ApplyBorder(fullMaskRect, Mathr.Mul(normalizedPadding, Mathr.Size(fullMaskRect)));
  620. } else {
  621. var spriteToCanvasScale = SpriteToCanvasScale(spritePixelsPerUnit);
  622. _parameters.maskRect = Mathr.ApplyBorder(fullMaskRect, padding * spriteToCanvasScale);
  623. var adjustedBorder = AdjustBorders(sprite.border * spriteToCanvasScale, fullMaskRect);
  624. _parameters.maskBorder = LocalMaskRect(adjustedBorder);
  625. _parameters.maskBorderUV = inner;
  626. }
  627. _parameters.texture = sprite.texture;
  628. _parameters.borderMode = borderMode;
  629. if (borderMode == BorderMode.Tiled)
  630. _parameters.tileRepeat = MaskRepeat(sprite, spritePixelsPerUnit, _parameters.maskBorder);
  631. }
  632. static Vector4 AdjustBorders(Vector4 border, Vector4 rect) {
  633. // Copied from Unity's Image.
  634. var size = Mathr.Size(rect);
  635. for (int axis = 0; axis <= 1; axis++) {
  636. // If the rect is smaller than the combined borders, then there's not room for
  637. // the borders at their normal size. In order to avoid artefacts with overlapping
  638. // borders, we scale the borders down to fit.
  639. float combinedBorders = border[axis] + border[axis + 2];
  640. if (size[axis] < combinedBorders && combinedBorders != 0) {
  641. float borderScaleRatio = size[axis] / combinedBorders;
  642. border[axis] *= borderScaleRatio;
  643. border[axis + 2] *= borderScaleRatio;
  644. }
  645. }
  646. return border;
  647. }
  648. void CalculateTextureBased(Texture texture, Rect uvRect) {
  649. FillCommonParameters();
  650. _parameters.maskRect = LocalMaskRect(Vector4.zero);
  651. _parameters.maskRectUV = Mathr.ToVector(uvRect);
  652. _parameters.texture = texture;
  653. _parameters.borderMode = BorderMode.Simple;
  654. }
  655. void CalculateSolidFill() {
  656. CalculateTextureBased(null, DefaultUVRect);
  657. }
  658. void FillCommonParameters() {
  659. _parameters.worldToMask = WorldToMask();
  660. _parameters.maskChannelWeights = _channelWeights;
  661. _parameters.invertMask = _invertMask;
  662. _parameters.invertOutsides = _invertOutsides;
  663. }
  664. float SpriteToCanvasScale(float spritePixelsPerUnit) {
  665. var canvasPixelsPerUnit = canvas ? canvas.referencePixelsPerUnit : 100;
  666. return canvasPixelsPerUnit / spritePixelsPerUnit;
  667. }
  668. Matrix4x4 WorldToMask() {
  669. return maskTransform.worldToLocalMatrix * canvas.rootCanvas.transform.localToWorldMatrix;
  670. }
  671. Vector4 LocalMaskRect(Vector4 border) {
  672. return Mathr.ApplyBorder(Mathr.ToVector(maskTransform.rect), border);
  673. }
  674. Vector2 MaskRepeat(Sprite sprite, float spritePixelsPerUnit, Vector4 centralPart) {
  675. var textureCenter = Mathr.ApplyBorder(Mathr.ToVector(sprite.rect), sprite.border);
  676. return Mathr.Div(Mathr.Size(centralPart) * SpriteToCanvasScale(spritePixelsPerUnit), Mathr.Size(textureCenter));
  677. }
  678. void WarnIfDefaultShaderIsNotSet() {
  679. if (!_defaultShader)
  680. Debug.LogWarning("SoftMask may not work because its defaultShader is not set", this);
  681. }
  682. void Set<T>(ref T field, T value) {
  683. field = value;
  684. _dirty = true;
  685. }
  686. void SetShader(ref Shader field, Shader value, bool warnIfNotSet = true) {
  687. if (field != value) {
  688. field = value;
  689. if (warnIfNotSet)
  690. WarnIfDefaultShaderIsNotSet();
  691. DestroyMaterials();
  692. InvalidateChildren();
  693. }
  694. }
  695. static readonly List<SoftMask> s_masks = new List<SoftMask>();
  696. static readonly List<SoftMaskable> s_maskables = new List<SoftMaskable>();
  697. class MaterialReplacerImpl : IMaterialReplacer {
  698. readonly SoftMask _owner;
  699. public MaterialReplacerImpl(SoftMask owner) {
  700. // Pass whole owner instead of just shaders because they can be changed dynamically.
  701. _owner = owner;
  702. }
  703. public int order { get { return 0; } }
  704. public Material Replace(Material original) {
  705. if (original == null || original.HasDefaultUIShader())
  706. return Replace(original, _owner._defaultShader);
  707. #if UNITY_5_4_OR_NEWER
  708. else if (original.HasDefaultETC1UIShader())
  709. return Replace(original, _owner._defaultETC1Shader);
  710. #endif
  711. else if (original.SupportsSoftMask())
  712. return new Material(original);
  713. else
  714. return null;
  715. }
  716. static Material Replace(Material original, Shader defaultReplacementShader) {
  717. var replacement = defaultReplacementShader
  718. ? new Material(defaultReplacementShader)
  719. : null;
  720. if (replacement && original)
  721. replacement.CopyPropertiesFromMaterial(original);
  722. return replacement;
  723. }
  724. }
  725. // Various operations on a Rect represented as Vector4 (xMin, yMin, xMax, yMax).
  726. static class Mathr {
  727. public static Vector4 ToVector(Rect r) { return new Vector4(r.xMin, r.yMin, r.xMax, r.yMax); }
  728. public static Vector4 Div(Vector4 v, Vector2 s) { return new Vector4(v.x / s.x, v.y / s.y, v.z / s.x, v.w / s.y); }
  729. public static Vector2 Div(Vector2 v, Vector2 s) { return new Vector2(v.x / s.x, v.y / s.y); }
  730. public static Vector4 Mul(Vector4 v, Vector2 s) { return new Vector4(v.x * s.x, v.y * s.y, v.z * s.x, v.w * s.y); }
  731. public static Vector2 Size(Vector4 r) { return new Vector2(r.z - r.x, r.w - r.y); }
  732. public static Vector4 Move(Vector4 v, Vector2 o) { return new Vector4(v.x + o.x, v.y + o.y, v.z + o.x, v.w + o.y); }
  733. public static Vector4 BorderOf(Vector4 outer, Vector4 inner) {
  734. return new Vector4(inner.x - outer.x, inner.y - outer.y, outer.z - inner.z, outer.w - inner.w);
  735. }
  736. public static Vector4 ApplyBorder(Vector4 v, Vector4 b) {
  737. return new Vector4(v.x + b.x, v.y + b.y, v.z - b.z, v.w - b.w);
  738. }
  739. public static Vector2 Min(Vector4 r) { return new Vector2(r.x, r.y); }
  740. public static Vector2 Max(Vector4 r) { return new Vector2(r.z, r.w); }
  741. public static Vector2 Remap(Vector2 c, Vector4 from, Vector4 to) {
  742. var fromSize = Max(from) - Min(from);
  743. var toSize = Max(to) - Min(to);
  744. return Vector2.Scale(Div((c - Min(from)), fromSize), toSize) + Min(to);
  745. }
  746. public static bool Inside(Vector2 v, Vector4 r) {
  747. return v.x >= r.x && v.y >= r.y && v.x <= r.z && v.y <= r.w;
  748. }
  749. }
  750. struct MaterialParameters {
  751. public Vector4 maskRect;
  752. public Vector4 maskBorder;
  753. public Vector4 maskRectUV;
  754. public Vector4 maskBorderUV;
  755. public Vector2 tileRepeat;
  756. public Color maskChannelWeights;
  757. public Matrix4x4 worldToMask;
  758. public Texture texture;
  759. public BorderMode borderMode;
  760. public bool invertMask;
  761. public bool invertOutsides;
  762. public Texture activeTexture { get { return texture ? texture : Texture2D.whiteTexture; } }
  763. public enum SampleMaskResult { Success, NonReadable, NonTexture2D }
  764. public SampleMaskResult SampleMask(Vector2 localPos, out float mask) {
  765. mask = 0;
  766. var texture2D = texture as Texture2D;
  767. if (!texture2D)
  768. return SampleMaskResult.NonTexture2D;
  769. var uv = XY2UV(localPos);
  770. try {
  771. mask = MaskValue(texture2D.GetPixelBilinear(uv.x, uv.y));
  772. return SampleMaskResult.Success;
  773. } catch (UnityException) {
  774. return SampleMaskResult.NonReadable;
  775. }
  776. }
  777. public void Apply(Material mat) {
  778. mat.SetTexture(Ids.SoftMask, activeTexture);
  779. mat.SetVector(Ids.SoftMask_Rect, maskRect);
  780. mat.SetVector(Ids.SoftMask_UVRect, maskRectUV);
  781. mat.SetColor(Ids.SoftMask_ChannelWeights, maskChannelWeights);
  782. mat.SetMatrix(Ids.SoftMask_WorldToMask, worldToMask);
  783. mat.SetFloat(Ids.SoftMask_InvertMask, invertMask ? 1 : 0);
  784. mat.SetFloat(Ids.SoftMask_InvertOutsides, invertOutsides ? 1 : 0);
  785. mat.EnableKeyword("SOFTMASK_SIMPLE", borderMode == BorderMode.Simple);
  786. mat.EnableKeyword("SOFTMASK_SLICED", borderMode == BorderMode.Sliced);
  787. mat.EnableKeyword("SOFTMASK_TILED", borderMode == BorderMode.Tiled);
  788. if (borderMode != BorderMode.Simple) {
  789. mat.SetVector(Ids.SoftMask_BorderRect, maskBorder);
  790. mat.SetVector(Ids.SoftMask_UVBorderRect, maskBorderUV);
  791. if (borderMode == BorderMode.Tiled)
  792. mat.SetVector(Ids.SoftMask_TileRepeat, tileRepeat);
  793. }
  794. }
  795. // The following functions performs the same logic as functions from SoftMask.cginc.
  796. // They implemented it a bit different way, because there is no such convenient
  797. // vector operations in Unity/C# and conditions are much cheaper here.
  798. Vector2 XY2UV(Vector2 localPos) {
  799. switch (borderMode) {
  800. case BorderMode.Simple: return MapSimple(localPos);
  801. case BorderMode.Sliced: return MapBorder(localPos, repeat: false);
  802. case BorderMode.Tiled: return MapBorder(localPos, repeat: true);
  803. default:
  804. Debug.LogAssertion("Unknown BorderMode");
  805. return MapSimple(localPos);
  806. }
  807. }
  808. Vector2 MapSimple(Vector2 localPos) {
  809. return Mathr.Remap(localPos, maskRect, maskRectUV);
  810. }
  811. Vector2 MapBorder(Vector2 localPos, bool repeat) {
  812. return
  813. new Vector2(
  814. Inset(
  815. localPos.x,
  816. maskRect.x, maskBorder.x, maskBorder.z, maskRect.z,
  817. maskRectUV.x, maskBorderUV.x, maskBorderUV.z, maskRectUV.z,
  818. repeat ? tileRepeat.x : 1),
  819. Inset(
  820. localPos.y,
  821. maskRect.y, maskBorder.y, maskBorder.w, maskRect.w,
  822. maskRectUV.y, maskBorderUV.y, maskBorderUV.w, maskRectUV.w,
  823. repeat ? tileRepeat.y : 1));
  824. }
  825. float Inset(float v, float x1, float x2, float u1, float u2, float repeat = 1) {
  826. var w = (x2 - x1);
  827. return Mathf.Lerp(u1, u2, w != 0.0f ? Frac((v - x1) / w * repeat) : 0.0f);
  828. }
  829. float Inset(float v, float x1, float x2, float x3, float x4, float u1, float u2, float u3, float u4, float repeat = 1) {
  830. if (v < x2)
  831. return Inset(v, x1, x2, u1, u2);
  832. else if (v < x3)
  833. return Inset(v, x2, x3, u2, u3, repeat);
  834. else
  835. return Inset(v, x3, x4, u3, u4);
  836. }
  837. float Frac(float v) { return v - Mathf.Floor(v); }
  838. float MaskValue(Color mask) {
  839. var value = mask * maskChannelWeights;
  840. return value.a + value.r + value.g + value.b;
  841. }
  842. static class Ids {
  843. public static readonly int SoftMask = Shader.PropertyToID("_SoftMask");
  844. public static readonly int SoftMask_Rect = Shader.PropertyToID("_SoftMask_Rect");
  845. public static readonly int SoftMask_UVRect = Shader.PropertyToID("_SoftMask_UVRect");
  846. public static readonly int SoftMask_ChannelWeights = Shader.PropertyToID("_SoftMask_ChannelWeights");
  847. public static readonly int SoftMask_WorldToMask = Shader.PropertyToID("_SoftMask_WorldToMask");
  848. public static readonly int SoftMask_BorderRect = Shader.PropertyToID("_SoftMask_BorderRect");
  849. public static readonly int SoftMask_UVBorderRect = Shader.PropertyToID("_SoftMask_UVBorderRect");
  850. public static readonly int SoftMask_TileRepeat = Shader.PropertyToID("_SoftMask_TileRepeat");
  851. public static readonly int SoftMask_InvertMask = Shader.PropertyToID("_SoftMask_InvertMask");
  852. public static readonly int SoftMask_InvertOutsides = Shader.PropertyToID("_SoftMask_InvertOutsides");
  853. }
  854. }
  855. struct Diagnostics {
  856. SoftMask _softMask;
  857. public Diagnostics(SoftMask softMask) { _softMask = softMask; }
  858. public Errors PollErrors() {
  859. var softMask = _softMask; // for use in lambda
  860. var result = Errors.NoError;
  861. softMask.GetComponentsInChildren(s_maskables);
  862. using (new ClearListAtExit<SoftMaskable>(s_maskables))
  863. if (s_maskables.Any(m => ReferenceEquals(m.mask, softMask) && m.shaderIsNotSupported))
  864. result |= Errors.UnsupportedShaders;
  865. if (ThereAreNestedMasks())
  866. result |= Errors.NestedMasks;
  867. result |= CheckSprite(sprite);
  868. result |= CheckImage();
  869. result |= CheckTexture();
  870. return result;
  871. }
  872. public static Errors CheckSprite(Sprite sprite) {
  873. var result = Errors.NoError;
  874. if (!sprite) return result;
  875. if (sprite.packed && sprite.packingMode == SpritePackingMode.Tight)
  876. result |= Errors.TightPackedSprite;
  877. if (sprite.associatedAlphaSplitTexture)
  878. result |= Errors.AlphaSplitSprite;
  879. return result;
  880. }
  881. Image image { get { return _softMask.DeduceSourceParameters().image; } }
  882. Sprite sprite { get { return _softMask.DeduceSourceParameters().sprite; } }
  883. Texture texture { get { return _softMask.DeduceSourceParameters().texture; } }
  884. bool ThereAreNestedMasks() {
  885. var softMask = _softMask; // for use in lambda
  886. var result = false;
  887. using (new ClearListAtExit<SoftMask>(s_masks)) {
  888. softMask.GetComponentsInParent(false, s_masks);
  889. result |= s_masks.Any(x => AreCompeting(softMask, x));
  890. softMask.GetComponentsInChildren(false, s_masks);
  891. result |= s_masks.Any(x => AreCompeting(softMask, x));
  892. }
  893. return result;
  894. }
  895. Errors CheckImage() {
  896. var result = Errors.NoError;
  897. if (!_softMask.isBasedOnGraphic) return result;
  898. if (image && !IsImageTypeSupported(image.type))
  899. result |= Errors.UnsupportedImageType;
  900. return result;
  901. }
  902. Errors CheckTexture() {
  903. var result = Errors.NoError;
  904. if (_softMask.isUsingRaycastFiltering && texture) {
  905. var texture2D = texture as Texture2D;
  906. if (!texture2D)
  907. result |= Errors.UnreadableRenderTexture;
  908. else if (!IsReadable(texture2D))
  909. result |= Errors.UnreadableTexture;
  910. }
  911. return result;
  912. }
  913. static bool AreCompeting(SoftMask softMask, SoftMask other) {
  914. Assert.IsNotNull(other);
  915. return softMask.isMaskingEnabled
  916. && softMask != other
  917. && other.isMaskingEnabled
  918. && softMask.canvas.rootCanvas == other.canvas.rootCanvas
  919. && !SelectChild(softMask, other).canvas.overrideSorting;
  920. }
  921. static T SelectChild<T>(T first, T second) where T : Component {
  922. Assert.IsNotNull(first);
  923. Assert.IsNotNull(second);
  924. return first.transform.IsChildOf(second.transform) ? first : second;
  925. }
  926. static bool IsReadable(Texture2D texture) {
  927. try {
  928. texture.GetPixel(0, 0);
  929. return true;
  930. } catch (UnityException) {
  931. return false;
  932. }
  933. }
  934. }
  935. struct WarningReporter {
  936. UnityEngine.Object _owner;
  937. Texture _lastReadTexture;
  938. Sprite _lastUsedSprite;
  939. Sprite _lastUsedImageSprite;
  940. Image.Type _lastUsedImageType;
  941. public WarningReporter(UnityEngine.Object owner) {
  942. _owner = owner;
  943. _lastReadTexture = null;
  944. _lastUsedSprite = null;
  945. _lastUsedImageSprite = null;
  946. _lastUsedImageType = Image.Type.Simple;
  947. }
  948. public void TextureRead(Texture texture, MaterialParameters.SampleMaskResult sampleResult) {
  949. if (_lastReadTexture == texture)
  950. return;
  951. _lastReadTexture = texture;
  952. switch (sampleResult) {
  953. case MaterialParameters.SampleMaskResult.NonReadable:
  954. Debug.LogErrorFormat(_owner,
  955. "Raycast Threshold greater than 0 can't be used on Soft Mask with texture '{0}' because "
  956. + "it's not readable. You can make the texture readable in the Texture Import Settings.",
  957. texture.name);
  958. break;
  959. case MaterialParameters.SampleMaskResult.NonTexture2D:
  960. Debug.LogErrorFormat(_owner,
  961. "Raycast Threshold greater than 0 can't be used on Soft Mask with texture '{0}' because "
  962. + "it's not a Texture2D. Raycast Threshold may be used only with regular 2D textures.",
  963. texture.name);
  964. break;
  965. }
  966. }
  967. public void SpriteUsed(Sprite sprite, Errors errors) {
  968. if (_lastUsedSprite == sprite)
  969. return;
  970. _lastUsedSprite = sprite;
  971. if ((errors & Errors.TightPackedSprite) != 0)
  972. Debug.LogError("SoftMask doesn't support tight packed sprites", _owner);
  973. if ((errors & Errors.AlphaSplitSprite) != 0)
  974. Debug.LogError("SoftMask doesn't support sprites with an alpha split texture", _owner);
  975. }
  976. public void ImageUsed(Image image) {
  977. if (!image) {
  978. _lastUsedImageSprite = null;
  979. _lastUsedImageType = Image.Type.Simple;
  980. return;
  981. }
  982. if (_lastUsedImageSprite == image.sprite && _lastUsedImageType == image.type)
  983. return;
  984. _lastUsedImageSprite = image.sprite;
  985. _lastUsedImageType = image.type;
  986. if (!image)
  987. return;
  988. if (IsImageTypeSupported(image.type))
  989. return;
  990. Debug.LogErrorFormat(_owner,
  991. "SoftMask doesn't support image type {0}. Image type Simple will be used.",
  992. image.type);
  993. }
  994. }
  995. }
  996. }