- Home /
Multithread x Terrain Loading
Hello.
I've made a paged terrain system here loading terrain heightmaps/splatmaps from TCP/IP (directly with the Sockets class). All is working fine, and my adjacent terrains get loaded and displayed as soon as I get near the center terrain border, so I can keep walking and I won't reach the end of the center terrain before the adjacent one is loaded.
Although it is fine, one thing still annoys me... While the data streaming in another thread (using backgroundworker to load the heightmap and splatmap data and store it, so the main thread can display the terrain (as we have the thread-unsafe unity methods which makes life harder)) is loading correctly and I can move around the map while the data comes in, when the main thread takes over the job of creating the terrain object, my "game" is getting hung until the terrain is indeed created and populated with the streamed data.
I though, then, if I should start using StartCoroutine to do so, and I tried it, without success. It does start and it does continue the flow of my Update method, but still I get a hang while creating the terrain (create object, add heightmap info, add splatmap info, attach the textures that will be used by the splatmap). How can I achieve a seamless terrain creation, without having my app freezing in the meanwhile?
Code where the StartCoroutine is called (in Update loop): (It is compared by 4 as 4 is my state in my 3x3 grid of terrains that tells the loop that this terrain is not visible yet, but background worker already loaded data for it)
if (Global.terrainloading[x, y] == 4) { GameObject obj = GameObject.Find("TerrainContainer"+y.ToString()+x.ToString()); StartCoroutine(Global.AssignTerrain(Global.terrainx + (-1 + x), Global.terrainy + (-1 + y), x, y, obj));
}
And this is the AssignTerrain method:
private static IEnumerator AssignTerrain(int x, int y, int tx, int ty, GameObject obj) { Debug.Log("Started terrain loading"); obj.AddComponent(typeof(Terrain)); TerrainData terrain = new TerrainData(); Vector3 sz = new Vector3(100, 1000, 100); terrain.size = sz; int w2 = 1025; terrain.heightmapResolution = w2;
float[,] heightmapData = (float[,])heightmaploading[tx,ty];
float[, ,] SplatmapData = (float[,,])splatmaploading[tx, ty];
terrain.SetHeights(0, 0, heightmapData);
// Set up SplatPrototypes with the correct terrain textures (for now 3, 0=grass,1=rock,2=sand)
SplatPrototype[] splatprotos = new SplatPrototype[3];
splatprotos[0] = new SplatPrototype();
splatprotos[0].texture = (Texture2D)Resources.Load("grass", typeof(Texture2D));
splatprotos[0].tileOffset = new Vector2(0, 0);
splatprotos[0].tileSize = new Vector2(15, 15);
splatprotos[1] = new SplatPrototype();
splatprotos[1].texture = (Texture2D)Resources.Load("rock", typeof(Texture2D));
splatprotos[1].tileOffset = new Vector2(0, 0);
splatprotos[1].tileSize = new Vector2(15, 15);
splatprotos[2] = new SplatPrototype();
splatprotos[2].texture = (Texture2D)Resources.Load("seasand", typeof(Texture2D));
splatprotos[2].tileOffset = new Vector2(0, 0);
splatprotos[2].tileSize = new Vector2(15, 15);
terrain.splatPrototypes = splatprotos;
terrain.alphamapResolution = 1025;
terrain.baseMapResolution = 1025;
((TerrainCollider)obj.AddComponent(typeof(TerrainCollider))).terrainData = terrain;
((Terrain)obj.GetComponent(typeof(Terrain))).terrainData = terrain;
((Terrain)obj.GetComponent(typeof(Terrain))).terrainData.SetAlphamaps(0, 0, SplatmapData);
((Terrain)obj.GetComponent(typeof(Terrain))).heightmapPixelError = 10;
Global.terrainloading[tx, ty] = 2;
yield return null;
}
I am having the same problem too. I didn't use sockets but ins$$anonymous$$d loaded the data with the WWW class and processed all the information with asynchronous methods (using BeginInvoke).
Here are the times I am getting with 1024 resolution terrain, 7 splatmaps (at 1024), three details (at 512), about 500 trees, and a precomputed lightmap:
Started at 0ms
Terrain created at 1695ms
Resources loaded at 1894ms
Alphamaps set at 2556ms
Detailmaps set at 2582ms
Trees placed at 2593ms
Trees adjusted at 2791ms
Lightmap set at 3101ms
Pretty annoying that it hangs for a full 3 seconds.
Well, after exaustive search in several months, and trying loads of things, and even working with Unity support guys, it seems impossible to get rid of the SetHeights lag. There's no way to call this method from another thread, and obviously, using Send$$anonymous$$essage, StartCoRoutine, using delegates, or any other method that would work asynchronously within the same thread won't solve the problem. Perhaps a C++ plugin could do it, it is the only thing I haven't tried as I don't have Unity Pro. The only thing then is to hope Unity guys will make their classes thread-friendly in the future.
Answer by zharramadar · Feb 08, 2011 at 05:22 PM
Well, just posting to close the question.
I've contacted Unity support and we worked for months in a solution for this, and unfortunately, there is none currently, until Unity becomes more thread-friendly, that is, so the only approach is to try to minimize the effect by loading smaller terrain chunks. A pity indeed.
Wow this is not good We were thinking to use Unity in our company for our projects. But with this limitation maybe we should go for another solution...
Answer by zharramadar · Jul 19, 2011 at 04:06 AM
Well, just for future reference to anyone who enters this post, and to make Unity justice.
I found a way to make this work perfectly, but unfortunately, it requires Unity Pro. Instead of streaming heightmap, splatmap and all the data and bake a terrainData on-the-fly, import the terrain in the editor, edit it as you will, and save it in the asset database. Navigating to your project folder, in the asset folder, you shall find a .asset file for your terrain. Copy it and place in your server. When requesting a terrain, stream this .asset file and in a thread, save it somewhere in the disk with the client, and then use AssetBundle methods to load the .asset file on-the-fly using StartCoRoutine. This will bring the asset to the asset database as it were when you built the client, and then just reference this terrainData to your displayed terrain.
This will avoid having to process several stuff in your main thread, and then you won't have a load when switching terrains.
Just keeping this thread updated, no, after several tests, even that does not stop any lag after all. Seems or Instantiate() becomes multithreaded or no use at all. No hope outside that.
Answer by gilley033 · Jan 08, 2013 at 10:43 PM
Correct me if I'm wrong, but if you save the TerrainData (which is what the .asset file you mention is), and use that to build your terrain, won't you lose certain data about your terrain? To be sure, the TerrainData stores most of the information, but the terrain itself has the terrain setting information; pixel error, detail distance, density, etc.
If you never changed any of those settings then no big deal I guess.
Also, your current method is creating new terrains at run time, right? I don't see how else you'd use the TerrainData. Perhaps that's causing your lag? Woudln't it be better to simply save your terrain into a prefab, save that prefab and the TerrainData into an assetbundle, then when you need the terrain, load the asset bundle and instantiate the terrain prefab? Don't know how that will work, but it's just an idea . . .
Your answer
Follow this Question
Related Questions
Randomizing tile textures 2 Answers
Connecting tiled terrains 2 Answers
How can I make my terrain look less tiled? 1 Answer
Procedural terrain tiles 0 Answers
How to Instantiate into a terrain array? 2 Answers