Sign in to follow this  
Drakken255

Blender to XNA: Skinned Model Renders Black

Recommended Posts

Drakken255    173
Hello all,

I am trying to import a skinned model into the Skinned Sample from Microsoft. The model was built in Blender 2.63a with an animation, a material, and a texture. The model renders perfectly with Blender, but once exported, the model loses all reference to the texture. The model renders black in XNA, and VS doesn't complain about the missing texture in the folder. Anyone know why Blender is not exporting the texture or at least a reference to it? I tried recreating the material and the texture, but to no avail.

Share this post


Link to post
Share on other sites
BCullis    1955
What effect are you using to render the model? If it's not a BasicEffect, can we see the shader code?

Also, does VS give you any warnings about the loaded assets?

if you put the associated texture into the <Projectname>Content folder of your project, do you get a warning that it's being compiled twice?

Share this post


Link to post
Share on other sites
Drakken255    173
The shader is the skinned model sample shader, yes, VS would warn me of a missing asset, and no it's not warning me that it's being compiled twice. I am fairly sure Blender is not exporting the texture or any reference to it. Edited by Drakken255

Share this post


Link to post
Share on other sites
CC Ricers    1491
I'm just assuming but I'm 99% sure you're exporting to .fbx since it's a common format for XNA animations and the sample uses this format, right?

Usually the compiler would give an error in the model processing phase of the build when it looks for a texture that is not found. Otherwise, the content processor you're using has a way to handle missing textures and just puts a default one in its place.

Coincidentally, I was fixing up a third-party model I exported from Blender yesterday in trying to get its textures to load too. I didn't export the animations but it was an .fbx format. When done incorrectly, exporting as .fbx would cause the model's texture to have "untitled" in its file path, so you will end up with nothing. You need to check the image list in the UV image editor. Select your mesh, go into Edit Mode and press A to highlight all the vertices. In the UV editor window, choose the image you want to associate the mesh with. Then save the file and export.

There's a chance you also have some unused image datablocks named "untitled" possibly followed with a number, which you will see in the list of images in the UV editor. Shift-click to unlink and remove these, save, and re-open the blend file. It was kind of tricky trying to figure this out myself, I was also struggling for hours figuring out why the model's texture wasn't showing.

[b]Edit:[/b] Yep, I checked my original model again and you will get references to "untitled" in the exported file (search for this string in the file with quotes included). You gotta remove any references to those image files as I've mentioned before. Edited by CC Ricers

Share this post


Link to post
Share on other sites
Drakken255    173
Turns out that is not quite the problem. When I open the .fbx in notepad, I find absolutely no reference to a texture whatsoever. I haven't checked about unnecessary links yet, though.

EDIT: I saved, closed, reopened, and exported and now there is a reference to textures... And it does point to the correct file... I don't just get it... Lemme provide a link to the fbx and blend files:

[url="https://dl.dropbox.com/u/10337688/SteveIdle01.blend"]https://dl.dropbox.com/u/10337688/SteveIdle01.blend[/url]
[url="https://dl.dropbox.com/u/10337688/SteveIdle01.fbx"]https://dl.dropbox.com/u/10337688/SteveIdle01.fbx[/url] Edited by Drakken255

Share this post


Link to post
Share on other sites
CC Ricers    1491
What do you see when you load the .fbx with the correct texture names? Does the new exported file work correctly? I checked the .fbx, and do see a texture name in RelativeFilename of the texture object, and it has a connection with the texture to the HumanMesh model.

Share this post


Link to post
Share on other sites
Drakken255    173
From SkinnedModelProcessor (Modified):

