Heroes_of_Hiis/Assets/Project Files/Runtime/Fantasy Adventure Environment/PigmentMapGenerator.cs

659 lines
21 KiB
C#
Raw Normal View History

2022-03-07 16:33:30 +00:00
// Fantasy Adventure Environment
// Copyright Staggart Creations
// staggart.xyz
using UnityEngine;
using System.Collections;
using System.IO;
using System;
using Workflow = FAE.TerrainUVUtil.Workflow;
namespace FAE
{
using System.Collections.Generic;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.SceneManagement;
[ExecuteInEditMode]
#endif
public class PigmentMapGenerator : MonoBehaviour
{
//Dev
public bool debug = false;
public bool performCleanup = true;
public bool manualInput = false;
//Terrain objects
public GameObject[] terrainObjects;
//Terrain utils
public TerrainUVUtil util;
public Workflow workflow;
public int resIdx = 4;
private int resolution = 1024;
public Vector3 targetSize;
public Vector3 targetOriginPosition;
public Vector3 targetCenterPosition;
//Runtime
[SerializeField]
public Vector4 terrainScaleOffset;
//Terrain terrain
public Terrain[] terrains;
//Mesh terrain
private MeshRenderer[] meshes;
private Material material;
#region Rendering
//Constants
const int HEIGHTOFFSET = 1000;
const int CLIP_PADDING = 100;
//Render options
public LayerMask layerMask = 1;
public float renderLightBrightness = 0.25f;
public bool useAlternativeRenderer = false;
//Rendering
private Camera renderCam;
private Light renderLight;
private Light[] lights;
#endregion
#region Inputs
//Inputs
public Texture2D inputHeightmap;
public Texture2D customPigmentMap;
public bool useCustomPigmentMap;
//Texture options
public bool flipVertically;
public bool flipHortizontally;
public enum TextureRotation
{
None,
Quarter,
Half,
ThreeQuarters
}
public TextureRotation textureRotation;
#endregion
//Textures
public Texture2D pigmentMap;
//Meta
public bool isMultiTerrain;
public string savePath;
private float originalTargetYPos;
[NonSerialized]
public bool showArea;
//MegaSplat
public bool hasTerrainData = true;
public bool isMegaSplat = false;
//Reset lighting settings
UnityEngine.Rendering.AmbientMode ambientMode;
Color ambientColor;
bool enableFog;
Material skyboxMat;
public enum HeightmapChannel
{
None,
Texture1,
Texture2,
Texture3,
Texture4,
Texture5,
Texture6,
Texture7,
Texture8
}
public HeightmapChannel heightmapChannel = HeightmapChannel.None;
public string HeightmapChannelName;
public string[] terrainTextureNames;
//Used at runtime
private void OnEnable()
{
Init();
}
private void OnDisable()
{
//This is to avoid the pigment map remaining in the shader
Shader.SetGlobalTexture("_PigmentMap", null);
}
private void OnDrawGizmosSelected()
{
if (showArea)
{
Color32 color = new Color(0f, 0.66f, 1f, 0.1f);
Gizmos.color = color;
Gizmos.DrawCube(targetCenterPosition, targetSize);
color = new Color(0f, 0.66f, 1f, 0.66f);
Gizmos.color = color;
Gizmos.DrawWireCube(targetCenterPosition, targetSize);
}
}
public void Init()
{
#if UNITY_EDITOR
CheckMegaSplat();
if (GetComponent<Terrain>() || GetComponent<MeshRenderer>())
{
isMultiTerrain = false;
//Single terrain, use first element
terrainObjects = new GameObject[1];
terrainObjects[0] = this.gameObject;
}
else
{
isMultiTerrain = true;
//Init array
if (terrainObjects == null) terrainObjects = new GameObject[0];
}
//Create initial pigment map
if (pigmentMap == null)
{
Generate();
}
#endif
SetPigmentMap();
}
private void CheckMegaSplat()
{
#if __MEGASPLAT__
if(workflow == TerrainUVUtil.Workflow.Terrain)
{
if (terrains[0].materialType == Terrain.MaterialType.Custom)
{
if (terrains[0].materialTemplate.shader.name.Contains("MegaSplat"))
{
isMegaSplat = true;
useAlternativeRenderer = true;
}
else
{
isMegaSplat = false;
}
}
}
#else
isMegaSplat = false;
#endif
}
public void GetChildTerrainObjects(Transform parent)
{
//All childs, recursive
Transform[] children = parent.GetComponentsInChildren<Transform>();
int childCount = 0;
//Count first level transforms
for (int i = 0; i < children.Length; i++)
{
if (children[i].parent == parent) childCount++;
}
//Temp list
List<GameObject> terrainList = new List<GameObject>();
//Init array with childcount length
this.terrainObjects = new GameObject[childCount];
//Fill array with first level transforms
for (int i = 0; i < children.Length; i++)
{
if (children[i].parent == parent)
{
terrainList.Add(children[i].gameObject);
}
}
terrainObjects = terrainList.ToArray();
}
//Grab the terrain position and size and pass it to the shaders
public void GetTargetInfo()
{
if (debug) Debug.Log("Getting target info for " + terrainObjects.Length + " object(s)");
if (!util) util = ScriptableObject.CreateInstance<TerrainUVUtil>();
util.GetObjectPlanarUV(terrainObjects);
//Determine if the object is a terrain or mesh
workflow = util.workflow;
//If using Unity Terrains
terrains = util.terrains;
//Avoid unused variable warning
material = null;
//Summed size
targetSize = util.size;
//First terrain makes up the corner
targetOriginPosition = util.originPosition;
//Center of terrain(s)
targetCenterPosition = util.centerPostion;
//Terrain UV
terrainScaleOffset = util.terrainScaleOffset;
SetPigmentMap();
}
//Set the pigmentmap texture on all shaders that utilize it
public void SetPigmentMap()
{
if (pigmentMap)
{
Shader.SetGlobalVector("_TerrainUV", new Vector4(targetSize.x, targetSize.z, Mathf.Abs(targetOriginPosition.x - 1), Mathf.Abs(targetOriginPosition.z - 1)));
//Set this at runtime to account for different instances having different pigment maps
Shader.SetGlobalTexture("_PigmentMap", pigmentMap);
}
}
public static int IndexToResolution(int i)
{
int res = 0;
switch (i)
{
case 0:
res = 64; break;
case 1:
res = 128; break;
case 2:
res = 256; break;
case 3:
res = 512; break;
case 4:
res = 1024; break;
case 5:
res = 2048; break;
case 6:
res = 4096; break;
}
return res;
}
//Editor functions
#if UNITY_EDITOR
//Primary function
public void Generate()
{
if (terrainObjects.Length == 0) return;
if (!manualInput)
{
GetTargetInfo();
}
else
{
workflow = (terrainObjects[0].GetComponent<Terrain>()) ? Workflow.Terrain : Workflow.Mesh;
}
//If a custom map is assigned, don't generate one, only assign
if (useCustomPigmentMap)
{
pigmentMap = customPigmentMap;
SetPigmentMap();
return;
}
LightSetup();
CameraSetup();
MoveTerrains();
RenderToTexture();
SetPigmentMap();
if (performCleanup) Cleanup();
ResetLights();
}
//Position a camera above the terrain(s) so that the world positions line up perfectly with the texture UV
public void CameraSetup()
{
//Create camera
if (!renderCam)
{
GameObject cameraObj = new GameObject();
cameraObj.name = this.name + " renderCam";
renderCam = cameraObj.AddComponent<Camera>();
}
//Set up a square camera rect
float rectWidth = resolution;
rectWidth /= Screen.width;
renderCam.rect = new Rect(0, 0, 1, 1);
//Camera set up
renderCam.orthographic = true;
renderCam.orthographicSize = (targetSize.x / 2);
renderCam.clearFlags = CameraClearFlags.Skybox;
renderCam.allowHDR = true;
renderCam.farClipPlane = 5000f;
renderCam.useOcclusionCulling = false;
renderCam.cullingMask = layerMask;
//Rendering in Forward mode is a tad darker, so a Directional Light is used to make up for the difference
renderCam.renderingPath = (useAlternativeRenderer || workflow == TerrainUVUtil.Workflow.Mesh) ? RenderingPath.Forward : RenderingPath.VertexLit;
if (workflow == Workflow.Terrain)
{
//Hide tree objects
foreach (Terrain terrain in terrains)
{
terrain.drawTreesAndFoliage = false;
}
}
//Position cam in given center of terrain(s)
renderCam.transform.position = new Vector3(
targetCenterPosition.x,
targetOriginPosition.y + targetSize.y + HEIGHTOFFSET + CLIP_PADDING,
targetCenterPosition.z
);
renderCam.transform.localEulerAngles = new Vector3(90, 0, 0);
}
private void MoveTerrains()
{
//Store terrain position value, to revert to
//Safe to assume all terrains have the same Y-position, should be the case for multi-terrains
originalTargetYPos = targetOriginPosition.y;
//Move terrain objects way up so they are rendered on top of all other objects
foreach (GameObject terrain in terrainObjects)
{
terrain.transform.position = new Vector3(terrain.transform.position.x, HEIGHTOFFSET, terrain.transform.position.z);
}
}
private void RenderToTexture()
{
if (!renderCam) return;
pigmentMap = null;
//If this is a terrain with no textures, abort (except in the case of MegaSplat)
if (workflow == Workflow.Terrain)
{
#if UNITY_2018_3_OR_NEWER
if (terrains[0].terrainData.terrainLayers.Length == 0 && !isMegaSplat) return;
#else
if (terrains[0].terrainData.splatPrototypes.Length == 0 && !isMegaSplat) return;
#endif
}
resolution = IndexToResolution(resIdx);
//Set up render texture
RenderTexture rt = new RenderTexture(resolution, resolution, 0);
renderCam.targetTexture = rt;
savePath = GetTargetFolder();
EditorUtility.DisplayProgressBar("PigmentMapGenerator", "Rendering texture", 1);
//Render camera into a texture
Texture2D render = new Texture2D(rt.width, rt.height, TextureFormat.ARGB32, false);
RenderTexture.active = rt;
renderCam.Render();
//Compose texture on GPU
rt = CompositePigmentMap(rt, inputHeightmap);
render.ReadPixels(new Rect(0, 0, resolution, resolution), 0, 0);
//Cleanup
renderCam.targetTexture = null;
RenderTexture.active = null;
DestroyImmediate(rt);
//Encode
byte[] bytes = render.EncodeToPNG();
//Create file
EditorUtility.DisplayProgressBar("PigmentMapGenerator", "Saving texture...", 1);
File.WriteAllBytes(savePath, bytes);
//Import file
AssetDatabase.Refresh();
//Load the file
pigmentMap = new Texture2D(resolution, resolution, TextureFormat.ARGB32, true);
pigmentMap = AssetDatabase.LoadAssetAtPath(savePath, typeof(Texture2D)) as Texture2D;
EditorUtility.ClearProgressBar();
}
//Add the heightmap and do texture transformations
private RenderTexture CompositePigmentMap(RenderTexture inputMap, Texture2D heightmap = null)
{
Material compositeMat = new Material(Shader.Find("Hidden/PigmentMapComposite"));
compositeMat.hideFlags = HideFlags.DontSave;
compositeMat.SetTexture("_MainTex", inputMap);
//No given heightmap, get from terrain splatmap
//If a channel is chosen, add heightmap to the pigment map's alpha channel
if (heightmap == null && workflow == Workflow.Terrain && (int)heightmapChannel > 0)
{
//Sample one of the two splatmaps (supporting 8 textures as input)
int spatmapIndex = ((int)heightmapChannel >= 5) ? 1 : 0;
int channelIndex = (spatmapIndex > 0) ? (int)heightmapChannel - 4 : (int)heightmapChannel;
Texture2D splatmap = terrains[0].terrainData.alphamapTextures[spatmapIndex];
compositeMat.SetTexture("_SplatMap", splatmap);
compositeMat.SetVector("_SplatMask", new Vector4(
channelIndex == 1 ? 1 : 0,
channelIndex == 2 ? 1 : 0,
channelIndex == 3 ? 1 : 0,
channelIndex == 4 ? 1 : 0)
);
}
if (workflow == Workflow.Mesh)
{
//Transforms
Vector4 transform = new Vector4(0, 0, 0, 0);
if (flipHortizontally) transform.x = 1;
if (flipVertically) transform.y = 1;
transform.z = -(int)textureRotation * (Mathf.PI / 2f);
compositeMat.SetVector("_Transform", transform);
}
if (heightmap != null && isMultiTerrain) //Custom heightmap only for multi-terrains
{
compositeMat.SetTexture("_SplatMap", heightmap);
//Given heightmap is already a grayscale map, unmask all color channels
compositeMat.SetVector("_SplatMask", new Vector4(1, 0, 0, 0));
}
//Render shader output
RenderTexture rt = new RenderTexture(inputMap.width, inputMap.height, 0);
RenderTexture.active = rt;
Graphics.Blit(inputMap, rt, compositeMat);
DestroyImmediate(compositeMat);
//inputMap.ReadPixels(new Rect(0, 0, inputMap.width, inputMap.height), 0, 0);
//inputMap.Apply();
//RenderTexture.active = null;
return rt;
}
//Store pigment map next to TerrainData asset, or mesh's material
private string GetTargetFolder()
{
string m_targetPath = null;
//Compose target file path
//For single terrain
if (terrainObjects.Length == 1)
{
if (workflow == TerrainUVUtil.Workflow.Terrain)
{
//If there is a TerraData asset, use its file location
if (terrains[0].terrainData.name != string.Empty)
{
hasTerrainData = true;
m_targetPath = AssetDatabase.GetAssetPath(terrains[0].terrainData) + string.Format("{0}_pigmentmap.png", terrains[0].terrainData.name);
m_targetPath = m_targetPath.Replace(terrains[0].terrainData.name + ".asset", string.Empty);
}
//If there is no TerrainData, store it next to the scene. Some terrain systems don't use TerrainData
else
{
hasTerrainData = false;
string scenePath = EditorSceneManager.GetActiveScene().path.Replace(".unity", string.Empty);
m_targetPath = scenePath + "_pigmentmap.png";
}
}
//If the target is a mesh, use the location of its material
else if (workflow == TerrainUVUtil.Workflow.Mesh)
{
material = terrainObjects[0].GetComponent<MeshRenderer>().sharedMaterial;
m_targetPath = AssetDatabase.GetAssetPath(material) + string.Format("{0}_pigmentmap.png", string.Empty);
m_targetPath = m_targetPath.Replace(".mat", string.Empty);
}
}
//For multi-terrain, use scene folder or material
else
{
if (workflow == TerrainUVUtil.Workflow.Mesh)
{
material = terrainObjects[0].GetComponent<MeshRenderer>().sharedMaterial;
m_targetPath = AssetDatabase.GetAssetPath(material) + string.Format("{0}_pigmentmap.png", string.Empty);
m_targetPath = m_targetPath.Replace(".mat", string.Empty);
}
else
{
string scenePath = EditorSceneManager.GetActiveScene().path.Replace(".unity", string.Empty);
m_targetPath = scenePath + "_pigmentmap.png";
}
}
return m_targetPath;
}
void Cleanup()
{
DestroyImmediate(renderCam.gameObject);
if (renderLight) DestroyImmediate(renderLight.gameObject);
//Reset terrains
foreach (GameObject terrain in terrainObjects)
{
//Reset terrain position(s)
terrain.transform.position = new Vector3(terrain.transform.position.x, originalTargetYPos, terrain.transform.position.z);
}
//Reset draw foliage
if (workflow == TerrainUVUtil.Workflow.Terrain)
{
foreach (Terrain terrain in terrains)
{
terrain.drawTreesAndFoliage = true;
}
}
renderCam = null;
renderLight = null;
}
//Disable directional light and set ambient color to white for an albedo result
void LightSetup()
{
//Set up lighting for a proper albedo color
lights = FindObjectsOfType<Light>();
foreach (Light light in lights)
{
if (light.type == LightType.Directional)
light.gameObject.SetActive(false);
}
//Store current settings to revert to
ambientMode = RenderSettings.ambientMode;
ambientColor = RenderSettings.ambientLight;
enableFog = RenderSettings.fog;
skyboxMat = RenderSettings.skybox;
//Flat lighting
RenderSettings.ambientMode = UnityEngine.Rendering.AmbientMode.Flat;
RenderSettings.ambientLight = Color.white;
RenderSettings.fog = false;
RenderSettings.skybox = null;
//To account for Forward rendering being slightly darker, add a light
if (useAlternativeRenderer)
{
if (!renderLight) renderLight = new GameObject().AddComponent<Light>();
renderLight.name = "renderLight";
renderLight.type = LightType.Directional;
renderLight.transform.localEulerAngles = new Vector3(90, 0, 0);
renderLight.intensity = renderLightBrightness;
}
}
//Re-enable directional light and reset ambient mode
void ResetLights()
{
foreach (Light light in lights)
{
if (light.type == LightType.Directional)
light.gameObject.SetActive(true);
}
RenderSettings.ambientMode = ambientMode;
RenderSettings.ambientLight = ambientColor;
RenderSettings.fog = enableFog;
RenderSettings.skybox = skyboxMat;
}
#endif
}
}