Monday, 13th April, 2009

Exporting with Lightmaps from 3ds max to Unity3D

Filed under: 3ds max, Maxscript, Unity3D — dominique @ 09:44

Last week I digged into the topic of exporting a lightmapped, vertex-colored scene from 3ds max to Unity. Unity3D does not come with special exporters but exports to standard formats like FBX and COLLADA. When exporting to FBX, 3ds max Shell Materials do not get converted automatically to a lightmapped shader upon import. No wonder as FBX (= Filmbox which became later MotionBuilder) is not specially targeted towards game content. Therefore you have to manually assign the shader and the second texture. Of course it would better if this could be automatized!

Here are two pictures from a test scene with vertex-colored houses we created once for Virtools-based projects. The houses are currently using only dummy lightmaps, but that's the result without any manual shader or texture assignments.

Raw 3ds max scene Result in Unity 3D

So, here is how I do it.

Preparations inside 3ds max with Maxscripting (MXS)

Unity allows to hook into the assets import pipeline via custom scripts. This is very cool concept and is similar to something we did for our Virtools Assets&Build pipeline which we called BogBuilder btw. The key concept is therefore to *somehow* pass hints to a assets post processor script. What I do is to tag material names inside 3ds max, for example using __lm__ to indicate that the material is a lightmapped one. I use two underscores on each side because it reduces the probability that the original name accidentally contains such a sequence of letters.

I did not found a way to extract the names of lightmap texture from FBX files inside a Unity post processor script. So I actually add the texture name to the material name itself too! Here is an example of how a material inside 3ds max can look like after preprocessing

wandputz-tex__lm__S_4_WaendeCompleteMap_ambient-schlagschattenMulti100.tga

Pretty long, hehe. But it helps!

