Imposters for Unity3D

Introduction

Not so long ago I was watching the talk that Ocean Quigley gave at GDC 2013 about SimCity art and technology behind it. One of the things discussed by him were imposters used for rendering great amounts of trees and people. Imposters are more advanced billoards that no only face the camera but alos provide image of model according to proper viewing angle usually taking into account scene lighting to achieve believable results. I’ve started to search for something like that for Unity but sadly I didn’t found much on that topic. I didn’t give up and decided to create something like that myself, because games for mobiles could really benefit from using such technique.

If you’re interested only in the code and package rather than reading the process behind it simply scroll to last section of this article.

Research

For other game engines like UnrealEngine or CryEngine I was able to find some tutorials on how to use their build in imposters functionality and get a better look at how it is approached from the toolset point of view. One thing that I was mentioned during my research time was that for SpeedTree such approach is used. I’ve downloaded the samples pack and found out that it was all there! The BillboardAsset data describing the UVs on the texture and BillboardRenderer rendering tree quad just like imposters are drawn.

My first though was of course that if it’s all there, there’s no point in doing anything about it more than just pick it up and use it. Well… I was wrong. BillboardingAsset instance could not be created even if it just derives from Object nor it can be accessed. It looks like Unity only have it for SpeedTree assets and does not allow it to be used anywhere else. Dead end. Even if it could be used with SpeedTree their shaders are not very optimal for mobiles so I’ve decided to build it from ground up.

Implementation

First of all I’ve started from preparing shader with billboarding functionality which was quite easy. Then I’ve created a simple script for rendering in Editor the different viewing angles of an object to texture atlas. Next I’ve implemented (also in shader) the proper look up for correct image in atlas according to viewing angle of camera (both horizontally and vertically) which gave me a  bit of a headache but I’ve made it… phew! You can see the result below (thanks to Blender Suzanne for helping me with presentation 😉 )

Imposters test

Imposters test

As you can see on the image, the center object is actually only model visible, the two on the sides are imposters, so just quads creating the illusion of full 3D model!

Batch or not to batch

Unfortunatelly one thing that I didn’t expect to happen was that such implementation (only through shader) didn’t worked for batching. All objects during batching are set to one root so the rotation of vertices no longer works for each object but for this one global root. What if I could pass some information through Material property in script? I could do that, but batching will not work anyway since the modified property will create Material instance which in fact will not batch.

This was the point at which I’ve though about leaving this concept and move on, because without batching there’s absolutely no point on doing that. There will be no real performance benefit when there’ll be houndreads or thousands of drawcalls even if those would be only quads.

And then it hit me… when I was watching more than 2 thousands of trees being rendered as imposters I’ve noticed that not all of them need to have different materials. For one frame being rendered where the camera is surrounded by imposters there will be like 5-6 different view tiles needed to render them on quads representing them. So I’ve moved the calculations from shader to script in order to calculate viewing angle and from generated materials table (each material being set to each tile in texture) get material instance with proper view tile and set it as shared material. Batching worked as it supposed to.

Not great but good enough

The one thing that comes right to mind is that of course the draw calls will be reduced down to minum, all the imposters will be batched to few materials but those calculations are now sitting in the Update call and will decrease the performance of CPU!

One thing I wanted to do was to take advantage of LOD group. If I would know the LOD group currently visible and if it’s culled I could only perform update when it’s needed, except I didn’t. That’s really major fail on Unity side. Many properties, or methods that could really come in handy just to be able to read from them, aren’t accesible at all. I’ve approached it by reducing the updates called in each frame. During scene startup I’m registering all Imposters in ImposterManager (a singleton) which updates Imposters in chunks during Update call. The performance is way better and on my Nexus7 during the Demo scene I’ve prepared rendering of almost 2 thousand imposters at once gives over 40 FPS on default Unity5 scene. That’s the result I can accept and it could be seen below.

Imposters Android

Imposters Android

There could be more

Of course I’ve just scratched the surface with what I’ve achieved on the topic of Imposters for Unity. One thing that could be done is to extend the Imposter shader to include Normal map and probably a Depth map would be good too. I think the code could also be better optimized (e.g. callculations could be performed asynchronously), maybe some advantages of Unity features that I didn’t think of could be taken to improve the calculations.

Downloads

I hope that my work may be useful for someone so I’ve provided download with Unity package below (for Unity5).

Cheers

[ Unity package containing code and demo scene as well as documentation can be downloaded here ]

Dodaj komentarz