Sunday, 10 March 2013

Chroma Keying (transparent background) with Vuforia AR Video Playback

It is possible to make augmented reality video playback with the Vuforia platform just that little bit more realistic by chroma keying the video. Chroma keying, aka the green screen effect, is an old trick used in the television industry of putting a solid colour background behind the person being filmed so they can replace that one colour in post-production with a completely different scene. Obviously this requires the person in the foreground to avoid wearing anything close to that colour otherwise they'll appear patchy/transparent in places.

I managed to come up with a basic way to implement this with the video playback functionality offered in the Vuforia SDK. Here's a snippet of the OpenGL ES shader code that I used:

static const char videoPlaybackFragmentShader[] =
  "#extension GL_OES_EGL_image_external : require \n"
  "precision mediump float; \n"
  "uniform samplerExternalOES texSamplerOES; \n"
  "   varying vec2 texCoord;\n"
  "   varying vec2 texdim0;\n"
  "   void main()\n\n"
  "   {\n"
  "       vec3 keying_color = vec3(%f, %f, %f);\n"
  "       float thresh = 0.8; // [0, 1.732]\n"
  "       float slope = 0.2; // [0, 1]\n"
  "       vec3 input_color = texture2D(texSamplerOES, texCoord).rgb;\n"
  "       float d = abs(length(abs(keying_color.rgb - input_color.rgb)));\n"
  "       float edge0 = thresh * (1.0 - slope);\n"
  "       float alpha = smoothstep(edge0, thresh, d);\n"
  "       gl_FragColor = vec4(input_color, alpha);\n"
  "   }";
UPDATE: I forgot to add I also needed to run these commands in order for the fragment shader above to work:
UPDATE #2: 2013/04/04:
Some people were reporting they had problems with their video content turning black. Re-reading this article I realised I'd missed out the vertex shader code as well so here it is. If I recall rightly, the variable "texCoord" that is used in the fragment shader must have the same name as in the vertex shader. Using the sample Video Playback app with the Vuforia SDK, if there are any problems with compiling your shader source code then there'll be some errors printed in LogCat that you can use to help debug.
static const char* videoPlaybackVertexShader =
    "attribute vec4 vertexPosition; \n"
    "attribute vec4 vertexNormal; \n"
    "attribute vec2 vertexTexCoord; \n"
    "varying vec2 texCoord; \n"
    "varying vec4 normal; \n"
    "uniform mat4 modelViewProjectionMatrix; \n"
    "void main() \n"
    "{ \n"
    "   gl_Position = modelViewProjectionMatrix * vertexPosition; \n"
    "   normal = vertexNormal; \n"
    "   texCoord = vertexTexCoord; \n"
    "} \n";
In the VideoPlayback sample code, then in VideoPlayback.cpp, inside the renderFrame() function I inserted these 3 lines above all the calls to SampleUtils::scalePoseMatrix. For those of you who have studied the source code of the Video Playback project that can be downloaded with the Vuforia SDK you can see this is a modified version the source code for the fragment shader for the video frames. What it does is, for any colour that is close to the specified colour that is processed by the fragment shader (any pixel to render on the screen from rasterization), that colour will be made transparent by modifying its alpha channel. The same code can be used for the keyframe's fragment shader too. Notice I have 3 %fs. These, in order, are the RGB values of the colour to chroma key - i.e. to make transparent. I used sprintf to replace the string with actual float values. Note the range of these numbers is 0.0-1.0. They're float numbers, not the integer range you often see of 0-255.
Unfortunately I don't have a project I can share this time as the source code I originally implemented this in is mixed in with other code I'm not able to release publicly.

To be a little more adventurous and dynamic you could consider implementing a function to dynamically change the value of the colour that is made transparent on-the-fly using a colour picker dialog or some other input method. Other input methods could be a text input field for the 0x RGB value or a different kind of colour picker like the ones often found in paint programs where the user touches a part of the screen and the colour value of that pixel is read in as the input. This would be handy so users could just touch the area of the background they want to remove and it would automatically detect the colour.

Friday, 11 January 2013

Fixing Slow Camera Animations in Unity

After importing one of any number of complex 3ds Max models as FBX files into my Unity scene and animating a single Unity camera, I found the camera's movement was extremely slow at anywhere from 0.3-3 FPS. The model was a static room and all I wanted animated was the camera itself. Everything was normal speed in the little Camera Preview box but on both playback on my laptop (which is medium-high spec) and on my Android it was extremely slow.

What I found happening was that after importing, say TestObject.fbx, then Unity was automatically enabling the Animator for the TestObject in my scene. Simply select TestObject in your Hierarchy panel in Unity and uncheck the Animator section found in the Inspector panel.

Saturday, 5 January 2013

Importing 3DS Max Models into Unity 3D for iPhone/Android Games

