DevOxyz - BLOG

Writing a Unity Custom 3D format plugin.


Nov 30th
2020
Nicolas Augustin


Up to last week, when I had to write a custom 3D format plugin for Unity I used the classic AssetPostprocessor. Having a look at the Unity API, I stumbled upon the ScriptedImporter API.

Why is it important ?

Now that real-time 3D (digital twin, training, AR, VR, desktop apps, ...) are used by classic industries and corporate companies, it's important to have a very efficient 3D pipeline. This pipeline must be kept as short as possible to avoid several time and budget consuming 3D file formats manual conversions and interoperability tasks. Writing a generic way to quickly support a new file format is, in that context, very important.
Please note that the ScriptedImporter API is indicated as experimental by Unity and may be removed or modified in the future. Let's be pretty confident that it is there to stay.
While writing such importer was working with the AssetPostprocessor class, some tasks, like creating a prefab from the loaded data, link the Prefab to the actual custom format asset, asset update on file format custom import inspector changes, were not very straightforward.
Once again Unity blows my mind creating a simple, very lean and relevant API.
Here is the current version of the ScriptedImporter I use, which is pretty simple compared to how things were done before this API:

                
                    [ScriptedImporter(1, "ifc")]
                    public class IFCScriptedImporter : ScriptedImporter
                    {
                        public override void OnImportAsset(AssetImportContext ctx)
                        {
                            IFC3DModelUnityImporter ifcimporter = new IFC3DModelUnityImporter();
                            GameObject importeddataroot = ifcimporter.ConvertToUnity(ctx.assetPath);
                            if (importeddataroot != null)
                            {
                                ctx.AddObjectToAsset(Path.GetFileName(ctx.assetPath), importeddataroot);
                                ctx.SetMainObject(importeddataroot);
                            }
                        }
                    }
                    
                

So I decided to write a more generic "framework" to write custom 3D file format plugin for Unity, especially in the case that the 3D file format API is a C++ API.
This article describes the main architecture of this work and the key tricks and APIs I generally use to make this (PInvoke, Native code callbacks, ...).
Testing the framework, I used the famous AEC IFC file format read by the IFCOpenshell C++ SDK.

The architecture

First let's have a look at the architecture I use.
While it can always be improved, I think it's a nice solution. Let me know what you think using the contact form at www.devoxyz.com.
The base idea is to reuse code as often as possible. This lead to the architecture shown below.
Let's start by the entry point which is an override of the ScriptedImporter OnImportAsset abstract method. ScriptedImporter provides this classic and simple mechanism to do the job.
OnImportAsset then calls the IFC3DModelUnityImporter method. IFC3DModelUnityImporter derives from 2 classes in cascade to achieve genericity and re usability.

SA formula

Parsing, delegates, and native callbacks

The NativeDllTraverserImporter class is in charge of handling the native call (Traverse method) to the DLL that actually performs the traversal of the 3D file. Let's focus on the native calls mechanism.
It consists essentially in:
  • calling a native DLL exported Traverse Method
  • calling (from the unmanaged Traverse method callees) managed delegates that we have set beforehand in the native DLL as unmanaged function pointers, when parsing 3D data (3D nodes, meshes, material,...) and metadata.
Here are some important functions I used in the PInvokeUtilities helper static class:
  • Kernel32 LoadLibrary
  • Kernel32 GetProcAddress
  • System.Runtime.InteropServices Marshal.GetDelegateForFunctionPointer
  • The MarshalAs parameter attribute to support Unicode filenames, for instance

Next steps

  • Add other 3D data specific callbacks
  • Find the best and up to date Unity Coroutine mechanism to load and create 3D data both in the editor and at runtime. I've already done this before, but need to find a more efficient method without using a non Unity asynchronous mechanism.
That's pretty much all for this small article. Tests went OK and I'm satisfied with the genericity of this system.
In case you have questions feel free to contact me here.