// 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 passes = new List(importer.settings.additionalPasses); if (StylizedWaterEditor.UnderwaterRenderingInstalled()) { string maskPassGUID = "46c22cecd601401a875ca4554695986f"; string maskPassPath = AssetDatabase.GUIDToAssetPath(maskPassGUID);; Object underwaterMaskPass = AssetDatabase.LoadAssetAtPath(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); } } }