I have started delving into the realm of 3D modeling for a Vuforia augmented reality based game I am developing. To that end I've been spending the past 2 weeks learning 3ds Max and some more Unity 3D (the game engine I am using to create the Vuforia AR based game). This article covers importing a 3D scene from 3ds Max into Unity with steps to optimize your scene for low spec devices by reducing the number of polygons, textures and draw calls.

I purchased a 3D model of a complex scene off of Turbo Squid. I'm a bit of a novice when it comes to 3D modeling so I decided to purchase an already made scene and modify it to suit my needs. As my game's intended platforms are Android and iOS devices I knew optimization would be key to good game experience. Following the standard Unity instructions on how to import a model from 3ds Max yielded me a few problems:
  • Importing the .max file directly into my Unity project's assets timed out after Unity gave up 3 minutes later trying to internally convert the .max file to an .fbx file.
  • Manually exporting the scene from 3ds to an .fbx file and then importing that into Unity gave me the scene, and despite the textures being imported into Unity no textures were being applied (everything was just gray).
  • Importing the .fbx into Unity took a very long time.
  • Deploying Unity app to my Android with just the gray scene crashed the app (more precisely, as soon as a Vuforia target was found in the camera frame).
  • A bunch of the Vray materials and lights were not exportable to .fbx.
The conclusion? My 90 MB max project with 70 MB of textures was too much for my 4 core laptop to achieve a workflow with any kind of acceptable speed, let alone the idea of trying to get the model to load on Android/iPhones ;-) What's more, Vray materials aren't compatible for exporting to .fbx so it would've been better if I'd got a 3D model using the standard renderer with standard materials. But as I had paid a nice sum for the Vray rendered version and liked the model, I pushed on.

The answer? Merge a bunch of objects all with their own materials/textures into a single mesh object and bake a rendered view of that mesh into a single texture (containing all the other textures/lighting/shadows) that then need be the only texture applied to the mesh as a material. In other words, we merge a bunch of multiple objects with different images attached to their surfaces into a single object with a single image file (texture).

Importing a multi-textured 3ds Max scene into Unity

I wrote the instructions below aimed at complete newbies who want to optimize their complex 3D scene suitable for a low spec handheld device like Android or iPhone. This is achieved by combining several objects into one mesh and baking a single texture image to apply to the mesh as the only material. The baked texture image will come from a rendered output of the object complete with its standard or Vray materials, any multisub object materials, shadows, lighting etc. The scene I happened to buy used the Vray renderer but the same instructions should be applicable to models that use the standard renderer as well.

