Thursday, 20 October 2011

Multiple FBX Animations

Today I used the Windows Phone SDK v7.1 for the first time.  This includes a fix for the FBX importer so that multiple animations can now be imported from one FBX file.

This is great but a friend of mine managed to find a bug with the process already!  I have also carried out some tests.

Whatever the length of the first animation in the FBX file is the length of all animation imported from that FBX file.  I only have access to FBX files exported myself from Blender.

To try to explain a bit better, if your first animation has 30 frames and your second take has 60 frames, the second animation in XNA will only play 30 frames instead of 60!  It is also a problem the other way round.  If the first animation is 240 frames and the you have an animation that should loop at 60 frames unfortunately it will pause after the 60th frame and not loop until it gets to 240 frames!


With that knowledge it is possible to create separate files with all the same length animations in each file, or just export one action per file as I already do.

As far as I can tell the Blender FBX file lists the correct number of frames and times for each action.  I would like to know if this affects other exporters, such as 3DS.  Unless I have a sample FBX file that does work with multiple takes in XNA with the first being a different length to the others then there is little chance of me being able to create a script that works round this peculiarity!

For myself I am already used to exporting individual animations so I will continue to do so. 

== Follow up ==
I posted a question on the XNA forums to see if it is a problem for others:
http://xboxforums.create.msdn.com/forums/p/93794/561812.aspx

I had a very nice reply from one of the XNA developers who confirmed that they see the same problem and it will now be reported as a bug.

I have a project that may help some people.  It includes methods for splitting FBX files and allows loading of one animation at a time for testing:
http://code.google.com/p/3d-model-prep/

I keep my animations separate from the model anyway so they can be shared but if you want to merge them all together the following article explains how:
http://blogs.msdn.com/b/shawnhar/archive/2010/06/18/merging-animation-files.aspx

Thursday, 13 October 2011

More On Shadows

I think I might finally have a solution that works for shadows.

I tried ID shadow maps and they worked well and in combination with the baked in ambient occlusion (AO) the scenes looked good...     in most places!

There is always a catch.  Objects that went underground shadowed the ground incorrectly!  As I had slopes it would have been very difficult to avoid some models that had to be partially underground on at least one side.

To cut a lot of time and trial and error out of this story, the solution was a combination of percentage closer filtering (PCF) and ID shadow maps combined.  I tried variance shadow maps (VSM) with ID but the limitations of the surface formats available on the Xbox meant I could not get enough depth precision at the same time as storing the object ID.  I might still be able to do it with VSM by packing the three variables in to the available formats but that is for another day.  PCF with ID only needs two variables so the Vector2 surface format works well for that.



The sampling method first checks the ID and avoids self shadowing models.  Only then does it resort to the common PCF method using 4 samples.  Anything much more than four samples on the Xbox results in a sudden massive halving of the frame rate which I put down to predicated tiling.


The following is the most significant part of the shader.



float PID_Sample(
        float2 vTexCoord, 
        float fLightDepth, 
        float entityID)
{
    float lit = 1.0f;

    float2 fSample = 
        SAMPLE_TEXTURE(ShadowMap, vTexCoord);
    float casterID = fSample.y;
    // The render target is initialised to white 
    // GraphicsDevice.Clear(Color.White)
    if (casterID < 1.0f && 
        casterID != entityID && 
        fLightDepth >= fSample.x)
    {
        lit = 0.0f;
    }
    return lit;
}


float PID_BoxSampleLightFactor(float4 shadowTexCoord)
{
    float fLightDepth = 
        shadowTexCoord.z - ShadowDepthBias;
    float texelStepSize = 1.0f / ShadowSize;
    // Work in floats
    float entityID = IntToFloat(ShadowEntityID);
    // Sample round the position
    float shadow[4];
    shadow[0] = PID_Sample(shadowTexCoord, 
        fLightDepth, entityID);
    shadow[1] = PID_Sample(shadowTexCoord + 
        float2(texelStepSize, 0), 
        fLightDepth, entityID);
    shadow[2] = PID_Sample(shadowTexCoord + 
        float2(0, texelStepSize), 
        fLightDepth, entityID);
    shadow[3] = PID_Sample(shadowTexCoord + 
        float2(texelStepSize, texelStepSize), 
        fLightDepth, entityID);

 float2 lerpFactor = frac(ShadowSize * shadowTexCoord);
 // Linear extrapolate between the samples
 return lerp(lerp(shadow[0], shadow[1], lerpFactor.x),
    lerp(shadow[2], shadow[3], lerpFactor.x),
    lerpFactor.y);
}



