Seven is the number.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

312 lines
11 KiB

3 years ago
  1. using System.Collections.Generic;
  2. using LeTai.Effects;
  3. using UnityEngine;
  4. using UnityEngine.Rendering;
  5. namespace LeTai.TrueShadow
  6. {
  7. public class ShadowFactory
  8. {
  9. private static ShadowFactory instance;
  10. public static ShadowFactory Instance => instance ?? (instance = new ShadowFactory());
  11. readonly Dictionary<int, ShadowContainer> shadowCache =
  12. new Dictionary<int, ShadowContainer>();
  13. readonly CommandBuffer cmd;
  14. readonly MaterialPropertyBlock materialProps;
  15. readonly ScalableBlur blurProcessor;
  16. readonly ScalableBlurConfig blurConfig;
  17. Material cutoutMaterial;
  18. Material imprintPostProcessMaterial;
  19. Material shadowPostProcessMaterial;
  20. Material CutoutMaterial =>
  21. cutoutMaterial ? cutoutMaterial : cutoutMaterial = new Material(Shader.Find("Hidden/TrueShadow/Cutout"));
  22. Material ImprintPostProcessMaterial =>
  23. imprintPostProcessMaterial
  24. ? imprintPostProcessMaterial
  25. : imprintPostProcessMaterial = new Material(Shader.Find("Hidden/TrueShadow/ImprintPostProcess"));
  26. Material ShadowPostProcessMaterial =>
  27. shadowPostProcessMaterial
  28. ? shadowPostProcessMaterial
  29. : shadowPostProcessMaterial = new Material(Shader.Find("Hidden/TrueShadow/PostProcess"));
  30. private ShadowFactory()
  31. {
  32. cmd = new CommandBuffer {name = "Shadow Commands"};
  33. materialProps = new MaterialPropertyBlock();
  34. materialProps.SetVector(ShaderId.CLIP_RECT,
  35. new Vector4(float.NegativeInfinity, float.NegativeInfinity,
  36. float.PositiveInfinity, float.PositiveInfinity));
  37. materialProps.SetInt(ShaderId.COLOR_MASK, (int) ColorWriteMask.All); // Render shadow even if mask hide graphic
  38. ShaderProperties.Init(8);
  39. blurConfig = ScriptableObject.CreateInstance<ScalableBlurConfig>();
  40. blurConfig.hideFlags = HideFlags.HideAndDontSave;
  41. blurProcessor = new ScalableBlur();
  42. blurProcessor.Configure(blurConfig);
  43. }
  44. ~ShadowFactory()
  45. {
  46. cmd.Dispose();
  47. Utility.SafeDestroy(blurConfig);
  48. Utility.SafeDestroy(cutoutMaterial);
  49. Utility.SafeDestroy(imprintPostProcessMaterial);
  50. }
  51. #if LETAI_TRUESHADOW_DEBUG
  52. RenderTexture debugTexture;
  53. #endif
  54. // public int createdContainerCount;
  55. // public int releasedContainerCount;
  56. internal void Get(ShadowSettingSnapshot snapshot, ref ShadowContainer container)
  57. {
  58. if (float.IsNaN(snapshot.dimensions.x) || snapshot.dimensions.x < 1 ||
  59. float.IsNaN(snapshot.dimensions.y) || snapshot.dimensions.y < 1)
  60. {
  61. ReleaseContainer(container);
  62. return;
  63. }
  64. #if LETAI_TRUESHADOW_DEBUG
  65. RenderTexture.ReleaseTemporary(debugTexture);
  66. if (snapshot.shadow.alwaysRender)
  67. debugTexture = GenerateShadow(snapshot).Texture;
  68. #endif
  69. // Each request need a coresponding shadow texture
  70. // Texture may be shared by multiple elements
  71. // Texture are released when no longer used by any element
  72. // ShadowContainer keep track of texture and their usage
  73. int requestHash = snapshot.GetHashCode();
  74. // Case: requester can keep the same texture
  75. if (container?.requestHash == requestHash)
  76. return;
  77. ReleaseContainer(container);
  78. if (shadowCache.TryGetValue(requestHash, out var existingContainer))
  79. {
  80. // Case: requester got texture from someone else
  81. existingContainer.RefCount++;
  82. container = existingContainer;
  83. }
  84. else
  85. {
  86. // Case: requester got new unique texture
  87. container = shadowCache[requestHash] = GenerateShadow(snapshot);
  88. // Debug.Log($"Created new container for request\t{requestHash}\tTotal Created: {++createdContainerCount}\t Alive: {createdContainerCount - releasedContainerCount}");
  89. }
  90. }
  91. internal void ReleaseContainer(ShadowContainer container)
  92. {
  93. if (container == null)
  94. return;
  95. if (--container.RefCount > 0)
  96. return;
  97. RenderTexture.ReleaseTemporary(container.Texture);
  98. shadowCache.Remove(container.requestHash);
  99. // Debug.Log($"Released container for request\t{container.requestHash}\tTotal Released: {++releasedContainerCount}\t Alive: {createdContainerCount - releasedContainerCount}");
  100. }
  101. static readonly Rect UNIT_RECT = new Rect(0, 0, 1, 1);
  102. ShadowContainer GenerateShadow(ShadowSettingSnapshot snapshot)
  103. {
  104. // return GenColoredTexture(request.GetHashCode());
  105. cmd.Clear();
  106. cmd.BeginSample("TrueShadow:Capture");
  107. var bounds = snapshot.shadow.SpriteMesh.bounds;
  108. var misalignment = CalcMisalignment(snapshot.canvas, snapshot.canvasRt, snapshot.shadow.RectTransform, bounds);
  109. var padding = Mathf.CeilToInt(snapshot.size);
  110. var imprintViewW = Mathf.RoundToInt(snapshot.dimensions.x + misalignment.bothSS.x);
  111. var imprintViewH = Mathf.RoundToInt(snapshot.dimensions.y + misalignment.bothSS.y);
  112. var tw = imprintViewW + padding * 2;
  113. var th = imprintViewH + padding * 2;
  114. var shadowTex = RenderTexture.GetTemporary(tw, th, 0, RenderTextureFormat.ARGB32);
  115. var imprintTexDesc = shadowTex.descriptor;
  116. imprintTexDesc.msaaSamples = snapshot.shouldAntialiasImprint ? Mathf.Max(1, QualitySettings.antiAliasing) : 1;
  117. var imprintTex = RenderTexture.GetTemporary(imprintTexDesc);
  118. RenderTexture imprintTexProcessed = null;
  119. bool needProcessImprint = snapshot.shadow.IgnoreCasterColor || snapshot.shadow.Inset;
  120. if (needProcessImprint)
  121. imprintTexProcessed = RenderTexture.GetTemporary(imprintTexDesc);
  122. var texture = snapshot.shadow.Content;
  123. if (texture)
  124. materialProps.SetTexture(ShaderId.MAIN_TEX, texture);
  125. else
  126. materialProps.SetTexture(ShaderId.MAIN_TEX, Texture2D.whiteTexture);
  127. cmd.SetRenderTarget(imprintTex);
  128. cmd.ClearRenderTarget(true, true, snapshot.shadow.ClearColor);
  129. cmd.SetViewport(new Rect(padding, padding, imprintViewW, imprintViewH));
  130. var imprintBoundMin = (Vector2) bounds.min - misalignment.minLS;
  131. var imprintBoundMax = (Vector2) bounds.max + misalignment.maxLS;
  132. cmd.SetViewProjectionMatrices(
  133. Matrix4x4.identity,
  134. Matrix4x4.Ortho(imprintBoundMin.x, imprintBoundMax.x,
  135. imprintBoundMin.y, imprintBoundMax.y,
  136. -1, 1)
  137. );
  138. snapshot.shadow.ModifyShadowCastingMesh(snapshot.shadow.SpriteMesh);
  139. snapshot.shadow.ModifyShadowCastingMaterialProperties(materialProps);
  140. cmd.DrawMesh(snapshot.shadow.SpriteMesh,
  141. Matrix4x4.identity,
  142. snapshot.shadow.GetShadowCastingMaterial(),
  143. 0, 0,
  144. materialProps);
  145. if (needProcessImprint)
  146. {
  147. ImprintPostProcessMaterial.SetKeyword("BLEACH", snapshot.shadow.IgnoreCasterColor);
  148. ImprintPostProcessMaterial.SetKeyword("INSET", snapshot.shadow.Inset);
  149. cmd.Blit(imprintTex, imprintTexProcessed, ImprintPostProcessMaterial);
  150. }
  151. cmd.EndSample("TrueShadow:Capture");
  152. var needPostProcess = snapshot.shadow.Spread > 1e-3;
  153. cmd.BeginSample("TrueShadow:Cast");
  154. RenderTexture blurSrc = needProcessImprint ? imprintTexProcessed : imprintTex;
  155. RenderTexture blurDst;
  156. if (needPostProcess)
  157. blurDst = RenderTexture.GetTemporary(shadowTex.descriptor);
  158. else
  159. blurDst = shadowTex;
  160. if (snapshot.size < 1e-2)
  161. {
  162. cmd.Blit(blurSrc, blurDst);
  163. }
  164. else
  165. {
  166. blurConfig.Strength = snapshot.size;
  167. blurProcessor.Blur(cmd, blurSrc, UNIT_RECT, blurDst);
  168. }
  169. cmd.EndSample("TrueShadow:Cast");
  170. var relativeOffset = new Vector2(snapshot.canvasRelativeOffset.x / tw,
  171. snapshot.canvasRelativeOffset.y / th);
  172. var overflowAlpha = snapshot.shadow.Inset ? 1 : 0;
  173. if (needPostProcess)
  174. {
  175. cmd.BeginSample("TrueShadow:PostProcess");
  176. ShadowPostProcessMaterial.SetTexture(ShaderId.SHADOW_TEX, blurDst);
  177. ShadowPostProcessMaterial.SetVector(ShaderId.OFFSET, relativeOffset);
  178. ShadowPostProcessMaterial.SetFloat(ShaderId.OVERFLOW_ALPHA, overflowAlpha);
  179. ShadowPostProcessMaterial.SetFloat(ShaderId.ALPHA_MULTIPLIER,
  180. 1f / Mathf.Max(1e-6f, 1f - snapshot.shadow.Spread));
  181. cmd.SetViewport(UNIT_RECT);
  182. cmd.Blit(blurSrc, shadowTex, ShadowPostProcessMaterial);
  183. cmd.EndSample("TrueShadow:PostProcess");
  184. }
  185. else if (snapshot.shadow.Cutout)
  186. {
  187. cmd.BeginSample("TrueShadow:Cutout");
  188. CutoutMaterial.SetVector(ShaderId.OFFSET, relativeOffset);
  189. CutoutMaterial.SetFloat(ShaderId.OVERFLOW_ALPHA, overflowAlpha);
  190. cmd.SetViewport(UNIT_RECT);
  191. cmd.Blit(blurSrc, shadowTex, CutoutMaterial);
  192. cmd.EndSample("TrueShadow:Cutout");
  193. }
  194. Graphics.ExecuteCommandBuffer(cmd);
  195. RenderTexture.ReleaseTemporary(imprintTex);
  196. RenderTexture.ReleaseTemporary(blurSrc);
  197. if (needPostProcess)
  198. RenderTexture.ReleaseTemporary(blurDst);
  199. return new ShadowContainer(shadowTex, snapshot, padding, misalignment.minLS);
  200. }
  201. readonly struct PixelMisalignment
  202. {
  203. public readonly Vector2 bothSS;
  204. public readonly Vector2 minLS;
  205. public readonly Vector2 maxLS;
  206. public PixelMisalignment(Vector2 bothSS, Vector2 minLS, Vector2 maxLS)
  207. {
  208. this.bothSS = bothSS;
  209. this.minLS = minLS;
  210. this.maxLS = maxLS;
  211. }
  212. }
  213. PixelMisalignment CalcMisalignment(Canvas canvas, RectTransform canvasRt, RectTransform casterRt, Bounds meshBound)
  214. {
  215. PixelMisalignment misalignment;
  216. if (canvas.renderMode == RenderMode.WorldSpace)
  217. {
  218. misalignment = new PixelMisalignment();
  219. }
  220. else
  221. {
  222. var referenceCamera = canvas.renderMode == RenderMode.ScreenSpaceCamera ? canvas.worldCamera : null;
  223. var pxMisalignmentAtMin = casterRt.LocalToScreenPoint(meshBound.min, referenceCamera).Frac();
  224. var pxMisalignmentAtMax =
  225. Vector2.one - casterRt.LocalToScreenPoint(meshBound.max, referenceCamera).Frac();
  226. if (pxMisalignmentAtMax.x > 1 - 1e-5)
  227. pxMisalignmentAtMax.x = 0;
  228. if (pxMisalignmentAtMax.y > 1 - 1e-5)
  229. pxMisalignmentAtMax.y = 0;
  230. misalignment = new PixelMisalignment(
  231. pxMisalignmentAtMin + pxMisalignmentAtMax,
  232. canvasRt.ScreenToCanvasSize(pxMisalignmentAtMin, referenceCamera),
  233. canvasRt.ScreenToCanvasSize(pxMisalignmentAtMax, referenceCamera)
  234. );
  235. }
  236. return misalignment;
  237. }
  238. RenderTexture GenColoredTexture(int hash)
  239. {
  240. var tex = new Texture2D(1, 1);
  241. tex.SetPixels32(new[] {new Color32((byte) (hash >> 8), (byte) (hash >> 16), (byte) (hash >> 24), 255)});
  242. tex.Apply();
  243. var rt = RenderTexture.GetTemporary(1, 1);
  244. Graphics.Blit(tex, rt);
  245. return rt;
  246. }
  247. }
  248. }