[spoiler]
[code]
[b]#region File Description[/b]
[b]//-----------------------------------------------------------------------------
// SkinnedModelProcessor.cs
//
// Microsoft XNA Community Game Platform
// Copyright © Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
#endregion[/b]
[b]#region Using Statements
using System;
using System.IO;
using System.Collections.Generic;
using System.ComponentModel;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content.Pipeline;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
using Microsoft.Xna.Framework.Content.Pipeline.Processors;
using SkinnedModel;
#endregion[/b]
[b]namespace SkinnedModelPipeline
{
/// <summary>
/// Custom processor extends the builtin framework ModelProcessor class,
/// adding animation support.
/// </summary>
[ContentProcessor(DisplayName = "Skinned Model")]
public class SkinnedModelProcessor : ModelProcessor
{
/// <summary>
/// The main Process method converts an intermediate format content pipeline
/// NodeContent tree to a ModelContent object with embedded animation data.
/// </summary>
public override ModelContent Process(NodeContent input,
ContentProcessorContext context)
{
RotateAll(input, 90, 0, 180, 2);[/b]
[b] ValidateMesh(input, context, null);[/b]
[b] // Find the skeleton.
BoneContent skeleton = MeshHelper.FindSkeleton(input);[/b]
[b] if (skeleton == null)
throw new InvalidContentException("Input skeleton not found.");[/b]
[b] // We don't want to have to worry about different parts of the model being
// in different local coordinate systems, so let's just bake everything.
FlattenTransforms(input, skeleton);[/b]
[b] // Read the bind pose and skeleton hierarchy data.
IList<BoneContent> bones = MeshHelper.FlattenSkeleton(skeleton);[/b]
[b] if (bones.Count > SkinnedEffect.MaxBones)
{
throw new InvalidContentException(string.Format(
"Skeleton has {0} bones, but the maximum supported is {1}.",
bones.Count, SkinnedEffect.MaxBones));
}[/b]
[b] List<Matrix> bindPose = new List<Matrix>();
List<Matrix> inverseBindPose = new List<Matrix>();
List<int> skeletonHierarchy = new List<int>();
Dictionary<string, int> boneIndices = new Dictionary<string, int>();[/b]
[b] foreach (BoneContent bone in bones)
{
bindPose.Add(bone.Transform);
inverseBindPose.Add(Matrix.Invert(bone.AbsoluteTransform));
skeletonHierarchy.Add(bones.IndexOf(bone.Parent as BoneContent));
boneIndices.Add(bone.Name, boneIndices.Count);
}[/b]
[b] // Convert animation data to our runtime format.
Dictionary<string, AnimationClip> animationClips;
animationClips = ProcessAnimations(skeleton.Animations, bones);[/b]
[b] // Chain to the base ModelProcessor class so it can convert the model data.
ModelContent model = base.Process(input, context);[/b]
[b] // Store our custom animation data in the Tag property of the model.
model.Tag = new SkinningData(animationClips, bindPose,
inverseBindPose, skeletonHierarchy,
boneIndices);[/b]
[b] return model;
}[/b]
[b] public static void RotateAll(NodeContent node, float degX, float degY, float degZ, float scaleFactor)
{
Matrix rotate = Matrix.Identity *
Matrix.CreateRotationX(MathHelper.ToRadians(degX)) *
Matrix.CreateRotationY(MathHelper.ToRadians(degY)) *
Matrix.CreateRotationZ(MathHelper.ToRadians(degZ));[/b]
[b] Matrix transform = Matrix.Identity * Matrix.CreateScale(scaleFactor) * rotate;
MeshHelper.TransformScene(node, transform);
}[/b]
[b] /// <summary>
/// Converts an intermediate format content pipeline AnimationContentDictionary
/// object to our runtime AnimationClip format.
/// </summary>
static Dictionary<string, AnimationClip> ProcessAnimations(
AnimationContentDictionary animations, IList<BoneContent> bones)
{
// Build up a table mapping bone names to indices.
Dictionary<string, int> boneMap = new Dictionary<string, int>();[/b]
[b] for (int i = 0; i < bones.Count; i++)
{
string boneName = bones[i].Name;[/b]
[b] if (!string.IsNullOrEmpty(boneName))
boneMap.Add(boneName, i);
}[/b]
[b] // Convert each animation in turn.
Dictionary<string, AnimationClip> animationClips;
animationClips = new Dictionary<string, AnimationClip>();[/b]
[b] foreach (KeyValuePair<string, AnimationContent> animation in animations)
{
AnimationClip processed = ProcessAnimation(animation.Value, boneMap);

animationClips.Add(animation.Key, processed);
}[/b]
[b] if (animationClips.Count == 0)
{
throw new InvalidContentException(
"Input file does not contain any animations.");
}[/b]
[b] return animationClips;
}[/b]

[b] /// <summary>
/// Converts an intermediate format content pipeline AnimationContent
/// object to our runtime AnimationClip format.
/// </summary>
static AnimationClip ProcessAnimation(AnimationContent animation,
Dictionary<string, int> boneMap)
{
List<Keyframe> keyframes = new List<Keyframe>();[/b]
[b] // For each input animation channel.
foreach (KeyValuePair<string, AnimationChannel> channel in
animation.Channels)
{
// Look up what bone this channel is controlling.
int boneIndex;[/b]
[b] if (!boneMap.TryGetValue(channel.Key, out boneIndex))
{
throw new InvalidContentException(string.Format(
"Found animation for bone '{0}', " +
"which is not part of the skeleton.", channel.Key));
}[/b]
[b] // Convert the keyframe data.
foreach (AnimationKeyframe keyframe in channel.Value)
{
keyframes.Add(new Keyframe(boneIndex, keyframe.Time,
keyframe.Transform));
}
}[/b]
[b] // Sort the merged keyframes by time.
keyframes.Sort(CompareKeyframeTimes);[/b]
[b] if (keyframes.Count == 0)
throw new InvalidContentException("Animation has no keyframes.");[/b]
[b] if (animation.Duration <= TimeSpan.Zero)
throw new InvalidContentException("Animation has a zero duration.");[/b]
[b] return new AnimationClip(animation.Duration, keyframes);
}[/b]

[b] /// <summary>
/// Comparison function for sorting keyframes into ascending time order.
/// </summary>
static int CompareKeyframeTimes(Keyframe a, Keyframe b)
{
return a.Time.CompareTo(b.Time);
}[/b]

[b] /// <summary>
/// Makes sure this mesh contains the kind of data we know how to animate.
/// </summary>
static void ValidateMesh(NodeContent node, ContentProcessorContext context,
string parentBoneName)
{
MeshContent mesh = node as MeshContent;[/b]
[b] if (mesh != null)
{
// Validate the mesh.
if (parentBoneName != null)
{
context.Logger.LogWarning(null, null,
"Mesh {0} is a child of bone {1}. SkinnedModelProcessor " +
"does not correctly handle meshes that are children of bones.",
mesh.Name, parentBoneName);
}[/b]
[b] if (!MeshHasSkinning(mesh))
{
context.Logger.LogWarning(null, null,
"Mesh {0} has no skinning information, so it has been deleted.",
mesh.Name);[/b]
[b] mesh.Parent.Children.Remove(mesh);
return;
}
}
else if (node is BoneContent)
{
// If this is a bone, remember that we are now looking inside it.
parentBoneName = node.Name;
}[/b]
[b] // Recurse (iterating over a copy of the child collection,
// because validating children may delete some of them).
foreach (NodeContent child in new List<NodeContent>(node.Children))
ValidateMesh(child, context, parentBoneName);
}[/b]

[b] /// <summary>
/// Checks whether a mesh contains skininng information.
/// </summary>
static bool MeshHasSkinning(MeshContent mesh)
{
foreach (GeometryContent geometry in mesh.Geometry)
{
if (!geometry.Vertices.Channels.Contains(VertexChannelNames.Weights()))
return false;
}[/b]
[b] return true;
}[/b]

[b] /// <summary>
/// Bakes unwanted transforms into the model geometry,
/// so everything ends up in the same coordinate system.
/// </summary>
static void FlattenTransforms(NodeContent node, BoneContent skeleton)
{
foreach (NodeContent child in node.Children)
{
// Don't process the skeleton, because that is special.
if (child == skeleton)
continue;[/b]
[b] // Bake the local transform into the actual geometry.
MeshHelper.TransformScene(child, child.Transform);[/b]
[b] // Having baked it, we can now set the local
// coordinate system back to identity.
child.Transform = Matrix.Identity;[/b]
[b] // Recurse.
FlattenTransforms(child, skeleton);
}
}[/b]

[b] /// <summary>
/// Force all the materials to use our skinned model effect.
/// </summary>
[DefaultValue(MaterialProcessorDefaultEffect.SkinnedEffect)]
public override MaterialProcessorDefaultEffect DefaultEffect
{
get { return MaterialProcessorDefaultEffect.SkinnedEffect; }
set { }
}
}
}[/b]
[/code]
[/spoiler] Edited by Drakken255

