Files
FORT-BO/Assets/Plugins/VolumetricLightBeam/Scripts/ShaderGenerator.cs
T
2026-08-29 13:34:57 +03:00

319 lines
13 KiB
C#

#if UNITY_EDITOR
using UnityEngine;
using UnityEngine.Rendering;
using UnityEditor;
using System.Collections.Generic;
using System.IO;
namespace VLB
{
public class ShaderGenerator : ScriptableObject
{
[SerializeField] TextAsset m_Base = null;
TextAsset textAssetBase { get { return m_Base; } }
[SerializeField] TextAsset m_Pass = null;
TextAsset textAssetPass { get { return m_Pass; } }
[SerializeField] TextAsset m_IncludesBuiltin = null;
TextAsset textAssetIncludesBuiltin { get { return m_IncludesBuiltin; } }
[SerializeField] TextAsset m_IncludesURP = null;
TextAsset textAssetIncludesURP { get { return m_IncludesURP; } }
[SerializeField] TextAsset m_IncludesHDRP = null;
TextAsset textAssetIncludesHDRP { get { return m_IncludesHDRP; } }
const string kShaderAssetName = "VLBGeneratedShader";
static string GetOutputPath()
{
var path = Path.GetDirectoryName(AssetDatabase.GetAssetPath(Instance));
if (string.IsNullOrEmpty(path)) path = Consts.PluginFolder + "/Shaders/";
path = path.Replace("\\", "/");
const string kAssetsFolder = "Assets/";
if (path.IndexOf(kAssetsFolder) == 0) path = path.Remove(0, kAssetsFolder.Length);
return path;
}
public class EnabledFeatures
{
public bool dithering;
public bool depthBlend;
public FeatureEnabledColorGradient colorGradient;
public bool noise3D;
public bool dynamicOcclusion;
public bool meshSkewing;
public bool shaderAccuracyHigh;
}
public static Shader Generate(RenderPipeline rp, RenderingMode rm, EnabledFeatures enabledFeatures)
{
// The instance might not be accessible yet, when called from Config.OnEnable for instance if the Config is enabled before the ShaderGenerator.
// 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.
if (Instance == null)
{
ms_GenerationParamOnEnable = new GenerationParam { renderPipeline = rp, renderingMode = rm, enabledFeatures = enabledFeatures };
return null;
}
return new GenShader(rp, rm, enabledFeatures).Generate();
}
static string LoadText(TextAsset textAsset)
{
Debug.Assert(textAsset != null, "Fail to load a TextAsset, please try to reinstall the VolumetricLightBeam plugin");
return textAsset.text;
}
TextAsset GetTextAssetIncludes(SRPHelper.RenderPipeline rp)
{
switch (rp)
{
case SRPHelper.RenderPipeline.BuiltIn:
return textAssetIncludesBuiltin;
case SRPHelper.RenderPipeline.LWRP:
case SRPHelper.RenderPipeline.URP:
return textAssetIncludesURP;
case SRPHelper.RenderPipeline.HDRP:
return textAssetIncludesHDRP;
}
return null;
}
static bool IsFogSupported(SRPHelper.RenderPipeline rp) { return rp != SRPHelper.RenderPipeline.HDRP; }
enum ShaderLangage { CG, HLSL }
static ShaderLangage GetShaderLangage(SRPHelper.RenderPipeline rp)
{
switch (rp)
{
case SRPHelper.RenderPipeline.BuiltIn:
case SRPHelper.RenderPipeline.LWRP:
return ShaderLangage.CG;
case SRPHelper.RenderPipeline.URP:
case SRPHelper.RenderPipeline.HDRP:
return ShaderLangage.HLSL;
}
return ShaderLangage.CG;
}
static string GetShaderLangagePre (ShaderLangage lang) { return lang == ShaderLangage.CG ? "CGPROGRAM" : "HLSLPROGRAM"; }
static string GetShaderLangagePost(ShaderLangage lang) { return lang == ShaderLangage.CG ? "ENDCG" : "ENDHLSL"; }
public class GenPass
{
CullMode m_CullMode;
public GenPass(CullMode cullMode)
{
m_CullMode = cullMode;
}
static void AppendMultiCompile(ref string str, bool genDefaultVariant, params string[] options)
{
#if UNITY_2019_1_OR_NEWER
const string kPrefix = " #pragma multi_compile_local";
#else
const string kPrefix = " #pragma multi_compile";
#endif
str += kPrefix;
if(genDefaultVariant) str += " __";
foreach (string opt in options) str += " " + opt;
str += System.Environment.NewLine;
}
public string Generate(SRPHelper.RenderPipeline rp, RenderingMode rm, EnabledFeatures enabledFeatures, int passID, int passCount)
{
var code = LoadText(Instance.textAssetPass);
code = code.Replace("{VLB_GEN_CULLING}", m_CullMode.ToString());
code = code.Replace("{VLB_GEN_PRAGMA_INSTANCING}", rm == RenderingMode.GPUInstancing ? "#pragma multi_compile_instancing" : "");
code = code.Replace("{VLB_GEN_PRAGMA_FOG}", IsFogSupported(rp) ? "#pragma multi_compile_fog" : "");
string multiCompileVariants = "";
AppendMultiCompile(ref multiCompileVariants, true, ShaderKeywords.AlphaAsBlack);
if (enabledFeatures.noise3D) AppendMultiCompile(ref multiCompileVariants, true, ShaderKeywords.Noise3D);
if (enabledFeatures.depthBlend) AppendMultiCompile(ref multiCompileVariants, true, ShaderKeywords.DepthBlend);
switch(enabledFeatures.colorGradient)
{
case FeatureEnabledColorGradient.HighOnly: AppendMultiCompile(ref multiCompileVariants, true, ShaderKeywords.ColorGradientMatrixHigh); break;
case FeatureEnabledColorGradient.HighAndLow: AppendMultiCompile(ref multiCompileVariants, true, ShaderKeywords.ColorGradientMatrixHigh, ShaderKeywords.ColorGradientMatrixLow); break;
}
if (enabledFeatures.dynamicOcclusion) AppendMultiCompile(ref multiCompileVariants, true, ShaderKeywords.OcclusionClippingPlane, ShaderKeywords.OcclusionDepthTexture);
if (enabledFeatures.meshSkewing) AppendMultiCompile(ref multiCompileVariants, true, ShaderKeywords.MeshSkewing);
if (enabledFeatures.shaderAccuracyHigh) AppendMultiCompile(ref multiCompileVariants, true, ShaderKeywords.ShaderAccuracyHigh);
code = code.Replace("{VLB_GEN_PRAGMA_MULTI_COMPILE_VARIANTS}", multiCompileVariants);
var lang = GetShaderLangage(rp);
code = code.Replace("{VLB_GEN_PROGRAM_PRE}", GetShaderLangagePre(lang));
code = code.Replace("{VLB_GEN_PROGRAM_POST}", GetShaderLangagePost(lang));
var passPre = "";
if (passCount > 1)
{
code = code.Replace("{VLB_GEN_INPUT_VS}", string.Format("{0}", passID));
code = code.Replace("{VLB_GEN_INPUT_FS}", string.Format("{0}", passID));
}
else
{
code = code.Replace("{VLB_GEN_INPUT_VS}", string.Format("{0}", "v.texcoord.y"));
code = code.Replace("{VLB_GEN_INPUT_FS}", string.Format("{0}", "i.cameraPosObjectSpace_outsideBeam.w"));
passPre += " #define VLB_PASS_OUTSIDEBEAM_FROM_VS_TO_FS 1" + System.Environment.NewLine;
}
if (rp != SRPHelper.RenderPipeline.BuiltIn)
{
passPre += " #define VLB_SRP_API 1" + System.Environment.NewLine;
if (rm == RenderingMode.SRPBatcher)
{
passPre += " #define VLB_SRP_BATCHER 1" + System.Environment.NewLine;
if (rp == SRPHelper.RenderPipeline.URP)
{
// force enable constant buffers to fix SRP Batcher support on Android
passPre += " #pragma enable_cbuffer" + System.Environment.NewLine;
}
}
}
if (enabledFeatures.dithering)
{
passPre += " #define VLB_DITHERING 1" + System.Environment.NewLine;
}
passPre += LoadText(Instance.GetTextAssetIncludes(rp));
code = code.Replace("{VLB_GEN_PRE}", passPre);
return code;
}
}
class GenShader
{
SRPHelper.RenderPipeline m_RenderPipeline;
RenderingMode m_RenderingMode;
EnabledFeatures m_EnabledFeatures;
List<GenPass> m_Passes = new List<GenPass>();
public GenShader(RenderPipeline rp, RenderingMode rm, EnabledFeatures enabledFeatures)
{
m_RenderingMode = rm;
m_EnabledFeatures = enabledFeatures;
switch (rp)
{
case RenderPipeline.BuiltIn:
AddPass(CullMode.Front);
if (rm == RenderingMode.MultiPass) AddPass(CullMode.Back);
m_RenderPipeline = SRPHelper.RenderPipeline.BuiltIn;
break;
case RenderPipeline.URP:
AddPass(CullMode.Front);
m_RenderPipeline = SRPHelper.RenderPipeline.URP;
break;
case RenderPipeline.HDRP:
AddPass(CullMode.Front);
m_RenderPipeline = SRPHelper.RenderPipeline.HDRP;
break;
}
}
GenShader AddPass(CullMode cullMode)
{
m_Passes.Add(new GenPass(cullMode));
return this;
}
public Shader Generate()
{
var shaderName = string.Format("Hidden/VLB_{0}_{1}", m_RenderPipeline, m_RenderingMode);
var code = LoadText(Instance.textAssetBase);
code = code.Replace("{VLB_GEN_SHADERNAME}", shaderName);
var passes = "";
for (int i = 0; i < m_Passes.Count; ++i)
passes += m_Passes[i].Generate(m_RenderPipeline, m_RenderingMode, m_EnabledFeatures, i, m_Passes.Count);
code = code.Replace("{VLB_GEN_PASSES}", passes);
code = code.Replace("{VLB_GEN_SPECIFIC_INCLUDE}", GetRenderPipelineInclude(m_RenderPipeline));
var outputFolderPath = ShaderGenerator.GetOutputPath();
var outputFilePath = Path.Combine(outputFolderPath, kShaderAssetName);
var outputFullPath = System.IO.Path.Combine(Application.dataPath, outputFilePath + ".shader");
File.WriteAllText(outputFullPath, code);
AssetDatabase.Refresh();
var shader = Shader.Find(shaderName);
Debug.Assert(shader != null, string.Format("Failed to generate shader '{0}' at '{1}'", shaderName, outputFullPath));
return shader;
}
string GetRenderPipelineInclude(SRPHelper.RenderPipeline rp)
{
switch(rp)
{
case SRPHelper.RenderPipeline.BuiltIn: return "ShaderSpecificBuiltin.cginc";
case SRPHelper.RenderPipeline.HDRP: return "ShaderSpecificHDRP.hlsl";
case SRPHelper.RenderPipeline.LWRP:
case SRPHelper.RenderPipeline.URP: return "ShaderSpecificURP.cginc";
}
return null;
}
}
static ShaderGenerator FindInstance()
{
var assetGUIDs = AssetDatabase.FindAssets("ShaderGenerator");
foreach (var guid in assetGUIDs)
{
var path = AssetDatabase.GUIDToAssetPath(guid);
var asset = AssetDatabase.LoadAssetAtPath<ShaderGenerator>(path);
if (asset)
return asset;
}
return null;
}
// Store data to generate the shader on OnEnable
class GenerationParam
{
public RenderPipeline renderPipeline;
public RenderingMode renderingMode;
public EnabledFeatures enabledFeatures;
}
static GenerationParam ms_GenerationParamOnEnable = null;
void OnEnable()
{
if(ms_GenerationParamOnEnable != null && Instance != null)
{
Generate(ms_GenerationParamOnEnable.renderPipeline, ms_GenerationParamOnEnable.renderingMode, ms_GenerationParamOnEnable.enabledFeatures);
ms_GenerationParamOnEnable = null;
}
}
// Singleton management
static ShaderGenerator m_Instance = null;
static ShaderGenerator Instance
{
get
{
if (m_Instance == null)
m_Instance = FindInstance();
return m_Instance;
}
}
}
}
#endif