ShaderGenerator.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. #if UNITY_EDITOR
  2. using UnityEngine;
  3. using UnityEngine.Rendering;
  4. using UnityEditor;
  5. using System.Collections.Generic;
  6. using System.IO;
  7. namespace VLB
  8. {
  9. public class ShaderGenerator : ScriptableObject
  10. {
  11. [SerializeField] TextAsset m_Base = null;
  12. TextAsset textAssetBase { get { return m_Base; } }
  13. [SerializeField] TextAsset m_Pass = null;
  14. TextAsset textAssetPass { get { return m_Pass; } }
  15. [SerializeField] TextAsset m_IncludesBuiltin = null;
  16. TextAsset textAssetIncludesBuiltin { get { return m_IncludesBuiltin; } }
  17. [SerializeField] TextAsset m_IncludesURP = null;
  18. TextAsset textAssetIncludesURP { get { return m_IncludesURP; } }
  19. [SerializeField] TextAsset m_IncludesHDRP = null;
  20. TextAsset textAssetIncludesHDRP { get { return m_IncludesHDRP; } }
  21. const string kShaderAssetName = "VLBGeneratedShader";
  22. static string GetOutputPath()
  23. {
  24. var path = Path.GetDirectoryName(AssetDatabase.GetAssetPath(Instance));
  25. if (string.IsNullOrEmpty(path)) path = "Plugins/VolumetricLightBeam/Shaders/";
  26. path = path.Replace("\\", "/");
  27. const string kAssetsFolder = "Assets/";
  28. if (path.IndexOf(kAssetsFolder) == 0) path = path.Remove(0, kAssetsFolder.Length);
  29. return path;
  30. }
  31. public class EnabledFeatures
  32. {
  33. public bool dithering;
  34. public bool depthBlend;
  35. public FeatureEnabledColorGradient colorGradient;
  36. public bool noise3D;
  37. public bool dynamicOcclusion;
  38. public bool meshSkewing;
  39. public bool shaderAccuracyHigh;
  40. }
  41. public static Shader Generate(RenderPipeline rp, RenderingMode rm, EnabledFeatures enabledFeatures)
  42. {
  43. // The instance might not be accessible yet, when called from Config.OnEnable for instance if the Config is enabled before the ShaderGenerator.
  44. // In this case we don't generate the shader right away, we store the parameters instead and we'll generate the shader in ShaderGenerator.OnEnable.
  45. if (Instance == null)
  46. {
  47. ms_GenerationParamOnEnable = new GenerationParam { renderPipeline = rp, renderingMode = rm, enabledFeatures = enabledFeatures };
  48. return null;
  49. }
  50. return new GenShader(rp, rm, enabledFeatures).Generate();
  51. }
  52. static string LoadText(TextAsset textAsset)
  53. {
  54. Debug.Assert(textAsset != null, "Fail to load a TextAsset, please try to reinstall the VolumetricLightBeam plugin");
  55. return textAsset.text;
  56. }
  57. TextAsset GetTextAssetIncludes(SRPHelper.RenderPipeline rp)
  58. {
  59. switch (rp)
  60. {
  61. case SRPHelper.RenderPipeline.BuiltIn:
  62. return textAssetIncludesBuiltin;
  63. case SRPHelper.RenderPipeline.LWRP:
  64. case SRPHelper.RenderPipeline.URP:
  65. return textAssetIncludesURP;
  66. case SRPHelper.RenderPipeline.HDRP:
  67. return textAssetIncludesHDRP;
  68. }
  69. return null;
  70. }
  71. static bool IsFogSupported(SRPHelper.RenderPipeline rp) { return rp != SRPHelper.RenderPipeline.HDRP; }
  72. enum ShaderLangage { CG, HLSL }
  73. static ShaderLangage GetShaderLangage(SRPHelper.RenderPipeline rp)
  74. {
  75. switch (rp)
  76. {
  77. case SRPHelper.RenderPipeline.BuiltIn:
  78. case SRPHelper.RenderPipeline.LWRP:
  79. return ShaderLangage.CG;
  80. case SRPHelper.RenderPipeline.URP:
  81. case SRPHelper.RenderPipeline.HDRP:
  82. return ShaderLangage.HLSL;
  83. }
  84. return ShaderLangage.CG;
  85. }
  86. static string GetShaderLangagePre (ShaderLangage lang) { return lang == ShaderLangage.CG ? "CGPROGRAM" : "HLSLPROGRAM"; }
  87. static string GetShaderLangagePost(ShaderLangage lang) { return lang == ShaderLangage.CG ? "ENDCG" : "ENDHLSL"; }
  88. public class GenPass
  89. {
  90. CullMode m_CullMode;
  91. public GenPass(CullMode cullMode)
  92. {
  93. m_CullMode = cullMode;
  94. }
  95. static void AppendMultiCompile(ref string str, bool genDefaultVariant, params string[] options)
  96. {
  97. #if UNITY_2019_1_OR_NEWER
  98. const string kPrefix = " #pragma multi_compile_local";
  99. #else
  100. const string kPrefix = " #pragma multi_compile";
  101. #endif
  102. str += kPrefix;
  103. if(genDefaultVariant) str += " __";
  104. foreach (string opt in options) str += " " + opt;
  105. str += System.Environment.NewLine;
  106. }
  107. public string Generate(SRPHelper.RenderPipeline rp, RenderingMode rm, EnabledFeatures enabledFeatures, int passID, int passCount)
  108. {
  109. var code = LoadText(Instance.textAssetPass);
  110. code = code.Replace("{VLB_GEN_CULLING}", m_CullMode.ToString());
  111. code = code.Replace("{VLB_GEN_PRAGMA_INSTANCING}", rm == RenderingMode.GPUInstancing ? "#pragma multi_compile_instancing" : "");
  112. code = code.Replace("{VLB_GEN_PRAGMA_FOG}", IsFogSupported(rp) ? "#pragma multi_compile_fog" : "");
  113. string multiCompileVariants = "";
  114. AppendMultiCompile(ref multiCompileVariants, true, ShaderKeywords.AlphaAsBlack);
  115. if (enabledFeatures.noise3D) AppendMultiCompile(ref multiCompileVariants, true, ShaderKeywords.Noise3D);
  116. if (enabledFeatures.depthBlend) AppendMultiCompile(ref multiCompileVariants, true, ShaderKeywords.DepthBlend);
  117. switch(enabledFeatures.colorGradient)
  118. {
  119. case FeatureEnabledColorGradient.HighOnly: AppendMultiCompile(ref multiCompileVariants, true, ShaderKeywords.ColorGradientMatrixHigh); break;
  120. case FeatureEnabledColorGradient.HighAndLow: AppendMultiCompile(ref multiCompileVariants, true, ShaderKeywords.ColorGradientMatrixHigh, ShaderKeywords.ColorGradientMatrixLow); break;
  121. }
  122. if (enabledFeatures.dynamicOcclusion) AppendMultiCompile(ref multiCompileVariants, true, ShaderKeywords.OcclusionClippingPlane, ShaderKeywords.OcclusionDepthTexture);
  123. if (enabledFeatures.meshSkewing) AppendMultiCompile(ref multiCompileVariants, true, ShaderKeywords.MeshSkewing);
  124. if (enabledFeatures.shaderAccuracyHigh) AppendMultiCompile(ref multiCompileVariants, true, ShaderKeywords.ShaderAccuracyHigh);
  125. code = code.Replace("{VLB_GEN_PRAGMA_MULTI_COMPILE_VARIANTS}", multiCompileVariants);
  126. var lang = GetShaderLangage(rp);
  127. code = code.Replace("{VLB_GEN_PROGRAM_PRE}", GetShaderLangagePre(lang));
  128. code = code.Replace("{VLB_GEN_PROGRAM_POST}", GetShaderLangagePost(lang));
  129. var passPre = "";
  130. if (passCount > 1)
  131. {
  132. code = code.Replace("{VLB_GEN_INPUT_VS}", string.Format("{0}", passID));
  133. code = code.Replace("{VLB_GEN_INPUT_FS}", string.Format("{0}", passID));
  134. }
  135. else
  136. {
  137. code = code.Replace("{VLB_GEN_INPUT_VS}", string.Format("{0}", "v.texcoord.y"));
  138. code = code.Replace("{VLB_GEN_INPUT_FS}", string.Format("{0}", "i.cameraPosObjectSpace_outsideBeam.w"));
  139. passPre += " #define VLB_PASS_OUTSIDEBEAM_FROM_VS_TO_FS 1" + System.Environment.NewLine;
  140. }
  141. if (rp != SRPHelper.RenderPipeline.BuiltIn)
  142. {
  143. passPre += " #define VLB_SRP_API 1" + System.Environment.NewLine;
  144. if (rm == RenderingMode.SRPBatcher)
  145. {
  146. passPre += " #define VLB_SRP_BATCHER 1" + System.Environment.NewLine;
  147. if (rp == SRPHelper.RenderPipeline.URP)
  148. {
  149. // force enable constant buffers to fix SRP Batcher support on Android
  150. passPre += " #pragma enable_cbuffer" + System.Environment.NewLine;
  151. }
  152. }
  153. }
  154. if (enabledFeatures.dithering)
  155. {
  156. passPre += " #define VLB_DITHERING 1" + System.Environment.NewLine;
  157. }
  158. passPre += LoadText(Instance.GetTextAssetIncludes(rp));
  159. code = code.Replace("{VLB_GEN_PRE}", passPre);
  160. return code;
  161. }
  162. }
  163. class GenShader
  164. {
  165. SRPHelper.RenderPipeline m_RenderPipeline;
  166. RenderingMode m_RenderingMode;
  167. EnabledFeatures m_EnabledFeatures;
  168. List<GenPass> m_Passes = new List<GenPass>();
  169. public GenShader(RenderPipeline rp, RenderingMode rm, EnabledFeatures enabledFeatures)
  170. {
  171. m_RenderingMode = rm;
  172. m_EnabledFeatures = enabledFeatures;
  173. switch (rp)
  174. {
  175. case RenderPipeline.BuiltIn:
  176. AddPass(CullMode.Front);
  177. if (rm == RenderingMode.MultiPass) AddPass(CullMode.Back);
  178. m_RenderPipeline = SRPHelper.RenderPipeline.BuiltIn;
  179. break;
  180. case RenderPipeline.URP:
  181. AddPass(CullMode.Front);
  182. m_RenderPipeline = SRPHelper.RenderPipeline.URP;
  183. break;
  184. case RenderPipeline.HDRP:
  185. AddPass(CullMode.Front);
  186. m_RenderPipeline = SRPHelper.RenderPipeline.HDRP;
  187. break;
  188. }
  189. }
  190. GenShader AddPass(CullMode cullMode)
  191. {
  192. m_Passes.Add(new GenPass(cullMode));
  193. return this;
  194. }
  195. public Shader Generate()
  196. {
  197. var shaderName = string.Format("Hidden/VLB_{0}_{1}", m_RenderPipeline, m_RenderingMode);
  198. var code = LoadText(Instance.textAssetBase);
  199. code = code.Replace("{VLB_GEN_SHADERNAME}", shaderName);
  200. var passes = "";
  201. for (int i = 0; i < m_Passes.Count; ++i)
  202. passes += m_Passes[i].Generate(m_RenderPipeline, m_RenderingMode, m_EnabledFeatures, i, m_Passes.Count);
  203. code = code.Replace("{VLB_GEN_PASSES}", passes);
  204. code = code.Replace("{VLB_GEN_SPECIFIC_INCLUDE}", GetRenderPipelineInclude(m_RenderPipeline));
  205. var outputFolderPath = ShaderGenerator.GetOutputPath();
  206. var outputFilePath = Path.Combine(outputFolderPath, kShaderAssetName);
  207. var outputFullPath = System.IO.Path.Combine(Application.dataPath, outputFilePath + ".shader");
  208. File.WriteAllText(outputFullPath, code);
  209. AssetDatabase.Refresh();
  210. var shader = Shader.Find(shaderName);
  211. Debug.Assert(shader != null, string.Format("Failed to generate shader '{0}' at '{1}'", shaderName, outputFullPath));
  212. return shader;
  213. }
  214. string GetRenderPipelineInclude(SRPHelper.RenderPipeline rp)
  215. {
  216. switch(rp)
  217. {
  218. case SRPHelper.RenderPipeline.BuiltIn: return "ShaderSpecificBuiltin.cginc";
  219. case SRPHelper.RenderPipeline.HDRP: return "ShaderSpecificHDRP.hlsl";
  220. case SRPHelper.RenderPipeline.LWRP:
  221. case SRPHelper.RenderPipeline.URP: return "ShaderSpecificURP.cginc";
  222. }
  223. return null;
  224. }
  225. }
  226. static ShaderGenerator FindInstance()
  227. {
  228. var assetGUIDs = AssetDatabase.FindAssets("ShaderGenerator");
  229. foreach (var guid in assetGUIDs)
  230. {
  231. var path = AssetDatabase.GUIDToAssetPath(guid);
  232. var asset = AssetDatabase.LoadAssetAtPath<ShaderGenerator>(path);
  233. if (asset)
  234. return asset;
  235. }
  236. return null;
  237. }
  238. // Store data to generate the shader on OnEnable
  239. class GenerationParam
  240. {
  241. public RenderPipeline renderPipeline;
  242. public RenderingMode renderingMode;
  243. public EnabledFeatures enabledFeatures;
  244. }
  245. static GenerationParam ms_GenerationParamOnEnable = null;
  246. void OnEnable()
  247. {
  248. if(ms_GenerationParamOnEnable != null && Instance != null)
  249. {
  250. Generate(ms_GenerationParamOnEnable.renderPipeline, ms_GenerationParamOnEnable.renderingMode, ms_GenerationParamOnEnable.enabledFeatures);
  251. ms_GenerationParamOnEnable = null;
  252. }
  253. }
  254. // Singleton management
  255. static ShaderGenerator m_Instance = null;
  256. static ShaderGenerator Instance
  257. {
  258. get
  259. {
  260. if (m_Instance == null)
  261. m_Instance = FindInstance();
  262. return m_Instance;
  263. }
  264. }
  265. }
  266. }
  267. #endif