Config.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535
  1. using UnityEngine;
  2. using UnityEngine.Serialization;
  3. #if UNITY_EDITOR
  4. using UnityEditor;
  5. #endif
  6. namespace VLB
  7. {
  8. [HelpURL(Consts.HelpUrlConfig)]
  9. public class Config : ScriptableObject
  10. {
  11. /// <summary>
  12. /// Override the layer on which the procedural geometry is created or not
  13. /// </summary>
  14. public bool geometryOverrideLayer = Consts.ConfigGeometryOverrideLayerDefault;
  15. /// <summary>
  16. /// The layer the procedural geometry gameObject is in (only if geometryOverrideLayer is enabled)
  17. /// </summary>
  18. public int geometryLayerID = Consts.ConfigGeometryLayerIDDefault;
  19. /// <summary>
  20. /// The tag applied on the procedural geometry gameObject
  21. /// </summary>
  22. public string geometryTag = Consts.ConfigGeometryTagDefault;
  23. /// <summary>
  24. /// Determine in which order beams are rendered compared to other objects.
  25. /// This way for example transparent objects are rendered after opaque objects, and so on.
  26. /// </summary>
  27. public int geometryRenderQueue = (int)Consts.ConfigGeometryRenderQueueDefault;
  28. /// <summary>
  29. /// Select the Render Pipeline (Built-In or SRP) in use.
  30. /// </summary>
  31. public RenderPipeline renderPipeline
  32. {
  33. get { return _RenderPipeline; }
  34. set
  35. {
  36. #if UNITY_EDITOR
  37. _RenderPipeline = value;
  38. #else
  39. Debug.LogError("Modifying the RenderPipeline in standalone builds is not permitted");
  40. #endif
  41. }
  42. }
  43. [FormerlySerializedAs("renderPipeline")]
  44. [SerializeField] RenderPipeline _RenderPipeline = Consts.ConfigGeometryRenderPipelineDefault;
  45. /// <summary>
  46. /// MultiPass: Use the 2 pass shader. Will generate 2 drawcalls per beam.
  47. /// SinglePass: Use the 1 pass shader. Will generate 1 drawcall per beam.
  48. /// GPUInstancing: Dynamically batch multiple beams to combine and reduce draw calls (Feature only supported in Unity 5.6 or above). More info: https://docs.unity3d.com/Manual/GPUInstancing.html
  49. /// SRPBatcher: Use the SRP Batcher to automatically batch multiple beams and reduce draw calls. Only available when using SRP.
  50. /// </summary>
  51. public RenderingMode renderingMode
  52. {
  53. get { return _RenderingMode; }
  54. set
  55. {
  56. #if UNITY_EDITOR
  57. _RenderingMode = value;
  58. #else
  59. Debug.LogError("Modifying the RenderingMode in standalone builds is not permitted");
  60. #endif
  61. }
  62. }
  63. [FormerlySerializedAs("renderingMode")]
  64. [SerializeField] RenderingMode _RenderingMode = Consts.ConfigGeometryRenderingModeDefault;
  65. public bool IsSRPBatcherSupported()
  66. {
  67. // The SRP Batcher Rendering Mode is only compatible when using a SRP
  68. if (renderPipeline == RenderPipeline.BuiltIn) return false;
  69. // SRP Batcher only works with URP and HDRP
  70. var rp = SRPHelper.renderPipelineType;
  71. return rp == SRPHelper.RenderPipeline.URP || rp == SRPHelper.RenderPipeline.HDRP;
  72. }
  73. /// <summary>
  74. /// Actual Rendering Mode used on the current platform
  75. /// </summary>
  76. public RenderingMode actualRenderingMode
  77. {
  78. get
  79. {
  80. #pragma warning disable 0162 // warning CS0162: Unreachable code detected
  81. if (renderingMode == RenderingMode.GPUInstancing && !BatchingHelper.isGpuInstancingSupported) return RenderingMode.SinglePass;
  82. #pragma warning restore 0162
  83. if (renderingMode == RenderingMode.SRPBatcher && !IsSRPBatcherSupported()) return RenderingMode.SinglePass;
  84. if (renderPipeline != RenderPipeline.BuiltIn)
  85. {
  86. // Using a Scriptable Render Pipeline with 'Multi-Pass' Rendering Mode is not supported
  87. if (renderingMode == RenderingMode.MultiPass) return RenderingMode.SinglePass;
  88. }
  89. return renderingMode;
  90. }
  91. }
  92. /// <summary>
  93. /// Depending on the actual Rendering Mode used, returns true if the single pass shader will be used, false otherwise.
  94. /// </summary>
  95. public bool useSinglePassShader { get { return actualRenderingMode != RenderingMode.MultiPass; } }
  96. public bool requiresDoubleSidedMesh { get { return useSinglePassShader; } }
  97. /// <summary>
  98. /// Main shader applied to the cone beam geometry
  99. /// </summary>
  100. public Shader beamShader
  101. {
  102. get
  103. {
  104. #if UNITY_EDITOR
  105. if(_BeamShader == null)
  106. RefreshShader(RefreshShaderFlags.All);
  107. #endif
  108. return _BeamShader;
  109. }
  110. }
  111. /// <summary>
  112. /// Depending on the quality of your screen, you might see some artifacts with high contrast visual (like a white beam over a black background).
  113. /// These is a very common problem known as color banding.
  114. /// To help with this issue, the plugin offers a Dithering factor: it smooths the banding by introducing a subtle pattern of noise.
  115. /// </summary>
  116. public float ditheringFactor = Consts.ConfigDitheringFactor;
  117. /// <summary>
  118. /// Number of Sides of the shared cone mesh
  119. /// </summary>
  120. public int sharedMeshSides = Consts.ConfigSharedMeshSides;
  121. /// <summary>
  122. /// Number of Segments of the shared cone mesh
  123. /// </summary>
  124. public int sharedMeshSegments = Consts.ConfigSharedMeshSegments;
  125. /// <summary>
  126. /// Global 3D Noise texture scaling: higher scale make the noise more visible, but potentially less realistic.
  127. /// </summary>
  128. [Range(Consts.NoiseScaleMin, Consts.NoiseScaleMax)]
  129. public float globalNoiseScale = Consts.NoiseScaleDefault;
  130. /// <summary>
  131. /// Global World Space direction and speed of the noise scrolling, simulating the fog/smoke movement
  132. /// </summary>
  133. public Vector3 globalNoiseVelocity = Consts.NoiseVelocityDefault;
  134. /// <summary>
  135. /// Tag used to retrieve the camera used to compute the fade out factor on beams
  136. /// </summary>
  137. public string fadeOutCameraTag = Consts.ConfigFadeOutCameraTagDefault;
  138. public Transform fadeOutCameraTransform
  139. {
  140. get
  141. {
  142. if (m_CachedFadeOutCamera == null)
  143. {
  144. ForceUpdateFadeOutCamera();
  145. }
  146. return m_CachedFadeOutCamera;
  147. }
  148. }
  149. /// <summary>
  150. /// Call this function if you want to manually change the fadeOutCameraTag property at runtime
  151. /// </summary>
  152. public void ForceUpdateFadeOutCamera()
  153. {
  154. var gao = GameObject.FindGameObjectWithTag(fadeOutCameraTag);
  155. if (gao)
  156. m_CachedFadeOutCamera = gao.transform;
  157. }
  158. /// <summary>
  159. /// Binary file holding the 3D Noise texture data (a 3D array). Must be exactly Size * Size * Size bytes long.
  160. /// </summary>
  161. [HighlightNull]
  162. public TextAsset noise3DData = null;
  163. /// <summary>
  164. /// Size (of one dimension) of the 3D Noise data. Must be power of 2. So if the binary file holds a 32x32x32 texture, this value must be 32.
  165. /// </summary>
  166. public int noise3DSize = Consts.ConfigNoise3DSizeDefault;
  167. /// <summary>
  168. /// ParticleSystem prefab instantiated for the Volumetric Dust Particles feature (Unity 5.5 or above)
  169. /// </summary>
  170. [HighlightNull]
  171. public ParticleSystem dustParticlesPrefab = null;
  172. /// <summary>
  173. /// Noise texture for dithering feature
  174. /// </summary>
  175. public Texture2D ditheringNoiseTexture = null;
  176. /// <summary>
  177. /// Off: do not support having a gradient as color.
  178. /// High Only: support gradient color only for devices with Shader Level = 35 or higher.
  179. /// High and Low: support gradient color for all devices.
  180. /// </summary>
  181. public FeatureEnabledColorGradient featureEnabledColorGradient = Consts.ConfigFeatureEnabledColorGradientDefault;
  182. /// <summary>
  183. /// Support 'Soft Intersection with Opaque Geometry' feature or not.
  184. /// </summary>
  185. public bool featureEnabledDepthBlend = Consts.ConfigFeatureEnabledDefault;
  186. /// <summary>
  187. /// Support 'Noise 3D' feature or not.
  188. /// </summary>
  189. public bool featureEnabledNoise3D = Consts.ConfigFeatureEnabledDefault;
  190. /// <summary>
  191. /// Support 'Dynamic Occlusion' features or not.
  192. /// </summary>
  193. public bool featureEnabledDynamicOcclusion = Consts.ConfigFeatureEnabledDefault;
  194. /// <summary>
  195. /// Support 'Mesh Skewing' feature or not.
  196. /// </summary>
  197. public bool featureEnabledMeshSkewing = Consts.ConfigFeatureEnabledDefault;
  198. /// <summary>
  199. /// Support 'Shader Accuracy' property set to 'High' or not.
  200. /// </summary>
  201. public bool featureEnabledShaderAccuracyHigh = Consts.ConfigFeatureEnabledDefault;
  202. // INTERNAL
  203. #pragma warning disable 0414
  204. [SerializeField] int pluginVersion = -1;
  205. [SerializeField] Material _DummyMaterial = null;
  206. #pragma warning restore 0414
  207. [SerializeField] Shader _BeamShader = null;
  208. Transform m_CachedFadeOutCamera = null;
  209. public bool hasRenderPipelineMismatch { get { return (SRPHelper.renderPipelineType == SRPHelper.RenderPipeline.BuiltIn) != (_RenderPipeline == RenderPipeline.BuiltIn); } }
  210. [RuntimeInitializeOnLoadMethod]
  211. static void OnStartup()
  212. {
  213. Instance.m_CachedFadeOutCamera = null;
  214. Instance.RefreshGlobalShaderProperties();
  215. #if UNITY_EDITOR
  216. Instance.RefreshShader(RefreshShaderFlags.All);
  217. #endif
  218. if(Instance.hasRenderPipelineMismatch)
  219. Debug.LogError("It looks like the 'Render Pipeline' is not correctly set in the config. Please make sure to select the proper value depending on your pipeline in use.", Instance);
  220. }
  221. public void Reset()
  222. {
  223. geometryOverrideLayer = Consts.ConfigGeometryOverrideLayerDefault;
  224. geometryLayerID = Consts.ConfigGeometryLayerIDDefault;
  225. geometryTag = Consts.ConfigGeometryTagDefault;
  226. geometryRenderQueue = (int)Consts.ConfigGeometryRenderQueueDefault;
  227. sharedMeshSides = Consts.ConfigSharedMeshSides;
  228. sharedMeshSegments = Consts.ConfigSharedMeshSegments;
  229. globalNoiseScale = Consts.NoiseScaleDefault;
  230. globalNoiseVelocity = Consts.NoiseVelocityDefault;
  231. renderPipeline = Consts.ConfigGeometryRenderPipelineDefault;
  232. renderingMode = Consts.ConfigGeometryRenderingModeDefault;
  233. ditheringFactor = Consts.ConfigDitheringFactor;
  234. fadeOutCameraTag = Consts.ConfigFadeOutCameraTagDefault;
  235. featureEnabledColorGradient = Consts.ConfigFeatureEnabledColorGradientDefault;
  236. featureEnabledDepthBlend = Consts.ConfigFeatureEnabledDefault;
  237. featureEnabledNoise3D = Consts.ConfigFeatureEnabledDefault;
  238. featureEnabledDynamicOcclusion = Consts.ConfigFeatureEnabledDefault;
  239. featureEnabledMeshSkewing = Consts.ConfigFeatureEnabledDefault;
  240. featureEnabledShaderAccuracyHigh = Consts.ConfigFeatureEnabledDefault;
  241. ResetInternalData();
  242. #if UNITY_EDITOR
  243. GlobalMesh.Destroy();
  244. VolumetricLightBeam._EditorSetAllMeshesDirty();
  245. #endif
  246. }
  247. void RefreshGlobalShaderProperties()
  248. {
  249. Shader.SetGlobalFloat(ShaderProperties.GlobalDitheringFactor, ditheringFactor);
  250. Shader.SetGlobalTexture(ShaderProperties.GlobalDitheringNoiseTex, ditheringNoiseTexture);
  251. }
  252. #if UNITY_EDITOR
  253. public void _EditorSetRenderingModeAndRefreshShader(RenderingMode mode)
  254. {
  255. renderingMode = mode;
  256. RefreshShader(RefreshShaderFlags.All);
  257. }
  258. void OnValidate()
  259. {
  260. noise3DSize = Mathf.Max(2, Mathf.NextPowerOfTwo(noise3DSize));
  261. sharedMeshSides = Mathf.Clamp(sharedMeshSides, Consts.GeomSidesMin, Consts.GeomSidesMax);
  262. sharedMeshSegments = Mathf.Clamp(sharedMeshSegments, Consts.GeomSegmentsMin, Consts.GeomSegmentsMax);
  263. ditheringFactor = Mathf.Clamp01(ditheringFactor);
  264. }
  265. void AutoSelectRenderPipeline()
  266. {
  267. var newPipeline = renderPipeline;
  268. switch (SRPHelper.renderPipelineType)
  269. {
  270. case SRPHelper.RenderPipeline.BuiltIn:
  271. newPipeline = RenderPipeline.BuiltIn;
  272. break;
  273. case SRPHelper.RenderPipeline.HDRP:
  274. newPipeline = RenderPipeline.HDRP;
  275. break;
  276. case SRPHelper.RenderPipeline.URP:
  277. case SRPHelper.RenderPipeline.LWRP:
  278. newPipeline = RenderPipeline.URP;
  279. break;
  280. }
  281. if (newPipeline != renderPipeline)
  282. {
  283. renderPipeline = newPipeline;
  284. EditorUtility.SetDirty(this); // make sure to save this property change
  285. RefreshShader(RefreshShaderFlags.All);
  286. }
  287. }
  288. public static void EditorSelectInstance()
  289. {
  290. Selection.activeObject = Config.Instance; // this will create the instance if it doesn't exist
  291. if (Selection.activeObject == null)
  292. Debug.LogError("Cannot find any Config resource");
  293. }
  294. [System.Flags]
  295. public enum RefreshShaderFlags
  296. {
  297. Reference = 1 << 1,
  298. Dummy = 1 << 2,
  299. All = Reference | Dummy,
  300. }
  301. public void RefreshShader(RefreshShaderFlags flags)
  302. {
  303. if (flags.HasFlag(RefreshShaderFlags.Reference))
  304. {
  305. var prevShader = _BeamShader;
  306. var enabledFeatures = new ShaderGenerator.EnabledFeatures
  307. {
  308. dithering = ditheringFactor > 0.0f,
  309. depthBlend = featureEnabledDepthBlend,
  310. noise3D = featureEnabledNoise3D,
  311. colorGradient = featureEnabledColorGradient,
  312. dynamicOcclusion = featureEnabledDynamicOcclusion,
  313. meshSkewing = featureEnabledMeshSkewing,
  314. shaderAccuracyHigh = featureEnabledShaderAccuracyHigh
  315. };
  316. _BeamShader = ShaderGenerator.Generate(_RenderPipeline, actualRenderingMode, enabledFeatures);
  317. if (_BeamShader != prevShader)
  318. {
  319. EditorUtility.SetDirty(this);
  320. }
  321. }
  322. if (flags.HasFlag(RefreshShaderFlags.Dummy) && _BeamShader != null)
  323. {
  324. bool gpuInstanced = actualRenderingMode == RenderingMode.GPUInstancing;
  325. _DummyMaterial = DummyMaterial.Create(_BeamShader, gpuInstanced);
  326. }
  327. if (_DummyMaterial == null)
  328. {
  329. Debug.LogError("No dummy material referenced to VLB config, please try to reset this asset.", this);
  330. }
  331. RefreshGlobalShaderProperties();
  332. }
  333. #endif // UNITY_EDITOR
  334. public void ResetInternalData()
  335. {
  336. #if UNITY_EDITOR
  337. RefreshShader(RefreshShaderFlags.All);
  338. #endif
  339. noise3DData = Resources.Load("Noise3D_64x64x64") as TextAsset;
  340. noise3DSize = Consts.ConfigNoise3DSizeDefault;
  341. dustParticlesPrefab = Resources.Load("DustParticles", typeof(ParticleSystem)) as ParticleSystem;
  342. ditheringNoiseTexture = Resources.Load("VLBDitheringNoise", typeof(Texture2D)) as Texture2D;
  343. }
  344. public ParticleSystem NewVolumetricDustParticles()
  345. {
  346. if (!dustParticlesPrefab)
  347. {
  348. if (Application.isPlaying)
  349. {
  350. Debug.LogError("Failed to instantiate VolumetricDustParticles prefab.");
  351. }
  352. return null;
  353. }
  354. var instance = Instantiate(dustParticlesPrefab);
  355. #if UNITY_5_4_OR_NEWER
  356. instance.useAutoRandomSeed = false;
  357. #endif
  358. instance.name = "Dust Particles";
  359. instance.gameObject.hideFlags = Consts.ProceduralObjectsHideFlags;
  360. instance.gameObject.SetActive(true);
  361. return instance;
  362. }
  363. void OnEnable()
  364. {
  365. HandleBackwardCompatibility(pluginVersion, Version.Current);
  366. pluginVersion = Version.Current;
  367. }
  368. void HandleBackwardCompatibility(int serializedVersion, int newVersion)
  369. {
  370. if (serializedVersion == -1) return; // freshly new spawned config: nothing to do
  371. if (serializedVersion == newVersion) return; // same version: nothing to do
  372. #if UNITY_EDITOR
  373. if (serializedVersion < 1830)
  374. {
  375. AutoSelectRenderPipeline();
  376. }
  377. if (newVersion > serializedVersion)
  378. {
  379. // Import to keep, we have to regenerate the shader each time the plugin is updated
  380. RefreshShader(RefreshShaderFlags.All);
  381. }
  382. #endif
  383. }
  384. // Singleton management
  385. static Config ms_Instance = null;
  386. public static Config Instance { get { return GetInstance(true); } }
  387. #if UNITY_EDITOR
  388. static bool ms_IsCreatingInstance = false;
  389. public bool IsCurrentlyUsedInstance() { return Instance == this; }
  390. public bool HasValidAssetName()
  391. {
  392. if (name.IndexOf(ConfigOverride.kAssetName) != 0)
  393. return false;
  394. return PlatformHelper.IsValidPlatformSuffix(GetAssetSuffix());
  395. }
  396. public string GetAssetSuffix()
  397. {
  398. var fullname = name;
  399. var strToFind = ConfigOverride.kAssetName;
  400. if (fullname.IndexOf(strToFind) == 0) return fullname.Substring(strToFind.Length);
  401. else return "";
  402. }
  403. #endif
  404. private static Config GetInstance(bool assertIfNotFound)
  405. {
  406. #if UNITY_EDITOR
  407. // Do not cache the instance during editing in order to handle new asset created or moved.
  408. if (!Application.isPlaying || ms_Instance == null)
  409. #else
  410. if (ms_Instance == null)
  411. #endif
  412. {
  413. #if UNITY_EDITOR
  414. if (ms_IsCreatingInstance)
  415. {
  416. Debug.LogError(string.Format("Trying to access Config.Instance while creating it. Breaking before infinite loop."));
  417. return null;
  418. }
  419. #endif
  420. {
  421. var newInstance = Resources.Load<ConfigOverride>(ConfigOverride.kAssetName + PlatformHelper.GetCurrentPlatformSuffix());
  422. if (newInstance == null)
  423. newInstance = Resources.Load<ConfigOverride>(ConfigOverride.kAssetName);
  424. #if UNITY_EDITOR
  425. if (newInstance && newInstance != ms_Instance)
  426. {
  427. ms_Instance = newInstance;
  428. newInstance.RefreshGlobalShaderProperties(); // make sure noise textures are properly loaded as soon as the editor is started
  429. }
  430. #endif
  431. ms_Instance = newInstance;
  432. }
  433. if (ms_Instance == null)
  434. {
  435. #if UNITY_EDITOR
  436. ms_IsCreatingInstance = true;
  437. ms_Instance = ConfigOverride.CreateInstanceOverride();
  438. ms_IsCreatingInstance = false;
  439. ms_Instance.AutoSelectRenderPipeline();
  440. if (Application.isPlaying)
  441. ms_Instance.Reset(); // Reset is not automatically when instancing a ScriptableObject when in playmode
  442. #endif
  443. Debug.Assert(!(assertIfNotFound && ms_Instance == null), string.Format("Can't find any resource of type '{0}'. Make sure you have a ScriptableObject of this type in a 'Resources' folder.", typeof(Config)));
  444. }
  445. }
  446. return ms_Instance;
  447. }
  448. }
  449. }