The shadows fade out after about 40m from the camera.  I have found that anything less is a bit distracting in game but 40m is hard to see.  Even with a 2048x2048 shadow map the shadows are a tiny bit more pixelated than my ideal but it is the best I can manage at the moment.




The most important thing is that it works on the Xbox 360 and keeps a solid 60 frames per second with room to spare.

I can now move on to other things... again.


Friday, 7 October 2011

That Hut Again

I have another screenshot for you showing the same hut that I created a few weeks ago.  The point this time is to demonstrate the improved shading of the texturing.  At least I think it looks better.  Feel free to comment.


I used the ambient occlusion (AO), described a couple of days ago, baked in to the texture of the model. 

Now I have AO pre-calculated I have removed the awkward and artifact prone self shadowing from the models.  For that purpose I am using ID based shadow maps.

There was surprisingly little information easily available on the Internet.  Perhaps I searched for the wrong key words.  Anyway, most of the ID shadow code is based on one of the many other shadow and filter methods I have tried up to now.

The difference being that instead of comparing the stored shadow depth it simply checks if the thing closest to the light is it'self or not using a unique index number for each model.

The tricky bit I found with ID shadow maps was converting a signed integer to a float in Shader Model 3.  In the newer Shader Model 4 there are loads of built in methods to do that and I could have used an unsigned int which would have been even easier.  None of that was available for the Xbox shader though!  I used the following simple code:



// Consistently convert a signed integer32 to a float
// in the range 0.0f to 1.0f
float IntToFloat(int inputID)
{
    // The maximum value for a signed 32 bit integer
    return (float)inputID / 2147483647.0f;
}




Once everything was a float it was easy to compare.

==

Unrelated to any of the above I have just exceeded the free 200M capacity of the Subversion (SVN) hosting service I have been using.  The project including documentation is 128M and the rest must be the change history.  I have become used to using source control and would miss it if it was not there so I have decided to pay for more capacity rather than mess about chopping up the project in to smaller chunks or trying to manage my own server.  I am trying out a couple of the lower cost SVN hosts to decide which I will use going forwards.

Wednesday, 5 October 2011

UV Unwrap In Blender

I have just worked out a way to unwrap models in Blender that produces a result that makes it much easier when creating and working with textures outside of Blender.  I thought I'd better write it down before I forget.

The default options on my setup produce some strangely stretched and distorted results and it is difficult to fit them on the rectangular shaped textures.  I have tried all the clever methods on the drop down list of unwrap choices and always end up using the standard unwrap even though I had not been entirely happy with the result.




Now, using the same menu selection, I have at last noticed the tools options panel appear at the bottom of the tool window, usually on the right hand side of a 3D window.  If you have expanded the little plus and if your screen and Blender window are big enough to see the bottom of the main tool list!

"I think this is why Blender has a reputation for having a difficult UI."

The Unwrap window ONLY appears AFTER you have pressed the Unwrap button and goes away again as soon as you move on to another task!

Look out for this small set of options:




The bit that now makes the Unwrap do exactly as I would like, is the method, 'Conformal'

Don't ask me what that means I have no idea.  What it does is to make all the shapes stay as close as possible in proportion to each other and not distorted.  So a square remains a square and not a squashed rhomboid shape that I used to get.  You have to change the default, 'Angle' option to 'Conformal', every time.  I don't know how to default to 'Conformal' yet.




I find this arrangement much easier to work on in GIMP to align the textures to look correct.  I use the Export UV menu item and use the result as a layer in GIMP and just hide it before saving the final texture.

Back to Blender...

As mentioned above, the odd thing about some Blender options is that they don't become visible until after you have performed the action. When you change the option, in the temporarily visible tool window, the result of the action is changed.   In this case you Unwrap the model to a mess and then change the option and it all neatly lines up for you as you watch.

It can only do as good a job as the seams you have marked, so I spend a lot of time in advance ensuring that all sections of the model can be unwrapped to a flat shape without having to stretch too many of the edges..


