413 lines
18 KiB
C#
413 lines
18 KiB
C#
|
using System.Collections.Generic;
|
|||
|
using UnityEngine;
|
|||
|
using UnityEditor;
|
|||
|
using System;
|
|||
|
using System.Linq;
|
|||
|
|
|||
|
#if UNITY_EDITOR
|
|||
|
|
|||
|
namespace Quixel
|
|||
|
{
|
|||
|
|
|||
|
public class MegascansTerrainTools
|
|||
|
{
|
|||
|
/*
|
|||
|
(Verify that the pipeline is HDRP.)
|
|||
|
* *******************************************************************************
|
|||
|
* ********************** Unity 2018.1 and 2018.2 ********************************
|
|||
|
* *******************************************************************************
|
|||
|
Terrain material blend setup steps:
|
|||
|
1. Get selected materials and make a terrain LayeredLit Material.
|
|||
|
2. Set Splat Prop types for the terrain.
|
|||
|
3. Set created materail to the terrain.
|
|||
|
4. Enable splat maps for the terrain.
|
|||
|
5. Set Layer Mask property of the terrain material to use Splat Alpha Map.
|
|||
|
6. Voila start painting!
|
|||
|
|
|||
|
* *******************************************************************************
|
|||
|
* ********************** Unity 2018.3 onwards ***********************************
|
|||
|
* *******************************************************************************
|
|||
|
* Terrain material blend setup steps:
|
|||
|
1. Get selected materials and make Terrain Layers from them.
|
|||
|
2. Assign newly created terrain layers to terrain data.
|
|||
|
3. Voila start painting!
|
|||
|
|
|||
|
*/
|
|||
|
|
|||
|
static int maxMaterialAllowed = 4;
|
|||
|
|
|||
|
public static void SetupTerrain()
|
|||
|
{
|
|||
|
string materialName = EditorPrefs.GetString("QuixelDefaultMaterialName", "Terrain Material");
|
|||
|
string materialPath = EditorPrefs.GetString("QuixelDefaultMaterialPath", "Quixel/");
|
|||
|
string tilingNumber = EditorPrefs.GetString("QuixelDefaultTiling", "10");
|
|||
|
|
|||
|
string[] versionParts = Application.unityVersion.Split('.');
|
|||
|
int majorVersion = int.Parse(versionParts[0]);
|
|||
|
|
|||
|
if (majorVersion < 2018)
|
|||
|
{
|
|||
|
Debug.Log("This Unity version doesn't support this feature.");
|
|||
|
return;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
float tiling;
|
|||
|
|
|||
|
try
|
|||
|
{
|
|||
|
tiling = float.Parse(tilingNumber);
|
|||
|
}
|
|||
|
catch (Exception ex)
|
|||
|
{
|
|||
|
Debug.Log("Exception: " + ex.ToString());
|
|||
|
tiling = 1f;
|
|||
|
}
|
|||
|
maxMaterialAllowed = MegascansUtilities.isLegacy() ? 4 : 8;
|
|||
|
|
|||
|
List<Material> sourceMaterials = new List<Material>();
|
|||
|
Terrain terrain = null;
|
|||
|
|
|||
|
try
|
|||
|
{
|
|||
|
//Get currently highlighted materials in the project window.
|
|||
|
sourceMaterials = GetSelectedMaterialsInProjectHierarchy();
|
|||
|
//Verify two or more materails were seleted.
|
|||
|
if (sourceMaterials.Count < 2 && MegascansUtilities.isLegacy())
|
|||
|
{
|
|||
|
Debug.Log("Not enough materials to create a blend material. Please select more materials.");
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
//Get selected terrain in the scene view.
|
|||
|
terrain = getCurrentlySelectedTerrain();
|
|||
|
//Verify a terrain is selected.
|
|||
|
if (terrain == null)
|
|||
|
{
|
|||
|
Debug.Log("No terrain selected. Please select a terrain.");
|
|||
|
//return;
|
|||
|
}
|
|||
|
}
|
|||
|
catch (Exception ex)
|
|||
|
{
|
|||
|
Debug.Log("Error getting select terrain/materials.");
|
|||
|
Debug.Log(ex);
|
|||
|
}
|
|||
|
|
|||
|
if (MegascansUtilities.isLegacy())
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
//Set up the material here.
|
|||
|
Material terrainMaterial = SetupTerrainMaterialDeprecated(sourceMaterials, materialPath, materialName, tiling);
|
|||
|
//Get terrain data for splat maps.
|
|||
|
TerrainData terrainData = terrain.terrainData;
|
|||
|
//Get the textures from source materials to add to the painting.
|
|||
|
if (terrainData)
|
|||
|
{
|
|||
|
#pragma warning disable
|
|||
|
terrainData.splatPrototypes = getMaterialTexturesForSplatMap(sourceMaterials, tiling);
|
|||
|
}
|
|||
|
terrain.materialType = Terrain.MaterialType.Custom;
|
|||
|
terrain.materialTemplate = terrainMaterial;
|
|||
|
EnableSplatmaps(terrainData);
|
|||
|
Texture2D alphaMap = terrainData.alphamapTextures[0];
|
|||
|
terrainMaterial.SetTexture("_LayerMaskMap", alphaMap);
|
|||
|
Debug.Log("Terrain blend material successfully created!");
|
|||
|
}
|
|||
|
catch (Exception ex)
|
|||
|
{
|
|||
|
Debug.Log("Error Generating terrain blend material!");
|
|||
|
Debug.Log(ex);
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
#if UNITY_2018_3 || UNITY_2018_4 || UNITY_2019 || UNITY_2020 || UNITY_2021
|
|||
|
//Create the material here.
|
|||
|
//Material terrainMaterial = SetupTerrainMaterial(materialPath, materialName);
|
|||
|
List<TerrainLayer> terrainLayers = new List<TerrainLayer>();
|
|||
|
|
|||
|
foreach (TerrainLayer tl in terrain.terrainData.terrainLayers)
|
|||
|
{
|
|||
|
terrainLayers.Add(tl);
|
|||
|
}
|
|||
|
|
|||
|
// Unity's terrain system uses the second terrain layer as default material on terrain.
|
|||
|
// To counter this we swap the position of the materials in our list.
|
|||
|
if (sourceMaterials.Count > 1 && terrainLayers.Count == 0)
|
|||
|
{
|
|||
|
Material tempMat = sourceMaterials[0];
|
|||
|
sourceMaterials[0] = sourceMaterials[1];
|
|||
|
sourceMaterials[1] = tempMat;
|
|||
|
}
|
|||
|
|
|||
|
foreach (Material mat in sourceMaterials)
|
|||
|
{
|
|||
|
if (terrainLayers.Count <= 8)
|
|||
|
{
|
|||
|
terrainLayers.Add(createTerrainLayer(mat, tiling));
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (terrainLayers.Count > 0)
|
|||
|
{
|
|||
|
terrain.terrainData.terrainLayers = terrainLayers.ToArray();
|
|||
|
}
|
|||
|
#endif
|
|||
|
}
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
public static Material CreateMaterial(string mPath, string name, string shaderName)
|
|||
|
{
|
|||
|
string path = MegascansUtilities.FixPath(mPath);
|
|||
|
|
|||
|
/// Unity doesn't allow you to create objects in directories which don't exist.
|
|||
|
/// So in this function, we create any and all necessary subdirectories that are required.
|
|||
|
/// We return the final subdirectory, which is used later in the asset creation too.
|
|||
|
|
|||
|
//first, create the user specified path from the importer settings.
|
|||
|
string[] pathParts = MegascansUtilities.FixSlashes(path).Split('/');
|
|||
|
string defPath = "Assets";
|
|||
|
if (pathParts.Length > 0)
|
|||
|
{
|
|||
|
for (int i = 0; i < pathParts.Length; ++i)
|
|||
|
{
|
|||
|
defPath = MegascansUtilities.ValidateFolderCreate(defPath, pathParts[i]);
|
|||
|
}
|
|||
|
}
|
|||
|
defPath = defPath + "/" + name + ".mat";
|
|||
|
Material terrainMaterial = new Material(Shader.Find(shaderName));
|
|||
|
AssetDatabase.CreateAsset(terrainMaterial, defPath);
|
|||
|
AssetDatabase.Refresh();
|
|||
|
return terrainMaterial;
|
|||
|
}
|
|||
|
|
|||
|
//This method sets the right parameters depending upon the selected settings.
|
|||
|
|
|||
|
public static Material SetupTerrainMaterial(string materialPath, string materialName)
|
|||
|
{
|
|||
|
Material terrainMaterial = CreateMaterial(materialPath, materialName, "HDRenderPipeline/TerrainLit");
|
|||
|
terrainMaterial.enableInstancing = true;
|
|||
|
return terrainMaterial;
|
|||
|
}
|
|||
|
|
|||
|
public static Material SetupTerrainMaterialDeprecated(List<Material> sourceMaterials, string materialPath, string materialName, float tiling)
|
|||
|
{
|
|||
|
int numberOfLayers = Mathf.Clamp(sourceMaterials.Count + 1, 2, 4);
|
|||
|
Material terrainMaterial = CreateMaterial(materialPath, materialName, "HDRenderPipeline/LayeredLit");
|
|||
|
terrainMaterial.SetFloat("_LayerCount", numberOfLayers);
|
|||
|
terrainMaterial.EnableKeyword("_LAYEREDLIT_" + numberOfLayers.ToString() + "_LAYERS");
|
|||
|
|
|||
|
numberOfLayers--;
|
|||
|
|
|||
|
if (numberOfLayers > 3)
|
|||
|
{
|
|||
|
SetFloatsAndTexturesDeprecated(terrainMaterial, sourceMaterials[3], "0", tiling);
|
|||
|
SetFloatsAndTexturesDeprecated(terrainMaterial, sourceMaterials[0], "1", tiling);
|
|||
|
SetFloatsAndTexturesDeprecated(terrainMaterial, sourceMaterials[1], "2", tiling);
|
|||
|
SetFloatsAndTexturesDeprecated(terrainMaterial, sourceMaterials[2], "3", tiling);
|
|||
|
}
|
|||
|
else if (numberOfLayers > 2)
|
|||
|
{
|
|||
|
SetFloatsAndTexturesDeprecated(terrainMaterial, sourceMaterials[2], "0", tiling);
|
|||
|
SetFloatsAndTexturesDeprecated(terrainMaterial, sourceMaterials[0], "1", tiling);
|
|||
|
SetFloatsAndTexturesDeprecated(terrainMaterial, sourceMaterials[1], "2", tiling);
|
|||
|
SetFloatsAndTexturesDeprecated(terrainMaterial, sourceMaterials[2], "3", tiling);
|
|||
|
}
|
|||
|
else if (numberOfLayers > 1)
|
|||
|
{
|
|||
|
SetFloatsAndTexturesDeprecated(terrainMaterial, sourceMaterials[1], "0", tiling);
|
|||
|
SetFloatsAndTexturesDeprecated(terrainMaterial, sourceMaterials[0], "1", tiling);
|
|||
|
SetFloatsAndTexturesDeprecated(terrainMaterial, sourceMaterials[1], "2", tiling);
|
|||
|
}
|
|||
|
|
|||
|
return terrainMaterial;
|
|||
|
}
|
|||
|
|
|||
|
public static void SetFloatsAndTexturesDeprecated(Material terrainMaterial, Material sourceMaterial, string suffix, float tiling)
|
|||
|
{
|
|||
|
terrainMaterial.EnableKeyword("_MASKMAP" + suffix);
|
|||
|
terrainMaterial.EnableKeyword("_NORMALMAP" + suffix);
|
|||
|
terrainMaterial.EnableKeyword("_NORMALMAP_TANGENT_SPACE" + suffix);
|
|||
|
terrainMaterial.EnableKeyword("_MASKMAP" + suffix);
|
|||
|
terrainMaterial.SetTexture("_BaseColorMap" + suffix, sourceMaterial.mainTexture);
|
|||
|
terrainMaterial.SetTexture("_MaskMap" + suffix, sourceMaterial.GetTexture("_MaskMap"));
|
|||
|
terrainMaterial.SetTexture("_NormalMap" + suffix, sourceMaterial.GetTexture("_NormalMap"));
|
|||
|
terrainMaterial.SetFloat("_NormalMapSpace" + suffix, 0f);
|
|||
|
terrainMaterial.SetFloat("_Metallic" + suffix, 1f);
|
|||
|
terrainMaterial.SetTextureScale("_BaseColorMap" + suffix, new Vector2(tiling, tiling));
|
|||
|
terrainMaterial.SetTextureScale("_MaskMap" + suffix, new Vector2(tiling, tiling));
|
|||
|
terrainMaterial.SetTextureScale("_NormalMap" + suffix, new Vector2(tiling, tiling));
|
|||
|
}
|
|||
|
|
|||
|
public static List<Material> GetSelectedMaterialsInProjectHierarchy()
|
|||
|
{
|
|||
|
List<Material> selectedMaterials = new List<Material>();
|
|||
|
List<string> selectedMaterialPaths = new List<string>();
|
|||
|
List<UnityEngine.Object> selection = new List<UnityEngine.Object>();
|
|||
|
|
|||
|
foreach (UnityEngine.Object obj in Selection.GetFiltered(typeof(UnityEngine.Object), SelectionMode.Assets))
|
|||
|
{
|
|||
|
if (obj.GetType() == typeof(Material) && selectedMaterials.Count <= maxMaterialAllowed)
|
|||
|
{
|
|||
|
selectedMaterials.Add((Material)obj);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
selection.Add(obj);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
List<string> selectedFolders = MegascansUtilities.GetSelectedFolders(selection);
|
|||
|
foreach (string folder in selectedFolders)
|
|||
|
{
|
|||
|
selectedMaterialPaths = selectedMaterialPaths.Concat(MegascansUtilities.GetFiles(folder, ".mat")).ToList();
|
|||
|
}
|
|||
|
|
|||
|
foreach (string mPath in selectedMaterialPaths)
|
|||
|
{
|
|||
|
Material mat = (Material)AssetDatabase.LoadAssetAtPath(mPath, typeof(Material));
|
|||
|
if (mat != null && selectedMaterials.Count <= maxMaterialAllowed) //4 is the max number of layers currently allowed in the HDRenderPipeline/LayeredLit shader.
|
|||
|
{
|
|||
|
selectedMaterials.Add(mat);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return selectedMaterials;
|
|||
|
}
|
|||
|
|
|||
|
public static SplatPrototype[] getMaterialTexturesForSplatMap(List<Material> materials, float tiling)
|
|||
|
{
|
|||
|
SplatPrototype[] splatPropsTypes = new SplatPrototype[materials.Count];
|
|||
|
|
|||
|
for (int i = 0; i < materials.Count; i++)
|
|||
|
{
|
|||
|
SplatPrototype prop = new SplatPrototype();
|
|||
|
prop.texture = (Texture2D)materials[i].mainTexture;
|
|||
|
prop.normalMap = (Texture2D)materials[i].GetTexture("_NormalMap");
|
|||
|
prop.tileSize = new Vector2(tiling, tiling);
|
|||
|
splatPropsTypes[i] = prop;
|
|||
|
}
|
|||
|
|
|||
|
return splatPropsTypes;
|
|||
|
}
|
|||
|
|
|||
|
public static Terrain getCurrentlySelectedTerrain()
|
|||
|
{
|
|||
|
foreach (UnityEngine.Object obj in Selection.objects)
|
|||
|
{
|
|||
|
if (obj.GetType() == typeof(GameObject))
|
|||
|
{
|
|||
|
GameObject terrain = (GameObject)obj;
|
|||
|
if (terrain.GetComponent<Terrain>())
|
|||
|
{
|
|||
|
return terrain.GetComponent<Terrain>();
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
return null;
|
|||
|
}
|
|||
|
|
|||
|
public static void EnableSplatmaps(TerrainData terrainData)
|
|||
|
{
|
|||
|
UnityEngine.Object[] data = AssetDatabase.LoadAllAssetsAtPath(AssetDatabase.GetAssetPath(terrainData));
|
|||
|
foreach (UnityEngine.Object o in data)
|
|||
|
{
|
|||
|
if (o is Texture2D)
|
|||
|
{
|
|||
|
(o as Texture2D).hideFlags = HideFlags.None;
|
|||
|
AssetDatabase.SaveAssets();
|
|||
|
AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(o));
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
#if UNITY_2018_3 || UNITY_2018_4 || UNITY_2019 || UNITY_2020 || UNITY_2021
|
|||
|
public static TerrainLayer createTerrainLayer(Material mat, float tiling)
|
|||
|
{
|
|||
|
TerrainLayer terrainLayer = new TerrainLayer();
|
|||
|
string path = AssetDatabase.GetAssetPath(mat);
|
|||
|
path = path.Replace(".mat", ".terrainlayer");
|
|||
|
AssetDatabase.CreateAsset(terrainLayer, path);
|
|||
|
AssetDatabase.Refresh();
|
|||
|
terrainLayer.diffuseTexture = (Texture2D)mat.mainTexture;
|
|||
|
|
|||
|
//attempt to auto-detect a settings file for Lightweight or HD pipelines
|
|||
|
switch (MegascansUtilities.getCurrentPipeline())
|
|||
|
{
|
|||
|
case Pipeline.HDRP:
|
|||
|
terrainLayer.normalMapTexture = (Texture2D)mat.GetTexture("_NormalMap");
|
|||
|
if (mat.GetFloat("_MaterialID") == 4)
|
|||
|
{
|
|||
|
terrainLayer.maskMapTexture = (Texture2D)mat.GetTexture("_SpecularColorMap");
|
|||
|
terrainLayer.metallic = 1.0f;
|
|||
|
}
|
|||
|
else if (mat.GetFloat("_MaterialID") == 1)
|
|||
|
{
|
|||
|
terrainLayer.maskMapTexture = (Texture2D)mat.GetTexture("_MaskMap");
|
|||
|
terrainLayer.specular = new Color(1.0f, 1.0f, 1.0f);
|
|||
|
}
|
|||
|
break;
|
|||
|
case Pipeline.LWRP:
|
|||
|
|
|||
|
terrainLayer.normalMapTexture = (Texture2D)mat.GetTexture("_BumpMap");
|
|||
|
if (mat.GetFloat("_WorkflowMode") == 1)
|
|||
|
{
|
|||
|
terrainLayer.maskMapTexture = (Texture2D)mat.GetTexture("_MetallicGlossMap");
|
|||
|
terrainLayer.specular = new Color(1.0f, 1.0f, 1.0f);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
terrainLayer.maskMapTexture = (Texture2D)mat.GetTexture("_SpecGlossMap");
|
|||
|
terrainLayer.metallic = 1.0f;
|
|||
|
}
|
|||
|
break;
|
|||
|
case Pipeline.Standard:
|
|||
|
terrainLayer.normalMapTexture = (Texture2D)mat.GetTexture("_BumpMap");
|
|||
|
if (mat.shader.ToString() == "Standard (Specular setup)")
|
|||
|
{
|
|||
|
terrainLayer.maskMapTexture = (Texture2D)mat.GetTexture("_SpecGlossMap");
|
|||
|
terrainLayer.metallic = 1.0f;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
terrainLayer.maskMapTexture = (Texture2D)mat.GetTexture("_MetallicGlossMap");
|
|||
|
terrainLayer.specular = new Color(1.0f, 1.0f, 1.0f);
|
|||
|
}
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
terrainLayer.tileSize = new Vector2(tiling, tiling);
|
|||
|
return terrainLayer;
|
|||
|
}
|
|||
|
|
|||
|
public static void CreateTerrainLayerFromMat()
|
|||
|
{
|
|||
|
try {
|
|||
|
Material selectedMat = MegascansUtilities.GetSelectedMaterial();
|
|||
|
if (!selectedMat)
|
|||
|
return;
|
|||
|
|
|||
|
Debug.Log("Here");
|
|||
|
createTerrainLayer(selectedMat, 1f);
|
|||
|
|
|||
|
Debug.Log("Successfully created the terrain layer.");
|
|||
|
}
|
|||
|
catch (Exception ex)
|
|||
|
{
|
|||
|
Debug.Log("Exception::MegascansImageUtils::Flip Green Channel:: " + ex.ToString());
|
|||
|
MegascansUtilities.HideProgressBar();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
#endif
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
#endif
|