Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

Time for a rousing game of… FIX MY CODE!

Discussion in 'Scripting' started by sladuuch, Jul 15, 2009.

  1. sladuuch

    sladuuch

    Joined:
    Jan 5, 2007
    Posts:
    51
    I have a script that does exactly what I want, except for one problem: It's god-awfully slow. My script randomly generates a planet, makes it a UV map, generates a heightmap from that UV map, then generates a texture from the heightmap. The results are good and they're what I want, but the whole process takes about 15 minutes to complete. :eek: I'm sure there are a zillion efficiency improvements that could be incorporated into this script, and I made several myself. However, I know that the Unity gurus of the intertubes are far, far more skilled than I so I was hoping for some hints, tips, and suggestions for improvement.

    I know this code is pretty terrible. Like, really bad. Maybe even cringe-worthily bad. But it's my first real project, and like I said, I'm happy with the results, just not the speed.

    Thanks!


    Code (csharp):
    1. import System.IO;
    2.  
    3.  
    4. var numOfTimes = 100;
    5. var waterCoverage = 1;
    6. var textureResolution = 512;
    7.  
    8. private var size = Mathf.Sqrt(textureResolution);
    9. private var worldFlatness = waterCoverage / 1000.0;
    10. private var iteration = 0;
    11. private var texture;
    12. private var heightMap;
    13. private var mesh : Mesh;
    14. private var origin = Vector3(0.0, 0.0, 0.0);
    15. private var uvsHaveBeenCreated = false;
    16. private var heightmapHasBeenCreated = false;
    17. private var textureHasBeenCreated = false;
    18.  
    19.  
    20. function Start()
    21. {
    22.     mesh = GetComponent(MeshFilter).mesh;
    23.    
    24.     texture = new Texture2D (textureResolution, textureResolution);
    25.    
    26.     // create a new all-black heightmap
    27.     heightMap = new Texture2D (textureResolution, textureResolution);
    28.     allBlack = new Color[textureResolution * textureResolution];
    29.     for (var q=0; q < allBlack.length; q++)
    30.     {
    31.         allBlack[q] = Color.black;
    32.     }
    33.     heightMap.SetPixels(allBlack, 0);
    34. }
    35.  
    36.  
    37. function Update () {
    38.     if (iteration < numOfTimes)
    39.     {
    40.         // get a cutting plane
    41.         while(true)
    42.         {
    43.             var cuttingPlaneVector = Vector3(Random.Range(-1.0, 1.1), Random.Range(-1.0, 1.1), Random.Range(-1.0, 1.1));
    44.             if (cuttingPlaneVector.magnitude < 1)
    45.             {
    46.                 break;
    47.             }
    48.             var m = (Random.Range(0, 2) == 0) ? -1 : 1;
    49.         }
    50.        
    51.         var vertices = mesh.vertices;
    52.         var normals = mesh.normals;
    53.         var uvs = new Vector2[vertices.Length];
    54.         var thisVectorDistance = 0.0;
    55.         var dotProduct;
    56.         var temp;
    57.        
    58.         // loop through all the vertices and displace them outward
    59.         // according to the nifty algorithm
    60.         for (var i=0;i<vertices.Length;i++)
    61.         {
    62.             temp = vertices[i] - cuttingPlaneVector;
    63.             dotProduct = Vector3.Dot(cuttingPlaneVector, temp);
    64.             if (dotProduct > 0)
    65.             {
    66.                 // move this vertex out a little bit
    67.                 vertices[i] += (normals[i] * worldFlatness * m);
    68.             }
    69.             else
    70.             {
    71.                 // move this vertex in a little bit
    72.                 vertices[i] -= (normals[i] * worldFlatness * m);
    73.             }
    74.         }
    75.         mesh.vertices = vertices;
    76.         iteration++;
    77.         print(iteration);
    78.     }
    79.    
    80.    
    81.    
    82.    
    83.    
    84.     // the globe is finished now, create the UV map!
    85.     if (iteration >= numOfTimes  uvsHaveBeenCreated == false)
    86.     {
    87.         for (i=0;i<mesh.vertices.Length;i++)
    88.         {
    89.             var unitVector = vertices[i].normalized;
    90.             var uv = Vector2(0.0, 0.0);
    91.             uv.x = (Mathf.Atan2(unitVector.x, unitVector.z) + Mathf.PI) / Mathf.PI / 2.0;
    92.             uv.y = (Mathf.Acos(unitVector.y) + Mathf.PI) / Mathf.PI - 1.0;
    93.             uvs[i] = Vector2(uv.x, uv.y);
    94.         }
    95.         mesh.uv = uvs;
    96.         uvsHaveBeenCreated = true;
    97.     }
    98.    
    99.    
    100.    
    101.     // move onto the heightmap
    102.     if (uvsHaveBeenCreated == true  heightmapHasBeenCreated == false)
    103.     {
    104.         // renderer.material.mainTexture = heightMap;
    105.        
    106.         var uvs1 = mesh.uv;
    107.         var uv_x = 0;
    108.         var uv_y = 0;
    109.        
    110.         for (i=0;i<mesh.vertices.Length;i++)
    111.         {
    112.             thisVectorDistance = Vector3.Distance(origin, mesh.vertices[i]);            
    113.             uv_x = Mathf.RoundToInt(uvs1[i].x * textureResolution);
    114.             uv_y = Mathf.RoundToInt(uvs1[i].y * textureResolution);
    115.    
    116.             // make some radial gradients!
    117.             DrawGrayscaleGradient(Vector2(uv_x, uv_y), thisVectorDistance);
    118.         }
    119.         heightMap.Apply();
    120.        
    121.         // write the heightmap to a file for perusal
    122.         var bytes = heightMap.EncodeToPNG();
    123.         File.WriteAllBytes(Application.dataPath + "/../heightMap.png", bytes);
    124.         heightmapHasBeenCreated = true;
    125.     }
    126.    
    127.    
    128.    
    129.     // now create the actual texture by consulting the heightmap
    130.     if (heightmapHasBeenCreated == true  textureHasBeenCreated == false)
    131.     {
    132.         var tempHeightmapArray = heightMap.GetPixels(0);
    133.         var tempTextureArray = new Color[textureResolution * textureResolution];
    134.         var color = Color.blue;
    135.         var heightmapValue = 0.0;
    136.        
    137.         for (var w=0; w < tempHeightmapArray.length; w++)
    138.         {
    139.             // grab the red component of this pixel
    140.             heightmapValue = tempHeightmapArray[w].r;
    141.            
    142.             // light blue ocean shelf
    143.             if (heightmapValue >= 0)
    144.             {color = Color(0.00, 0.49, 1.00);}
    145.            
    146.             // sandy tan
    147.             if (heightmapValue > 0.01)
    148.             {color = Color(0.95, 1.00, 0.64);}
    149.            
    150.             // dark green
    151.             if (heightmapValue > 0.25)
    152.             {color = Color(0.067, 0.31, 0.0);}
    153.            
    154.             // light green
    155.             if (heightmapValue > 0.5)
    156.             {color = Color(0.38, 0.59, 0.0);}
    157.            
    158.             // gray mountains
    159.             if (heightmapValue > 0.7)
    160.             {color = Color(0.3, 0.3, 0.3);}
    161.            
    162.             // white snow
    163.             if (heightmapValue > 0.85)
    164.             {color = Color.white;}
    165.  
    166.            
    167.             print ("This pixel is " + heightmapValue + " high and will be " + color);
    168.            
    169.             tempTextureArray[w] = color;
    170.         }
    171.         texture.SetPixels(tempTextureArray, 0);
    172.         texture.Apply();
    173.         renderer.material.mainTexture = texture;
    174.        
    175.         bytes = texture.EncodeToPNG();
    176.         File.WriteAllBytes(Application.dataPath + "/../texture.png", bytes);
    177.         textureHasBeenCreated = true;
    178.         mesh.RecalculateBounds();
    179.         print ("Finished!");
    180.     }
    181. }
    182.  
    183.  
    184. function DrawGrayscaleGradient(pixelCoord : Vector2, height: float)
    185. {  
    186.     // calculate height on a 0 - 1 scale
    187.     height = (height - 1) * 20;
    188.     // draw an imaginary box
    189.     var topLeft_x = pixelCoord.x - size;
    190.     var topLeft_y = pixelCoord.y - size;
    191.     var existingColor;
    192.     var thisColorValue;
    193.     var thisColor;
    194.     var thisDistance;
    195.    
    196.     // loop through all the pixels in the gradient's imaginary box
    197.    
    198.     // refactor this to use SetPixels() for better speed
    199.     for (var x = topLeft_x; x < (topLeft_x + (size * 2)); x++)
    200.     {
    201.         for (var y = topLeft_y; y < (topLeft_y + (size * 2)); y++)
    202.         {
    203.             // find out the color at this pixel for blending purposes
    204.             existingColor = heightMap.GetPixel(x, y);
    205.            
    206.             // calculate (and normalize) the value of this pixel
    207.             // by figuring out its distance from the gradient's center
    208.             thisDistance = Vector2.Distance(pixelCoord, Vector2(x, y)) / size;
    209.             // turn the distance into a usable color value
    210.             thisColorValue = ((1 - thisDistance) * height) / 6;
    211.            
    212.             // don't waste time assigning pixels their own colors
    213.             if (thisColorValue > 0)
    214.             {
    215.                 // add the resulting value to the existing value
    216.                 thisColor = Color(thisColorValue, thisColorValue, thisColorValue) + existingColor;
    217.                 heightMap.SetPixel(x, y, thisColor);
    218.             }
    219.         }
    220.     }
    221. }
     
  2. tomvds

    tomvds

    Joined:
    Oct 10, 2008
    Posts:
    1,028
    First of all, from a readability point of view, I'd recommend using Coroutines instead of emulating coroutines in the Update function. This would look something like:
    Code (csharp):
    1.  
    2. function Start()
    3. {
    4.     // start iterating over the sphere vertices and wait for it to finish
    5.     yield StartCoroutine(IterateSphere());
    6.  
    7.     // iterating has finished: generate uvs now
    8.     GenerateUV();
    9.     // uvs are generated: generate heightmap
    10.     GenerateHM();
    11.     // finally, create the texture
    12.     CreateActualTexture();
    13. }
    14.  
    15. function IterateSphere()
    16. {
    17.     // initialize your arrays here! this will prevent
    18.     // having to create large arrays every frame
    19.  
    20.     for (iteration = 0; iteration < numOfTimes; iteration++)
    21.     {
    22.          // sphere iteration code here ...
    23.  
    24.          // wait for next frame:
    25.          yield;
    26.     }
    27. }
    28.  
    29. function GenerateUV()
    30. {
    31.     // generate uv code here...
    32. }
    33.  
    34. // etc...
    35.  
    Concerning the performance, what is it that takes (most of the) 15 minutes? 1 frame? the first 100 frames? the 101th frame that does the texture stuff? By measuring the time of each individual function (use Time.realtimeSinceStartup), you can get a sense of where the problem lies.

    Without such information, performance debugging is a rather frustrating business ;).

    The only thing that I notice right away is this:

    Code (csharp):
    1.  
    2.     // get a cutting plane
    3.     while(true)
    4.     {
    5.         var cuttingPlaneVector = Vector3(Random.Range(-1.0, 1.1), Random.Range(-1.0, 1.1), Random.Range(-1.0, 1.1));
    6.         if (cuttingPlaneVector.magnitude < 1)
    7.         {
    8.             break;
    9.         }
    10.         var m = (Random.Range(0, 2) == 0) ? -1 : 1;
    11.     }
    12.  
    Whenever cuttingPlaneVector.magnitude is < 1 in the first attempt, var m is not assigned and most likely 0 (or anything else, you never know with this loose compilation :p). Are you sure the 'var m' line is supposed to be inside the while loop?
     
  3. Eric5h5

    Eric5h5

    Volunteer Moderator Moderator

    Joined:
    Jul 19, 2006
    Posts:
    32,401
    One thing I see, is why is there an Update function? Update is for things that occur every frame...as far as I know, you just want that to run once, so it should be in the Start function. You've got variables declared with no type, which makes them dynamically typed, which makes them slow. Things like

    Code (csharp):
    1.         var dotProduct;
    2.         var temp;
    should be defined by what type they are, although there's really no reason to define them there at all. Just get rid of them, and use:

    Code (csharp):
    1.         for (var i=0;i<vertices.Length;i++)
    2.         {
    3.             var temp = vertices[i] - cuttingPlaneVector;
    4.             var dotProduct = Vector3.Dot(cuttingPlaneVector, temp);
    That will get rid of the dynamic typing and make it a lot faster.

    --Eric
     
  4. 3Lighthouses

    3Lighthouses

    Joined:
    Sep 15, 2009
    Posts:
    10
    Also, when you use print() inside an Update function or a fast coroutine (something happening several times a second), the print() itself will slow things down a lot.
     
  5. andeeeee

    andeeeee

    Joined:
    Jul 19, 2005
    Posts:
    8,768
    The previous comments are all correct, but I suspect what is really slowing your code down is the fact that you are getting the mesh vertices and normals each time the Update function is called, and also setting the vertices back again. Whenever you use these properties, the arrays are copied to your variable - you are not just accessing the original via a pointer as you are with most other arrays. Instead of getting these values into local vars in the Update function, create some script vars and set them from the Start function:-
    Code (csharp):
    1. var verts = mesh.vertices;
    2.  
    3. function Start() {
    4.     verts = mesh.vertices;
    5.    ...
    6. }
    7.  
    Then, make your changes to this verts array and only assign it back to mesh.vertices when you have finished all your calculations. Use the same principle with the normals and uvs arrays. Even if uvs is only assigned once, you are currently allocating memory each update and that will trigger garbage collection much more often than necessary.