It's a bit labourious but just select an edge or two or three and press the 'Mark Seam' button.  Change your mind, select the same edge and press, you guessed it, 'Clear Seam'.  You can Unwrap again and again, to try it out.

Anyway, as I started out by saying, I have found a UV Unwrap method I am happy with.

Tuesday, 4 October 2011

Ambient Occlusion Baked In

I've had a busy time working on the shadows and failing to make the results of my shaders quite as pretty as I would like and still get them to run on the Xbox.  That has made me realise I am unlikely to get enough spare GPU resources to add Screen Space Ambient Occlusion (SSAO) as a post process to make things look even prettier.

That led me back to some thoughts I had in the back of my mind that I should be baking more in to the textures at design time so I am doing less work at runtime.  I write code so I prefer a technical solution to problems rather than a pure artistic however there comes a time when even I have to admit defeat.



Bake
This is the term used to describe fixing a machine calculated effect in to a static texture. The modeling or graphics programme calculates some pattern which can be changed and tweaked by the artist until they are happy with the result. Once they have the final version it has to be merged in to the product, usually a texture, so that it can be viewed or used outside of the programme that generated it.

It only took me two minutes of searching to find instructions and tutorials for Blender to create the Ambient Occlusion (AO) effect and to bake it in to the UV wrapped texture.

There were loads of links here are just a couple:
http://www.katsbits.com/tutorials/blender/blender-2.5-baking-ambient-occlusion.php

I used some bits of the tutorial to do the following:


The process was very simple:
  • Create the model
  • UV wrap the model and manually position the UV's so they do not overlap.
  • Select the model in Object Mode
  • Find the Bake panel at the end of the Render section (little camera icon)
  • Select Ambient Occlusion... 
  • Tick (check), Normalise and Clear.
  • Press the Bake button.
It might take a minute or two depending on the complexity of the model and how fast your computer is.



The result a model showing just the shaded areas.  Now I need that and the diffuse texture together.


  • Save the new UV texture, which is just the shading.
  • Use that texture as a layer in GIMP or Photoshop.  Set the layer type to Multiply so the white areas use more of the underlying colour and the black areas use less of that colour.  (Initially I was converting the white areas transparent, Color to Alpha in the layer menu and change the white colour to transparent.  That works just as well but had one more step.)




  • Save it as a PNG
  • Reload it in to Blender.


Now I have a textured model with shading.

  

  
Just what I was after.

The advantage of using AO is that it is independent of the lighting in a scene.  It is actually completely fake, it is an artistic effect, there is no such thing in the real world.  It makes things look more 3D.

I will be using this on many of the models in the game.

XBox HLSL Peculiarity

While fiddling with my shadow code to try and get more reliable performance on the Xbox I changed my HLSL shader code so I could quickly make changes to a pair of nested loops that calculated the weighted average.

Most of the testing was done on the PC and it all worked fine.  When I tried it on the Xbox the shadows had completely gone!

It took a while to find but it was the simplest code possible that did not work on the Xbox but worked perfectly on the PC.




 float shadowTerm = 0.0f;
 int sampleCount = 0;
 for (float y = 0; y <= sampleRadius; y++)
 {
   for (float x = -sampleRadius; x <= sampleRadius; x++)
   {
     float sample = something();     
     shadowTerm += sample;
     sampleCount++;
   }                      
 }    
 // Average by the number of values
 shadowTerm /= sampleCount;
  


That code does NOT work on the Xbox.
Note the sampleCount immediately following the shadowTerm in the middle of the loop.
What could be simpler code.

Take it out and replace with a sum at the end after the loop and...
the following code does work on the Xbox!


  float shadowTerm = 0.0f;
  for (float y = 0; y <= sampleRadius; y++)
  {
    for (float x = -sampleRadius; x <= sampleRadius; x++)
    {
      float sample = something(); 
      shadowTerm += sample;
    } 
  } 
  // Average by the number of values
  shadowTerm /= 
        (sampleRadius + 1) * 
        ((sampleRadius *2) + 1);
  


If anyone has a reason other than the compilers are different, please let me know.

Sunday, 2 October 2011

3D Modeling Tools

I've just read a comment on one of my other posts asking about what I think about the free modeling tools available.  The reply to the comment became too long so I decided to reply by way of this post.

