COLLADA for XNA - Take 2


Update – please download take 2 - patch 1

  • hack to load non compliant exports from Blender
  • fixed bug reported below in this thread

You can download here a new release of the COLLADA for XNA package, replacing the first version posted in this thread.

Although there has been many changes since the first post, those changes are mostly structural, in order to finally be able to create a set of model/mesh/material that can be extended to all what COLLADA has to offer.

As the previous thread discussion mentioned, Model, the 3D model primitive provided by XNA is ‘just a sample’ and should be replaced with something that fits better the application. In our case, we need a COLLADAModel.

The problem is that there is a lot of hidden and non extensible code associated with Model, which makes it really difficult to replace. Once I started adding a COLLADAModel, I realized that ModelMesh cannot be used, and need to be replaces as well. The reason is that in order to create a Mesh, one has to provide a MeshContent, and attach it to a ModelContent, and then use the <Model> load/processor, which will do the conversion to the necessary vertexBuffer, indexBuffer and so forth.

So the task to replace the sample Model to something else is large, since a lot of code needs to be provided to replaceModel Mesh, ModelMeshPart.

Since COLLADA is very flexible, the main effort to create the IndexBuffers and VertexBuffers can be done directly in a COLLADA conditionner. So I created and added an re-indexor condionner that pack all the float arrays in a single array, and create the indexes into this array.

(For the curious)
The resulting COLLADA primitive have <source> without any <float_array> data, their <accessor> is referencing the one <source> that has the single float_array. This was designed like this since 1.0, but I am curious if COLLADA importers can cope with this construct. Would be interesting to add a ‘save document’ feature to find out.

Then I create COLLADAModel, and associated COLLADAModel.ModelMesh, COLLADAModel.ModelMeshParts

I also added COLLADAModel.ModelBone, and COLLADAModel.BasicMaterial since it was easy enough. Converting from COLLADAModel.BasicMaterial to BasicEffect is actually possible, since BasicEffect has been designed to be created at run-time, and not only in the content pipeline.

Those new objects are mimic of the original XNA corresponding ones, except for the following:

  • The Indices are not packed per mesh, but are an index vector per MeshPart. There is no reason for this expect that I did not do the packing. I also did not try to optimize the index to short, or to test if the number of indexes is larger than what the graphics hardware is supporting… so some more work is needed there.

  • I discovered that XNA Model does not support instancing. Since the Mesh reference back to the bone (position) and ModelMeshPart to their Effect, it is not possible to reuse the same Model at different position, with a different Effect. It was weird, since this is a very basic feature that I did not expect would be missing. So I added InstanceMesh, which reference a Mesh, a Bone, and has a list of Effect to be used for all the MeshParts

In addition to having extensible objects as a basis for additional developement, I also wanted to add the capability to load content either through the content pipeline, or directly in the application, even on the 360. In game development, it is very important to allow for two data path: the complete path that goes through the full content pipeline, and the fast-path, that enable last minute art to be injected in the game for artist to do in-game tuning, it is also necessary if you want to create a level editor to be able to load the data in the application directly.

So this version enables:

  • loading COLLADAModel using the content pipeline
  • loading COLLADAModel without using the content pipeline (direct file load)
  • loading the COLLADA Document with the content pipeline, and then creating the COLLADAModel in the application
  • loading the content as a XNA Model through the content pipeline. This is the ‘old’ code that I kept in there for comparison

One interesting note, is that you’ll notice that content.load<Model> does create index and vertex buffers. To do this, you need to have access to the graphicsDevice, since the graphics driver is necessary to create those objects. But the only ‘plug-in’ for content.load is the ContentTypeReader<>, which does not have graphicsDevice as a parameter. So you have to finalize the creation of your own ‘Model’ after calling content.load<MyModel>… This is weird, since this is really a design where hidden code happens on load<Model>, and this is contradicting the claim that Model is really just a sample designed to be replaced… at least not easily.

