484 lines
19 KiB
C#
484 lines
19 KiB
C#
// Stylized Water 3 by Staggart Creations (http://staggart.xyz)
|
|
// COPYRIGHT PROTECTED UNDER THE UNITY ASSET STORE EULA (https://unity.com/legal/as-terms)
|
|
// • Copying or referencing source code for the production of new asset store, or public, content is strictly prohibited!
|
|
// • Uploading this file to a public repository will subject it to an automated DMCA takedown request.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Text;
|
|
using UnityEditor;
|
|
using Object = UnityEngine.Object;
|
|
|
|
namespace StylizedWater3
|
|
{
|
|
public static class TemplateParser
|
|
{
|
|
public const int SHADER_GENERATOR_VERSION_MAJOR = 5;
|
|
public const int SHADER_GENERATOR_MINOR = 1;
|
|
public const int SHADER_GENERATOR_PATCH = 3;
|
|
|
|
//Stencil mask used by water shader. Currently used for underwater rendering
|
|
//Values 1-65 are used by deferred rendering
|
|
public const int STENCIL_REF = 65;
|
|
|
|
//Converts relative include paths such as (../../Libraries/File.hlsl) to an absolute path
|
|
//Supports the source file being part of a package
|
|
public static string RelativeToAbsoluteIncludePath(string filePath, string relativePath)
|
|
{
|
|
string fileDir = Path.GetDirectoryName(filePath);
|
|
|
|
//Count how many folders should be traversed up
|
|
int levels = relativePath.Split(new[]
|
|
{
|
|
".."
|
|
}, StringSplitOptions.None).Length - 1;
|
|
|
|
string traveledPath = fileDir;
|
|
if (levels > 0)
|
|
{
|
|
for (int i = 0; i < levels; i++)
|
|
{
|
|
//Remove the number of needed sub-directories needed to reach the destination
|
|
int strimStart = traveledPath.LastIndexOf(Path.DirectorySeparatorChar);
|
|
traveledPath = traveledPath.Remove(strimStart);
|
|
}
|
|
}
|
|
|
|
//The directory without the "up" navigators
|
|
string relativeFolder = relativePath.Replace("../", string.Empty);
|
|
|
|
//Concatenate them together
|
|
string absolutePath = traveledPath + "/" + relativeFolder;
|
|
|
|
//Convert back- to forward slashes
|
|
absolutePath = absolutePath.Replace("\\", "/");
|
|
|
|
return absolutePath;
|
|
}
|
|
|
|
//Pre-process the template to inject additional template contents into it
|
|
private static void ModifyTemplate(ref string[] lines, WaterShaderImporter importer)
|
|
{
|
|
StringBuilder templateBuilder = new StringBuilder();
|
|
|
|
for (int i = 0; i < lines.Length; i++)
|
|
{
|
|
string line = lines[i];
|
|
|
|
//Inject additional passes into template
|
|
if (line.Contains("%passes%"))
|
|
{
|
|
List<Object> passes = new List<Object>(importer.settings.additionalPasses);
|
|
|
|
if (StylizedWaterEditor.UnderwaterRenderingInstalled())
|
|
{
|
|
string maskPassGUID = "46c22cecd601401a875ca4554695986f";
|
|
string maskPassPath = AssetDatabase.GUIDToAssetPath(maskPassGUID);;
|
|
|
|
Object underwaterMaskPass = AssetDatabase.LoadAssetAtPath<Object>(maskPassPath);
|
|
|
|
passes.Add(underwaterMaskPass);
|
|
}
|
|
|
|
int passCount = passes.Count;
|
|
for (int j = 0; j < passCount; j++)
|
|
{
|
|
if (passes[j] != null)
|
|
{
|
|
string filePath = AssetDatabase.GetAssetPath(passes[j]);
|
|
|
|
importer.RegisterDependency(filePath);
|
|
|
|
string[] passContexts = File.ReadAllLines(filePath);
|
|
|
|
for (int k = 0; k < passContexts.Length; k++)
|
|
{
|
|
templateBuilder.AppendLine(passContexts[k]);
|
|
}
|
|
}
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
templateBuilder.AppendLine(lines[i]);
|
|
}
|
|
lines = templateBuilder.ToString().Split(new[] { Environment.NewLine }, StringSplitOptions.None);
|
|
}
|
|
|
|
public static string CreateShaderCode(string templatePath, ref string[] lines, WaterShaderImporter importer, bool tessellation = false)
|
|
{
|
|
if (importer == null)
|
|
{
|
|
throw new Exception("Failed to compile shader from template code. The importer is invalid, this should not even be possible. Whatever you did, undo it...");
|
|
}
|
|
|
|
//Extension installation states
|
|
var underwaterInstalled = StylizedWaterEditor.UnderwaterRenderingInstalled();
|
|
var dynamicEffectsInstalled = StylizedWaterEditor.DynamicEffectsInstalled();
|
|
|
|
FogIntegration.Integration fogIntegration = importer.GetFogIntegration();
|
|
|
|
AssetInfo.VersionChecking.CheckUnityVersion();
|
|
|
|
//Shader name
|
|
string prefix = importer.settings.hidden ? "Hidden/" : string.Empty;
|
|
string suffix = tessellation ? ShaderParams.ShaderNames.TESSELLATION_NAME_SUFFIX : string.Empty;
|
|
string shaderName = $"{prefix}{AssetInfo.ASSET_NAME}/{importer.settings.shaderName}";
|
|
|
|
string shaderPath = importer.assetPath;
|
|
|
|
ModifyTemplate(ref lines, importer);
|
|
|
|
StringBuilder sb = new StringBuilder();
|
|
for (int i = 0; i < lines.Length; i++)
|
|
{
|
|
//Ignore blank lines and comments for analysis
|
|
if (string.IsNullOrWhiteSpace(lines[i]) || lines[i].StartsWith("//"))
|
|
{
|
|
sb.AppendLine(lines[i]);
|
|
continue;
|
|
}
|
|
|
|
//First non-space character
|
|
int indent = System.Text.RegularExpressions.Regex.Match(lines[i], "[^-\\s]").Index;
|
|
|
|
string whitespace = lines[i].Replace(lines[i].Substring(indent), "");
|
|
|
|
//AppendLine using previous line's white spacing
|
|
void AddLine(string source)
|
|
{
|
|
sb.AppendLine(source.Insert(0, whitespace));
|
|
}
|
|
|
|
//Remove whitespaces
|
|
string line = lines[i].Remove(0, indent);
|
|
|
|
bool Matches(string source) { return string.CompareOrdinal(source, line) == 0; }
|
|
|
|
if (Matches("%asset_version%"))
|
|
{
|
|
AddLine($"//Asset version {AssetInfo.INSTALLED_VERSION}");
|
|
continue;
|
|
}
|
|
|
|
if (Matches("%compiler_version%"))
|
|
{
|
|
AddLine($"//Shader generator version: {new Version(SHADER_GENERATOR_VERSION_MAJOR, SHADER_GENERATOR_MINOR, SHADER_GENERATOR_PATCH)}");
|
|
continue;
|
|
}
|
|
|
|
if (Matches("%unity_version%"))
|
|
{
|
|
AddLine($"//Unity version: {AssetInfo.VersionChecking.GetUnityVersion()}");
|
|
continue;
|
|
}
|
|
|
|
if (Matches("%shader_name%"))
|
|
{
|
|
AddLine($"Shader \"{shaderName}{suffix}\"");
|
|
continue;
|
|
}
|
|
|
|
if (Matches("%custom_directives%"))
|
|
{
|
|
foreach (WaterShaderImporter.Directive directive in importer.settings.customIncludeDirectives)
|
|
{
|
|
if(directive.enabled == false) continue;
|
|
|
|
string directivePrefix = string.Empty;
|
|
|
|
switch (directive.type)
|
|
{
|
|
case WaterShaderImporter.Directive.Type.define:
|
|
directivePrefix = "#define ";
|
|
break;
|
|
case WaterShaderImporter.Directive.Type.include:
|
|
directivePrefix = "#include ";
|
|
break;
|
|
case WaterShaderImporter.Directive.Type.include_with_pragmas:
|
|
directivePrefix = "#include_with_pragmas ";
|
|
break;
|
|
case WaterShaderImporter.Directive.Type.pragma:
|
|
directivePrefix = "#pragma ";
|
|
break;
|
|
}
|
|
|
|
if (directive.value != string.Empty) AddLine($"{directivePrefix}{directive.value}");
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (Matches("%global_defines%"))
|
|
{
|
|
if (importer.settings.singleCausticsLayer)
|
|
{
|
|
AddLine("#define CAUSTICS_SINGLE_LAYER");
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
if (Matches("%pragma_target%"))
|
|
{
|
|
if (tessellation)
|
|
{
|
|
AddLine("#pragma target 4.6");
|
|
}
|
|
else
|
|
{
|
|
AddLine("#pragma target 3.0");
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
if (Matches("%pragma_renderers%"))
|
|
{
|
|
if (tessellation)
|
|
{
|
|
AddLine("#pragma exclude_renderers gles");
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
if (line.StartsWith("Fallback"))
|
|
{
|
|
if (tessellation)
|
|
{
|
|
//Fallback to non-tessellation variant (with without suffix)
|
|
AddLine($"Fallback \"{shaderName}\"");
|
|
//Test, disable fallback
|
|
//AddLine(line);
|
|
}
|
|
else
|
|
{
|
|
//Leave as is
|
|
AddLine(line);
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
if (Matches("%tessellation_properties%"))
|
|
{
|
|
if (tessellation)
|
|
{
|
|
AddLine("_TessValue(\"Max subdivisions\", Range(1, 64)) = 16");
|
|
AddLine("_TessMin(\"Start Distance\", Float) = 0");
|
|
AddLine("_TessMax(\"End Distance\", Float) = 15");
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
if (Matches("%tessellation_directives%"))
|
|
{
|
|
if (tessellation)
|
|
{
|
|
AddLine("#define TESSELLATION_ON");
|
|
AddLine("#pragma require tessellation tessHW");
|
|
AddLine("#pragma hull Hull");
|
|
AddLine("#pragma domain Domain");
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
if (line.Contains("%stencilID%"))
|
|
{
|
|
int stencilID = STENCIL_REF;
|
|
|
|
if (importer.settings.fogIntegration == FogIntegration.Assets.COZY)
|
|
{
|
|
|
|
}
|
|
|
|
line = line.Replace("%stencilID%", stencilID.ToString());
|
|
AddLine(line);
|
|
|
|
continue;
|
|
}
|
|
|
|
if (Matches("%stencil%"))
|
|
{
|
|
|
|
continue;
|
|
}
|
|
|
|
if (Matches("%multi_compile_light_cookies%"))
|
|
{
|
|
if (importer.settings.lightCookies)
|
|
{
|
|
AddLine("#pragma multi_compile_fragment _ _LIGHT_COOKIES");
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
if (line.Contains("%render_queue_offset%"))
|
|
{
|
|
int offset = 0;
|
|
|
|
switch (fogIntegration.asset)
|
|
{
|
|
//case FogIntegration.Assets.COZY: offset = 2;
|
|
//break;
|
|
//case Fog.Assets.AtmosphericHeightFog : offset = 2; //Should actually render after the fog sphere, but asset inherently relies on double fog shading it seems?
|
|
//break;
|
|
default: offset = 0;
|
|
break;
|
|
}
|
|
|
|
line = line.Replace("%render_queue_offset%", offset.ToString());
|
|
AddLine(line);
|
|
|
|
continue;
|
|
}
|
|
|
|
if (Matches("%defines%"))
|
|
{
|
|
if (importer.settings.additionalLightCaustics)
|
|
{
|
|
AddLine("#define _ADDITIONAL_LIGHT_CAUSTICS");
|
|
}
|
|
if (importer.settings.additionalLightTranslucency)
|
|
{
|
|
AddLine("#define _ADDITIONAL_LIGHT_TRANSLUCENCY");
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
if (Matches("%multi_compile underwater rendering%"))
|
|
{
|
|
if (underwaterInstalled)
|
|
{
|
|
importer.configurationState.underwaterRendering = true;
|
|
|
|
AddLine($"#pragma multi_compile_fragment _ {ShaderParams.Keywords.UnderwaterRendering}");
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
if (Matches("%multi_compile_vertex dynamic effects%"))
|
|
{
|
|
if (dynamicEffectsInstalled)
|
|
{
|
|
importer.configurationState.dynamicEffects = true;
|
|
|
|
AddLine($"#pragma multi_compile_vertex _ {ShaderParams.Keywords.DynamicEffects}");
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
if (Matches("%multi_compile dynamic effects%"))
|
|
{
|
|
if (dynamicEffectsInstalled)
|
|
{
|
|
importer.configurationState.dynamicEffects = true;
|
|
|
|
AddLine($"#pragma multi_compile _ {ShaderParams.Keywords.DynamicEffects}");
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
if (line.StartsWith("#include "))
|
|
{
|
|
string includePath = line.Replace("#include ", string.Empty);
|
|
//Remove parenthesis
|
|
includePath = includePath.Replace("\"", string.Empty);
|
|
|
|
importer.RegisterDependency(includePath);
|
|
}
|
|
|
|
if (importer.settings.type == WaterShaderImporter.WaterShaderSettings.ShaderType.WaterSurface)
|
|
{
|
|
if (Matches("%define_fog_integration%"))
|
|
{
|
|
AddLine($"#define {fogIntegration.asset.ToString()}");
|
|
|
|
if (fogIntegration.asset == FogIntegration.Assets.UnityFog)
|
|
{
|
|
AddLine("#pragma multi_compile_fog");
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
/* include FogLibrary */
|
|
if (Matches("%include_fog_integration_library%"))
|
|
{
|
|
//Default until otherwise valid
|
|
line = string.Empty;
|
|
|
|
//Mark the asset integration as being compiled in
|
|
importer.configurationState.fogIntegration = fogIntegration;
|
|
|
|
if (fogIntegration.asset != FogIntegration.Assets.None && fogIntegration.asset != FogIntegration.Assets.UnityFog)
|
|
{
|
|
string includePath = AssetDatabase.GUIDToAssetPath(fogIntegration.libraryGUID);
|
|
|
|
importer.RegisterDependency(includePath);
|
|
|
|
//Not found error
|
|
if (includePath == string.Empty)
|
|
{
|
|
if (EditorUtility.DisplayDialog(AssetInfo.ASSET_NAME,
|
|
fogIntegration.name + " fog shader library could not be found with the GUID \"" + fogIntegration.libraryGUID + "\".\n\n" +
|
|
"This means it was changed by the author (rare!), you deleted the \".meta\" file at some point, or the asset simply isn't installed.", "Ok"))
|
|
{
|
|
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var pragma = fogIntegration.includeWithPragmas ? "include_with_pragmas" : "include";
|
|
line = $"#{pragma} \"{includePath}\"";
|
|
|
|
AddLine(line);
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//Shaders created using "ShaderUtil.CreateShaderAsset" don't exist in a literal sense. Hence any relative file paths are invalid
|
|
//Convert them to absolute file paths
|
|
//Bonus: moving a shader file (or its folder) triggers it to re-import, thus always keeping the file path up-to-date
|
|
if (line.StartsWith("#include_library"))
|
|
{
|
|
string relativePath = line.Replace("#include_library ", string.Empty);
|
|
//Remove parenthesis
|
|
relativePath = relativePath.Replace("\"", string.Empty);
|
|
|
|
string includePath = RelativeToAbsoluteIncludePath(shaderPath, relativePath);
|
|
|
|
line = $"#include \"{includePath}\"";
|
|
|
|
importer.RegisterDependency(includePath);
|
|
|
|
AddLine(line);
|
|
continue;
|
|
}
|
|
|
|
//Insert whitespace back in
|
|
line = line.Insert(0, whitespace);
|
|
|
|
//Nothing special, keep whatever line this is
|
|
sb.AppendLine(line);
|
|
}
|
|
|
|
//Convert to separate lines again
|
|
lines = sb.ToString().Split(new[] { Environment.NewLine }, StringSplitOptions.None);
|
|
|
|
//Convert to single string, respecting line breaks and spacing.
|
|
return String.Join(Environment.NewLine, lines);
|
|
}
|
|
}
|
|
} |