Share this post


Link to post
Share on other sites
Sesha    107
Hi Drakken255 try removing the TEXCOORD from the effect file and compile just with the material if it succeed then it is with blender. I know some in directx and not XNA.

Share this post


Link to post
Share on other sites
Drakken255    173
If I am correct, removing the TEXCOORD from the effect and he texture from the mesh would result in a flat grey model, right? Well I am highly certain the problem is with Blender, anyway. How else would the standard dude.fbx work where my own won't? Also, it seems that despite my program recognizing SkinnedEffect, it's not in my project! I don't get it. I thought SkinnedEffect was a custom MS effect and came with the sample... Edited by Drakken255

Share this post


Link to post
Share on other sites
AmzBee    485
Try the following to debug the problem:
[list=1]
[*]Add a breakpoint during the render of your model, check that the texture is actually loaded in the first place.
[*]Make sure when you export your FBX file through blender that you tick the option: [b]XNA Strict Options[/b]
[*]As your model is skinned, make sure that the textures are placed where XNA expects them, you can check this by opening up the fbx file and looking for the texture references.
[*]Make sure (and I know this is obvious but it's worth checking) that you have UV mapped your mesh and not just applied a texture material, because generated UV's don't automatically export.
[*]If none of the above fix the issue, then it's possible there is something wrong with the rendering code you are using, if you are using a custom shader to display
the model, let us see what you have so that we can better help. If you have your own lighting system, perhaps the model is simply not being lit.
[/list]
Hope this helps, and keep us up to date on your progress.

Aimee

Share this post


Link to post
Share on other sites
Drakken255    173
Thanks for your reply.

1: I may be using custom code, but even using the Skinned Sample by MS with no changes other than the model and animation name strings, it stays black.

2: I am always sure to correctly export the model.

3: My texture is placed in the right spot and when it isn't, VS complains like it is supposed to.

4: Wait... Blender doesn't export my UV map? I did make my own... This sounds like a part of the problem, but I don't know...

5: Similar to 1.

EDIT: My models did work ok with Blender 2.6, whereas now I am using 2.63a. Is this an issue? Edited by Drakken255

Share this post


Link to post
Share on other sites
CC Ricers    1491
I use Blender 2.59 so I'm not sure if the issue is there.
Things that you may want to keep in mind: The SkinnedModelProcessor is not where the problem is. Something seems to be up with the way Blender prepares textures in your model upon export.

I opened your Blend file, scrapped the extra textures, and just exported the Steve mesh with one texture. In XNA he appears tiny (probably just the size the model was made) and indeed appears black. Then it hit me... Did you choose to pre-multiply Alpha in your texture processor? My guess it's set to True by default in the Skinned sample. Try turning it off. Had the same problem when I dug up an SSAO shader I made many months ago and for some reason the randomized normals texture was rendering in black making the AO look weird. The alpha channel of the texture was black, so I had to disable pre-multiply alpha to get it working again. Edited by CC Ricers

Share this post


Link to post
Share on other sites
CC Ricers    1491
[url="http://msdn.microsoft.com/en-us/library/bb199633.aspx"]Here's how.[/url]

With this window open, click on the little arrow or plus sign next to Content Processor and then you should see an option called "Premultiply Texture Alpha". Set it to false.

Share this post


Link to post
Share on other sites
Drakken255    173
Thanks. Will try after work and edit with results.

EDIT: Setting the pre-multiply alpha setting on either the model or the texture didn't work...
Not even removing all transparency options in Blender works... Edited by Drakken255

Share this post


Link to post
Share on other sites
AmzBee    485
Just wanted to follow up on this as it seems you haven't solved the issue yet, and I needed a break from just writing shader code for our tech featuring at eurogamer expo this year lol. Just now I grabbed your blend file and had a poke around, seems that you have unwrapped the mesh, but you have not yet coupled a texture to the UV's, so heres what I did, I loaded a basic texture I had in edit mode while the UV's were visible, then went to the texture properties tab, scrolled down to [b]Mapping -> Map [/b]and chose [u]UVTex[/u] so that the UV's were properly assigned.

After that I exported as I mentioned, and it works fine, to prove it here is a screenie of your model running in our engine Phobius (this is an XNA engine):

[img]http://imageshack.us/a/img222/6275/stevehpx.jpg[/img]

The issue is entirely down to making sure that your model is properly UV mapped, so try have another pop. If you still have problems PM me and I'll send you the modified blend file so you can see what I did.

Aimee.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this