[C#]Terrain Split et Optimisation

Cette section est destinée aux scripts partagés par la communauté. Chaque post est destiné à un script. Suivez bien les recommandations.
brunom99
Messages : 200
Inscription : 02 Mai 2015 15:41

[C#]Terrain Split et Optimisation

Message par brunom99 » 03 Fév 2016 13:17

Bonjour à tous

Suite à un precedent sujet et grace au code de Kostiantyn Dvornik trouvé sur :
http://kostiantyn-dvornik.blogspot.fr/2 ... cript.html

J'ai modifié le code pour permettre la decoupe de 4 à 256 sous terrains (4 - 16 - 64 - 256 au choix).

Creer un fichier SplitTerrain.cs dans le dossier Editor :

Code : Tout sélectionner

/// <summary>
/// Dvornik
/// </summary>
using UnityEngine;
using UnityEditor;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Collections.Generic;

/// <summary>
/// Split terrain.
/// </summary>
/// UPDATE 02/2016 MARTIOL Bruno
public class SplitTerrain : EditorWindow {
	
	int repeatProcess = 0;                          //!\\ IF repeatProcess = 0 : 4 terrains, if repeatProcess = 1 : 16 terrains, ....
	string destFolder = "split_terrain";			// Folder in Assets/
	string tagTerrain = "";
	bool deleteGroupIfExists = false;
	
	List<Terrain> terrainsToDelete;	
	GameObject emptyParent;
	int totalTerrain;

	// Add submenu
	[MenuItem("Dvornik/Split Terrain")]
	static void Init()
	{
		
		// Get existing open window or if none, make a new one:
		SplitTerrain window = (SplitTerrain)EditorWindow.GetWindow(typeof(SplitTerrain));
		
		window.minSize =  new Vector2( 300f, 200f );		
		window.maxSize =  new Vector2( 300f, 200f );		
		
		window.autoRepaintOnSceneChange = true;
		window.title = "Split terrain";
		window.Show();
		
		
	}

	void OnGUI()
	{
		GUILayout.Label ("Settings", EditorStyles.boldLabel);
		destFolder = EditorGUILayout.TextField("Group name :", destFolder);
		deleteGroupIfExists = EditorGUILayout.Toggle ("Delete group if exists :", deleteGroupIfExists);
		tagTerrain = EditorGUILayout.TextField("Tag name :", tagTerrain);
		repeatProcess = EditorGUILayout.IntField("Repeat operation (0 - 3) :", repeatProcess);
		GUILayout.Label (" /!\\ WARNING");
		GUILayout.Label (" Repeat operation = 0 - created 4 terrains ");
		GUILayout.Label (" Repeat operation = 1 - created 16 terrains ");
		GUILayout.Label (" Repeat operation = 2 - created 64 terrains ");
		GUILayout.Label (" Repeat operation = 3 - created 256 terrains ");
		
		if(GUILayout.Button("Split terrain"))
		{
			// repeatProcess too big ?
			if(repeatProcess > 3) {
				Debug.Log("Repeat operation too big !");
				return;
			}
			// Folder
			if(destFolder == null || "".Equals(destFolder)) {
				Debug.Log("Group name missing !");
				return;
			}
			// Delete Group ?
			GameObject goToDelete = null;
			if(deleteGroupIfExists) {
				// Delete gameobject
				goToDelete = GameObject.Find(destFolder);
				// Delete Asset
				if(AssetDatabase.IsValidFolder("Assets/" + destFolder)) {
					string[] files = Directory.GetFiles("Assets/" + destFolder + "/");
					foreach(string file in files) {
						AssetDatabase.DeleteAsset(file);
					}
				}
			}
			// Empty parent
			emptyParent = new GameObject();
			emptyParent.name = destFolder;
			// Progress
			totalTerrain = (repeatProcess + 1) * 4;
			// 1 terrain -> 4 terrains
			List<Terrain> terrains = SplitIt(null);
			// Terrains to delete
			terrainsToDelete = new List<Terrain>();
			// if repeatProcess = 1, 4 terrains -> 16 terrains
			splitTerrains(terrains, repeatProcess);
			// Delete terrains not finals
			foreach (Terrain terrain in terrainsToDelete) {
				AssetDatabase.DeleteAsset("Assets/" + destFolder + "/" + terrain.name + ".asset");
				DestroyImmediate(terrain.gameObject);
			}
			if(goToDelete != null) {
				DestroyImmediate(goToDelete);
			}
		}
		
		
	}
	
	void splitTerrains(List<Terrain> terrains, int lvl) {
		if (lvl <= 0 || terrains == null) {
			return;
		}        
		lvl--;
		foreach (Terrain terrain in terrains) {
			List<Terrain> sub_terrains = SplitIt(terrain);
			splitTerrains(sub_terrains, lvl);
			if(sub_terrains != null && sub_terrains.Count > 0) {
				terrainsToDelete.Add(terrain);
			}
		}
	}

	/// <summary>
	/// Determines whether this instance is power of two the specified x.
	/// </summary>
	/// <returns>
	/// <c>true</c> if this instance is power of two the specified x; otherwise, <c>false</c>.
	/// </returns>
	/// <param name='x'>
	/// If set to <c>true</c> x.
	/// </param>
	bool IsPowerOfTwo(int x)
	{
		return (x & (x - 1)) == 0;
	}
	
	List<Terrain> SplitIt(Terrain terrain)
	{
		List<Terrain> listeTerrain = new List<Terrain>();
		List<TerrainData> terrainData = new List<TerrainData>();
		List<GameObject> terrainGo = new List<GameObject>();	
		Terrain parentTerrain;	
		
		if (terrain == null && Selection.activeGameObject == null )
		{
			Debug.Log("No terrain was selected");
			return null;
		}
		
		if (terrain == null) {
			parentTerrain = Selection.activeGameObject.GetComponent(typeof(Terrain)) as Terrain;
		} else {
			parentTerrain = terrain;
		}
		
		if ( parentTerrain == null )
		{
			Debug.Log("Current selection is not a terrain");
			return null;
		}

		// Folder terrains_split
		if(!AssetDatabase.IsValidFolder("Assets/" + destFolder)) {
			AssetDatabase.CreateFolder("Assets", destFolder);
		}
		
		//Split terrain 
		for ( int i=0; i< 4; i++)
		{										
			
			EditorUtility.DisplayProgressBar("Split terrain","Process " + i, (float) i / 4 );
			
			TerrainData td = new TerrainData();
			GameObject tgo = Terrain.CreateTerrainGameObject( td );
			
			tgo.name = parentTerrain.name + "_" + i;
            		tgo.transform.SetParent(emptyParent.transform);
            		if (tagTerrain != null && tagTerrain != "") {
                		tgo.tag = tagTerrain;
            		}

			terrainData.Add( td );
			terrainGo.Add ( tgo );
			
			Terrain genTer = tgo.GetComponent(typeof(Terrain)) as Terrain;								
			genTer.terrainData = td;

			AssetDatabase.CreateAsset(td, "Assets/" + destFolder + "/" + genTer.name+ ".asset");
			
			
			// Assign splatmaps
			genTer.terrainData.splatPrototypes = parentTerrain.terrainData.splatPrototypes;
			
			// Assign detail prototypes
			genTer.terrainData.detailPrototypes = parentTerrain.terrainData.detailPrototypes;
			
			// Assign tree information
			genTer.terrainData.treePrototypes = parentTerrain.terrainData.treePrototypes;
			
			
			// Copy parent terrain propeties
			#region parent properties
			genTer.basemapDistance = parentTerrain.basemapDistance;			
			genTer.castShadows = parentTerrain.castShadows;
			genTer.detailObjectDensity = parentTerrain.detailObjectDensity;
			genTer.detailObjectDistance = parentTerrain.detailObjectDistance;
			genTer.heightmapMaximumLOD = parentTerrain.heightmapMaximumLOD;
			genTer.heightmapPixelError = parentTerrain.heightmapPixelError;
			genTer.treeBillboardDistance = parentTerrain.treeBillboardDistance;
			genTer.treeCrossFadeLength = parentTerrain.treeCrossFadeLength;
			genTer.treeDistance = parentTerrain.treeDistance;
			genTer.treeMaximumFullLODCount = parentTerrain.treeMaximumFullLODCount;
			
			#endregion
			
			//Start processing it			
			
			// Translate peace to position
			#region translate peace to right position 
			
			Vector3 parentPosition = parentTerrain.GetPosition();
			
			int terraPeaces = (int) Mathf.Sqrt( 4 );
			
			float spaceShiftX = parentTerrain.terrainData.size.z / terraPeaces;
			float spaceShiftY = parentTerrain.terrainData.size.x / terraPeaces;
			
			float xWShift = (i % terraPeaces ) * spaceShiftX;
			float zWShift = ( i / terraPeaces ) * spaceShiftY;
			
			tgo.transform.position = new Vector3( tgo.transform.position.x + zWShift,
			                                     tgo.transform.position.y,
			                                     tgo.transform.position.z + xWShift ); 	
			
			// Shift last position
			tgo.transform.position = new Vector3( tgo.transform.position.x + parentPosition.x,
			                                     tgo.transform.position.y + parentPosition.y,
			                                     tgo.transform.position.z + parentPosition.z
			                                     );
			
			
			
			#endregion 
			
			// Split height
			#region split height
			
			// Debug.Log ( "Split height" );
			
			//Copy heightmap											
			td.heightmapResolution = parentTerrain.terrainData.heightmapResolution /  terraPeaces;							
			
			//Keep y same
			td.size = new Vector3( parentTerrain.terrainData.size.x / terraPeaces,
			                      parentTerrain.terrainData.size.y,
			                      parentTerrain.terrainData.size.z / terraPeaces 
			                      );
			
			float[,] parentHeight = parentTerrain.terrainData.GetHeights(0,0, parentTerrain.terrainData.heightmapResolution, parentTerrain.terrainData.heightmapResolution );
			
			float[,] peaceHeight = new float[ parentTerrain.terrainData.heightmapResolution / terraPeaces + 1,
			                                 parentTerrain.terrainData.heightmapResolution / terraPeaces + 1
			                                 ];
			
			// Shift calc
			int heightShift = parentTerrain.terrainData.heightmapResolution / terraPeaces;								
			
			int startX = 0;
			int startY = 0;
			
			int endX = 0;
			int endY = 0;
			
			if ( i==0 )
			{
				startX = startY = 0;				
				endX = endY = parentTerrain.terrainData.heightmapResolution / terraPeaces + 1;
			}
			
			if ( i==1 )
			{
				startX = startY = 0;				
				endX = parentTerrain.terrainData.heightmapResolution / terraPeaces + 1;
				endY = parentTerrain.terrainData.heightmapResolution / terraPeaces + 1;
			}
			
			if ( i==2 )
			{
				startX = startY = 0;				
				endX = parentTerrain.terrainData.heightmapResolution / terraPeaces + 1;
				endY = parentTerrain.terrainData.heightmapResolution / terraPeaces + 1;
			}
			
			if ( i==3 )
			{
				startX = startY = 0;				
				endX = parentTerrain.terrainData.heightmapResolution / terraPeaces + 1;
				endY = parentTerrain.terrainData.heightmapResolution / terraPeaces + 1;
			}
			
			// iterate
			for ( int x=startX;x< endX;x++)
			{	
				
				EditorUtility.DisplayProgressBar("Split terrain","Split height", (float) x / ( endX - startX ));  
				
				for ( int y=startY;y< endY;y++)
				{
					
					int xShift=0; 
					int yShift=0;
					
					//
					if ( i==0 )
					{
						xShift = 0;
						yShift = 0;						
					}
					
					//
					if ( i==1 )
					{						
						xShift = heightShift;
						yShift = 0;						
					}
					
					//
					if ( i==2 )
					{
						xShift = 0;
						yShift = heightShift;	
					}
					
					if ( i==3 )
					{
						xShift = heightShift;
						yShift = heightShift;	
					}
					
					float ph = parentHeight[ x + xShift,y + yShift];	
					
					peaceHeight[x ,y ] = ph;
					
				}
				
			}
			
			EditorUtility.ClearProgressBar();
			
			// Set heightmap to child
			genTer.terrainData.SetHeights( 0,0, peaceHeight );
			#endregion
			
			// Split splat map
			#region split splat map	
			
			td.alphamapResolution = parentTerrain.terrainData.alphamapResolution /  terraPeaces;													
			
			float[,,] parentSplat = parentTerrain.terrainData.GetAlphamaps(0,0, parentTerrain.terrainData.alphamapResolution, parentTerrain.terrainData.alphamapResolution );			
			
			float[,,] peaceSplat = new float[ parentTerrain.terrainData.alphamapResolution / terraPeaces ,
			                                 parentTerrain.terrainData.alphamapResolution / terraPeaces,
			                                 parentTerrain.terrainData.alphamapLayers
			                                 ];
			
			// Shift calc
			int splatShift = parentTerrain.terrainData.alphamapResolution / terraPeaces;								
			
			if ( i==0 )
			{
				startX = startY = 0;				
				endX = endY = parentTerrain.terrainData.alphamapResolution / terraPeaces;
			}
			
			if ( i==1 )
			{
				startX = startY = 0;				
				endX = parentTerrain.terrainData.alphamapResolution / terraPeaces;
				endY = parentTerrain.terrainData.alphamapResolution / terraPeaces;
			}
			
			if ( i==2 )
			{
				startX = startY = 0;				
				endX = parentTerrain.terrainData.alphamapResolution / terraPeaces;
				endY = parentTerrain.terrainData.alphamapResolution / terraPeaces;
			}
			
			if ( i==3 )
			{
				startX = startY = 0;				
				endX = parentTerrain.terrainData.alphamapResolution / terraPeaces;
				endY = parentTerrain.terrainData.alphamapResolution / terraPeaces;
			}
			
			// iterate
			for ( int s=0;s<parentTerrain.terrainData.alphamapLayers;s++)
			{				
				for ( int x=startX;x< endX;x++)
				{	
					
					EditorUtility.DisplayProgressBar("Split terrain","Split splat", (float) x / ( endX - startX ));  
					
					for ( int y=startY;y< endY;y++)
					{
						
						int xShift=0; 
						int yShift=0;
						
						//
						if ( i==0 )
						{
							xShift = 0;
							yShift = 0;						
						}
						
						//
						if ( i==1 )
						{						
							xShift = splatShift;
							yShift = 0;						
						}
						
						//
						if ( i==2 )
						{
							xShift = 0;
							yShift = splatShift;	
						}
						
						if ( i==3 )
						{
							xShift = splatShift;
							yShift = splatShift;	
						}
						
						float ph = parentSplat[x + xShift,y + yShift, s];	
						peaceSplat[x ,y, s] = ph;
						
					}
					
					
				}			
			}
			
			EditorUtility.ClearProgressBar();
			
			// Set heightmap to child
			genTer.terrainData.SetAlphamaps( 0,0, peaceSplat );
			#endregion
			
			// Split detail map
			#region split detail map	
			
			td.SetDetailResolution( parentTerrain.terrainData.detailResolution / terraPeaces, 8 );													
			
			for ( int detLay=0; detLay< parentTerrain.terrainData.detailPrototypes.Length; detLay++)
			{								
				int[,] parentDetail = parentTerrain.terrainData.GetDetailLayer(0,0, parentTerrain.terrainData.detailResolution, parentTerrain.terrainData.detailResolution, detLay );			
				
				int[,] peaceDetail = new int[ parentTerrain.terrainData.detailResolution / terraPeaces,
				                             parentTerrain.terrainData.detailResolution / terraPeaces												  
				                             ];
				
				// Shift calc
				int detailShift = parentTerrain.terrainData.detailResolution / terraPeaces;								
				
				if ( i==0 )
				{
					startX = startY = 0;				
					endX = endY = parentTerrain.terrainData.detailResolution / terraPeaces;
				}
				
				if ( i==1 )
				{
					startX = startY = 0;				
					endX = parentTerrain.terrainData.detailResolution / terraPeaces;
					endY = parentTerrain.terrainData.detailResolution / terraPeaces;
				}
				
				if ( i==2 )
				{
					startX = startY = 0;				
					endX = parentTerrain.terrainData.detailResolution / terraPeaces;
					endY = parentTerrain.terrainData.detailResolution / terraPeaces;
				}
				
				if ( i==3 )
				{
					startX = startY = 0;				
					endX = parentTerrain.terrainData.detailResolution / terraPeaces;
					endY = parentTerrain.terrainData.detailResolution / terraPeaces;
				}
				
				// iterate				
				for ( int x=startX;x< endX;x++)
				{		
					
					EditorUtility.DisplayProgressBar("Split terrain","Split detail", (float) x / (endX - startX ));
					
					for ( int y=startY;y< endY;y++)
					{
						
						int xShift=0; 
						int yShift=0;
						
						//
						if ( i==0 )
						{
							xShift = 0;
							yShift = 0;						
						}
						
						//
						if ( i==1 )
						{						
							xShift = detailShift;
							yShift = 0;						
						}
						
						//
						if ( i==2 )
						{
							xShift = 0;
							yShift = detailShift;	
						}
						
						if ( i==3 )
						{
							xShift = detailShift;
							yShift = detailShift;	
						}
						
						int ph = parentDetail[x + xShift,y + yShift];	
						peaceDetail[x ,y] = ph;
						
					}										
					
				}				
				EditorUtility.ClearProgressBar();
				
				// Set heightmap to child
				genTer.terrainData.SetDetailLayer( 0,0, detLay, peaceDetail );
				
			}
			#endregion
			
			// Split tree data
			#region  split tree data
			
			for( int t=0; t< parentTerrain.terrainData.treeInstances.Length;t++)
			{
				
				EditorUtility.DisplayProgressBar("Split terrain","Split trees "  , (float) t / parentTerrain.terrainData.treeInstances.Length );					
				
				// Get tree instance					
				TreeInstance ti = parentTerrain.terrainData.treeInstances[t];				
				
				// First section	
				if ( i==0 && 
				    ti.position.x > 0f &&	ti.position.x < 0.5f &&
				    ti.position.z > 0f &&	ti.position.z < 0.5f 
				    )
				{
					// Recalculate new tree position	
					ti.position = new Vector3( ti.position.x * 2f, ti.position.y, ti.position.z * 2f );
					
					// Add tree instance						
					genTer.AddTreeInstance( ti );												
				}
				
				// Second section
				if ( i==1 && 
				    ti.position.x > 0.0f &&ti.position.x < 0.5f &&
				    ti.position.z >= 0.5f &&	ti.position.z <= 1.0f 
				    )
				{
					// Recalculate new tree position	
					ti.position = new Vector3( (ti.position.x ) * 2f, ti.position.y, ( ti.position.z - 0.5f ) * 2f );
					
					// Add tree instance						
					genTer.AddTreeInstance( ti );												
				}
				
				// Third section
				if ( i==2 && 
				    ti.position.x >= 0.5f && ti.position.x <= 1.0f &&
				    ti.position.z > 0.0f && ti.position.z < 0.5f 
				    )
				{
					// Recalculate new tree position	
					ti.position = new Vector3( (ti.position.x - 0.5f ) * 2f, ti.position.y, ( ti.position.z ) * 2f );
					
					// Add tree instance						
					genTer.AddTreeInstance( ti );												
				}
				
				// Fourth section
				if ( i==3 && 
				    ti.position.x >= 0.5f && ti.position.x <= 1.0f &&
				    ti.position.z >= 0.5f && ti.position.z <= 1.0f 
				    )
				{
					// Recalculate new tree position	
					ti.position = new Vector3( (ti.position.x - 0.5f ) * 2f, ti.position.y, ( ti.position.z - 0.5f ) * 2f );
					
					// Add tree instance						
					genTer.AddTreeInstance( ti );												
				}
				
				
			}
			#endregion
			
			listeTerrain.Add(genTer);
			
			AssetDatabase.SaveAssets();
			
			
			
		}
		
		EditorUtility.ClearProgressBar();
		
		return listeTerrain;
		
	}
	

	
	
}
Image

Donc en fait, je n'ai quasiment pas touché a sa routine principale de division en 4, ce que je fais c'est que je la rappelle x fois pour chaque sous terrain cree, en supprimant a la fin les terrains intermediaires qui ne servent plus.
Je ne supprime biensur pas le terrain original.

J'en ai profité aussi pour regrouper les sous terrains crees dans un gameobject vide et je place aussi les assets SplatAlpha dans un folder.

Du coup, j'ai mis a jour la fenetre GUI parametrable, permettant ainsi de choisir le ratio de decoupe :
- 0 : 4 terrains
- 1 : 16 terrains
- 2 : 64 terrains
- 3 : 256 terrains
et le nom du groupe permettant de creer un dossier dans l'asset et un objet vide.

Voila :)

MAJ : Possibilite d'attribuer un tag a chaque terrain crée et possibilite d'effacer l'ancien groupe si on fait souvent cette operation.

brunom99
Messages : 200
Inscription : 02 Mai 2015 15:41

Terrain - Optimisation - Projet

Message par brunom99 » 05 Fév 2016 22:13

a supprimer
Dernière édition par brunom99 le 12 Fév 2016 12:43, édité 1 fois.

Avatar de l’utilisateur
Silverglade
Messages : 264
Inscription : 04 Août 2012 17:52

Re: Terrain - Optimisation - Projet

Message par Silverglade » 06 Fév 2016 00:42

Je ne suis pas assez calé pour aider mais suivant tes posts depuis le début c'est vraiment cool de lancer ça :)

Avatar de l’utilisateur
Freelax
Messages : 1595
Inscription : 30 Déc 2009 23:02
Localisation : Niort
Contact :

Re: Terrain - Optimisation - Projet

Message par Freelax » 06 Fév 2016 03:19

Salut,

Ouvrir X post sur le sujet va vite devenir lassant perso.

Tu aura beau tout faire pour trouver une solution aux soucis de perf du terrain unity, le temps que tu y passe t'aurais déjà pu reproduire la même chose en custom meshs, textures et tout le florilège de liberté que tu as si tu crée tes propres modèles. C'est minime pour un terrain de le faire à la mano, des zones, tes textures etc, la tout géré avec un système de lod rapide, au moins pour des tests de base, je vois vraiment pas pourquoi tu t'acharne.

Ta pas des masses de solutions, soit tu continu sur la lancé de perdre du temps a vouloir opti un truc complément deprecated, pour voir dans quelques mois le nouveau systeme d'unity pour les terrains et avoir cette fois vraiment perdu son temps car tu devra tout refaire. Soit stopper mnt et avancer sur d'autres aspects de ton projet le temps de voir sortir ce que UT nous prépare.

Des fois faut faire des choix cruciaux pour pas perdre trop de temps pour rien ;) C'est que mon avis mais "Ras la barbe des terrain unity :? "

Tu as trouvé un script, décortique le ;) Meme si a vue d’œil c'est pas très clair leur truc.
Image

brunom99
Messages : 200
Inscription : 02 Mai 2015 15:41

Re: [C#]Terrain Split et Optimisation

Message par brunom99 » 06 Fév 2016 12:52

oui bon d'accord je lance plus de sujet la dessus :)

seulement le point 3 de mon sujet ci dessus m'interresse bcp..

Répondre

Revenir vers « Scripts »