Anyway, now that this is done, it is now possible to add COLLADA features to XNA.

  • animation.
    Unlike XNA COLLADA animations are not limited to tranforms. One can anything that has an id, including textures, colors and so forth… should it be the next feature ?

  • Drawing order.
    XNA drawing loop first select a Mesh, and then loop through all the MeshParts in this mesh. Each MeshPart has its own Effect. Switching Effects is in general the most expensive thing to do in graphics. So the rendering loop is wrong. It should be for each Effect, render all the MeshParts that are using this Effect.
    In addition, drawing it is important to draw transparent objects last, and in a back to front order, which is not possible with the current structure. So more changes are necessary to Mesh and MeshParts.

  • Skining.
    It is not clear to me how skinning needs to be implement. Does BasicEffect has support for skinning, or should the skinning be done in the CPU, or should a full FX, with shader code, be provided to do skinning ?

So, what do you think ?
Should the code be re factored differently ?
What feature should be done next ?
How many bugs did I introduced :slight_smile:

Thanks for reading.

Hey remi,

I have taken a look at your code and it works nicely with the collada models you provided. I have not read through all the threads here about collada and XNA, maybe I will do that and answer some questions you guys have here at the weekend, when I got a little bit of free time.

Just to test out your code I tested a little bit more complex collada file, which uses a .fx shader and was exported from 3D Studio Max 8 using a mesh using that shader as its material. The collada file works fine with my collada importer in XNA, another one I wrote for MDX and I’m currently testing it on a OpenGL implementation as well. The model also works fine as a .x file and .3ds file. I can maybe provide some complex collada test files later.

