////////////////////////////////////////////////////// // MicroSplat // Copyright (c) Jason Booth ////////////////////////////////////////////////////// using UnityEngine; using System.Collections; using UnityEditor; using UnityEditor.Callbacks; using System.Collections.Generic; namespace JBooth.MicroSplat { #if __MICROSPLAT__ [InitializeOnLoad] public class MicroSplatTriplanar : FeatureDescriptor { const string sDefine = "__MICROSPLAT_TRIPLANAR__"; static MicroSplatTriplanar() { MicroSplatDefines.InitDefine(sDefine); } [PostProcessSceneAttribute (0)] public static void OnPostprocessScene() { MicroSplatDefines.InitDefine(sDefine); } public override string ModuleName() { return "Triplanar"; } public override string GetHelpPath () { return "https://docs.google.com/document/d/1eRYatxNlDNHb72Z8mRzBPBqgYD7pYc_CWtwxzKLCjXk/edit?usp=sharing"; } public enum DefineFeature { _TRIPLANAR, _PERTEXTRIPLANAR, _TRIPLANARHEIGHTBLEND, _PERTEXTRIPLANARCONTRAST, _TRIPLANARLOCALSPACE, _TRIPLANARUSEFACENORMALS, kNumFeatures, } public enum TriplanarSpace { World = 0, Local } public enum TriplanarMode { None, Triplanar, HeightBlended } public override int CompileSortOrder() { return -1000; // has to be first, since things need uvs.. } static TextAsset funcs; public TriplanarMode triplanarMode = TriplanarMode.None; public bool perTexTriplanar; public bool perTexTriplanarContrast; public TriplanarSpace space = TriplanarSpace.World; public bool useFaceNormals; GUIContent CTriplanar = new GUIContent("Triplanar Mode", "Use triplanar UV projection, optionally with height based blending"); GUIContent CSpace = new GUIContent("Space", "Coordinate space to use for triplanar"); // Can we template these somehow? static Dictionary sFeatureNames = new Dictionary(); public static string GetFeatureName(DefineFeature feature) { string ret; if (sFeatureNames.TryGetValue(feature, out ret)) { return ret; } string fn = System.Enum.GetName(typeof(DefineFeature), feature); sFeatureNames[feature] = fn; return fn; } public static bool HasFeature(string[] keywords, DefineFeature feature) { string f = GetFeatureName(feature); for (int i = 0; i < keywords.Length; ++i) { if (keywords[i] == f) return true; } return false; } static GUIContent CTriplanarContrast = new GUIContent("Triplanar Contrast", "Tightness of blend between three triplanar angles"); public override string GetVersion() { return "3.9"; } public override void DrawFeatureGUI(MicroSplatKeywords keywords) { triplanarMode = (TriplanarMode)EditorGUILayout.EnumPopup(CTriplanar, triplanarMode); if (triplanarMode != TriplanarMode.None) { EditorGUI.indentLevel++; space = (TriplanarSpace)EditorGUILayout.EnumPopup(CSpace, space); useFaceNormals = EditorGUILayout.Toggle ("Blend Face Normals", useFaceNormals); EditorGUI.indentLevel--; } } public override void DrawShaderGUI(MicroSplatShaderGUI shaderGUI, MicroSplatKeywords keywords, Material mat, MaterialEditor materialEditor, MaterialProperty[] props) { if (triplanarMode != TriplanarMode.None) { if (MicroSplatUtilities.DrawRollup("Triplanar") && mat.HasProperty("_TriplanarContrast")) { materialEditor.ShaderProperty(shaderGUI.FindProp("_TriplanarContrast", props), CTriplanarContrast); if (mat.HasProperty("_TriplanarUVScale")) { EditorGUI.BeginChangeCheck(); Vector4 uvScale = shaderGUI.FindProp("_TriplanarUVScale", props).vectorValue; Vector2 scl = new Vector2(uvScale.x, uvScale.y); Vector2 offset = new Vector2(uvScale.z, uvScale.w); scl = EditorGUILayout.Vector2Field("Triplanar UV Scale", scl); offset = EditorGUILayout.Vector2Field("Triplanar UV Offset", offset); if (EditorGUI.EndChangeCheck ()) { uvScale.x = scl.x; uvScale.y = scl.y; uvScale.z = offset.x; uvScale.w = offset.y; shaderGUI.FindProp ("_TriplanarUVScale", props).vectorValue = uvScale; EditorUtility.SetDirty (mat); } } if (mat.HasProperty("_TriplanarFaceBlend")) { materialEditor.ShaderProperty (shaderGUI.FindProp ("_TriplanarFaceBlend", props), "Vertex -> Face Normal"); } } } } public override string[] Pack() { List features = new List(); if (triplanarMode != TriplanarMode.None) { features.Add(GetFeatureName(DefineFeature._TRIPLANAR)); if (space == TriplanarSpace.Local) { features.Add(GetFeatureName(DefineFeature._TRIPLANARLOCALSPACE)); } if (perTexTriplanar) { features.Add(GetFeatureName(DefineFeature._PERTEXTRIPLANAR)); } if (triplanarMode == TriplanarMode.HeightBlended) { features.Add(GetFeatureName(DefineFeature._TRIPLANARHEIGHTBLEND)); if (perTexTriplanarContrast) { features.Add(GetFeatureName(DefineFeature._PERTEXTRIPLANARCONTRAST)); } } if (useFaceNormals) { features.Add (GetFeatureName (DefineFeature._TRIPLANARUSEFACENORMALS)); } } return features.ToArray(); } public override void Unpack(string[] keywords) { triplanarMode = TriplanarMode.None; if (HasFeature(keywords, DefineFeature._TRIPLANAR)) triplanarMode = TriplanarMode.Triplanar; if (HasFeature(keywords, DefineFeature._TRIPLANARHEIGHTBLEND)) triplanarMode = TriplanarMode.HeightBlended; useFaceNormals = HasFeature (keywords, DefineFeature._TRIPLANARUSEFACENORMALS); space = HasFeature(keywords, DefineFeature._TRIPLANARLOCALSPACE) ? TriplanarSpace.Local : TriplanarSpace.World; perTexTriplanar = HasFeature(keywords, DefineFeature._PERTEXTRIPLANAR); perTexTriplanarContrast = HasFeature(keywords, DefineFeature._PERTEXTRIPLANARCONTRAST); } public override void InitCompiler(string[] paths) { for (int i = 0; i < paths.Length; ++i) { string p = paths[i]; if (p.EndsWith("microsplat_func_triplanar.txt")) { funcs = AssetDatabase.LoadAssetAtPath(p); } } } public override void WriteProperties(string[] features, System.Text.StringBuilder sb) { if (triplanarMode != TriplanarMode.None || System.Array.Exists(features, element => element == "_TERRAINBLENDMATCHWORLDUV")) { sb.AppendLine(" _TriplanarContrast(\"Triplanar Contrast\", Range(1.0, 8)) = 4"); if (useFaceNormals) { sb.AppendLine (" _TriplanarFaceBlend(\"Triplanar Face Blend\", Range(0,1)) = 0"); } } // always write this property so conversion can set it.. sb.AppendLine (" _TriplanarUVScale(\"Triplanar UV Scale\", Vector) = (1, 1, 0, 0)"); } public override void WritePerMaterialCBuffer (string[] features, System.Text.StringBuilder sb) { if (triplanarMode != TriplanarMode.None || System.Array.Exists(features, element => element == "_TERRAINBLENDMATCHWORLDUV")) { sb.AppendLine (" float _TriplanarContrast;"); sb.AppendLine (" float4 _TriplanarUVScale;"); if (useFaceNormals) { sb.AppendLine (" half _TriplanarFaceBlend;"); } } } public override void WriteFunctions(string [] features, System.Text.StringBuilder sb) { // needs to happen first.. if (triplanarMode != TriplanarMode.None) { sb.AppendLine(funcs.text); } } static GUIContent CPerTexTriplanar = new GUIContent("Triplanar Projection", "Allows you to choose between Triplanar, side projection, and top down projection for this texture"); static GUIContent CPerTexTriplanarContrast = new GUIContent("Triplanar Height Contrast", "Allows you to control the blend width of height based triplanar transitions per texture"); public override void DrawPerTextureGUI(int index, MicroSplatKeywords keywords, Material mat, MicroSplatPropData propData) { if (triplanarMode != TriplanarMode.None) { InitPropData(9, propData, new Color(0.0f, 0.5f, 0.0f, 0.0f)); perTexTriplanar = DrawPerTexFloatSlider(index, 9, GetFeatureName(DefineFeature._PERTEXTRIPLANAR), keywords, propData, Channel.R, CPerTexTriplanar, 0, 1); if (triplanarMode == TriplanarMode.HeightBlended) { perTexTriplanarContrast = DrawPerTexFloatSlider(index, 9, GetFeatureName(DefineFeature._PERTEXTRIPLANARCONTRAST), keywords, propData, Channel.G, CPerTexTriplanarContrast, 0.01f, 1); } } } public override void ComputeSampleCounts(string[] features, ref int arraySampleCount, ref int textureSampleCount, ref int maxSamples, ref int tessellationSamples, ref int depTexReadLevel) { if (triplanarMode != TriplanarMode.None) { // need to have this evaluated last arraySampleCount *= 3; } } } #endif }