Gamieon

Christopher joined Mar 14, 2009

Gamieon is a privately owned entertainment software development company located in Tampa, Florida. Since October of 2004, we have aimed to provide quality video game software which emphasizes both intellectual and action-driven challenge to the gaming community. Gamieon depends on the talent of individuals working as a team to develop video games and video game engines with a focus on exceptional game play and surrealism.

Report article RSS Feed Porting Hyperspace Pinball to the iPhone: Part 1

Posted by Gamieon on May 20th, 2011

When developing a cross-platform game with Unity, one must not forget that performance wildly varies from one platform to the next. One of the factors that determines performance is the number of triangles rendered per frame.

Consider Hyperspace Pinball. Once all the levels were finished, I copied the project from my PC to my Macbook and then immediately deployed it to my iPhone 3G without making any changes to the game. I used two scripts active during the game to determine performance: One counted the number of triangles in all visible meshes, and the other calculated the running frames-per-second. You may find them at the end of this article. Here were the results on my first run with all controls idle:

22 fps
24,666 triangles per frame

We're going to have to do better than that if we want the game to be smooth and responsive. Here are the steps I took to a solution of improved frame rate and reduced triangle count:

Step 1: Determine the limitations of the platform
Unity3d.com cites:
 "Generally you should aim at 40K or less vertices visible per frame when targeting iPhone 3GS or newer devices. You should aim at 10K or less vertices visible per frame when targeting older devices equipped with MBX GPU, such as: iPhone, iPhone 3G, iPod Touch 1st and 2nd Generation."

Step 2: Figure out where all the triangles are coming from
Using a script I wrote (listed at the end of the article) to report each mesh in the playfield and their triangle counts, I was able to find the worst offending objects. As you may have guessed, the playfield as a whole was the biggest single one...but there were other much smaller objects with a hefty count as well!
 
Step 3: Cull static back facing triangles...permanently!
It's not enough that Unity hides back facing triangles that will never appear; I want to hide from Unity altogether. There's no need to make the Unity rendering or physics engine even look at them in the first place. I accomplished this by using the Slice tool in 3DSMax 3.1 (that's right, 3.1, with parallel port hardware key and all) on the playfield model.

I set the slice plane close to the back of the playfield, and then cut off the back side. I removed maybe 10% of the polygons in the model. Not what I was hoping for, but better than nothing.

Step 4: Replace Unity primitive meshes with your own
So, how many triangles are added when you add a plane or a sphere to your scene? The answer may surprise you: A plane is 600, and a sphere is 2280. In this project, I don't need the kind of lighting accuracy that Unity's higher fidelity primitives provide. I didn't find a way to reduce the triangle counts of those on my own, so here's how I dealt with them:

- Modeled my own plane in 3DSMax and imported it
- Replaced the spheres with planes where the textures are Transparent/Cutout/VertexLit renders of spheres...the spheres were only meant to be statically lit anyway so I'm not losing anything.

As I had several spheres and planes in the scene, this was a big help.

Step 5: Have a "low quality" mode where we pre-render what we can
The playfield isn't going anywhere, nor are the bonus letters. So, I rendered both in 3DSMax, added planes to the scene with the renders on them, and hid the triangles of the existing meshes. To my suprise, it actually looked good!

       

The left is rendered in high quality, the right is rendered in low quality.

Putting it all together
After all my optimizations, I deployed the project to my iPhone 3G again. Here are the new results with all controls idle:

High Quality mode (showing the playfield mesh): 29 fps, 10875 tris
Low Quality mode (showing the pre-rendered playfield): 29 fps, 2034 tris
 
For extra credit, I went into AppController.mm and increased the refresh rate threshold to 60. After that I got 32 fps on high quality, 37 fps on low.

The optimizations don't end here, though. Unity has a great article on optimizations at Unity3d.com . I may write another blog after I go through that. I may not get 60 FPS, but it won't be for lack of trying!


Script 1: Calculate FPS in Unity

javascript code:
var updateInterval = 0.5;

private var accum = 0.0; // FPS accumulated over the interval
private var frames = 0; // Frames drawn over the interval
private var timeleft : float; // Left time for current interval
private var fpsText = "";

function Start()
{
  timeleft = updateInterval;
}

function Update()
{
    timeleft -= Time.deltaTime;
    accum += Time.timeScale/Time.deltaTime;
    ++frames;
   
    // Interval ended - update GUI text and start new interval
    if( timeleft <= 0.0 )
    {
        // display two fractional digits (f2 format)
        fpsText = "" + (accum/frames).ToString("f2");
        timeleft = updateInterval;
        accum = 0.0;
        frames = 0;
    }
}

function OnGUI()
{
  GUI.Label(Rect(5,0,200,20), fpsText);
}
 

 
Script 2: Calculating triangles per frame in Unity, and dumping a list of all objects and their triangle counts. (The delayed start is to let things finish spawning on startup)

javascript code:
var updateInterval = 0.5;

private var accum = 0.0; // FPS accumulated over the interval
private var frames = 0; // Frames drawn over the interval
private var timeleft : float; // Left time for current interval

private var meshes : MeshFilter[];
private var m : int = 0;

private var triText = "";

function DelayedStart()
{
  CancelInvoke();
  meshes = GameObject.FindObjectsOfType(typeof(MeshFilter));
  for (m=0; m < meshes.length; m++)
  {
  if (null != meshes[m].renderer &amp;&amp; meshes[m].renderer.enabled) {
   Debug.Log(meshes[m].name + ": " + meshes[m].mesh.triangles.length);
   }
  }
}

function Start()
{
  timeleft = updateInterval;
  InvokeRepeating("DelayedStart", 1, 0);
}

function Update()
{
    timeleft -= Time.deltaTime;
    accum += Time.timeScale/Time.deltaTime;
    ++frames;
   
    // Interval ended - update GUI text and start new interval
    if( timeleft <= 0.0 )
    {
        timeleft = updateInterval;
        accum = 0.0;
        frames = 0;
       
        tris = 0;
        meshes = GameObject.FindObjectsOfType(typeof(MeshFilter));
        for (m=0; m < meshes.length; m++)
        {
      if (null != meshes[m].renderer &amp;&amp; meshes[m].renderer.enabled) {
        tris = tris + meshes[m].mesh.triangles.length;
      }
        }
        triText = "" + tris.ToString();
    }
}

function OnGUI()
{
  GUI.Label(Rect(5,20,200,40), triText);
}
 

 If you're a Unity Developer and find a problem with a script, please reply for all to learn!

Post a Comment

Only registered members can share their thoughts. So come on! Join the community today (totally free) and do things you never thought possible.

Level
Avatar
Avatar
Offline Since
May 25, 2012
Country
United States United States
Gender
Male
Age
33
Member Watch
Track this member
Blog
Browse
Blogs
Report Abuse
Report article