The importer crashes with the following error message:
Building Content\Models\bones1.DAE -> bin\x86\Debug\Content\Models\bones1.xnb
Content\Models\bones1.DAE : error : Building content threw Exception: effect id=file____Models_Shaders_SpecularNormalMapping_fx has no profile_COMMON :C:\code\Xna\colladaforxna-take2\COLLADA for XNA - take 2\COLLADAViewer\COLLADAViewer\Content\Models\bones1.DAE
Content\Models\bones1.DAE : error : at COLLADA.Document.Effect…ctor(Document doc, XmlNode node) in C:\code\Xna\colladaforxna-take2\COLLADA for XNA - take 2\COLLADADocument\COLLADADocument\COLLADADocument.cs:line 1113
Content\Models\bones1.DAE : error : at COLLADA.Document…ctor(String name) in C:\code\Xna\colladaforxna-take2\COLLADA for XNA - take 2\COLLADADocument\COLLADADocument\COLLADADocument.cs:line 2190
Content\Models\bones1.DAE : error : at COLLADA.COLLADAImporter.Import(String filename, ContentImporterContext context) in C:\code\Xna\colladaforxna-take2\COLLADA for XNA - take 2\COLLADAPipeline\COLLADAPipeline\COLLADAPipeliner.cs:line 745
Content\Models\bones1.DAE : error : at Microsoft.Xna.Framework.Content.Pipeline.ContentImporter`1.Microsoft.Xna.Framework.Content.Pipeline.IContentImporter.Import(String filename, ContentImporterContext context)
Content\Models\bones1.DAE : error : at Microsoft.Xna.Framework.Content.Pipeline.BuildCoordinator.ImportAssetDirectly(BuildItem item, String importerName)
Content\Models\bones1.DAE : error : at Microsoft.Xna.Framework.Content.Pipeline.BuildCoordinator.ImportAsset(BuildItem item)
Content\Models\bones1.DAE : error : at Microsoft.Xna.Framework.Content.Pipeline.BuildCoordinator.BuildAssetWorker(BuildItem item)
Content\Models\bones1.DAE : error : at Microsoft.Xna.Framework.Content.Pipeline.BuildCoordinator.BuildAsset(BuildItem item)
Content\Models\bones1.DAE : error : at Microsoft.Xna.Framework.Content.Pipeline.BuildCoordinator.RunTheBuild()
Content\Models\bones1.DAE : error : at Microsoft.Xna.Framework.Content.Pipeline.Tasks.BuildContent.RemoteProxy.RunTheBuild(BuildCoordinatorSettings settings, ITaskItem[] sourceAssets, TaskLoggingHelper msbuildLog, String[]& outputContent, String[]& rebuiltContent, String[]& intermediates)
Done building project “COLLADAViewer.csproj” – FAILED.

The shader file and the used textures from the shader exist, but they are not really loaded. I guess this can be fixed with a couple of changes in the code. I noticed however that your viewer project is rather complex, try to compare that with my model viewer:

	#region Unit Testing
	public static void TestRenderModel()
	{
		Model testModel = null;
		TestGame.Start("TestRenderModel",
			delegate
			{
				testModel = new Model("Apple");
			},
			delegate
			{
				testModel.Render(Matrix.CreateScale(10));
			});
	} // TestRenderModel()
	#endregion

Anyway, great work you are doing there. I used a completly different approach loading the collada files directly (or using an internal binary format, which is automatically generated through serializing the class that renders the collada model), which I think was much easier to implement than fighting with the limitations of the content pipeline.

Again, not much time right now, I will take a closer look at your project this weekend.

Remi,

I’m sorry I still have no time to make a considered reply to all your great work but I just wanted to point out that you can get at the graphics device from inside a content reader (it really would have been an oversight on Microsoft’s part to not enable that). You can use code like this in your content reader’s Read() method:


IGraphicsDeviceService graphicsDeviceService = (IGraphicsDeviceService)input.ContentManager.ServiceProvider.GetService(typeof(IGraphicsDeviceService));

Not terribly obvious I agree. The XNA docs have a section on adding new types to the content pipeline that mentions this:

Cheers,
Leaf.

Many thanks for your help. It works great and make the code cleaner.

This actually prompt me for additional questions:
In the current code doing the postProcessing on the Model (called inside content.load<COLLADAModel>), there is the creation of the BasicEffect from the BasicMaterial.

  • new BasicMaterial() is asking for graphicsDevice, as well as effectPool. I currently use ‘null’, since I am not sure what I am supposed to use
  • effect.Texture is created using Texture2D.FromFile(). This works fine, and is the only way to load the texture in you load the COLLADAModel directly. But when the post load processing is called from within the content pipeline, there must be better way to get the texture from the content pipeline ?

Hi Abi,
many thanks for your feedback.

As of 1.4.1 COLLADA does not have default support for HLSL/FX. Built in are CG , GLSL and GLSL-ES, since we first focussed on PS3 content (CG) and content for all the Khronos graphics API (GLSL, GLSL-ES).

But since COLLADA is extensible, the Max exporter/importer can save HLSL/.fx, in a Max specific technique. It can also import/export the standard CG technique, but wont be able to display it. This works because COLLADA externalize the parameters and everything that makes an effect from the source code of the shader programs, so you can use the same COLLADA FX and provided different shader programs code depending on your target platform. In practice this means you can save a COLLADA FX from Maya with CG shaders, and you can load it in Max, and if you save it back from Max all the CG profile will be kept, which is important as far as COLLADA is concerned to enable data interchange. If you provide the HLSL profile(code) as well as the CG profile, then you can see the effect within Max as well. Tools like FXComposer 2.0 have dual viewports where you can see the DirectX/HLSL and the OpenGL/CG side by side in the same tool, so you can create and test cross platform FX content.

[note: this has changed recently with the new release Max8 has now support for CG, it will take some time for COLLADA Max to leverage this new capability]

That actually is a bug in the exporter !
The COLLADA specification says that all <effects> must have a profile_COMMON (no programmable shader), and can have as many additional profiles for specific platforms.

This version does not implement anything else than common profile, but that would be very interesting to add this next. I will probably only work for Max exports, until the .fx/HLSL profiles are added to the COLLADA specification, but that’s a good start for sure.

Would you mind sending me your .dae file, effects and textures ? If you have the original max file as well this would enable me to check why the exporter does not have a common_profile as well ? (remi (at) collada.org)

I just copy/paste a sample from the XNA doc (and fixed it). There is nothing of importance in the Viewer file, it is just convenient to load a bunch of models, rotate them with the mouse or the XBOX joystick, and switch model to models.

Although I forgot to gather the draw code back in the COLLADAModel, so I’ll do that to simplify this code.

I am trying for the COLLADA importer to be a good XNA citizen and take advantage of the content pipeline. I think that a good content pipeline is really important, so hopefully the work I am doing will be an improvement over what is provided.

BTW, I had no idea you were taking advantage of COLLADA already. Interested to hear your feedback on this too !

Thanks again for your time.

Actually I think it’s Max 9 with the Cg and Cgfx support.

Null is fine for the EffectPool param, that basically means that all Effect instances created by BasicMaterial will have no EffectPool (used for sharing EffectParameters between different Effect instances). Another option (as used by the default Effect content reader) is to create a static EffectPool and use that for all Effects loaded. That way all Effect instances will be able to share EffectParameters. Neither is quite correct since the application may want control over the EffectPools.

In the content pipeline you can use ExternalReference<Texture2D> to reference the textures. ContentProcessorContext.BuildAsset()/BuildAndLoadAsset() to build textures. ContentWriter.WriteExternalReference() and ContentReader.ReadExternalReference() to serialize references to the textures in a .xnb file. Not sure how that fits in with your latest code but hopefully it points you in a useful direction.

Cheers,
Leaf.

Thanks for providing this code! I did not have a chance of using it myself (I am currently working with FBX models in XNA) but I think COLLADA can be a huge help to many people using XNA. From what I see at XNA forums, many people are using XNA with Blender and Blender’s X exporter is very unreliable (and Blender has no support for FBX). Blender’s COLLADA exporter is already in good shape and I feel it has potential for quikc improvement. That will send a stream of users for COLLADA with XNA.

I think situation with Blender and .X format is typical and the same will start happening with other tools. COLLADA plugins for Maya and 3DMAX are very well supporter and updated often, in contrast with Microsoft’s .X plugins. The .X is a dying format and the only alternative Microsoft offered with XNA is FBX which is not a great choice.

I am using XNA and am thinking of moving from FBX to COLLADA in the future because of general advantages of COLLADA as a format for games contents. But there are definetely many people around who already have troubles with .X and COLLADA seems to be a natural sulution.

Pardon my going into these minor details but I just wanted to give some feedback. As one of principal contributors to www.codeplex.com/animationcomponents , I have a seen lots of posts from people having troubles with .X.

Michael Nikonov

As I already mentioned, I am one of the guys behind www.codeplex.com/animationcomponents. The animationcomponents is not a big deal in it’s current form but we definetely collected some knowledge of animations with XNA. So I will provide a few comments in hope they will be useful.

XNA does not have any build-in support for animations other then bone transforms. This would be a new feature if someone would add it with COLLADA. Currently, people struggle to get skinned models animated in simpliest form, so I guess nobody’s thinking of advanced features yet.

BasicEffect does not have support for skinning. Let me suggest that you use www.codeplex.com/animationcomponents as a starting point for that - it is a working example of skinned animation in XNA, with skinning done in shader (BasicPeletteEffect). I don’t think it’s worth doing CPU skinning - CPU skinning is yeterday of animation. Shader skinning is a natural choice for XNA (especially taking into account that XNA is more about XBox development then PC, and XBox has very powerfull GPU).

Animationcomponents has also a good example of custom importer with support for skinning. I think looking at that code can be useful when doing COLLADA importer for skinned models.

Some quick tips:
Microsoft’s default implementation of ModelProcessor.ProcessVertexChannel() converts VertexElementUsage.Color channels to Color format, and replaces VertexChannelNames.Weights () channels with a pair of VertexElementUsage.BlendIndices and VertexElementUsage.BlendWeight channels (using Byte4 format for the VertexElementUsage.BlendIndices, Color for the VertexElementUsage.BlendWeight, and discarding excess weights if there are more than four per vertex).

MeshHelper.FlattenSkeleton(MeshHelper.FindSkeleton(root)) gets a flattened list of all bones contained by the specified skeleton. The tree is calculated using a depth–first traversal. The bones are returned in the same order used by ModelProcessor to convert BoneWeight vertex channels into the BlendIndices format.

Beware: Microsoft’s model processor assigns BlendIndices in a very obscure way. What MeshHelper.FlattenSkeleton(MeshHelper.FindSkeleton(root)) returns has nothing to do with Model.Bones[i].BoneIndex at runtime. Perharps the best practice would be to override ModelProcessor.ProcessVertexChannel() and assign BlendIndices in a mode predictable way.

I agree that Microsoft’s Content DOM is not ideal and COLLADA DOM is much better. But for the beginning I would recommend to stick with Microsoft’s Content DOM. Alternative approach would require too much coding and would create too much confusion. At this point in time, people have trouble figuring out fairly basic XNA Content Pipeline stuff.

Is there a way to contribute to COLLADA for XNA? I think I could help with animation support but I don’t know how to start. I mean, is it possible to host COLLADA for XNA at Sourceforge.net or Codeplex.com, so that people could contribute in an organized way?

Michael Nikonov

I apologies if I am being off-topic, but let me suggest a feature:

We want C# at PS3!
(and an equivalent of XNA Framework, that is some good C# API from Sony).

Hello,
I’m getting this unusual error and I can’t figure out for the life of me why.
All I’ve done here is added the collada libraries to my content pipeline and loaded a mesh. It is not even being pointed to in my code yet.

Any thoughts?

Thanks Kyle

Error 1 Building content threw KeyNotFoundException: The given key was not present in the dictionary.
at System.ThrowHelper.ThrowKeyNotFoundException()
at System.Collections.Generic.Dictionary2.get_Item(TKey key) at COLLADA.Conditioner.Reindexor(Document doc) in C:\Workspace-Kyle\SmokeAndMirrors\External\COLLADAPipeline\COLLADAPipeline\COLLADAConditioner.cs:line 128 at COLLADA.COLLADAImporter.Import(String filename, ContentImporterContext context) in C:\Workspace-Kyle\SmokeAndMirrors\External\COLLADAPipeline\COLLADAPipeline\COLLADAPipeliner.cs:line 747 at Microsoft.Xna.Framework.Content.Pipeline.ContentImporter1.Microsoft.Xna.Framework.Content.Pipeline.IContentImporter.Import(String filename, ContentImporterContext context)
at Microsoft.Xna.Framework.Content.Pipeline.BuildCoordinator.ImportAssetDirectly(BuildItem item, String importerName)
at Microsoft.Xna.Framework.Content.Pipeline.BuildCoordinator.ImportAsset(BuildItem item)
at Microsoft.Xna.Framework.Content.Pipeline.BuildCoordinator.BuildAssetWorker(BuildItem item)
at Microsoft.Xna.Framework.Content.Pipeline.BuildCoordinator.BuildAsset(BuildItem item)
at Microsoft.Xna.Framework.Content.Pipeline.BuildCoordinator.RunTheBuild()
at Microsoft.Xna.Framework.Content.Pipeline.Tasks.BuildContent.RemoteProxy.RunTheBuild(BuildCoordinatorSettings settings, ITaskItem[] sourceAssets, TaskLoggingHelper msbuildLog, String[]& outputContent, String[]& rebuiltContent, String[]& intermediates) C:\Documents and Settings\Kyle Rocha\My Documents\Visual Studio 2005\Projects\WindowsGame2\WindowsGame2\Content\Models\Collada-Smoke_skelmesh.dae WindowsGame2

BTW: I have just posted a little project to load collada files with skinning and skeletal bone animation (plus normal mapping, etc. the usual) on my blog.

Check it out: http://abi.exdream.com
Full source code, video and demo app can be downloaded there.

Screenshot from the project:

Hi,

Can you send me the .dae file ?
remi at collada.org

thanks

That is a really good job you did there, lots of technical details. Thanks a lot for making this available !

That unusual error only seems to be on the objects that I export and not the ones that are in the collada model library.

I’m not sure what to do about the data being incorrect if its coming out of the 3DS MAX exporter.

Cheers
Kyle

It is most probably a bug in the conditioner. Could you send me an example that does not work to remi (at) collada.org, so I can fix that for you.

Regards

Thanks for reporting this bug, turns out it was the new exporter now includes additional inputs that I had not tested before.

The problem is fixed (see patch 1 on top of this thread)

Please keep reporting issues !

Remi,

thanks for the work you have done on this!

Having tried it, I have noticed that you use nested classes a great deal. This isn’t really the C# way of doing things, and it rather messes up any attempt to follow the structure of the software using the class designer in VS2005 professional. Would it be possible for you to use namespaces to group classes, rather than nesting classes? I have already done this to some extent after I downloaded the code, in an attempt to make some changes to pick up data that was missing from some files exported from 3DS Max.

This brings me onto another issue - to really be able to commit to using this library we need some way of merging improvements back into the main development stream.

Andy.

Heja out there…

Firstly a big thank you for this work. Haven’t it tested it deeply yet but right now it works quite well.

What about making an Open Source Project out of this? It would be much easier to report bugs and help and first of all: to download it :lol:

markus

Hi!

I’m actually writing a COLLADA loader for Managed DX. I’ve started with the COLLADA loader from remi a while ago. Everything (including bone animation) worked fine for a time but now i stumbled upon a problem:

In the Visual Scene I’ve got a root node with no sid and “NODE” as type instead of “JOINT” or “BONE”. Any suggestion how to deal with that or how to prevent that?

Matthias

PS: I’m using Max 8 & the Feeling exporter