NB. There is a plugin for 3ds Max called Flatiron that be used to bake entire scenes into one image texture. The full version costs a bit but if you have luck with the trial version you may wish to purchase it. I didn't have much luck so I decided to go the manual route, as detailed below.
  1. Open your 3D model scene in 3DS Max. I use 3ds Max 2010 with the trial version of Vray 1.5 SP4.
  2. Select the objects that have their own standard (and/or multi/sub) materials that you want to turn into a single mesh object with a single material.
    * To select objects, make sure the Select Object icon is the highlighted icon on the top horizontal toolbar (just below the row of main menus you should see a drop down list of All, Geometry, Shapes, Cameras, Lights etc. and beside that the Select Object icon which is a cube with a mouse pointer - in Max 2010 at least).
    Press Ctrl+left mouse click to select multiple objects within your scene. You also can click on the Select by Name icon and choose the objects from their names.
    (or Edit -> Select All) will select all objects currently displayed in the viewport.
  3. Optional: press Alt+Q or Tools -> Isolate Selection to display only the selected objects in the current viewport.
  4. Select Group -> Ungroup as many times until all the selected objects are ungrouped. The Ungrouped menu item will become grayed out. This is to make sure you can Attach all items (covered below).
  5. Select one object, right click it and choose Convert To -> Convert to Editable Mesh.
  6. With the same object still selected, on the right hand panel of Max, select the Modify icon (2nd icon from the left on the top of the right-hand panel in 2010) and directly below that is the object's name, Modifier List (a drop down menu) and then the stack list (think layers of operations done on the object). Currently the only layer in the stack list should be Editable Mesh. After clicking Editable Mesh in the stack list you will see a few Editable Mesh related configuration sections appear below it.
  7. In the Edit Geometry section, click Attach List and the Attach List dialog window will popup. Assuming you're in isolated mode (Alt+Q a few steps back) then you can happily select all objects in that list and click the Attach button in the dialog window. This operation will effectively make all your objects into one single mesh object. Alternatively, click the Attach button next to Attach List and select objects individually if you're not in isolated mode. With the newly created Editable Mesh object selected, select Unwrap UVW from the Modifier List. You should now see in order from bottom to top, Editable Mesh and Unwrap UVW in your object's stack list.
  8. In the Unwrap UVW's Parameters panel, look for the section called Channel and change Map Channel to 2. If you get a warning about changing channels with the options to Move or Abandon, go with Move. I noticed this started appearing after I upgraded to 3ds Max 2013 and after trial and error Move seemed to be the best option.
  9. Click the small + icon next to Unwrap UVW in the stack list, select Face and press Ctrl+A (or Edit -> Select All) to select all faces.
  10. Click the Edit... button in the same Parameters panel and the UVW Editor window will open.
  11. Open Mapping -> Flatten Mapping and with the default values press OK.
  12. Depending on how many faces your object has, a while later you should see a bunch of green/white wireframe like figures neatly laid out in a square region representing the unwrapped version of all faces of your object. This so-called UV map will be the map of coordinates that tell how the 2D texture image is to be wrapped around the 3D mesh object.
  13. Close the UVW Editor.
  14. Select Unwrap UVW in the stack list.
  15. Making sure the object is still selected, press 0 or Rendering -> Render To Texture to open the Render To Texture dialog window. Here is where we generate the texture image by rendering the 3D model in Max and following the unwrapped UV map coordinates, the object's faces' materials will be baked onto that area of the texture image file.
  16. Under the General Settings section, Output set Path to a folder where you want the output image file saved.
  17. Under the Objects to Bake section, you should see the mesh object's name in the list.
  18. Under the Mapping Coordinates section, select Use Existing Channel next to Object.
  19. Select 2 from the drop down list next to Channel. If there is no 2, then it means the UV map was not unwrapped properly to channel 2.
  20. Under the Output section, click the Add button. If you are using the Vray renderer, select VRayCompleteMap. If you are using the standard renderer you can probably use CompleteMap but I haven't tried with that one. Using the complete map will ensure not only materials, but also lighting and shadows etc are baked in.
  21. Under the Selected Element Common Settings section, you should see something like:
    • Name: VRayCompleteMap
    • File Name and Type: <object name>
    • Target Map Slot: Diffuse Map (select Diffuse map if its in the list)
    • Element Type: VRayCompleteMap
    • Width/height dimensions: the resolution of the output texture image.

      A few notes about this section:
      • If you select multiple mesh objects to render to texture at the same time (which is completely possible, just select them all before opening the Render To Texture dialog and they will all be listed under Objects to Bake), then the file name and type cannot be changed from the UI.
      • You can change the default type by:
        1. Open C:\Program Files\Autodesk\3ds Max 2010\ui\macroscripts\Macro_BakeTextures.mcr in a text editor.
        2. Change local    defaultFileType = ".tga" to local    defaultFileType = ".png"
          TGA seems to be the Max default, I prefer PNG but you can use JPG or TIF too.
        3. Restart Max.
      • Choosing the right resolution is a bit of an experiment and I'm still experimenting and discovering. It all comes down to the specs of the target devices that your game will run on, how many objects you have in total, how many faces in one mesh object, the actual content of the object (is it a far away stone in the background or a closeup thing requiring more detail?). So far I've been playing around with 512x512 to 1024x1024 resolutions with 10-30 objects and the scene has been rendering on my Android okay. This is a static environment though and those numbers still feel too high. You'll just have to experiment here. My approach is bake all your textures to high resolution and then scale them down as you test the level of detail of actual content rendered on a device until you're happy with the lowest image size vs content detail.
      • When selecting multiple objects to bake at the same time you may not be able to select Diffuse map in the Target Map Slot setting and may get some warnings about it not being set for some objects. So far I've been fine just ignoring these warnings or just having the Target Map Slot for multiple objects set to "varies".
  22. Down the very bottom, I set both Views and Render to Original. Then click the Render button.
  23. The render process may take some time. When it's finished it's not obvious with a "Render finished" message or anything. You should just see the preview window of the rendered texture image appear to be finished (and the fact your computer is no longer slowed down to a snail's pace and you can actually open the menus in Max etc!)
  24. Back on the stack list with Unwrap UVW selected, change the Map Channel that you set to 2 back to 1.
  25. Right-click Unwrap UVW and select Collapse All. This collapses all the layers in your stack into one layer.
  26. Open the Material Editor (4th icon from right on top toolbar in 2010 - the icon with a chequered circle with a small square bottom right).
  27. In one of the free slots (the spheres), drag/drop the texture image that was created from Windows Explorer. You will now have a new standard material with the image file attached as a bitmap texture on the diffuse channel.
  28. Now drag/drop that material slot (the sphere) onto the actual mesh object in your scene. And presto! You now have a single mesh object with a single rendered-to-texture image material.
  29. Things may look a bit fuzzy in 3ds Max viewport more than they actually do in Unity, I'm not sure why.
  30. Next I simply export the selected object(s) as an FBX (ensuring Insert Media is selected in the FBX settings window) and then import that FBX file into Unity and I have the same textured object to play with in Unity!
It's a long process, and quite manual. Same may prefer using Flatiron but I had more control over the individual objects here. If anyone with more experience than me finds an error in an article, please contact me or leave a comment and I'll amend it.