HighlightEffectOccluderManager.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. using System;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. using UnityEngine.Rendering;
  5. namespace HighlightPlus {
  6. public partial class HighlightEffect : MonoBehaviour {
  7. static readonly List<HighlightSeeThroughOccluder> occluders = new List<HighlightSeeThroughOccluder>();
  8. static readonly Dictionary<Camera, int> occludersFrameCount = new Dictionary<Camera, int>();
  9. static Material fxMatSeeThroughOccluder, fxMatDepthWrite;
  10. static RaycastHit[] hits;
  11. static Collider[] colliders;
  12. /// <summary>
  13. /// True if the see-through is cancelled by an occluder using raycast method
  14. /// </summary>
  15. public bool IsSeeThroughOccluded(Camera cam) {
  16. // Compute bounds
  17. Bounds bounds = new Bounds();
  18. for (int r = 0; r < rms.Length; r++) {
  19. if (rms[r].renderer != null) {
  20. if (bounds.size.x == 0) {
  21. bounds = rms[r].renderer.bounds;
  22. } else {
  23. bounds.Encapsulate(rms[r].renderer.bounds);
  24. }
  25. }
  26. }
  27. Vector3 pos = bounds.center;
  28. Vector3 camPos = cam.transform.position;
  29. Vector3 offset = pos - camPos;
  30. float maxDistance = Vector3.Distance(pos, camPos);
  31. if (hits == null || hits.Length == 0) {
  32. hits = new RaycastHit[64];
  33. }
  34. int occludersCount = occluders.Count;
  35. int hitCount = Physics.BoxCastNonAlloc(pos - offset, bounds.extents * 0.9f, offset.normalized, hits, Quaternion.identity, maxDistance);
  36. for (int k = 0; k < hitCount; k++) {
  37. for (int j = 0; j < occludersCount; j++) {
  38. if (hits[k].collider.transform == occluders[j].transform) {
  39. return true;
  40. }
  41. }
  42. }
  43. return false;
  44. }
  45. public static void RegisterOccluder(HighlightSeeThroughOccluder occluder) {
  46. if (!occluders.Contains(occluder)) {
  47. occluders.Add(occluder);
  48. }
  49. }
  50. public static void UnregisterOccluder(HighlightSeeThroughOccluder occluder) {
  51. if (occluders.Contains(occluder)) {
  52. occluders.Remove(occluder);
  53. }
  54. }
  55. /// <summary>
  56. /// Test see-through occluders.
  57. /// </summary>
  58. /// <param name="cam">The camera to be tested</param>
  59. /// <returns>Returns true if there's no raycast-based occluder cancelling the see-through effect</returns>
  60. public bool RenderSeeThroughOccluders(CommandBuffer cb, Camera cam) {
  61. int occludersCount = occluders.Count;
  62. if (occludersCount == 0 || rmsCount == 0) return true;
  63. bool useRayCastCheck = false;
  64. // Check if raycast method is needed
  65. for (int k = 0; k < occludersCount; k++) {
  66. HighlightSeeThroughOccluder occluder = occluders[k];
  67. if (occluder == null || !occluder.isActiveAndEnabled) continue;
  68. if (occluder.detectionMethod == DetectionMethod.RayCast) {
  69. useRayCastCheck = true;
  70. break;
  71. }
  72. }
  73. if (useRayCastCheck) {
  74. if (IsSeeThroughOccluded(cam)) return false;
  75. }
  76. // do not render see-through occluders more than once this frame per camera (there can be many highlight effect scripts in the scene, we only need writing to stencil once)
  77. int lastFrameCount;
  78. occludersFrameCount.TryGetValue(cam, out lastFrameCount);
  79. int currentFrameCount = Time.frameCount;
  80. if (currentFrameCount == lastFrameCount) return true;
  81. occludersFrameCount[cam] = currentFrameCount;
  82. if (fxMatSeeThroughOccluder == null) {
  83. InitMaterial(ref fxMatSeeThroughOccluder, "HighlightPlus/Geometry/SeeThroughOccluder");
  84. if (fxMatSeeThroughOccluder == null) return true;
  85. }
  86. if (fxMatDepthWrite == null) {
  87. InitMaterial(ref fxMatDepthWrite, "HighlightPlus/Geometry/JustDepth");
  88. if (fxMatDepthWrite == null) return true;
  89. }
  90. for (int k = 0; k < occludersCount; k++) {
  91. HighlightSeeThroughOccluder occluder = occluders[k];
  92. if (occluder == null || !occluder.isActiveAndEnabled) continue;
  93. if (occluder.detectionMethod == DetectionMethod.Stencil) {
  94. if (occluder.meshData == null) continue;
  95. int meshDataLength = occluder.meshData.Length;
  96. // Per renderer
  97. for (int m = 0; m < meshDataLength; m++) {
  98. // Per submesh
  99. Renderer renderer = occluder.meshData[m].renderer;
  100. if (renderer.isVisible) {
  101. for (int s = 0; s < occluder.meshData[m].subMeshCount; s++) {
  102. cb.DrawRenderer(renderer, occluder.mode == OccluderMode.BlocksSeeThrough ? fxMatSeeThroughOccluder : fxMatDepthWrite, s);
  103. }
  104. }
  105. }
  106. }
  107. }
  108. return true;
  109. }
  110. bool CheckOcclusion(Camera cam) {
  111. if (!perCameraOcclusionData.TryGetValue(cam, out PerCameraOcclusionData occlusionData)) {
  112. occlusionData = new PerCameraOcclusionData();
  113. perCameraOcclusionData[cam] = occlusionData;
  114. }
  115. float now = Time.time;
  116. int frameCount = Time.frameCount; // ensure all cameras are checked this frame
  117. if (now - occlusionData.checkLastTime < seeThroughOccluderCheckInterval && Application.isPlaying && occlusionData.occlusionRenderFrame != frameCount) return occlusionData.lastOcclusionTestResult;
  118. occlusionData.checkLastTime = now;
  119. occlusionData.occlusionRenderFrame = frameCount;
  120. if (rms == null || rms.Length == 0 || rms[0].renderer == null) return false;
  121. Vector3 camPos = cam.transform.position;
  122. Quaternion quaternionIdentity = Quaternion.identity;
  123. if (colliders == null || colliders.Length == 0) {
  124. colliders = new Collider[1];
  125. }
  126. if (seeThroughOccluderCheckIndividualObjects) {
  127. for (int r = 0; r < rms.Length; r++) {
  128. if (rms[r].renderer != null) {
  129. Bounds bounds = rms[r].renderer.bounds;
  130. Vector3 pos = bounds.center;
  131. float maxDistance = Vector3.Distance(pos, camPos);
  132. Vector3 extents = bounds.extents * seeThroughOccluderThreshold;
  133. if (Physics.OverlapBoxNonAlloc(pos, extents, colliders, quaternionIdentity, seeThroughOccluderMask) > 0) {
  134. occlusionData.lastOcclusionTestResult = true;
  135. return true;
  136. }
  137. if (Physics.BoxCast(pos, extents, (camPos - pos).normalized, quaternionIdentity, maxDistance, seeThroughOccluderMask)) {
  138. occlusionData.lastOcclusionTestResult = true;
  139. return true;
  140. }
  141. }
  142. }
  143. occlusionData.lastOcclusionTestResult = false;
  144. return false;
  145. } else {
  146. // Compute combined bounds
  147. Bounds bounds = rms[0].renderer.bounds;
  148. for (int r = 1; r < rms.Length; r++) {
  149. if (rms[r].renderer != null) {
  150. bounds.Encapsulate(rms[r].renderer.bounds);
  151. }
  152. }
  153. Vector3 pos = bounds.center;
  154. Vector3 extents = bounds.extents * seeThroughOccluderThreshold;
  155. if (Physics.OverlapBoxNonAlloc(pos, extents, colliders, quaternionIdentity, seeThroughOccluderMask) > 0) {
  156. occlusionData.lastOcclusionTestResult = true;
  157. return true;
  158. }
  159. float maxDistance = Vector3.Distance(pos, camPos);
  160. occlusionData.lastOcclusionTestResult = Physics.BoxCast(pos, extents, (camPos - pos).normalized, quaternionIdentity, maxDistance, seeThroughOccluderMask);
  161. return occlusionData.lastOcclusionTestResult;
  162. }
  163. }
  164. const int MAX_OCCLUDER_HITS = 50;
  165. static RaycastHit[] occluderHits;
  166. void AddWithoutRepetition(List<Renderer> target, List<Renderer> source) {
  167. int sourceCount = source.Count;
  168. for (int k = 0; k < sourceCount; k++) {
  169. Renderer entry = source[k];
  170. if (entry != null && !target.Contains(entry) && ValidRenderer(entry)) {
  171. target.Add(entry);
  172. }
  173. }
  174. }
  175. void CheckOcclusionAccurate(CommandBuffer cbuf, Camera cam) {
  176. if (!perCameraOcclusionData.TryGetValue(cam, out PerCameraOcclusionData occlusionData)) {
  177. occlusionData = new PerCameraOcclusionData();
  178. perCameraOcclusionData[cam] = occlusionData;
  179. }
  180. float now = Time.time;
  181. int frameCount = Time.frameCount; // ensure all cameras are checked this frame
  182. bool reuse = now - occlusionData.checkLastTime < seeThroughOccluderCheckInterval && Application.isPlaying && occlusionData.occlusionRenderFrame != frameCount;
  183. if (!reuse) {
  184. if (rms == null || rms.Length == 0 || rms[0].renderer == null) return;
  185. occlusionData.checkLastTime = now;
  186. occlusionData.occlusionRenderFrame = frameCount;
  187. Quaternion quaternionIdentity = Quaternion.identity;
  188. Vector3 camPos = cam.transform.position;
  189. occlusionData.cachedOccluders.Clear();
  190. if (occluderHits == null || occluderHits.Length < MAX_OCCLUDER_HITS) {
  191. occluderHits = new RaycastHit[MAX_OCCLUDER_HITS];
  192. }
  193. if (seeThroughOccluderCheckIndividualObjects) {
  194. for (int r = 0; r < rms.Length; r++) {
  195. if (rms[r].renderer != null) {
  196. Bounds bounds = rms[r].renderer.bounds;
  197. Vector3 pos = bounds.center;
  198. float maxDistance = Vector3.Distance(pos, camPos);
  199. int numOccluderHits = Physics.BoxCastNonAlloc(pos, bounds.extents * seeThroughOccluderThreshold, (camPos - pos).normalized, occluderHits, quaternionIdentity, maxDistance, seeThroughOccluderMask);
  200. for (int k = 0; k < numOccluderHits; k++) {
  201. occluderHits[k].collider.transform.root.GetComponentsInChildren(tempRR);
  202. AddWithoutRepetition(occlusionData.cachedOccluders, tempRR);
  203. }
  204. }
  205. }
  206. } else {
  207. // Compute combined bounds
  208. Bounds bounds = rms[0].renderer.bounds;
  209. for (int r = 1; r < rms.Length; r++) {
  210. if (rms[r].renderer != null) {
  211. bounds.Encapsulate(rms[r].renderer.bounds);
  212. }
  213. }
  214. Vector3 pos = bounds.center;
  215. float maxDistance = Vector3.Distance(pos, camPos);
  216. int numOccluderHits = Physics.BoxCastNonAlloc(pos, bounds.extents * seeThroughOccluderThreshold, (camPos - pos).normalized, occluderHits, quaternionIdentity, maxDistance, seeThroughOccluderMask);
  217. for (int k = 0; k < numOccluderHits; k++) {
  218. occluderHits[k].collider.transform.root.GetComponentsInChildren(tempRR);
  219. AddWithoutRepetition(occlusionData.cachedOccluders, tempRR);
  220. }
  221. }
  222. }
  223. // render occluders
  224. int occluderRenderersCount = occlusionData.cachedOccluders.Count;
  225. if (occluderRenderersCount > 0) {
  226. for (int k = 0; k < occluderRenderersCount; k++) {
  227. Renderer r = occlusionData.cachedOccluders[k];
  228. if (r != null) {
  229. cbuf.DrawRenderer(r, fxMatSeeThroughMask);
  230. }
  231. }
  232. }
  233. }
  234. public List<Renderer> GetOccluders(Camera camera) {
  235. if (perCameraOcclusionData.TryGetValue(camera, out PerCameraOcclusionData occlusionData)) {
  236. return occlusionData.cachedOccluders;
  237. }
  238. return null;
  239. }
  240. }
  241. }