| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320 | using UnityEditor;using UnityEngine;using UnityEngine.Experimental.Rendering;using UnityEngine.Rendering;namespace FlatKit {public class TerrainEditor : StylizedSurfaceEditor, ITerrainLayerCustomUI {    private class StylesLayer    {        public readonly GUIContent warningHeightBasedBlending = new GUIContent("Height-based blending is disabled if you have more than four TerrainLayer materials!");        public readonly GUIContent enableHeightBlend = new GUIContent("Enable Height-based Blend", "Blend terrain layers based on height values.");        public readonly GUIContent heightTransition = new GUIContent("Height Transition", "Size in world units of the smooth transition between layers.");        public readonly GUIContent enableInstancedPerPixelNormal = new GUIContent("Enable Per-pixel Normal", "Enable per-pixel normal when the terrain uses instanced rendering.");        public readonly GUIContent diffuseTexture = new GUIContent("Diffuse");        public readonly GUIContent colorTint = new GUIContent("Color Tint");        public readonly GUIContent opacityAsDensity = new GUIContent("Opacity as Density", "Enable Density Blend (if unchecked, opacity is used as Smoothness)");        public readonly GUIContent normalMapTexture = new GUIContent("Normal Map");        public readonly GUIContent normalScale = new GUIContent("Normal Scale");        public readonly GUIContent maskMapTexture = new GUIContent("Mask", "R: Metallic\nG: AO\nB: Height\nA: Smoothness");        public readonly GUIContent maskMapTextureWithoutHeight = new GUIContent("Mask Map", "R: Metallic\nG: AO\nA: Smoothness");        public readonly GUIContent channelRemapping = new GUIContent("Channel Remapping");        public readonly GUIContent defaultValues = new GUIContent("Channel Default Values");        public readonly GUIContent metallic = new GUIContent("R: Metallic");        public readonly GUIContent ao = new GUIContent("G: AO");        public readonly GUIContent height = new GUIContent("B: Height");        public readonly GUIContent heightParametrization = new GUIContent("Parametrization");        public readonly GUIContent heightAmplitude = new GUIContent("Amplitude (cm)");        public readonly GUIContent heightBase = new GUIContent("Base (cm)");        public readonly GUIContent heightMin = new GUIContent("Min (cm)");        public readonly GUIContent heightMax = new GUIContent("Max (cm)");        public readonly GUIContent heightCm = new GUIContent("B: Height (cm)");        public readonly GUIContent smoothness = new GUIContent("A: Smoothness");    }    static StylesLayer s_Styles = null;    private static StylesLayer styles {        get {            if (s_Styles == null) s_Styles = new StylesLayer();            return s_Styles;        }    }    // Height blend params    MaterialProperty enableHeightBlend = null;    const string kEnableHeightBlend = "_EnableHeightBlend";    MaterialProperty heightTransition = null;    const string kHeightTransition = "_HeightTransition";    // Per-pixel Normal (while instancing)    MaterialProperty enableInstancedPerPixelNormal = null;    const string kEnableInstancedPerPixelNormal = "_EnableInstancedPerPixelNormal";    private bool m_ShowChannelRemapping = false;    enum HeightParametrization    {        Amplitude,        MinMax    };    private HeightParametrization m_HeightParametrization = HeightParametrization.Amplitude;    private static bool DoesTerrainUseMaskMaps(TerrainLayer[] terrainLayers)    {        for (int i = 0; i < terrainLayers.Length; ++i)        {            if (terrainLayers[i].maskMapTexture != null)                return true;        }        return false;    }    protected void FindMaterialProperties(MaterialProperty[] props)    {        enableHeightBlend = FindProperty(kEnableHeightBlend, props, false);        heightTransition = FindProperty(kHeightTransition, props, false);        enableInstancedPerPixelNormal = FindProperty(kEnableInstancedPerPixelNormal, props, false);    }    static public void SetupMaterialKeywords(Material material)    {        bool enableHeightBlend = (material.HasProperty(kEnableHeightBlend) && material.GetFloat(kEnableHeightBlend) > 0);        CoreUtils.SetKeyword(material, "_TERRAIN_BLEND_HEIGHT", enableHeightBlend);        bool enableInstancedPerPixelNormal = material.GetFloat(kEnableInstancedPerPixelNormal) > 0.0f;        CoreUtils.SetKeyword(material, "_TERRAIN_INSTANCED_PERPIXEL_NORMAL", enableInstancedPerPixelNormal);    }    static public bool TextureHasAlpha(Texture2D inTex)    {        if (inTex != null)        {            return GraphicsFormatUtility.HasAlphaChannel(GraphicsFormatUtility.GetGraphicsFormat(inTex.format, true));        }        return false;    }    public override void OnGUI(MaterialEditor materialEditorIn, MaterialProperty[] properties) {        base.OnGUI(materialEditorIn, properties);        FindMaterialProperties(properties);        bool optionsChanged = false;        EditorGUI.BeginChangeCheck();        {            if (enableHeightBlend != null)            {                //EditorGUI.indentLevel++;                materialEditorIn.ShaderProperty(enableHeightBlend, styles.enableHeightBlend);                if (enableHeightBlend.floatValue > 0)                {                    EditorGUI.indentLevel++;                    EditorGUILayout.HelpBox(styles.warningHeightBasedBlending.text, MessageType.Info);                    materialEditorIn.ShaderProperty(heightTransition, styles.heightTransition);                    EditorGUI.indentLevel--;                }                //EditorGUI.indentLevel--;            }            EditorGUILayout.Space();        }        if (EditorGUI.EndChangeCheck())        {            optionsChanged = true;        }        bool enablePerPixelNormalChanged = false;        // Since Instanced Per-pixel normal is actually dependent on instancing enabled or not, it is not        // important to check it in the GUI.  The shader will make sure it is enabled/disabled properly.s        if (enableInstancedPerPixelNormal != null)        {            //EditorGUI.indentLevel++;            EditorGUI.BeginChangeCheck();            materialEditorIn.ShaderProperty(enableInstancedPerPixelNormal, styles.enableInstancedPerPixelNormal);            enablePerPixelNormalChanged = EditorGUI.EndChangeCheck();            //EditorGUI.indentLevel--;        }        if (optionsChanged || enablePerPixelNormalChanged)        {            foreach (var obj in materialEditorIn.targets)            {                SetupMaterialKeywords((Material)obj);            }        }        // We should always do this call at the end        materialEditorIn.serializedObject.ApplyModifiedProperties();    }    bool ITerrainLayerCustomUI.OnTerrainLayerGUI(TerrainLayer terrainLayer, Terrain terrain)    {        var terrainLayers = terrain.terrainData.terrainLayers;        // Don't use the member field enableHeightBlend as ShaderGUI.OnGUI might not be called if the material UI is folded.        // heightblend shouldn't be available if we are in multi-pass mode, because it is guaranteed to be broken.        bool heightBlendAvailable = (terrainLayers.Length <= 4);        bool heightBlend = heightBlendAvailable && terrain.materialTemplate.HasProperty(kEnableHeightBlend) && (terrain.materialTemplate.GetFloat(kEnableHeightBlend) > 0);        terrainLayer.diffuseTexture = EditorGUILayout.ObjectField(styles.diffuseTexture, terrainLayer.diffuseTexture, typeof(Texture2D), false) as Texture2D;        TerrainLayerUtility.ValidateDiffuseTextureUI(terrainLayer.diffuseTexture);        var diffuseRemapMin = terrainLayer.diffuseRemapMin;        var diffuseRemapMax = terrainLayer.diffuseRemapMax;        EditorGUI.BeginChangeCheck();        bool enableDensity = false;        if (terrainLayer.diffuseTexture != null)        {            var rect = GUILayoutUtility.GetLastRect();            rect.y += 16 + 4;            rect.width = EditorGUIUtility.labelWidth + 64;            rect.height = 16;            ++EditorGUI.indentLevel;            var diffuseTint = new Color(diffuseRemapMax.x, diffuseRemapMax.y, diffuseRemapMax.z);            diffuseTint = EditorGUI.ColorField(rect, styles.colorTint, diffuseTint, true, false, false);            diffuseRemapMax.x = diffuseTint.r;            diffuseRemapMax.y = diffuseTint.g;            diffuseRemapMax.z = diffuseTint.b;            diffuseRemapMin.x = diffuseRemapMin.y = diffuseRemapMin.z = 0;            if (!heightBlend)            {                rect.y = rect.yMax + 2;                enableDensity = EditorGUI.Toggle(rect, styles.opacityAsDensity, diffuseRemapMin.w > 0);            }            --EditorGUI.indentLevel;        }        diffuseRemapMax.w = 1;        diffuseRemapMin.w = enableDensity ? 1 : 0;        if (EditorGUI.EndChangeCheck())        {            terrainLayer.diffuseRemapMin = diffuseRemapMin;            terrainLayer.diffuseRemapMax = diffuseRemapMax;        }        // Display normal map UI        terrainLayer.normalMapTexture = EditorGUILayout.ObjectField(styles.normalMapTexture, terrainLayer.normalMapTexture, typeof(Texture2D), false) as Texture2D;        TerrainLayerUtility.ValidateNormalMapTextureUI(terrainLayer.normalMapTexture, TerrainLayerUtility.CheckNormalMapTextureType(terrainLayer.normalMapTexture));        if (terrainLayer.normalMapTexture != null)        {            var rect = GUILayoutUtility.GetLastRect();            rect.y += 16 + 4;            rect.width = EditorGUIUtility.labelWidth + 64;            rect.height = 16;            ++EditorGUI.indentLevel;            terrainLayer.normalScale = EditorGUI.FloatField(rect, styles.normalScale, terrainLayer.normalScale);            --EditorGUI.indentLevel;        }        // Display the mask map UI and the remap controls        terrainLayer.maskMapTexture = EditorGUILayout.ObjectField(heightBlend ? styles.maskMapTexture : styles.maskMapTextureWithoutHeight, terrainLayer.maskMapTexture, typeof(Texture2D), false) as Texture2D;        TerrainLayerUtility.ValidateMaskMapTextureUI(terrainLayer.maskMapTexture);        var maskMapRemapMin = terrainLayer.maskMapRemapMin;        var maskMapRemapMax = terrainLayer.maskMapRemapMax;        var smoothness = terrainLayer.smoothness;        var metallic = terrainLayer.metallic;        ++EditorGUI.indentLevel;        EditorGUI.BeginChangeCheck();        m_ShowChannelRemapping = EditorGUILayout.Foldout(m_ShowChannelRemapping, terrainLayer.maskMapTexture != null ? s_Styles.channelRemapping : s_Styles.defaultValues);        if (m_ShowChannelRemapping)        {            if (terrainLayer.maskMapTexture != null)            {                float min, max;                min = maskMapRemapMin.x; max = maskMapRemapMax.x;                EditorGUILayout.MinMaxSlider(s_Styles.metallic, ref min, ref max, 0, 1);                maskMapRemapMin.x = min; maskMapRemapMax.x = max;                min = maskMapRemapMin.y; max = maskMapRemapMax.y;                EditorGUILayout.MinMaxSlider(s_Styles.ao, ref min, ref max, 0, 1);                maskMapRemapMin.y = min; maskMapRemapMax.y = max;                if (heightBlend)                {                    EditorGUILayout.LabelField(styles.height);                    ++EditorGUI.indentLevel;                    m_HeightParametrization = (HeightParametrization)EditorGUILayout.EnumPopup(styles.heightParametrization, m_HeightParametrization);                    if (m_HeightParametrization == HeightParametrization.Amplitude)                    {                        // (height - heightBase) * amplitude                        float amplitude = Mathf.Max(maskMapRemapMax.z - maskMapRemapMin.z, Mathf.Epsilon); // to avoid divide by zero                        float heightBase = maskMapRemapMin.z / amplitude;                        amplitude = EditorGUILayout.FloatField(styles.heightAmplitude, amplitude * 100) / 100;                        heightBase = EditorGUILayout.FloatField(styles.heightBase, heightBase * 100) / 100;                        maskMapRemapMin.z = heightBase * amplitude;                        maskMapRemapMax.z = (1.0f - heightBase) * amplitude;                    }                    else                    {                        maskMapRemapMin.z = EditorGUILayout.FloatField(styles.heightMin, maskMapRemapMin.z * 100) / 100;                        maskMapRemapMax.z = EditorGUILayout.FloatField(styles.heightMax, maskMapRemapMax.z * 100) / 100;                    }                    --EditorGUI.indentLevel;                }                min = maskMapRemapMin.w; max = maskMapRemapMax.w;                EditorGUILayout.MinMaxSlider(s_Styles.smoothness, ref min, ref max, 0, 1);                maskMapRemapMin.w = min; maskMapRemapMax.w = max;            }            else            {                metallic = EditorGUILayout.Slider(s_Styles.metallic, metallic, 0, 1);                // AO and Height are still exclusively controlled via the maskRemap controls                // metallic and smoothness have their own values as fields within the LayerData.                maskMapRemapMax.y = EditorGUILayout.Slider(s_Styles.ao, maskMapRemapMax.y, 0, 1);                if (heightBlend)                {                    maskMapRemapMax.z = EditorGUILayout.FloatField(s_Styles.heightCm, maskMapRemapMax.z * 100) / 100;                }                // There's a possibility that someone could slide max below the existing min value                // so we'll just protect against that by locking the min value down a little bit.                // In the case of height (Z), we are trying to set min to no lower than zero value unless                // max goes negative.  Zero is a good sensible value for the minimum.  For AO (Y), we                // don't need this extra protection step because the UI blocks us from going negative                // anyway.  In both cases, pushing the slider below the min value will lock them together,                // but min will be "left behind" if you go back up.                maskMapRemapMin.y = Mathf.Min(maskMapRemapMin.y, maskMapRemapMax.y);                maskMapRemapMin.z = Mathf.Min(Mathf.Max(0, maskMapRemapMin.z), maskMapRemapMax.z);                if (TextureHasAlpha(terrainLayer.diffuseTexture))                {                    GUIStyle warnStyle = new GUIStyle(GUI.skin.label);                    warnStyle.wordWrap = true;                    GUILayout.Label("Smoothness is controlled by diffuse alpha channel", warnStyle);                }                else                    smoothness = EditorGUILayout.Slider(s_Styles.smoothness, smoothness, 0, 1);            }        }        if (EditorGUI.EndChangeCheck())        {            terrainLayer.maskMapRemapMin = maskMapRemapMin;            terrainLayer.maskMapRemapMax = maskMapRemapMax;            terrainLayer.smoothness = smoothness;            terrainLayer.metallic = metallic;        }        --EditorGUI.indentLevel;        EditorGUILayout.Space();        TerrainLayerUtility.TilingSettingsUI(terrainLayer);        return true;    }}}
 |