1099 lines
57 KiB
C#
1099 lines
57 KiB
C#
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using UnityEditor;
|
|
using System;
|
|
using System.IO;
|
|
using System.Text.RegularExpressions;
|
|
using System.Text;
|
|
using System.Globalization;
|
|
|
|
// Based on Kaj Shader Optimizer, v9, licensed under MIT license
|
|
// https://github.com/DarthShader/Kaj-Unity-Shaders/blob/master/Shaders/Kaj/Editor/KajShaderOptimizer.cs
|
|
// Thanks, Kaj!
|
|
|
|
// Some parts of this code have not been fully integrated yet.
|
|
|
|
namespace SilentCelShading.Unity.Baking
|
|
{
|
|
|
|
public enum LightMode
|
|
{
|
|
Always=1,
|
|
ForwardBase=2,
|
|
ForwardAdd=4,
|
|
Deferred=8,
|
|
ShadowCaster=16,
|
|
MotionVectors=32,
|
|
PrepassBase=64,
|
|
PrepassFinal=128,
|
|
Vertex=256,
|
|
VertexLMRGBM=512,
|
|
VertexLM=1024
|
|
}
|
|
|
|
// Static methods to generate new shader files with in-place constants based on a material's properties
|
|
// and link that new shader to the material automatically
|
|
public class ShaderOptimizer
|
|
{
|
|
// For some reason, 'if' statements with replaced constant (literal) conditions cause some compilation error
|
|
// So until that is figured out, branches will be removed by default
|
|
// Set to false if you want to keep UNITY_BRANCH and [branch]
|
|
public static bool RemoveUnityBranches = true;
|
|
|
|
// LOD Crossfade Dithing doesn't have multi_compile keyword correctly toggled at build time (its always included) so
|
|
// this hard-coded material property will uncomment //#pragma multi_compile _ LOD_FADE_CROSSFADE in optimized .shader files
|
|
public static readonly string LODCrossFadePropertyName = "_LODCrossfade";
|
|
|
|
// IgnoreProjector and ForceNoShadowCasting don't work as override tags, so material properties by these names
|
|
// will determine whether or not //"IgnoreProjector"="True" etc. will be uncommented in optimized .shader files
|
|
public static readonly string IgnoreProjectorPropertyName = "_IgnoreProjector";
|
|
public static readonly string ForceNoShadowCastingPropertyName = "_ForceNoShadowCasting";
|
|
|
|
// Material property suffix that controls whether the property of the same name gets baked into the optimized shader
|
|
// e.g. if _Color exists and _ColorAnimated = 1, _Color will not be baked in
|
|
public static readonly string AnimatedPropertySuffix = "Animated";
|
|
|
|
// Currently, Material.SetShaderPassEnabled doesn't work on "ShadowCaster" lightmodes,
|
|
// and doesn't let "ForwardAdd" lights get turned into vertex lights if "ForwardAdd" is simply disabled
|
|
// vs. if the pases didn't exist at all in the shader.
|
|
// The Optimizer will take a mask property by this name and attempt to correct these issues
|
|
// by hard-removing the shadowcaster and fwdadd passes from the shader being optimized.
|
|
public static readonly string DisabledLightModesPropertyName = "_LightModes";
|
|
|
|
// Property that determines whether or not to evaluate KSOInlineSamplerState comments.
|
|
// Inline samplers can be used to get a wider variety of wrap/filter combinations at the cost
|
|
// of only having 1x anisotropic filtering on all textures
|
|
public static readonly string UseInlineSamplerStatesPropertyName = "_InlineSamplerStates";
|
|
private static bool UseInlineSamplerStates = true;
|
|
|
|
// Material properties are put into each CGPROGRAM as preprocessor defines when the optimizer is run.
|
|
// This is mainly targeted at culling interpolators and lines that rely on those interpolators.
|
|
// (The compiler is not smart enough to cull VS output that isn't used anywhere in the PS)
|
|
// Additionally, simply enabling the optimizer can define a keyword, whose name is stored here.
|
|
// This keyword is added to the beginning of all passes, right after CGPROGRAM
|
|
public static readonly string OptimizerEnabledKeyword = "FINALPASS";
|
|
|
|
// Mega shaders are expected to have geometry and tessellation shaders enabled by default,
|
|
// but with the ability to be disabled by convention property names when the optimizer is run.
|
|
// Additionally, they can be removed per-lightmode by the given property name plus
|
|
// the lightmode name as a suffix (e.g. group_toggle_GeometryShadowCaster)
|
|
// Geometry and Tessellation shaders are REMOVED by default, but if the main gorups
|
|
// are enabled certain pass types are assumed to be ENABLED
|
|
// Note: This is short-circuited for outlines, because geometry is only used for outlines.
|
|
public static readonly string GeometryShaderEnabledPropertyName = "group_toggle_Geometry";
|
|
public static readonly string TessellationEnabledPropertyName = "group_toggle_Tessellation";
|
|
private static bool UseGeometry = true;
|
|
private static bool UseGeometryForwardBase = true;
|
|
private static bool UseGeometryForwardAdd = true;
|
|
private static bool UseGeometryShadowCaster = true;
|
|
private static bool UseGeometryMeta = true;
|
|
private static bool UseTessellation = false;
|
|
private static bool UseTessellationForwardBase = true;
|
|
private static bool UseTessellationForwardAdd = true;
|
|
private static bool UseTessellationShadowCaster = true;
|
|
private static bool UseTessellationMeta = false;
|
|
|
|
// Tessellation can be slightly optimized with a constant max tessellation factor attribute
|
|
// on the hull shader. A non-animated property by this name will replace the argument of said
|
|
// attribute if it exists.
|
|
public static readonly string TessellationMaxFactorPropertyName = "_TessellationFactorMax";
|
|
|
|
// Text to prepend to log entries.
|
|
public static readonly string LogHeader = "Shader Baking: ";
|
|
|
|
|
|
private static string CurrentLightmode = "";
|
|
|
|
// In-order list of inline sampler state names that will be replaced by InlineSamplerState() lines
|
|
public static readonly string[] InlineSamplerStateNames = new string[]
|
|
{
|
|
"_linear_repeat",
|
|
"_linear_clamp",
|
|
"_linear_mirror",
|
|
"_linear_mirroronce",
|
|
"_point_repeat",
|
|
"_point_clamp",
|
|
"_point_mirror",
|
|
"_point_mirroronce",
|
|
"_trilinear_repeat",
|
|
"_trilinear_clamp",
|
|
"_trilinear_mirror",
|
|
"_trilinear_mirroronce"
|
|
};
|
|
|
|
// Would be better to dynamically parse the "C:\Program Files\UnityXXXX\Editor\Data\CGIncludes\" folder
|
|
// to get version specific includes but eh
|
|
public static readonly string[] DefaultUnityShaderIncludes = new string[]
|
|
{
|
|
"UnityUI.cginc",
|
|
"AutoLight.cginc",
|
|
"GLSLSupport.glslinc",
|
|
"HLSLSupport.cginc",
|
|
"Lighting.cginc",
|
|
"SpeedTreeBillboardCommon.cginc",
|
|
"SpeedTreeCommon.cginc",
|
|
"SpeedTreeVertex.cginc",
|
|
"SpeedTreeWind.cginc",
|
|
"TerrainEngine.cginc",
|
|
"TerrainSplatmapCommon.cginc",
|
|
"Tessellation.cginc",
|
|
"UnityBuiltin2xTreeLibrary.cginc",
|
|
"UnityBuiltin3xTreeLibrary.cginc",
|
|
"UnityCG.cginc",
|
|
"UnityCG.glslinc",
|
|
"UnityCustomRenderTexture.cginc",
|
|
"UnityDeferredLibrary.cginc",
|
|
"UnityDeprecated.cginc",
|
|
"UnityGBuffer.cginc",
|
|
"UnityGlobalIllumination.cginc",
|
|
"UnityImageBasedLighting.cginc",
|
|
"UnityInstancing.cginc",
|
|
"UnityLightingCommon.cginc",
|
|
"UnityMetaPass.cginc",
|
|
"UnityPBSLighting.cginc",
|
|
"UnityShaderUtilities.cginc",
|
|
"UnityShaderVariables.cginc",
|
|
"UnityShadowLibrary.cginc",
|
|
"UnitySprites.cginc",
|
|
"UnityStandardBRDF.cginc",
|
|
"UnityStandardConfig.cginc",
|
|
"UnityStandardCore.cginc",
|
|
"UnityStandardCoreForward.cginc",
|
|
"UnityStandardCoreForwardSimple.cginc",
|
|
"UnityStandardInput.cginc",
|
|
"UnityStandardMeta.cginc",
|
|
"UnityStandardParticleInstancing.cginc",
|
|
"UnityStandardParticles.cginc",
|
|
"UnityStandardParticleShadow.cginc",
|
|
"UnityStandardShadow.cginc",
|
|
"UnityStandardUtils.cginc"
|
|
};
|
|
|
|
public static readonly char[] ValidSeparators = new char[] {' ','\t','\r','\n',';',',','.','(',')','[',']','{','}','>','<','=','!','&','|','^','+','-','*','/','#' };
|
|
|
|
public static readonly string[] ValidPropertyDataTypes = new string[]
|
|
{
|
|
"int",
|
|
"float",
|
|
"float2",
|
|
"float3",
|
|
"float4",
|
|
"half",
|
|
"half2",
|
|
"half3",
|
|
"half4",
|
|
"fixed",
|
|
"fixed2",
|
|
"fixed3",
|
|
"fixed4"
|
|
};
|
|
|
|
public enum PropertyType
|
|
{
|
|
Vector,
|
|
Float
|
|
}
|
|
|
|
public class PropertyData
|
|
{
|
|
public PropertyType type;
|
|
public string name;
|
|
public Vector4 value;
|
|
}
|
|
|
|
public class Macro
|
|
{
|
|
public string name;
|
|
public string[] args;
|
|
public string contents;
|
|
}
|
|
|
|
public class ParsedShaderFile
|
|
{
|
|
public string filePath;
|
|
public string[] lines;
|
|
}
|
|
|
|
public class TextureProperty
|
|
{
|
|
public string name;
|
|
public Texture texture;
|
|
public int uv;
|
|
public Vector2 scale;
|
|
public Vector2 offset;
|
|
}
|
|
|
|
public class GrabPassReplacement
|
|
{
|
|
public string originalName;
|
|
public string newName;
|
|
}
|
|
|
|
public static bool Lock(Material material, MaterialProperty[] props)
|
|
{
|
|
// File filepaths and names
|
|
Shader shader = material.shader;
|
|
string shaderFilePath = AssetDatabase.GetAssetPath(shader);
|
|
string materialFilePath = AssetDatabase.GetAssetPath(material);
|
|
string materialFolder = Path.GetDirectoryName(materialFilePath);
|
|
string smallguid = Guid.NewGuid().ToString().Split('-')[0];
|
|
string newShaderName = "Hidden/" + shader.name + "/" + material.name + "-" + smallguid;
|
|
string newShaderDirectory = materialFolder + "/BakedShaders/" + material.name + "-" + smallguid + "/";
|
|
|
|
// Get collection of all properties to replace
|
|
// Simultaneously build a string of #defines for each CGPROGRAM
|
|
StringBuilder definesSB = new StringBuilder();
|
|
// Append convention OPTIMIZER_ENABLED keyword
|
|
definesSB.Append(Environment.NewLine);
|
|
definesSB.Append("#define ");
|
|
definesSB.Append(OptimizerEnabledKeyword);
|
|
definesSB.Append(Environment.NewLine);
|
|
// Append all keywords active on the material
|
|
foreach (string keyword in material.shaderKeywords)
|
|
{
|
|
if (keyword == "") continue; // idk why but null keywords exist if _ keyword is used and not removed by the editor at some point
|
|
definesSB.Append("#define ");
|
|
definesSB.Append(keyword);
|
|
definesSB.Append(Environment.NewLine);
|
|
}
|
|
|
|
List<PropertyData> constantProps = new List<PropertyData>();
|
|
foreach (MaterialProperty prop in props)
|
|
{
|
|
if (prop == null) continue;
|
|
|
|
// Every property gets turned into a preprocessor variable
|
|
switch(prop.type)
|
|
{
|
|
case MaterialProperty.PropType.Float:
|
|
case MaterialProperty.PropType.Range:
|
|
definesSB.Append("#define PROP");
|
|
definesSB.Append(prop.name.ToUpper());
|
|
definesSB.Append(' ');
|
|
definesSB.Append(prop.floatValue.ToString(CultureInfo.InvariantCulture));
|
|
definesSB.Append(Environment.NewLine);
|
|
break;
|
|
case MaterialProperty.PropType.Texture:
|
|
if (prop.textureValue != null)
|
|
{
|
|
definesSB.Append("#define PROP");
|
|
definesSB.Append(prop.name.ToUpper());
|
|
definesSB.Append(Environment.NewLine);
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (prop.name.EndsWith(AnimatedPropertySuffix)) continue;
|
|
else if (prop.name == UseInlineSamplerStatesPropertyName)
|
|
{
|
|
UseInlineSamplerStates = (prop.floatValue == 1);
|
|
continue;
|
|
}
|
|
else if (prop.name.StartsWith(GeometryShaderEnabledPropertyName))
|
|
{
|
|
if (prop.name == GeometryShaderEnabledPropertyName)
|
|
UseGeometry = (prop.floatValue == 1);
|
|
else if (prop.name == GeometryShaderEnabledPropertyName + "ForwardBase")
|
|
UseGeometryForwardBase = (prop.floatValue == 1);
|
|
else if (prop.name == GeometryShaderEnabledPropertyName + "ForwardAdd")
|
|
UseGeometryForwardAdd = (prop.floatValue == 1);
|
|
else if (prop.name == GeometryShaderEnabledPropertyName + "ShadowCaster")
|
|
UseGeometryShadowCaster = (prop.floatValue == 1);
|
|
else if (prop.name == GeometryShaderEnabledPropertyName + "Meta")
|
|
UseGeometryMeta = (prop.floatValue == 1);
|
|
}
|
|
else if (prop.name.StartsWith(TessellationEnabledPropertyName))
|
|
{
|
|
if (prop.name == TessellationEnabledPropertyName)
|
|
UseTessellation = (prop.floatValue == 1);
|
|
else if (prop.name == TessellationEnabledPropertyName + "ForwardBase")
|
|
UseTessellationForwardBase = (prop.floatValue == 1);
|
|
else if (prop.name == TessellationEnabledPropertyName + "ForwardAdd")
|
|
UseTessellationForwardAdd = (prop.floatValue == 1);
|
|
else if (prop.name == TessellationEnabledPropertyName + "ShadowCaster")
|
|
UseTessellationShadowCaster = (prop.floatValue == 1);
|
|
else if (prop.name == TessellationEnabledPropertyName + "Meta")
|
|
UseTessellationMeta = (prop.floatValue == 1);
|
|
}
|
|
|
|
|
|
|
|
// Check for the convention 'Animated' Property to be true otherwise assume all properties are constant
|
|
// nlogn trash
|
|
MaterialProperty animatedProp = Array.Find(props, x => x.name == prop.name + AnimatedPropertySuffix);
|
|
if (animatedProp != null && animatedProp.floatValue == 1)
|
|
continue;
|
|
|
|
PropertyData propData;
|
|
switch(prop.type)
|
|
{
|
|
case MaterialProperty.PropType.Color:
|
|
propData = new PropertyData();
|
|
propData.type = PropertyType.Vector;
|
|
propData.name = prop.name;
|
|
if ((prop.flags & MaterialProperty.PropFlags.HDR) != 0)
|
|
{
|
|
if ((prop.flags & MaterialProperty.PropFlags.Gamma) != 0)
|
|
propData.value = prop.colorValue.linear;
|
|
else propData.value = prop.colorValue;
|
|
}
|
|
else if ((prop.flags & MaterialProperty.PropFlags.Gamma) != 0)
|
|
propData.value = prop.colorValue;
|
|
else propData.value = prop.colorValue.linear;
|
|
constantProps.Add(propData);
|
|
break;
|
|
case MaterialProperty.PropType.Vector:
|
|
propData = new PropertyData();
|
|
propData.type = PropertyType.Vector;
|
|
propData.name = prop.name;
|
|
propData.value = prop.vectorValue;
|
|
constantProps.Add(propData);
|
|
break;
|
|
case MaterialProperty.PropType.Float:
|
|
case MaterialProperty.PropType.Range:
|
|
propData = new PropertyData();
|
|
propData.type = PropertyType.Float;
|
|
propData.name = prop.name;
|
|
propData.value = new Vector4(prop.floatValue, 0, 0, 0);
|
|
constantProps.Add(propData);
|
|
break;
|
|
case MaterialProperty.PropType.Texture:
|
|
animatedProp = Array.Find(props, x => x.name == prop.name + "_ST" + AnimatedPropertySuffix);
|
|
if (!(animatedProp != null && animatedProp.floatValue == 1))
|
|
{
|
|
PropertyData ST = new PropertyData();
|
|
ST.type = PropertyType.Vector;
|
|
ST.name = prop.name + "_ST";
|
|
Vector2 offset = material.GetTextureOffset(prop.name);
|
|
Vector2 scale = material.GetTextureScale(prop.name);
|
|
ST.value = new Vector4(scale.x, scale.y, offset.x, offset.y);
|
|
constantProps.Add(ST);
|
|
}
|
|
animatedProp = Array.Find(props, x => x.name == prop.name + "_TexelSize" + AnimatedPropertySuffix);
|
|
if (!(animatedProp != null && animatedProp.floatValue == 1))
|
|
{
|
|
PropertyData TexelSize = new PropertyData();
|
|
TexelSize.type = PropertyType.Vector;
|
|
TexelSize.name = prop.name + "_TexelSize";
|
|
Texture t = prop.textureValue;
|
|
if (t != null)
|
|
TexelSize.value = new Vector4(1.0f / t.width, 1.0f / t.height, t.width, t.height);
|
|
else TexelSize.value = new Vector4(1.0f, 1.0f, 1.0f, 1.0f);
|
|
constantProps.Add(TexelSize);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
string optimizerDefines = definesSB.ToString();
|
|
|
|
// Get list of lightmode passes to delete
|
|
List<string> disabledLightModes = new List<string>();
|
|
var disabledLightModesProperty = Array.Find(props, x => x.name == DisabledLightModesPropertyName);
|
|
if (disabledLightModesProperty != null)
|
|
{
|
|
int lightModesMask = (int)disabledLightModesProperty.floatValue;
|
|
if ((lightModesMask & (int)LightMode.ForwardAdd) != 0)
|
|
disabledLightModes.Add("ForwardAdd");
|
|
if ((lightModesMask & (int)LightMode.ShadowCaster) != 0)
|
|
disabledLightModes.Add("ShadowCaster");
|
|
}
|
|
|
|
// Parse shader and cginc files, also gets preprocessor macros
|
|
List<ParsedShaderFile> shaderFiles = new List<ParsedShaderFile>();
|
|
List<Macro> macros = new List<Macro>();
|
|
if (!ParseShaderFilesRecursive(shaderFiles, newShaderDirectory, shaderFilePath, macros))
|
|
return false;
|
|
|
|
|
|
List<GrabPassReplacement> grabPassVariables = new List<GrabPassReplacement>();
|
|
// Loop back through and do macros, props, and all other things line by line as to save string ops
|
|
// Will still be a massive n2 operation from each line * each property
|
|
foreach (ParsedShaderFile psf in shaderFiles)
|
|
{
|
|
// Shader file specific stuff
|
|
if (psf.filePath.EndsWith(".shader"))
|
|
{
|
|
for (int i=0; i<psf.lines.Length;i++)
|
|
{
|
|
string trimmedLine = psf.lines[i].TrimStart();
|
|
if (trimmedLine.StartsWith("Shader"))
|
|
{
|
|
string originalSgaderName = psf.lines[i].Split('\"')[1];
|
|
psf.lines[i] = psf.lines[i].Replace(originalSgaderName, newShaderName);
|
|
}
|
|
else if (trimmedLine.StartsWith("//#pragmamulti_compile_LOD_FADE_CROSSFADE"))
|
|
{
|
|
MaterialProperty crossfadeProp = Array.Find(props, x => x.name == LODCrossFadePropertyName);
|
|
if (crossfadeProp != null && crossfadeProp.floatValue == 1)
|
|
psf.lines[i] = psf.lines[i].Replace("//#pragma", "#pragma");
|
|
}
|
|
else if (trimmedLine.StartsWith("//\"IgnoreProjector\"=\"True\""))
|
|
{
|
|
MaterialProperty projProp = Array.Find(props, x => x.name == IgnoreProjectorPropertyName);
|
|
if (projProp != null && projProp.floatValue == 1)
|
|
psf.lines[i] = psf.lines[i].Replace("//\"IgnoreProjector", "\"IgnoreProjector");
|
|
}
|
|
else if (trimmedLine.StartsWith("//\"ForceNoShadowCasting\"=\"True\""))
|
|
{
|
|
MaterialProperty forceNoShadowsProp = Array.Find(props, x => x.name == ForceNoShadowCastingPropertyName);
|
|
if (forceNoShadowsProp != null && forceNoShadowsProp.floatValue == 1)
|
|
psf.lines[i] = psf.lines[i].Replace("//\"ForceNoShadowCasting", "\"ForceNoShadowCasting");
|
|
}
|
|
else if (trimmedLine.StartsWith("GrabPass {"))
|
|
{
|
|
GrabPassReplacement gpr = new GrabPassReplacement();
|
|
string[] splitLine = trimmedLine.Split('\"');
|
|
if (splitLine.Length == 1)
|
|
gpr.originalName = "_GrabTexture";
|
|
else
|
|
gpr.originalName = splitLine[1];
|
|
gpr.newName = material.GetTag("GrabPass" + grabPassVariables.Count, false, "_GrabTexture");
|
|
psf.lines[i] = "GrabPass { \"" + gpr.newName + "\" }";
|
|
grabPassVariables.Add(gpr);
|
|
}
|
|
else if (trimmedLine.StartsWith("CGINCLUDE"))
|
|
{
|
|
for (int j=i+1; j<psf.lines.Length;j++)
|
|
if (psf.lines[j].TrimStart().StartsWith("ENDCG"))
|
|
{
|
|
ReplaceShaderValues(material, psf.lines, i+1, j, props, constantProps, macros, grabPassVariables);
|
|
break;
|
|
}
|
|
}
|
|
else if (trimmedLine.StartsWith("CGPROGRAM"))
|
|
{
|
|
psf.lines[i] += optimizerDefines;
|
|
for (int j=i+1; j<psf.lines.Length;j++)
|
|
if (psf.lines[j].TrimStart().StartsWith("ENDCG"))
|
|
{
|
|
ReplaceShaderValues(material, psf.lines, i+1, j, props, constantProps, macros, grabPassVariables);
|
|
break;
|
|
}
|
|
}
|
|
// Lightmode based pass removal, requires strict formatting
|
|
else if (trimmedLine.StartsWith("Tags"))
|
|
{
|
|
string lineFullyTrimmed = trimmedLine.Replace(" ", "").Replace("\t", "");
|
|
// expects lightmode tag to be on the same line like: Tags { "LightMode" = "ForwardAdd" }
|
|
if (lineFullyTrimmed.Contains("\"LightMode\"=\""))
|
|
{
|
|
string lightModeName = lineFullyTrimmed.Split('\"')[3];
|
|
// Store current lightmode name in a static, useful for per-pass geometry and tessellation removal
|
|
CurrentLightmode = lightModeName;
|
|
if (disabledLightModes.Contains(lightModeName))
|
|
{
|
|
// Loop up from psf.lines[i] until standalone "Pass" line is found, delete it
|
|
int j=i-1;
|
|
for (;j>=0;j--)
|
|
if (psf.lines[j].Replace(" ", "").Replace("\t", "") == "Pass")
|
|
break;
|
|
// then delete each line until a standalone ENDCG line is found
|
|
for (;j<psf.lines.Length;j++)
|
|
{
|
|
if (psf.lines[j].Replace(" ", "").Replace("\t", "") == "ENDCG")
|
|
break;
|
|
psf.lines[j] = "";
|
|
}
|
|
// then delete each line until a standalone '}' line is found
|
|
for (;j<psf.lines.Length;j++)
|
|
{
|
|
string temp = psf.lines[j];
|
|
psf.lines[j] = "";
|
|
if (temp.Replace(" ", "").Replace("\t", "") == "}")
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else // CGINC file
|
|
ReplaceShaderValues(material, psf.lines, 0, psf.lines.Length, props, constantProps, macros, grabPassVariables);
|
|
|
|
// Recombine file lines into a single string
|
|
int totalLen = psf.lines.Length*2; // extra space for newline chars
|
|
foreach (string line in psf.lines)
|
|
totalLen += line.Length;
|
|
StringBuilder sb = new StringBuilder(totalLen);
|
|
// This appendLine function is incompatible with the '\n's that are being added elsewhere
|
|
foreach (string line in psf.lines)
|
|
sb.AppendLine(line);
|
|
string output = sb.ToString();
|
|
|
|
// Write output to file
|
|
string newShaderFilePath = Path.GetFileName(psf.filePath);
|
|
(new FileInfo(newShaderDirectory + newShaderFilePath)).Directory.Create();
|
|
try
|
|
{
|
|
StreamWriter sw = new StreamWriter(newShaderDirectory + newShaderFilePath);
|
|
sw.Write(output);
|
|
sw.Close();
|
|
}
|
|
catch (IOException e)
|
|
{
|
|
Debug.LogError(LogHeader + "Processed shader file " + newShaderDirectory + newShaderFilePath + " could not be written. " + e.ToString());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
AssetDatabase.Refresh();
|
|
// Write original shader to override tag
|
|
material.SetOverrideTag("OriginalShader", shader.name);
|
|
// Write the new shader folder name in an override tag so it will be deleted
|
|
material.SetOverrideTag("BakedShaderFolder", material.name + "-" + smallguid);
|
|
|
|
// Remove ALL keywords
|
|
foreach (string keyword in material.shaderKeywords)
|
|
material.DisableKeyword(keyword);
|
|
|
|
// For some reason when shaders are swapped on a material the RenderType override tag gets completely deleted and render queue set back to -1
|
|
// So these are saved as temp values and reassigned after switching shaders
|
|
string renderType = material.GetTag("RenderType", false, "");
|
|
int renderQueue = material.renderQueue;
|
|
|
|
// Actually switch the shader
|
|
Shader newShader = Shader.Find(newShaderName);
|
|
if (newShader == null)
|
|
{
|
|
Debug.LogError(LogHeader + "Generated shader " + newShaderName + " could not be found");
|
|
return false;
|
|
}
|
|
material.shader = newShader;
|
|
material.SetOverrideTag("RenderType", renderType);
|
|
material.renderQueue = renderQueue;
|
|
|
|
return true;
|
|
}
|
|
|
|
// Preprocess each file for macros and includes
|
|
// Save each file as string[], parse each macro with //KSOEvaluateMacro
|
|
// Only editing done is replacing #include "X" filepaths where necessary
|
|
// most of these args could be private static members of the class
|
|
private static bool ParseShaderFilesRecursive(List<ParsedShaderFile> filesParsed, string newTopLevelDirectory, string filePath, List<Macro> macros)
|
|
{
|
|
// Infinite recursion check
|
|
if (filesParsed.Exists(x => x.filePath == filePath)) return true;
|
|
|
|
ParsedShaderFile psf = new ParsedShaderFile();
|
|
psf.filePath = filePath;
|
|
filesParsed.Add(psf);
|
|
|
|
// Read file
|
|
string fileContents = null;
|
|
try
|
|
{
|
|
StreamReader sr = new StreamReader(filePath);
|
|
fileContents = sr.ReadToEnd();
|
|
sr.Close();
|
|
}
|
|
catch (FileNotFoundException e)
|
|
{
|
|
Debug.LogError(LogHeader + "Shader file " + filePath + " not found. " + e.ToString());
|
|
return false;
|
|
}
|
|
catch (IOException e)
|
|
{
|
|
Debug.LogError(LogHeader + "Error reading shader file. " + e.ToString());
|
|
return false;
|
|
}
|
|
|
|
// Parse file line by line
|
|
List<String> macrosList = new List<string>();
|
|
string[] fileLines = Regex.Split(fileContents, "\r\n|\r|\n");
|
|
for (int i=0; i<fileLines.Length; i++)
|
|
{
|
|
string lineParsed = fileLines[i].TrimStart();
|
|
// Specifically requires no whitespace between # and include, as it should be
|
|
if (lineParsed.StartsWith("#include"))
|
|
{
|
|
int firstQuotation = lineParsed.IndexOf('\"',0);
|
|
int lastQuotation = lineParsed.IndexOf('\"',firstQuotation+1);
|
|
string includeFilename = lineParsed.Substring(firstQuotation+1, lastQuotation-firstQuotation-1);
|
|
|
|
// Skip default includes
|
|
if (Array.Exists(DefaultUnityShaderIncludes, x => x.Equals(includeFilename, StringComparison.InvariantCultureIgnoreCase)))
|
|
continue;
|
|
|
|
// cginclude filepath is either absolute or relative
|
|
if (includeFilename.StartsWith("Assets/"))
|
|
{
|
|
if (!ParseShaderFilesRecursive(filesParsed, newTopLevelDirectory, includeFilename, macros))
|
|
return false;
|
|
// Only absolute filepaths need to be renampped in-file
|
|
fileLines[i] = fileLines[i].Replace(includeFilename, newTopLevelDirectory + includeFilename);
|
|
}
|
|
else
|
|
{
|
|
string includeFullpath = GetFullPath(includeFilename, Path.GetDirectoryName(filePath));
|
|
if (!ParseShaderFilesRecursive(filesParsed, newTopLevelDirectory, includeFullpath, macros))
|
|
return false;
|
|
}
|
|
}
|
|
// Remove unique fallbacks
|
|
else if (lineParsed.StartsWith("FallBack"))
|
|
fileLines[i] = "//" + fileLines[i];
|
|
// Specifically requires no whitespace between // and KSOEvaluateMacro
|
|
else if (lineParsed == "//KSOEvaluateMacro")
|
|
{
|
|
string macro = "";
|
|
string lineTrimmed = null;
|
|
do
|
|
{
|
|
i++;
|
|
lineTrimmed = fileLines[i].TrimEnd();
|
|
if (lineTrimmed.EndsWith("\\"))
|
|
macro += lineTrimmed.TrimEnd('\\') + Environment.NewLine; // keep new lines in macro to make output more readable
|
|
else macro += lineTrimmed;
|
|
}
|
|
while (lineTrimmed.EndsWith("\\"));
|
|
macrosList.Add(macro);
|
|
}
|
|
}
|
|
|
|
// Prepare the macros list into pattern matchable structs
|
|
// Revise this later to not do so many string ops
|
|
foreach (string macroString in macrosList)
|
|
{
|
|
string m = macroString;
|
|
Macro macro = new Macro();
|
|
m = m.TrimStart();
|
|
if (m[0] != '#') continue;
|
|
m = m.Remove(0, "#".Length).TrimStart();
|
|
if (!m.StartsWith("define")) continue;
|
|
m = m.Remove(0, "define".Length).TrimStart();
|
|
int firstParenthesis = m.IndexOf('(');
|
|
macro.name = m.Substring(0, firstParenthesis);
|
|
m = m.Remove(0, firstParenthesis + "(".Length);
|
|
int lastParenthesis = m.IndexOf(')');
|
|
string allArgs = m.Substring(0, lastParenthesis).Replace(" ", "").Replace("\t", "");
|
|
macro.args = allArgs.Split(',');
|
|
m = m.Remove(0, lastParenthesis + ")".Length);
|
|
macro.contents = m;
|
|
macros.Add(macro);
|
|
}
|
|
|
|
// Save psf lines to list
|
|
psf.lines = fileLines;
|
|
return true;
|
|
}
|
|
|
|
// error CS1501: No overload for method 'Path.GetFullPath' takes 2 arguments
|
|
// Thanks Unity
|
|
// Could be made more efficent with stringbuilder
|
|
public static string GetFullPath(string relativePath, string basePath)
|
|
{
|
|
while (relativePath.StartsWith("./"))
|
|
relativePath = relativePath.Remove(0, "./".Length);
|
|
while (relativePath.StartsWith("../"))
|
|
{
|
|
basePath = basePath.Remove(basePath.LastIndexOf("/"), basePath.Length - basePath.LastIndexOf("/"));
|
|
relativePath = relativePath.Remove(0, "../".Length);
|
|
}
|
|
return basePath + '/' + relativePath;
|
|
}
|
|
|
|
// Replace properties! The meat of the shader optimization process
|
|
// For each constantProp, pattern match and find each instance of the property that isn't a declaration
|
|
// most of these args could be private static members of the class
|
|
private static void ReplaceShaderValues(Material material, string[] lines, int startLine, int endLine,
|
|
MaterialProperty[] props, List<PropertyData> constants, List<Macro> macros, List<GrabPassReplacement> grabPassVariables)
|
|
{
|
|
List <TextureProperty> uniqueSampledTextures = new List<TextureProperty>();
|
|
|
|
// Outside loop is each line
|
|
for (int i=startLine;i<endLine;i++)
|
|
{
|
|
string lineTrimmed = lines[i].TrimStart();
|
|
if (lineTrimmed.StartsWith("#pragma geometry"))
|
|
{
|
|
if (!UseGeometry)
|
|
lines[i] = "//" + lines[i];
|
|
else
|
|
{
|
|
switch (CurrentLightmode)
|
|
{
|
|
case "ForwardBase":
|
|
if (!UseGeometryForwardBase)
|
|
lines[i] = "//" + lines[i];
|
|
break;
|
|
case "ForwardAdd":
|
|
if (!UseGeometryForwardAdd)
|
|
lines[i] = "//" + lines[i];
|
|
break;
|
|
case "ShadowCaster":
|
|
if (!UseGeometryShadowCaster)
|
|
lines[i] = "//" + lines[i];
|
|
break;
|
|
case "Meta":
|
|
if (!UseGeometryMeta)
|
|
lines[i] = "//" + lines[i];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else if (lineTrimmed.StartsWith("#pragma hull") || lineTrimmed.StartsWith("#pragma domain"))
|
|
{
|
|
if (!UseTessellation)
|
|
lines[i] = "//" + lines[i];
|
|
else
|
|
{
|
|
switch (CurrentLightmode)
|
|
{
|
|
case "ForwardBase":
|
|
if (!UseTessellationForwardBase)
|
|
lines[i] = "//" + lines[i];
|
|
break;
|
|
case "ForwardAdd":
|
|
if (!UseTessellationForwardAdd)
|
|
lines[i] = "//" + lines[i];
|
|
break;
|
|
case "ShadowCaster":
|
|
if (!UseTessellationShadowCaster)
|
|
lines[i] = "//" + lines[i];
|
|
break;
|
|
case "Meta":
|
|
if (!UseTessellationMeta)
|
|
lines[i] = "//" + lines[i];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
// Remove all shader_feature directives
|
|
else if (lineTrimmed.StartsWith("#pragma shader_feature") || lineTrimmed.StartsWith("#pragma shader_feature_local"))
|
|
lines[i] = "//" + lines[i];
|
|
// Replace inline smapler states
|
|
else if (UseInlineSamplerStates && lineTrimmed.StartsWith("//KSOInlineSamplerState"))
|
|
{
|
|
string lineParsed = lineTrimmed.Replace(" ", "").Replace("\t", "");
|
|
// Remove all whitespace
|
|
int firstParenthesis = lineParsed.IndexOf('(');
|
|
int lastParenthesis = lineParsed.IndexOf(')');
|
|
string argsString = lineParsed.Substring(firstParenthesis+1, lastParenthesis - firstParenthesis-1);
|
|
string[] args = argsString.Split(',');
|
|
MaterialProperty texProp = Array.Find(props, x => x.name == args[1]);
|
|
if (texProp != null)
|
|
{
|
|
Texture t = texProp.textureValue;
|
|
int inlineSamplerIndex = 0;
|
|
if (t != null)
|
|
{
|
|
switch (t.filterMode)
|
|
{
|
|
case FilterMode.Bilinear:
|
|
break;
|
|
case FilterMode.Point:
|
|
inlineSamplerIndex += 1 * 4;
|
|
break;
|
|
case FilterMode.Trilinear:
|
|
inlineSamplerIndex += 2 * 4;
|
|
break;
|
|
}
|
|
switch (t.wrapMode)
|
|
{
|
|
case TextureWrapMode.Repeat:
|
|
break;
|
|
case TextureWrapMode.Clamp:
|
|
inlineSamplerIndex += 1;
|
|
break;
|
|
case TextureWrapMode.Mirror:
|
|
inlineSamplerIndex += 2;
|
|
break;
|
|
case TextureWrapMode.MirrorOnce:
|
|
inlineSamplerIndex += 3;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Replace the token on the following line
|
|
lines[i+1] = lines[i+1].Replace(args[0], InlineSamplerStateNames[inlineSamplerIndex]);
|
|
}
|
|
}
|
|
else if (lineTrimmed.StartsWith("//KSODuplicateTextureCheckStart"))
|
|
{
|
|
// Since files are not fully parsed and instead loosely processed, each shader function needs to have
|
|
// its sampled texture list reset somewhere before KSODuplicateTextureChecks are made.
|
|
// As long as textures are sampled in-order inside a single function, this method will work.
|
|
uniqueSampledTextures = new List<TextureProperty>();
|
|
}
|
|
else if (lineTrimmed.StartsWith("//KSODuplicateTextureCheck"))
|
|
{
|
|
// Each KSODuplicateTextureCheck line gets evaluated when the shader is optimized
|
|
// If the texture given has already been sampled as another texture (i.e. one texture is used in two slots)
|
|
// AND has been sampled with the same UV mode - as indicated by a convention UV property,
|
|
// AND has been sampled with the exact same Tiling/Offset values
|
|
// AND has been logged by KSODuplicateTextureCheck,
|
|
// then the variable corresponding to the first instance of that texture being
|
|
// sampled will be assigned to the variable corresponding to the given texture.
|
|
// The compiler will then skip the duplicate texture sample since its variable is overwritten before being used
|
|
|
|
// Parse line for argument texture property name
|
|
string lineParsed = lineTrimmed.Replace(" ", "").Replace("\t", "");
|
|
int firstParenthesis = lineParsed.IndexOf('(');
|
|
int lastParenthesis = lineParsed.IndexOf(')');
|
|
string argName = lineParsed.Substring(firstParenthesis+1, lastParenthesis-firstParenthesis-1);
|
|
// Check if texture property by argument name exists and has a texture assigned
|
|
if (Array.Exists(props, x => x.name == argName))
|
|
{
|
|
MaterialProperty argProp = Array.Find(props, x => x.name == argName);
|
|
if (argProp.textureValue != null)
|
|
{
|
|
// If no convention UV property exists, sampled UV mode is assumed to be 0
|
|
// Any UV enum or mode indicator can be used for this
|
|
int UV = 0;
|
|
if (Array.Exists(props, x => x.name == argName + "UV"))
|
|
UV = (int)(Array.Find(props, x => x.name == argName + "UV").floatValue);
|
|
|
|
Vector2 texScale = material.GetTextureScale(argName);
|
|
Vector2 texOffset = material.GetTextureOffset(argName);
|
|
|
|
// Check if this texture has already been sampled
|
|
if (uniqueSampledTextures.Exists(x => (x.texture == argProp.textureValue)
|
|
&& (x.uv == UV)
|
|
&& (x.scale == texScale)
|
|
&& x.offset == texOffset))
|
|
{
|
|
string texName = uniqueSampledTextures.Find(x => (x.texture == argProp.textureValue) && (x.uv == UV)).name;
|
|
// convention _var variables requried. i.e. _MainTex_var and _CoverageMap_var
|
|
lines[i] = argName + "_var = " + texName + "_var;";
|
|
}
|
|
else
|
|
{
|
|
// Texture/UV/ST combo hasn't been sampled yet, add it to the list
|
|
TextureProperty tp = new TextureProperty();
|
|
tp.name = argName;
|
|
tp.texture = argProp.textureValue;
|
|
tp.uv = UV;
|
|
tp.scale = texScale;
|
|
tp.offset = texOffset;
|
|
uniqueSampledTextures.Add(tp);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (lineTrimmed.StartsWith("[maxtessfactor("))
|
|
{
|
|
MaterialProperty maxTessFactorProperty = Array.Find(props, x => x.name == TessellationMaxFactorPropertyName);
|
|
if (maxTessFactorProperty != null)
|
|
{
|
|
float maxTessellation = maxTessFactorProperty.floatValue;
|
|
MaterialProperty maxTessFactorAnimatedProperty = Array.Find(props, x => x.name == TessellationMaxFactorPropertyName + AnimatedPropertySuffix);
|
|
if (maxTessFactorAnimatedProperty != null && maxTessFactorAnimatedProperty.floatValue == 1)
|
|
maxTessellation = 64.0f;
|
|
lines[i] = "[maxtessfactor(" + maxTessellation.ToString(".0######") + ")]";
|
|
}
|
|
}
|
|
|
|
// then replace macros
|
|
foreach (Macro macro in macros)
|
|
{
|
|
// Expects only one instance of a macro per line!
|
|
int macroIndex;
|
|
if ((macroIndex = lines[i].IndexOf(macro.name + "(")) != -1)
|
|
{
|
|
// Macro exists on this line, make sure its not the definition
|
|
string lineParsed = lineTrimmed.Replace(" ", "").Replace("\t", "");
|
|
if (lineParsed.StartsWith("#define")) continue;
|
|
|
|
// parse args between first '(' and first ')'
|
|
int firstParenthesis = macroIndex + macro.name.Length;
|
|
int lastParenthesis = lines[i].IndexOf(')', macroIndex + macro.name.Length+1);
|
|
string allArgs = lines[i].Substring(firstParenthesis+1, lastParenthesis-firstParenthesis-1);
|
|
string[] args = allArgs.Split(',');
|
|
|
|
// Replace macro parts
|
|
string newContents = macro.contents;
|
|
for (int j=0; j<args.Length;j++)
|
|
{
|
|
args[j] = args[j].Trim();
|
|
int argIndex;
|
|
int lastIndex = 0;
|
|
while ((argIndex = newContents.IndexOf(macro.args[j], lastIndex)) != -1)
|
|
{
|
|
lastIndex = argIndex+1;
|
|
char charLeft = ' ';
|
|
if (argIndex-1 >= 0)
|
|
charLeft = newContents[argIndex-1];
|
|
char charRight = ' ';
|
|
if (argIndex+macro.args[j].Length < newContents.Length)
|
|
charRight = newContents[argIndex+macro.args[j].Length];
|
|
if (Array.Exists(ValidSeparators, x => x == charLeft) && Array.Exists(ValidSeparators, x => x == charRight))
|
|
{
|
|
// Replcae the arg!
|
|
StringBuilder sbm = new StringBuilder(newContents.Length - macro.args[j].Length + args[j].Length);
|
|
sbm.Append(newContents, 0, argIndex);
|
|
sbm.Append(args[j]);
|
|
sbm.Append(newContents, argIndex + macro.args[j].Length, newContents.Length - argIndex - macro.args[j].Length);
|
|
newContents = sbm.ToString();
|
|
}
|
|
}
|
|
}
|
|
newContents = newContents.Replace("##", ""); // Remove token pasting separators
|
|
// Replace the line with the evaluated macro
|
|
StringBuilder sb = new StringBuilder(lines[i].Length + newContents.Length);
|
|
sb.Append(lines[i], 0, macroIndex);
|
|
sb.Append(newContents);
|
|
sb.Append(lines[i], lastParenthesis+1, lines[i].Length - lastParenthesis-1);
|
|
lines[i] = sb.ToString();
|
|
}
|
|
}
|
|
// then replace properties
|
|
foreach (PropertyData constant in constants)
|
|
{
|
|
int constantIndex;
|
|
int lastIndex = 0;
|
|
bool declarationFound = false;
|
|
while ((constantIndex = lines[i].IndexOf(constant.name, lastIndex)) != -1)
|
|
{
|
|
lastIndex = constantIndex+1;
|
|
char charLeft = ' ';
|
|
if (constantIndex-1 >= 0)
|
|
charLeft = lines[i][constantIndex-1];
|
|
char charRight = ' ';
|
|
if (constantIndex + constant.name.Length < lines[i].Length)
|
|
charRight = lines[i][constantIndex + constant.name.Length];
|
|
// Skip invalid matches (probably a subname of another symbol)
|
|
if (!(Array.Exists(ValidSeparators, x => x == charLeft) && Array.Exists(ValidSeparators, x => x == charRight)))
|
|
continue;
|
|
|
|
// Skip basic declarations of unity shader properties i.e. "uniform float4 _Color;"
|
|
if (!declarationFound)
|
|
{
|
|
string precedingText = lines[i].Substring(0, constantIndex-1).TrimEnd(); // whitespace removed string immediately to the left should be float or float4
|
|
string restOftheFile = lines[i].Substring(constantIndex + constant.name.Length).TrimStart(); // whitespace removed character immediately to the right should be ;
|
|
if (Array.Exists(ValidPropertyDataTypes, x => precedingText.EndsWith(x)) && restOftheFile.StartsWith(";"))
|
|
{
|
|
declarationFound = true;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Replace with constant!
|
|
// This could technically be more efficient by being outside the IndexOf loop
|
|
StringBuilder sb = new StringBuilder(lines[i].Length * 2);
|
|
sb.Append(lines[i], 0, constantIndex);
|
|
switch (constant.type)
|
|
{
|
|
case PropertyType.Float:
|
|
sb.Append("float(" + constant.value.x.ToString(CultureInfo.InvariantCulture) + ")");
|
|
break;
|
|
case PropertyType.Vector:
|
|
sb.Append("float4("+constant.value.x.ToString(CultureInfo.InvariantCulture)+","
|
|
+constant.value.y.ToString(CultureInfo.InvariantCulture)+","
|
|
+constant.value.z.ToString(CultureInfo.InvariantCulture)+","
|
|
+constant.value.w.ToString(CultureInfo.InvariantCulture)+")");
|
|
break;
|
|
}
|
|
sb.Append(lines[i], constantIndex+constant.name.Length, lines[i].Length-constantIndex-constant.name.Length);
|
|
lines[i] = sb.ToString();
|
|
|
|
// Check for Unity branches on previous line here?
|
|
}
|
|
}
|
|
|
|
// Then replace grabpass variable names
|
|
foreach (GrabPassReplacement gpr in grabPassVariables)
|
|
{
|
|
// find indexes of all instances of gpr.originalName that exist on this line
|
|
int lastIndex = 0;
|
|
int gbIndex;
|
|
while ((gbIndex = lines[i].IndexOf(gpr.originalName, lastIndex)) != -1)
|
|
{
|
|
lastIndex = gbIndex+1;
|
|
char charLeft = ' ';
|
|
if (gbIndex-1 >= 0)
|
|
charLeft = lines[i][gbIndex-1];
|
|
char charRight = ' ';
|
|
if (gbIndex + gpr.originalName.Length < lines[i].Length)
|
|
charRight = lines[i][gbIndex + gpr.originalName.Length];
|
|
// Skip invalid matches (probably a subname of another symbol)
|
|
if (!(Array.Exists(ValidSeparators, x => x == charLeft) && Array.Exists(ValidSeparators, x => x == charRight)))
|
|
continue;
|
|
|
|
// Replace with new variable name
|
|
// This could technically be more efficient by being outside the IndexOf loop
|
|
StringBuilder sb = new StringBuilder(lines[i].Length * 2);
|
|
sb.Append(lines[i], 0, gbIndex);
|
|
sb.Append(gpr.newName);
|
|
sb.Append(lines[i], gbIndex+gpr.originalName.Length, lines[i].Length-gbIndex-gpr.originalName.Length);
|
|
lines[i] = sb.ToString();
|
|
}
|
|
}
|
|
|
|
// Then remove Unity branches
|
|
if (RemoveUnityBranches)
|
|
lines[i] = lines[i].Replace("UNITY_BRANCH", "").Replace("[branch]", "");
|
|
}
|
|
}
|
|
|
|
public static bool Unlock (Material material)
|
|
{
|
|
// Revert to original shader
|
|
string originalShaderName = material.GetTag("OriginalShader", false, "");
|
|
if (originalShaderName == "")
|
|
{
|
|
Debug.LogError(LogHeader + "Original shader not saved to material, could not unlock shader");
|
|
return false;
|
|
}
|
|
Shader orignalShader = Shader.Find(originalShaderName);
|
|
if (orignalShader == null)
|
|
{
|
|
Debug.LogError(LogHeader + "Original shader " + originalShaderName + " could not be found");
|
|
return false;
|
|
}
|
|
|
|
// For some reason when shaders are swapped on a material the RenderType override tag gets completely deleted and render queue set back to -1
|
|
// So these are saved as temp values and reassigned after switching shaders
|
|
string renderType = material.GetTag("RenderType", false, "");
|
|
int renderQueue = material.renderQueue;
|
|
material.shader = orignalShader;
|
|
material.SetOverrideTag("RenderType", renderType);
|
|
material.renderQueue = renderQueue;
|
|
|
|
// Delete the variants folder and all files in it, as to not orhpan files and inflate Unity project
|
|
string shaderDirectory = material.GetTag("BakedShaderFolder", false, "");
|
|
if (shaderDirectory == "")
|
|
{
|
|
Debug.LogError(LogHeader + "Optimized shader folder could not be found, not deleting anything");
|
|
return false;
|
|
}
|
|
string materialFilePath = AssetDatabase.GetAssetPath(material);
|
|
string materialFolder = Path.GetDirectoryName(materialFilePath);
|
|
string newShaderDirectory = materialFolder + "/BakedShaders/" + shaderDirectory;
|
|
// Both safe ways of removing the shader make the editor GUI throw an error, so just don't refresh the
|
|
// asset database immediately
|
|
//AssetDatabase.DeleteAsset(shaderFilePath);
|
|
FileUtil.DeleteFileOrDirectory(newShaderDirectory + "/");
|
|
FileUtil.DeleteFileOrDirectory(newShaderDirectory + ".meta");
|
|
//AssetDatabase.Refresh();
|
|
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
/***
|
|
|
|
MIT License
|
|
|
|
Copyright (c) 2020 DarthShader
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
of this software and associated documentation files (the "Software"), to deal
|
|
in the Software without restriction, including without limitation the rights
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
copies of the Software, and to permit persons to whom the Software is
|
|
furnished to do so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in all
|
|
copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
SOFTWARE.
|
|
|
|
***/ |