VolumetricLightBeam.cs 41 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049
  1. //#define DEBUG_SHOW_APEX
  2. using UnityEngine;
  3. using UnityEngine.Serialization;
  4. using System.Collections;
  5. namespace VLB
  6. {
  7. [ExecuteInEditMode]
  8. [DisallowMultipleComponent]
  9. [SelectionBase]
  10. [HelpURL(Consts.HelpUrlBeam)]
  11. public partial class VolumetricLightBeam : MonoBehaviour
  12. {
  13. /// <summary>
  14. /// Get the color value from the light (when attached to a Spotlight) or not
  15. /// </summary>
  16. public bool colorFromLight = true;
  17. /// <summary>
  18. /// Apply a flat/plain/single color, or a gradient
  19. /// </summary>
  20. public ColorMode colorMode = Consts.ColorModeDefault;
  21. public ColorMode usedColorMode
  22. {
  23. get
  24. {
  25. if (Config.Instance.featureEnabledColorGradient == FeatureEnabledColorGradient.Off) return ColorMode.Flat;
  26. return colorMode;
  27. }
  28. }
  29. /// <summary>
  30. /// RGBA plain color, if colorMode is Flat (takes account of the alpha value).
  31. /// </summary>
  32. #if UNITY_2018_1_OR_NEWER
  33. [ColorUsageAttribute(false, true)]
  34. #else
  35. [ColorUsageAttribute(false, true, 0f, 8f, 0.125f, 3f)]
  36. #endif
  37. [FormerlySerializedAs("colorValue")]
  38. public Color color = Consts.FlatColor;
  39. /// <summary>
  40. /// Gradient color applied along the beam, if colorMode is Gradient (takes account of the color and alpha variations).
  41. /// </summary>
  42. public Gradient colorGradient;
  43. /// <summary>
  44. /// Get the intensity value from the light (when attached to a Spotlight) or not
  45. /// </summary>
  46. public bool intensityFromLight = true;
  47. /// <summary>
  48. /// Disabled: the inside and outside intensity values are the same and controlled by intensityGlobal property
  49. /// Enabled: the inside and outside intensity values are distinct (intensityInside and intensityOutside)
  50. /// </summary>
  51. public bool intensityModeAdvanced = false;
  52. /// <summary>
  53. /// Beam inside intensity (when looking at the beam from the inside directly at the source).
  54. /// You can change this property only if intensityModeAdvanced is true. Use intensityGlobal otherwise.
  55. /// </summary>
  56. [FormerlySerializedAs("alphaInside")]
  57. [Range(Consts.IntensityMin, Consts.IntensityMax)] public float intensityInside = Consts.IntensityDefault;
  58. [System.Obsolete("Use 'intensityGlobal' or 'intensityInside' instead")]
  59. public float alphaInside { get { return intensityInside; } set { intensityInside = value; } }
  60. /// <summary>
  61. /// Beam outside intensity (when looking at the beam from behind).
  62. /// You can change this property only if intensityModeAdvanced is true. Use intensityGlobal otherwise.
  63. /// </summary>
  64. [FormerlySerializedAs("alphaOutside"), FormerlySerializedAs("alpha")]
  65. [Range(Consts.IntensityMin, Consts.IntensityMax)] public float intensityOutside = Consts.IntensityDefault;
  66. [System.Obsolete("Use 'intensityGlobal' or 'intensityOutside' instead")]
  67. public float alphaOutside { get { return intensityOutside; } set { intensityOutside = value; } }
  68. /// <summary>
  69. /// Global beam intensity, to use when intensityModeAdvanced is false.
  70. /// Otherwise use intensityOutside and intensityInside independently.
  71. /// </summary>
  72. public float intensityGlobal { get { return intensityOutside; } set { intensityInside = value; intensityOutside = value; } }
  73. public void GetInsideAndOutsideIntensity(out float inside, out float outside)
  74. {
  75. if(intensityModeAdvanced)
  76. {
  77. inside = intensityInside;
  78. outside = intensityOutside;
  79. }
  80. else
  81. {
  82. #if UNITY_EDITOR
  83. if (Application.isPlaying)
  84. #endif
  85. {
  86. Debug.AssertFormat(Mathf.Approximately(intensityInside, intensityOutside), "Beam '{0}' is not using advanced intensity mode, but its inside ({1}) and outside ({2}) have not the same value.", name, intensityInside, intensityOutside);
  87. }
  88. inside = outside = intensityOutside;
  89. }
  90. }
  91. /// <summary>
  92. /// Change how the light beam colors will be mixed with the scene
  93. /// </summary>
  94. public BlendingMode blendingMode = Consts.BlendingModeDefault;
  95. /// <summary>
  96. /// Get the spotAngle value from the light (when attached to a Spotlight) or not
  97. /// </summary>
  98. [FormerlySerializedAs("angleFromLight")]
  99. public bool spotAngleFromLight = true;
  100. /// <summary>
  101. /// Spot Angle (in degrees). This doesn't take account of the radiusStart, and is not necessarily the same than the cone angle.
  102. /// </summary>
  103. [Range(Consts.SpotAngleMin, Consts.SpotAngleMax)] public float spotAngle = Consts.SpotAngleDefault;
  104. /// <summary>
  105. /// Cone Angle (in degrees). This takes account of the radiusStart, and is not necessarily the same than the spot angle.
  106. /// </summary>
  107. public float coneAngle { get { return Mathf.Atan2(coneRadiusEnd - coneRadiusStart, maxGeometryDistance) * Mathf.Rad2Deg * 2f; } }
  108. /// <summary>
  109. /// Start radius of the cone geometry.
  110. /// 0 will generate a perfect cone geometry. Higher values will generate truncated cones.
  111. /// </summary>
  112. [FormerlySerializedAs("radiusStart")]
  113. public float coneRadiusStart = Consts.ConeRadiusStart;
  114. /// <summary>
  115. /// End radius of the cone geometry
  116. /// </summary>
  117. public float coneRadiusEnd { get { return Utils.ComputeConeRadiusEnd(maxGeometryDistance, spotAngle); } }
  118. /// <summary>
  119. /// Volume (in unit^3) of the cone (from the base to fallOffEnd)
  120. /// </summary>
  121. public float coneVolume { get { float r1 = coneRadiusStart, r2 = coneRadiusEnd; return (Mathf.PI / 3) * (r1 * r1 + r1 * r2 + r2 * r2) * fallOffEnd; } }
  122. /// <summary>
  123. /// Apex distance of the truncated radius
  124. /// If coneRadiusStart = 0, the apex is the at the truncated radius, so coneApexOffsetZ = 0
  125. /// Otherwise, coneApexOffsetZ > 0 and represents the local position Z offset
  126. /// </summary>
  127. public float coneApexOffsetZ {
  128. get { // simple intercept
  129. float ratioRadius = coneRadiusStart / coneRadiusEnd;
  130. return ratioRadius == 1f ? float.MaxValue : ((maxGeometryDistance * ratioRadius) / (1 - ratioRadius));
  131. }
  132. }
  133. /// <summary>
  134. /// - Fast: a lot of computation are done on the vertex shader to maximize performance.
  135. /// - High: most of the computation are done on the pixel shader to maximize graphical quality at some performance cost.
  136. /// </summary>
  137. public ShaderAccuracy shaderAccuracy = Consts.ShaderAccuracyDefault;
  138. /// <summary>
  139. /// Shared: this beam will use the global shared mesh (recommended setting, since it will save a lot on memory).
  140. /// Custom: this beam will use a custom mesh instead. Check the following properties to control how the mesh will be generated.
  141. /// </summary>
  142. public MeshType geomMeshType = Consts.GeomMeshType;
  143. /// <summary>
  144. /// Set a custom number of Sides for the cone geometry.
  145. /// Higher values give better looking results, but require more memory and graphic performance.
  146. /// This value is only used when geomMeshType is Custom.
  147. /// </summary>
  148. [FormerlySerializedAs("geomSides")]
  149. public int geomCustomSides = Consts.GeomSidesDefault;
  150. /// <summary>
  151. /// Returns the effective number of Sides used by this beam.
  152. /// Could come from the shared mesh, or the custom mesh
  153. /// </summary>
  154. public int geomSides
  155. {
  156. get { return geomMeshType == MeshType.Custom ? geomCustomSides : Config.Instance.sharedMeshSides; }
  157. set { geomCustomSides = value; Debug.LogWarning("The setter VLB.VolumetricLightBeam.geomSides is OBSOLETE and has been renamed to geomCustomSides."); }
  158. }
  159. /// <summary>
  160. /// Set a custom Segments for the cone geometry.
  161. /// Higher values give better looking results, but require more memory and graphic performance.
  162. /// This value is only used when geomMeshType is Custom.
  163. /// </summary>
  164. public int geomCustomSegments = Consts.GeomSegmentsDefault;
  165. /// <summary>
  166. /// Returns the effective number of Segments used by this beam.
  167. /// Could come from the shared mesh, or the custom mesh
  168. /// </summary>
  169. public int geomSegments
  170. {
  171. get { return geomMeshType == MeshType.Custom ? geomCustomSegments : Config.Instance.sharedMeshSegments; }
  172. set { geomCustomSegments = value; Debug.LogWarning("The setter VLB.VolumetricLightBeam.geomSegments is OBSOLETE and has been renamed to geomCustomSegments."); }
  173. }
  174. public Vector3 skewingLocalForwardDirection = Consts.SkewingLocalForwardDirectionDefault;
  175. public Vector3 skewingLocalForwardDirectionNormalized
  176. {
  177. get
  178. {
  179. if (Mathf.Approximately(skewingLocalForwardDirection.z, 0.0f))
  180. {
  181. Debug.LogErrorFormat("Beam {0} has a skewingLocalForwardDirection with a null Z, which is forbidden", name);
  182. return Vector3.forward;
  183. }
  184. else return skewingLocalForwardDirection.normalized;
  185. }
  186. }
  187. public Transform clippingPlaneTransform = Consts.ClippingPlaneTransformDefault;
  188. public Vector4 additionalClippingPlane { get { return clippingPlaneTransform == null ? Vector4.zero : Utils.PlaneEquation(clippingPlaneTransform.forward, clippingPlaneTransform.position); } }
  189. public bool canHaveMeshSkewing { get { return geomMeshType == MeshType.Custom; } }
  190. public bool hasMeshSkewing
  191. {
  192. get
  193. {
  194. if (!Config.Instance.featureEnabledMeshSkewing) return false;
  195. if (!canHaveMeshSkewing) return false;
  196. var dotForward = Vector3.Dot(skewingLocalForwardDirectionNormalized, Vector3.forward);
  197. if (Mathf.Approximately(dotForward, 1.0f)) return false;
  198. return true;
  199. }
  200. }
  201. /// <summary>
  202. /// Show the cone cap (only visible from inside)
  203. /// </summary>
  204. public bool geomCap = Consts.GeomCap;
  205. /// <summary>
  206. /// Get the fallOffEnd value from the light (when attached to a Spotlight) or not
  207. /// </summary>
  208. [FormerlySerializedAs("fadeEndFromLight")]
  209. public bool fallOffEndFromLight = true;
  210. [System.Obsolete("Use 'fallOffEndFromLight' instead")]
  211. public bool fadeEndFromLight { get { return fallOffEndFromLight; } set { fallOffEndFromLight = value; } }
  212. /// <summary>
  213. /// Light attenuation formula used to compute fading between 'fallOffStart' and 'fallOffEnd'
  214. /// </summary>
  215. public AttenuationEquation attenuationEquation = Consts.AttenuationEquationDefault;
  216. /// <summary>
  217. /// Custom blending mix between linear and quadratic attenuation formulas.
  218. /// Only used if attenuationEquation is set to AttenuationEquation.Blend.
  219. /// 0.0 = 100% Linear
  220. /// 0.5 = Mix between 50% Linear and 50% Quadratic
  221. /// 1.0 = 100% Quadratic
  222. /// </summary>
  223. [Range(0f, 1f)] public float attenuationCustomBlending = Consts.AttenuationCustomBlending;
  224. /// <summary>
  225. /// Proper lerp value between linear and quadratic attenuation, used by the shader.
  226. /// </summary>
  227. public float attenuationLerpLinearQuad {
  228. get {
  229. if (attenuationEquation == AttenuationEquation.Linear) return 0f;
  230. else if (attenuationEquation == AttenuationEquation.Quadratic) return 1f;
  231. return attenuationCustomBlending;
  232. }
  233. }
  234. /// <summary>
  235. /// Distance from the light source (in units) the beam will start to fade out.
  236. /// </summary>
  237. ///
  238. [FormerlySerializedAs("fadeStart")]
  239. public float fallOffStart = Consts.FallOffStart;
  240. [System.Obsolete("Use 'fallOffStart' instead")]
  241. public float fadeStart { get { return fallOffStart; } set { fallOffStart = value; } }
  242. /// <summary>
  243. /// Distance from the light source (in units) the beam is entirely faded out.
  244. /// </summary>
  245. [FormerlySerializedAs("fadeEnd")]
  246. public float fallOffEnd = Consts.FallOffEnd;
  247. [System.Obsolete("Use 'fallOffEnd' instead")]
  248. public float fadeEnd { get { return fallOffEnd; } set { fallOffEnd = value; } }
  249. public float maxGeometryDistance { get { return fallOffEnd + Mathf.Max(Mathf.Abs(tiltFactor.x), Mathf.Abs(tiltFactor.y)); } }
  250. /// <summary>
  251. /// Distance from the world geometry the beam will fade.
  252. /// 0 = hard intersection
  253. /// Higher values produce soft intersection when the beam intersects other opaque geometry.
  254. /// </summary>
  255. public float depthBlendDistance = Consts.DepthBlendDistance;
  256. /// <summary>
  257. /// Distance from the camera the beam will fade.
  258. /// 0 = hard intersection
  259. /// Higher values produce soft intersection when the camera is near the cone triangles.
  260. /// </summary>
  261. public float cameraClippingDistance = Consts.CameraClippingDistance;
  262. /// <summary>
  263. /// Boost intensity factor when looking at the beam from the inside directly at the source.
  264. /// </summary>
  265. [Range(0f, 1f)]
  266. public float glareFrontal = Consts.GlareFrontal;
  267. /// <summary>
  268. /// Boost intensity factor when looking at the beam from behind.
  269. /// </summary>
  270. [Range(0f, 1f)]
  271. public float glareBehind = Consts.GlareBehind;
  272. /// <summary>
  273. /// Modulate the thickness of the beam when looking at it from the side.
  274. /// Higher values produce thinner beam with softer transition at beam edges.
  275. /// </summary>
  276. [FormerlySerializedAs("fresnelPowOutside")]
  277. public float fresnelPow = Consts.FresnelPow;
  278. /// <summary>
  279. /// Enable 3D Noise effect and choose the mode
  280. /// </summary>
  281. public NoiseMode noiseMode = Consts.NoiseModeDefault;
  282. public bool isNoiseEnabled { get { return noiseMode != NoiseMode.Disabled; } }
  283. [System.Obsolete("Use 'noiseMode' instead")]
  284. public bool noiseEnabled { get { return isNoiseEnabled; } set { noiseMode = value ? NoiseMode.WorldSpace : NoiseMode.Disabled; } }
  285. /// <summary>
  286. /// Contribution factor of the 3D Noise (when enabled).
  287. /// Higher intensity means the noise contribution is stronger and more visible.
  288. /// </summary>
  289. [Range(Consts.NoiseIntensityMin, Consts.NoiseIntensityMax)] public float noiseIntensity = Consts.NoiseIntensityDefault;
  290. /// <summary>
  291. /// Get the noiseScale value from the Global 3D Noise configuration
  292. /// </summary>
  293. public bool noiseScaleUseGlobal = true;
  294. /// <summary>
  295. /// 3D Noise texture scaling: higher scale make the noise more visible, but potentially less realistic.
  296. /// </summary>
  297. [Range(Consts.NoiseScaleMin, Consts.NoiseScaleMax)] public float noiseScaleLocal = Consts.NoiseScaleDefault;
  298. /// <summary>
  299. /// Get the noiseVelocity value from the Global 3D Noise configuration
  300. /// </summary>
  301. public bool noiseVelocityUseGlobal = true;
  302. /// <summary>
  303. /// World Space direction and speed of the 3D Noise scrolling, simulating the fog/smoke movement.
  304. /// </summary>
  305. public Vector3 noiseVelocityLocal = Consts.NoiseVelocityDefault;
  306. /// <summary>
  307. /// Fade out starting distance.
  308. /// Beyond this distance, the beam intensity will start to be dimmed.
  309. /// </summary>
  310. public float fadeOutBegin
  311. {
  312. get { return _FadeOutBegin; }
  313. set { SetFadeOutValue(ref _FadeOutBegin, value); }
  314. }
  315. /// <summary>
  316. /// Fade out ending distance.
  317. /// Beyond this distance, the beam will be culled off to save on performance.
  318. /// </summary>
  319. public float fadeOutEnd
  320. {
  321. get { return _FadeOutEnd; }
  322. set { SetFadeOutValue(ref _FadeOutEnd, value); }
  323. }
  324. /// <summary>
  325. /// Is Fade Out feature enabled or not?
  326. /// </summary>
  327. public bool isFadeOutEnabled { get { return _FadeOutBegin >= 0 && _FadeOutEnd >= 0; } }
  328. /// <summary>
  329. /// - 3D: beam along the Z axis.
  330. /// - 2D: beam along the X axis, so you won't have to rotate it to see it in 2D.
  331. /// </summary>
  332. public Dimensions dimensions = Consts.DimensionsDefault;
  333. /// <summary>
  334. /// Tilt the color and attenuation gradient compared to the global beam's direction.
  335. /// Should be used with 'High' Shader Accuracy mode.
  336. /// </summary>
  337. public Vector2 tiltFactor = Consts.TiltDefault;
  338. public bool isTilted { get { return !tiltFactor.Approximately(Vector2.zero); } }
  339. /// <summary>
  340. /// Unique ID of the beam's sorting layer.
  341. /// </summary>
  342. public int sortingLayerID
  343. {
  344. get { return _SortingLayerID; }
  345. set {
  346. _SortingLayerID = value;
  347. if (m_BeamGeom) m_BeamGeom.sortingLayerID = value;
  348. }
  349. }
  350. /// <summary>
  351. /// Name of the beam's sorting layer.
  352. /// </summary>
  353. public string sortingLayerName
  354. {
  355. get { return SortingLayer.IDToName(sortingLayerID); }
  356. set { sortingLayerID = SortingLayer.NameToID(value); }
  357. }
  358. /// <summary>
  359. /// The overlay priority within its layer.
  360. /// Lower numbers are rendered first and subsequent numbers overlay those below.
  361. /// </summary>
  362. public int sortingOrder
  363. {
  364. get { return _SortingOrder; }
  365. set
  366. {
  367. _SortingOrder = value;
  368. if (m_BeamGeom) m_BeamGeom.sortingOrder = value;
  369. }
  370. }
  371. /// <summary>
  372. /// If true, the light beam will keep track of the changes of its own properties and the spotlight attached to it (if any) during playtime.
  373. /// This would allow you to modify the light beam in realtime from Script, Animator and/or Timeline.
  374. /// Enabling this feature is at very minor performance cost. So keep it disabled if you don't plan to modify this light beam during playtime.
  375. /// </summary>
  376. public bool trackChangesDuringPlaytime
  377. {
  378. get { return _TrackChangesDuringPlaytime; }
  379. set { _TrackChangesDuringPlaytime = value; StartPlaytimeUpdateIfNeeded(); }
  380. }
  381. /// <summary> Is the beam currently tracking property changes? </summary>
  382. public bool isCurrentlyTrackingChanges { get { return m_CoPlaytimeUpdate != null; } }
  383. /// <summary> Has the geometry already been generated? </summary>
  384. public bool hasGeometry { get { return m_BeamGeom != null; } }
  385. /// <summary> Bounds of the geometry's mesh (if the geometry exists) </summary>
  386. public Bounds bounds { get { return m_BeamGeom != null ? m_BeamGeom.meshRenderer.bounds : new Bounds(Vector3.zero, Vector3.zero); } }
  387. public int blendingModeAsInt { get { return Mathf.Clamp((int)blendingMode, 0, System.Enum.GetValues(typeof(BlendingMode)).Length); } }
  388. public Quaternion beamInternalLocalRotation { get { return dimensions == Dimensions.Dim3D ? Quaternion.identity : Quaternion.LookRotation(Vector3.right, Vector3.up); } }
  389. public Vector3 beamLocalForward { get { return dimensions == Dimensions.Dim3D ? Vector3.forward : Vector3.right; } }
  390. public Vector3 lossyScale { get { return dimensions == Dimensions.Dim3D ? transform.lossyScale : new Vector3(transform.lossyScale.z, transform.lossyScale.y, transform.lossyScale.x); } }
  391. public float raycastDistance {
  392. get {
  393. if (!hasMeshSkewing) return maxGeometryDistance;
  394. else
  395. {
  396. var skewingZ = skewingLocalForwardDirectionNormalized.z;
  397. return Mathf.Approximately(skewingZ, 0.0f) ? maxGeometryDistance : (maxGeometryDistance / skewingZ);
  398. }
  399. }
  400. }
  401. public Vector3 raycastGlobalForward {
  402. get {
  403. var fwd = transform.forward;
  404. if (hasMeshSkewing)
  405. fwd = transform.TransformDirection(skewingLocalForwardDirectionNormalized);
  406. return beamInternalLocalRotation * fwd;
  407. }
  408. }
  409. public Vector3 raycastGlobalUp { get { return beamInternalLocalRotation * transform.up; } }
  410. public Vector3 raycastGlobalRight { get { return beamInternalLocalRotation * transform.right; } }
  411. // INTERNAL
  412. public MaterialManager.DynamicOcclusion _INTERNAL_DynamicOcclusionMode
  413. {
  414. get { return Config.Instance.featureEnabledDynamicOcclusion ? m_INTERNAL_DynamicOcclusionMode : MaterialManager.DynamicOcclusion.Off; }
  415. set { m_INTERNAL_DynamicOcclusionMode = value; }
  416. }
  417. public MaterialManager.DynamicOcclusion _INTERNAL_DynamicOcclusionMode_Runtime { get { return m_INTERNAL_DynamicOcclusionMode_Runtime ? _INTERNAL_DynamicOcclusionMode : MaterialManager.DynamicOcclusion.Off; } }
  418. MaterialManager.DynamicOcclusion m_INTERNAL_DynamicOcclusionMode = MaterialManager.DynamicOcclusion.Off;
  419. bool m_INTERNAL_DynamicOcclusionMode_Runtime = false;
  420. public void _INTERNAL_SetDynamicOcclusionCallback(string shaderKeyword, MaterialModifier.Callback cb)
  421. {
  422. m_INTERNAL_DynamicOcclusionMode_Runtime = cb != null;
  423. if (m_BeamGeom)
  424. m_BeamGeom.SetDynamicOcclusionCallback(shaderKeyword, cb);
  425. }
  426. public delegate void OnWillCameraRenderCB(Camera cam);
  427. public event OnWillCameraRenderCB onWillCameraRenderThisBeam;
  428. public void _INTERNAL_OnWillCameraRenderThisBeam(Camera cam)
  429. {
  430. if (onWillCameraRenderThisBeam != null)
  431. onWillCameraRenderThisBeam(cam);
  432. }
  433. public delegate void OnBeamGeometryInitialized();
  434. private OnBeamGeometryInitialized m_OnBeamGeometryInitialized;
  435. public void RegisterOnBeamGeometryInitializedCallback(OnBeamGeometryInitialized cb)
  436. {
  437. m_OnBeamGeometryInitialized += cb;
  438. if(m_BeamGeom)
  439. {
  440. CallOnBeamGeometryInitializedCallback();
  441. }
  442. }
  443. void CallOnBeamGeometryInitializedCallback()
  444. {
  445. if (m_OnBeamGeometryInitialized != null)
  446. {
  447. m_OnBeamGeometryInitialized();
  448. m_OnBeamGeometryInitialized = null;
  449. }
  450. }
  451. #pragma warning disable 0414
  452. [SerializeField] int pluginVersion = -1;
  453. public int _INTERNAL_pluginVersion { get { return pluginVersion; } }
  454. #pragma warning restore 0414
  455. [FormerlySerializedAs("trackChangesDuringPlaytime")]
  456. [SerializeField] bool _TrackChangesDuringPlaytime = false;
  457. [SerializeField] int _SortingLayerID = 0;
  458. [SerializeField] int _SortingOrder = 0;
  459. [FormerlySerializedAs("fadeOutBegin")]
  460. [SerializeField] float _FadeOutBegin = Consts.FadeOutBeginDefault;
  461. [FormerlySerializedAs("fadeOutEnd")]
  462. [SerializeField] float _FadeOutEnd = Consts.FadeOutEndDefault;
  463. void SetFadeOutValue(ref float propToChange, float value)
  464. {
  465. bool wasEnabled = isFadeOutEnabled;
  466. propToChange = value;
  467. #if UNITY_EDITOR
  468. if (Application.isPlaying)
  469. #endif
  470. {
  471. if (isFadeOutEnabled != wasEnabled)
  472. OnFadeOutStateChanged();
  473. }
  474. }
  475. void OnFadeOutStateChanged()
  476. {
  477. #if UNITY_EDITOR
  478. if (Application.isPlaying)
  479. #endif
  480. {
  481. // Restart only when fadeout is enabled: on disable, the coroutine will kill itself automatically
  482. if (isFadeOutEnabled && m_BeamGeom) m_BeamGeom.RestartFadeOutCoroutine();
  483. }
  484. }
  485. /// Internal property used for QA testing purpose, do not change
  486. public uint _INTERNAL_InstancedMaterialGroupID { get; protected set; }
  487. BeamGeometry m_BeamGeom = null;
  488. Coroutine m_CoPlaytimeUpdate = null;
  489. #if UNITY_EDITOR
  490. static VolumetricLightBeam[] _EditorFindAllInstances()
  491. {
  492. return Resources.FindObjectsOfTypeAll<VolumetricLightBeam>();
  493. }
  494. public static void _EditorSetAllMeshesDirty()
  495. {
  496. foreach (var instance in _EditorFindAllInstances())
  497. instance._EditorSetMeshDirty();
  498. }
  499. public static void _EditorSetAllBeamGeomDirty()
  500. {
  501. foreach (var instance in _EditorFindAllInstances())
  502. instance.m_EditorDirtyFlags |= EditorDirtyFlags.FullBeamGeomGAO;
  503. }
  504. public void _EditorSetMeshDirty() { m_EditorDirtyFlags |= EditorDirtyFlags.Mesh; }
  505. public void _EditorSetBeamGeomDirty() { m_EditorDirtyFlags |= EditorDirtyFlags.FullBeamGeomGAO; }
  506. [System.Flags]
  507. enum EditorDirtyFlags
  508. {
  509. Clean = 0,
  510. Props = 1 << 1,
  511. Mesh = 1 << 2,
  512. BeamGeomGAO = 1 << 3,
  513. FullBeamGeomGAO = Mesh | BeamGeomGAO,
  514. Everything = Props | Mesh | BeamGeomGAO,
  515. }
  516. EditorDirtyFlags m_EditorDirtyFlags;
  517. CachedLightProperties m_PrevCachedLightProperties;
  518. public UnityEditor.StaticEditorFlags GetStaticEditorFlagsForSubObjects()
  519. {
  520. // Apply the same static flags to the BeamGeometry and DustParticles than the VLB GAO
  521. var flags = UnityEditor.GameObjectUtility.GetStaticEditorFlags(gameObject);
  522. flags &= ~(
  523. // remove the Lightmap static flag since it will generate error messages when selecting the BeamGeometry GAO in the editor
  524. #if UNITY_2019_2_OR_NEWER
  525. UnityEditor.StaticEditorFlags.ContributeGI
  526. #else
  527. UnityEditor.StaticEditorFlags.LightmapStatic
  528. #endif
  529. | UnityEditor.StaticEditorFlags.NavigationStatic
  530. | UnityEditor.StaticEditorFlags.OffMeshLinkGeneration
  531. | UnityEditor.StaticEditorFlags.OccluderStatic
  532. );
  533. return flags;
  534. }
  535. #endif
  536. public string meshStats
  537. {
  538. get
  539. {
  540. Mesh mesh = m_BeamGeom ? m_BeamGeom.coneMesh : null;
  541. if (mesh) return string.Format("Cone angle: {0:0.0} degrees\nMesh: {1} vertices, {2} triangles", coneAngle, mesh.vertexCount, mesh.triangles.Length / 3);
  542. else return "no mesh available";
  543. }
  544. }
  545. public int meshVerticesCount { get { return (m_BeamGeom && m_BeamGeom.coneMesh) ? m_BeamGeom.coneMesh.vertexCount : 0; } }
  546. public int meshTrianglesCount { get { return (m_BeamGeom && m_BeamGeom.coneMesh) ? m_BeamGeom.coneMesh.triangles.Length / 3 : 0; } }
  547. Light _CachedLight = null;
  548. Light lightSpotAttached
  549. {
  550. get
  551. {
  552. if(_CachedLight == null) _CachedLight = GetComponent<Light>();
  553. if (_CachedLight && _CachedLight.type == LightType.Spot) return _CachedLight;
  554. return null;
  555. }
  556. }
  557. /// <summary>
  558. /// Returns a value indicating if the world position passed in argument is inside the light beam or not.
  559. /// This functions treats the beam like infinite (like the beam had an infinite length and never fell off)
  560. /// </summary>
  561. /// <param name="posWS">World position</param>
  562. /// <returns>
  563. /// < 0 position is out
  564. /// = 0 position is exactly on the beam geometry
  565. /// > 0 position is inside the cone
  566. /// </returns>
  567. public float GetInsideBeamFactor(Vector3 posWS) { return GetInsideBeamFactorFromObjectSpacePos(transform.InverseTransformPoint(posWS)); }
  568. public float GetInsideBeamFactorFromObjectSpacePos(Vector3 posOS)
  569. {
  570. if(dimensions == Dimensions.Dim2D)
  571. {
  572. posOS = new Vector3(posOS.z, posOS.y, posOS.x);
  573. }
  574. if (posOS.z < 0f) return -1f;
  575. Vector2 posOSXY = posOS.xy();
  576. if (hasMeshSkewing)
  577. {
  578. Vector3 localForwardDirN = skewingLocalForwardDirectionNormalized;
  579. posOSXY -= localForwardDirN.xy() * (posOS.z / localForwardDirN.z);
  580. }
  581. // Compute a factor to know how far inside the beam cone the camera is
  582. var triangle2D = new Vector2(posOSXY.magnitude, posOS.z + coneApexOffsetZ).normalized;
  583. const float maxRadiansDiff = 0.1f;
  584. float slopeRad = (coneAngle * Mathf.Deg2Rad) / 2;
  585. return Mathf.Clamp((Mathf.Abs(Mathf.Sin(slopeRad)) - Mathf.Abs(triangle2D.x)) / maxRadiansDiff, -1, 1);
  586. }
  587. [System.Obsolete("Use 'GenerateGeometry()' instead")]
  588. public void Generate() { GenerateGeometry(); }
  589. /// <summary>
  590. /// Regenerate the beam mesh (and also the material).
  591. /// This can be slow (it recreates a mesh from scratch), so don't call this function during playtime.
  592. /// You would need to call this function only if you want to change the properties 'geomSides' and 'geomCap' during playtime.
  593. /// Otherwise, for the other properties, just enable 'trackChangesDuringPlaytime', or manually call 'UpdateAfterManualPropertyChange()'
  594. /// </summary>
  595. public virtual void GenerateGeometry()
  596. {
  597. HandleBackwardCompatibility(pluginVersion, Version.Current);
  598. pluginVersion = Version.Current;
  599. ValidateProperties();
  600. if (m_BeamGeom == null)
  601. {
  602. m_BeamGeom = Utils.NewWithComponent<BeamGeometry>("Beam Geometry");
  603. m_BeamGeom.Initialize(this);
  604. CallOnBeamGeometryInitializedCallback();
  605. }
  606. m_BeamGeom.RegenerateMesh();
  607. m_BeamGeom.visible = enabled;
  608. }
  609. /// <summary>
  610. /// Update the beam material and its bounds.
  611. /// Calling manually this function is useless if your beam has its property 'trackChangesDuringPlaytime' enabled
  612. /// (because then this function is automatically called each frame).
  613. /// However, if 'trackChangesDuringPlaytime' is disabled, and you change a property via Script for example,
  614. /// you need to call this function to take the property change into account.
  615. /// All properties changes are took into account, expect 'geomSides' and 'geomCap' which require to regenerate the geometry via 'GenerateGeometry()'
  616. /// </summary>
  617. public virtual void UpdateAfterManualPropertyChange()
  618. {
  619. ValidateProperties();
  620. if (m_BeamGeom) m_BeamGeom.UpdateMaterialAndBounds();
  621. }
  622. #if !UNITY_EDITOR
  623. void Start()
  624. {
  625. // In standalone builds, simply generate the geometry once in Start
  626. GenerateGeometry();
  627. }
  628. #else
  629. void Start()
  630. {
  631. if (Application.isPlaying)
  632. {
  633. GenerateGeometry();
  634. m_EditorDirtyFlags = EditorDirtyFlags.Clean;
  635. }
  636. else
  637. {
  638. // In Editor, creating geometry from Start and/or OnValidate generates warning in Unity 2017.
  639. // So we do it from Update
  640. m_EditorDirtyFlags = EditorDirtyFlags.Everything;
  641. }
  642. StartPlaytimeUpdateIfNeeded();
  643. }
  644. void OnValidate()
  645. {
  646. m_EditorDirtyFlags |= EditorDirtyFlags.Props; // Props have been modified from Editor
  647. }
  648. void Update() // EDITOR ONLY
  649. {
  650. // Handle edition of light properties in Editor
  651. if (!Application.isPlaying)
  652. {
  653. var newProps = new CachedLightProperties(lightSpotAttached);
  654. if(!newProps.Equals(m_PrevCachedLightProperties))
  655. m_EditorDirtyFlags |= EditorDirtyFlags.Props;
  656. m_PrevCachedLightProperties = newProps;
  657. }
  658. if (m_EditorDirtyFlags == EditorDirtyFlags.Clean)
  659. {
  660. if (Application.isPlaying)
  661. {
  662. if (!trackChangesDuringPlaytime) // during Playtime, realtime changes are handled by CoUpdateDuringPlaytime
  663. return;
  664. }
  665. }
  666. else
  667. {
  668. if (m_EditorDirtyFlags.HasFlag(EditorDirtyFlags.Mesh))
  669. {
  670. if (m_EditorDirtyFlags.HasFlag(EditorDirtyFlags.BeamGeomGAO))
  671. DestroyBeam();
  672. GenerateGeometry(); // regenerate everything
  673. }
  674. else if (m_EditorDirtyFlags.HasFlag(EditorDirtyFlags.Props))
  675. {
  676. ValidateProperties();
  677. }
  678. }
  679. // If we modify the attached Spotlight properties, or if we animate the beam via Unity 2017's timeline,
  680. // we are not notified of properties changes. So we update the material anyway.
  681. UpdateAfterManualPropertyChange();
  682. m_EditorDirtyFlags = EditorDirtyFlags.Clean;
  683. }
  684. public void Reset()
  685. {
  686. colorMode = Consts.ColorModeDefault;
  687. color = Consts.FlatColor;
  688. colorFromLight = true;
  689. intensityFromLight = true;
  690. intensityModeAdvanced = false;
  691. intensityInside = Consts.IntensityDefault;
  692. intensityOutside = Consts.IntensityDefault;
  693. blendingMode = Consts.BlendingModeDefault;
  694. shaderAccuracy = Consts.ShaderAccuracyDefault;
  695. spotAngleFromLight = true;
  696. spotAngle = Consts.SpotAngleDefault;
  697. coneRadiusStart = Consts.ConeRadiusStart;
  698. geomMeshType = Consts.GeomMeshType;
  699. geomCustomSides = Consts.GeomSidesDefault;
  700. geomCustomSegments = Consts.GeomSegmentsDefault;
  701. geomCap = Consts.GeomCap;
  702. attenuationEquation = Consts.AttenuationEquationDefault;
  703. attenuationCustomBlending = Consts.AttenuationCustomBlending;
  704. fallOffEndFromLight = true;
  705. fallOffStart = Consts.FallOffStart;
  706. fallOffEnd = Consts.FallOffEnd;
  707. depthBlendDistance = Consts.DepthBlendDistance;
  708. cameraClippingDistance = Consts.CameraClippingDistance;
  709. glareFrontal = Consts.GlareFrontal;
  710. glareBehind = Consts.GlareBehind;
  711. fresnelPow = Consts.FresnelPow;
  712. noiseMode = Consts.NoiseModeDefault;
  713. noiseIntensity = Consts.NoiseIntensityDefault;
  714. noiseScaleUseGlobal = true;
  715. noiseScaleLocal = Consts.NoiseScaleDefault;
  716. noiseVelocityUseGlobal = true;
  717. noiseVelocityLocal = Consts.NoiseVelocityDefault;
  718. sortingLayerID = 0;
  719. sortingOrder = 0;
  720. fadeOutBegin = Consts.FadeOutBeginDefault;
  721. fadeOutEnd = Consts.FadeOutEndDefault;
  722. dimensions = Consts.DimensionsDefault;
  723. tiltFactor = Consts.TiltDefault;
  724. skewingLocalForwardDirection = Consts.SkewingLocalForwardDirectionDefault;
  725. clippingPlaneTransform = Consts.ClippingPlaneTransformDefault;
  726. trackChangesDuringPlaytime = false;
  727. m_EditorDirtyFlags = EditorDirtyFlags.Everything;
  728. }
  729. #endif
  730. void OnEnable()
  731. {
  732. if (m_BeamGeom) m_BeamGeom.visible = true;
  733. StartPlaytimeUpdateIfNeeded();
  734. #if UNITY_EDITOR
  735. EditorLoadPrefs();
  736. #endif
  737. }
  738. void OnDisable()
  739. {
  740. if (m_BeamGeom) m_BeamGeom.visible = false;
  741. m_CoPlaytimeUpdate = null;
  742. }
  743. void StartPlaytimeUpdateIfNeeded()
  744. {
  745. if (Application.isPlaying && trackChangesDuringPlaytime && m_CoPlaytimeUpdate == null)
  746. {
  747. m_CoPlaytimeUpdate = StartCoroutine(CoPlaytimeUpdate());
  748. }
  749. }
  750. IEnumerator CoPlaytimeUpdate()
  751. {
  752. while (trackChangesDuringPlaytime && enabled)
  753. {
  754. UpdateAfterManualPropertyChange();
  755. yield return null;
  756. }
  757. m_CoPlaytimeUpdate = null;
  758. }
  759. void OnDestroy()
  760. {
  761. DestroyBeam();
  762. }
  763. void DestroyBeam()
  764. {
  765. if (m_BeamGeom) DestroyImmediate(m_BeamGeom.gameObject); // Make sure to delete the GAO
  766. m_BeamGeom = null;
  767. }
  768. void AssignPropertiesFromSpotLight(Light lightSpot)
  769. {
  770. if (lightSpot && lightSpot.type == LightType.Spot)
  771. {
  772. if (intensityFromLight) { intensityModeAdvanced = false; intensityGlobal = lightSpot.intensity; }
  773. if (fallOffEndFromLight) fallOffEnd = lightSpot.range;
  774. if (spotAngleFromLight) spotAngle = lightSpot.spotAngle;
  775. if (colorFromLight)
  776. {
  777. colorMode = ColorMode.Flat;
  778. color = lightSpot.color;
  779. }
  780. }
  781. }
  782. void ClampProperties()
  783. {
  784. intensityInside = Mathf.Clamp(intensityInside, Consts.IntensityMin, Consts.IntensityMax);
  785. intensityOutside = Mathf.Clamp(intensityOutside, Consts.IntensityMin, Consts.IntensityMax);
  786. attenuationCustomBlending = Mathf.Clamp01(attenuationCustomBlending);
  787. fallOffEnd = Mathf.Max(Consts.FallOffDistancesMinThreshold, fallOffEnd);
  788. fallOffStart = Mathf.Clamp(fallOffStart, 0f, fallOffEnd - Consts.FallOffDistancesMinThreshold);
  789. spotAngle = Mathf.Clamp(spotAngle, Consts.SpotAngleMin, Consts.SpotAngleMax);
  790. coneRadiusStart = Mathf.Max(coneRadiusStart, 0f);
  791. depthBlendDistance = Mathf.Max(depthBlendDistance, 0f);
  792. cameraClippingDistance = Mathf.Max(cameraClippingDistance, 0f);
  793. geomCustomSides = Mathf.Clamp(geomCustomSides, Consts.GeomSidesMin, Consts.GeomSidesMax);
  794. geomCustomSegments = Mathf.Clamp(geomCustomSegments, Consts.GeomSegmentsMin, Consts.GeomSegmentsMax);
  795. fresnelPow = Mathf.Max(0f, fresnelPow);
  796. glareBehind = Mathf.Clamp01(glareBehind);
  797. glareFrontal = Mathf.Clamp01(glareFrontal);
  798. noiseIntensity = Mathf.Clamp(noiseIntensity, Consts.NoiseIntensityMin, Consts.NoiseIntensityMax);
  799. }
  800. void ValidateProperties()
  801. {
  802. AssignPropertiesFromSpotLight(lightSpotAttached);
  803. ClampProperties();
  804. }
  805. void HandleBackwardCompatibility(int serializedVersion, int newVersion)
  806. {
  807. if (serializedVersion == -1) return; // freshly new spawned entity: nothing to do
  808. if (serializedVersion == newVersion) return; // same version: nothing to do
  809. if (serializedVersion < 1301)
  810. {
  811. // quadratic attenuation is a new feature of 1.3
  812. attenuationEquation = AttenuationEquation.Linear;
  813. }
  814. if (serializedVersion < 1501)
  815. {
  816. // custom mesh is a new feature of 1.5
  817. geomMeshType = MeshType.Custom;
  818. geomCustomSegments = 5;
  819. }
  820. if (serializedVersion < 1610)
  821. {
  822. // intensity global/advanced mode is a feature of 1.61
  823. intensityFromLight = false;
  824. intensityModeAdvanced = !Mathf.Approximately(intensityInside, intensityOutside);
  825. }
  826. if (serializedVersion < 1910)
  827. {
  828. // prevent from triggering error message if inside and outside intensity are not equal with advanced mode disabled
  829. if(!intensityModeAdvanced && !Mathf.Approximately(intensityInside, intensityOutside))
  830. {
  831. intensityInside = intensityOutside;
  832. }
  833. }
  834. Utils.MarkCurrentSceneDirty();
  835. }
  836. #if UNITY_EDITOR
  837. public static bool editorShowTiltFactor = false;
  838. public static bool editorShowClippingPlane = false;
  839. private static bool editorPrefsLoaded = false;
  840. static void EditorLoadPrefs()
  841. {
  842. if (!editorPrefsLoaded)
  843. {
  844. editorShowTiltFactor = UnityEditor.EditorPrefs.GetBool("VLB_BEAM_SHOWTILTDIR", true);
  845. editorPrefsLoaded = true;
  846. }
  847. }
  848. void OnDrawGizmos()
  849. {
  850. #if DEBUG_SHOW_APEX
  851. Gizmos.matrix = transform.localToWorldMatrix;
  852. Gizmos.color = Color.green;
  853. Gizmos.DrawWireSphere(new Vector3(0, 0, -coneApexOffsetZ), 0.025f);
  854. #endif
  855. if (editorShowTiltFactor)
  856. {
  857. Utils.GizmosDrawPlane(
  858. new Vector3(tiltFactor.x, tiltFactor.y, 1.0f),
  859. Vector3.zero,
  860. Color.white,
  861. transform.localToWorldMatrix,
  862. 0.25f,
  863. 0.5f);
  864. }
  865. if (editorShowClippingPlane && clippingPlaneTransform != null)
  866. {
  867. float kPlaneSize = 0.7f;
  868. Utils.GizmosDrawPlane(
  869. Vector3.forward,
  870. Vector3.zero,
  871. Color.white,
  872. clippingPlaneTransform.localToWorldMatrix,
  873. kPlaneSize,
  874. kPlaneSize * 0.5f);
  875. }
  876. }
  877. #endif // UNITY_EDITOR
  878. }
  879. }