Despite how my blog may appear, I'm not as confident about 3D modeling as I sound.  I've just been learning it for nearly two years on and off so have some experience!  Based on my school results some 30 plus years ago, I do have an aptitude for technical drawing.  I think that helps a bit.

The type of models I produce tend to be architectural.  When it comes to more organic shapes I have to get others to do those.

About 2 years ago I tried most of the free modeling tools.  Some were difficult to use, some too simple for game models and for some there were no working FBX or DirectX exporters suitable for XNA.  I ended up with just three that I gave any serious time to.

I had a brief look at the commercial products but the popular ones used throughout the professional games industry have a price tag of about US$ 3,500.  I suspect that a lot of the people using those for Indie games would be the first to complain about their game being pirated!   Some hobby developers can get away with educational licences.   I want to distribute my game for the Xbox which Microsoft have forced to have a price tag.  Therefore any game released for the Xbox is commercial, no matter what the intention or how low the cost is.

That left me with the shareware, open source or free products.  Without much effort it is easy to establish that by far the most fully featured, continuously developed and suitable tool is Blender.  I have never used 3DS or Maya but from what I can tell Blender is as powerful and in some areas probably more feature rich than those paid for products.

Blender is a powerful tool but like nearly all complicated products it requires quite a while to master.  Having tried it and become very lost, especially with a non-Windows style interface, I looked at other products first.

The three tools that I had reduced my list to were, the free Autodesk Mod Tool, Blender and the free version of Google SketchUp.

To quickly dispense with one of those, Mod Tool.  I tried it, found it's pipeline so confusing I could not work out how to export a model and the whole layout just put me off.  I think but didn't give it much of a chance, that it attempts to simplify some of the technical details about 3D modeling but in the process loses the plot!  I have not looked at it since.

The product I do like is Google SketchUp.  I have only used the free version and I have not spent much time using version 8 but I did use version 7 quite a bit.  It is very quick to learn and easy to produce buildings and things.  The interface is very natural to use.  I wish more powerful tools had this simple and well thought out UI.

For XNA there is a snag with the free SketchUp.  It does not come with any suitable exporters!  The official FBX exporter only comes with the paid for Pro version.  However, if you hunt long enough on the Internet you should find some third party FBX exporters that work with the free SketchUp.  Also SketchUp files are just compressed Colada files and Autodesk do a free converter from Colada to FBX.

SketchUp gets tricky when you want to have more controls to optimise the result for use in game.  Controlling the UV map to minimise the number and sizes of textures or reducing the number of vertices is tricky and may not even be possible in SketchUp.  It's at these times that I kept ending up in Blender to finish a model.  Also I have no idea if SketchUp can do boned animations which I need for my game.  Another plus point for Blender.

Once I had decided that I would need Blender, I spent two weeks just learning the very basics.  Remember I was learning both the product and 3D modeling from nearly scratch.  Over the last two years I have watched numerous tutorials on line and have even purchased two Blender training DVD's.  I also laminated the Blender Hot Key chart and have it permanently to hand.

What I now have is a huge respect for 3D artists.  There is no quick fix that us developers would like.  As a developer I think nothing of spending hours on one small method to get it just right.  That time and more goes in to the fine detail of each good model and each texture on those models.  The work on some of the individual models in AAA games must be as much as I have spent modeling... ever.

All modelling tools, except perhaps SketchUp require a lot of practice to be able to work with.  I decided to concentrate on knowing just one rather than a little about all.  I now almost exclusively use Blender.

Blurring Variance Shadow Maps

Before we get too far, let me point out from the start I have not got this working!

This is the original Variance Shadow Map (VSM) article I have been reading:
http://developer.download.nvidia.com/SDK/10/direct3d/Source/VarianceShadowMapping/Doc/VarianceShadowMapping.pdf

This is one of several Gaussian Blurs I have tried:
http://www.dhpoware.com/demos/xnaGaussianBlur.html


The process sounded simple.  Take the RenderTarget for the shadow map.  Apply the blur in a shader and use that blurred shadow map to render the scene.

Well after lots of late night fiddling with samples and blurs, and after solving the errors about Vector2 requiring Point filtering I still get blurred shadows that are too blurred at the wrong edge where it should be solid and still pixelated at the shadow margin where I want it blurred! 

If anyone knows what important information I am missing, please post a comment telling me how.

I have stuck with my edge filtering for now and intend to try ID based shadow maps next.