The custom maxscript does thus the following for every shell material

  • take the texture from baked material and put it into the original material's self-illumination slot
  • add the lightmap tag to the original material name (if it's not already there)
  • add the lightmap texture filename (including extension) to the material name
  • assign the original material back onto the geometry

Don't forget to check if the original or baked material is of type multi-material and handle it accordingly. Another issue I *sometimes* have is with special German characters like öäü. Unity sometimes replaces those upon import with some other symbols and may therefore break your postprocessor scripts when they will look for the lightmap textures. I created two more custom maxscripts that check and replaces those characters in material and texture names. (For object names it would be good, too, I guess). As a little hint, in order to access easily all bitmap-materials inside 3ds max you can use the following maxscript snippet:

local maps = getClassInstances bitmaptexture

Using enumerateFiles or usedMaps() only gives you strings and might turn things more complicated. As some of our meshes use Vertex Colors, I check that too and tag material then with __lmc__ instead of __lm__. To detect the use of vertex colors you can do the following

local tmesh = snapshotAsMesh myObjectNode
if ( getNumCPVVerts tmesh > 0 ) then

Using AssetPostprocessor

There are several types of asset postprocessors. To create one, you have to place/create your script inside a project folder called "Editor". It's not created by default, so create one if you can't find it. Using Javascript you usually start like this

class AssetPost_ShadersByPostfix extends AssetPostprocessor
{    …

and then you implement a static function depending into which kind of event you want to hook into.

OnAssignMaterialModel gets triggered each time a material has to be imported. In this callback you have the control if, where and how you create the new material. If you organize your project in a way that you cluster all materials in specific directories rather to have them close to the geometry assets, then this works fine. Otherwise this isn't the best callback to use as you don't get any hint where, inside the project directory hierarchy, the imported FBX is. Usually on FBX import a "Materials" folder is created on the same level, something you can't do easily with OnAssignMaterialModel. Alternatively you can use

OnPostprocessAllAssets: The benefit of this callback hook is, that the assets creation is automatically done for you and you get the target directory paths as array. To detect materials you can simply do something like this

        for (var aFile: String in importedAssets)
        {
            // identify materials by .mat extension
            if( aFile.ToLower().EndsWith(".mat") )
            {
               …

This works pretty good. But also with this there is a scenario where it's not the best fit. If you use the FBX exporter option "embed Media" that includes all textures inside the FBX file, then it does not import the lightmap textures during the first import/refresh activation. They get imported if you do a refresh or if you switch to another app and back. As result, your OnPostprocessorAllAssets may not find the lightmap textures because it's called during the first run, when the materials are created (and only diffuse textures get imported) and the lightmaps are added in the second run to the project.

So what I do is calling manually a custom ScriptableWizard inside Unity after import. It's therefore not totally automatic, but quite robust and only something like 3 clicks.

Somehow I miss some built-in functionality to deal with project things inside Unity but you can parse through all material assets inside your project using standard DotNet, like this

import System;
import System.IO;

var matFiles : String[] = Directory.GetFiles(Application.dataPath, "*.mat", SearchOption.AllDirectories);

for(var aMatFile : String in matFiles)
{     …

The rest is quite straight forward: the Wizard iterates through all project materials, checks if they contain any shader tags in their names, assigns the corresponding shader, extracts the lightmap texture name, finds the texture and assigns it as second texture to the shader.

Well, that's it. I hope this helps you to setup a better pipeline for importing assets with lightmaps from 3ds max. Of course the key concept can be used for anything else too! 

Bookmark and Share: These icons link to social bookmarking sites where readers can share and discover new web pages.
  • del.icio.us
  • Technorati
  • Digg
  • Reddit
  • Google Bookmarks
  • YahooMyWeb
  • Live-MSN
  • Facebook

6 Comments »

  1. Hello there, Great article about unity, lightmaps &  asset importing ! I wish i could Maxscript the ShellMaterials like you do. So it means the FBX is not exporting the texture name of the self illum channel ? (quite tricky part of putting the texture name in the material name) Unity also automatically imports .MAX (i guess it still goes through FBX somehow), works fine here. (but again, only diffuse, no transparent tga or lightmapped material) Editor Scripts are one of the best feature of Unity in production. Keep up the good work Dominique and respect :) !

    Comment by Marc — Thursday, 16th April, 2009 @ 02:11

  2. Hi Dom, thanks sharing you impressions and knownledge. I do have a question for you concerning different file formats. As far as i know many of the available so called "indie" game engines is limited to 16-bit vertices in the mesh buffer (65k). This seems to be true for unity,virtools,iirlicht etc. However Quest3D can import larger models, is this becuase it has the .x file import option ??. It seems to be able to go 32bit if needed. However, i am suprised how good the CAD models has turned out when running some polygonreduction tools in polytrans so it might not be needed to have more vertices per mesh but it is alayws nice to have. 

    Comment by Lundin — Saturday, 23rd May, 2009 @ 06:16

  3. Lundin, Virtools is able to export/import meshes with more than 65k vertices. This was improved for the Virtools 4.0 release - I think after users requested it.

    So it's an 3d engine thing and maybe also a hardware thing. One solution is to internally split the mesh into chunks - but hiding this to the user (hiding complexity which is task of CAD od DCC tools). Therefore users probably need to inform the developers of the need of big meshes so they can find a solution :)

    Comment by dominique — Wednesday, 27th May, 2009 @ 05:14

  4. Hi, I have some questions and i supposed maybe some of them could be answered here. Thanks. :) 1. we have a lot of .FX shaders for 3dsmax available online free to download. I would like to know if there is one that will give a good preview in max viewport of what I would se inside unity. 2. How can I know the language that the shader was written. Is there some code in the begginig of the file that shows like HLSL, CGSL or Nvidia CG? 3. Unity shaders have their own language? 4. When I use an .FX shader inside 3dsmax for viewport visualization the features and information of the shader goes to unity when i export the fbx? 5. Where I can find in the forum a most update tutorial or pdf etc of the process of making lightmaps in 3dsmax. I'm using 3dsmax2009 with the latest fbx plugin from autodesk (2010). How to bake the lights, create the second UV set and uv channel etc. Thanks a lot and sorry for the big post.

    Comment by Bruno Rojas — Thursday, 5th November, 2009 @ 06:37

  5. Bruno, shader languages can be identified by their syntax or the file ending. Unity has it's own material language called "shader lab" - it embeds CG. HLSL or CGFX code it not directly compatible and thus shader are not directly exportable from DCC tools. In regards to the lightmap how-to, i suggest to search the Unity forum and Wiki.

    Comment by dominique — Monday, 9th November, 2009 @ 02:19

  6. Hallo Dominique, ich glaube ich kenne Dich. Ich war mal über meinen ehemaligen Arbeitgeber Raysono bei Dir in Berlin. Damals ging es um einen Wassershader. Ich bin neu im Umgang mit Unity und möchte gerne 3d Objekte zur Laufzeit in eine mit Unity erzeugte Anwendung importieren. Geht das überhaupt? Wahrscheinlich muss man einen Importer schreiben. Aber wie geht das genau? Hast Du da Informationen zu? Das würde mir sehr helfen, weil ich mich gerade im Softwarebereich selbstständig mache und würde lieber Unity anstatt Virtools verwenden (ist billiger). Über eine Antwort würde ich mich sehr freuen. Viele Grüße aus Bremen Timo

    Comment by Timo K+Ârner — Friday, 20th November, 2009 @ 11:16

RSS feed for comments on this post. | TrackBack URI

Leave a comment

XHTML ( You can use these tags): <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> .

:D :) ^_^ :( :o 8O :shock: 8) ;-( :lol: xD :wink: :evil: :p :whistle: :woot: :sleep: =] :sick: :straight: :ninja: :love: :kiss: :angel: :bandit: :alien:
Bookmark and Share: These icons link to social bookmarking sites where readers can share and discover new web pages.
  • del.icio.us
  • Technorati
  • Digg
  • Reddit
  • Google Bookmarks
  • YahooMyWeb
  • Live-MSN
  • Facebook