Thursday, 28 June 2012

Bump/Normal Mapping



Now I have had a hell of a game with this in the past and have now come across a few people who have had the same or similar issues that I have had. So I have decided to put up and example of how to do this. The example given on theCreators Club is great, but relies on you embedding your textures in the mesh you intend to bump. This is all fine, but if you have one model and you want to apply different textures to different instances of it then this method is limiting.

So I have decided to post up my code to do this. Now as I said before, I have had a right load of trouble with this right from the start, you can see my tales of woe on the HazyMind forum and on the XNA Creators Club forum, as usual a HUGE thanks goes out to Leaf from the XNA UK User Group

So my initial issue was that I didn't know that certain model formats do not come with tangent data in them, at least that is what I am told. So I started off trying to apply my shader to an X formated mesh and got some really odd results (see the HazyMind posts), after much time and with the release of Benjamin Nitschke's book on XNA I found out thanks to the guys on the HM forum that you have to pass the model through a custom content importer and generate your tangent data there.

So I added to my existing custom model content pipeline, but did not get the method of generating the Tangent data correctly. Now this gave rise to some really odd results, basically the models would have my old issue when I did not have my bumped terrain in the draw call, but one it was drawn it got the effect rendered correctly. This was because before the terrain was rendered there was just random data in the Tangent Chanel that was being passed to the shader, once it was drawn and by sheer luck the tangent data from the terrain object just happened to sit in the same memory location as the unassigned tangent data for the model, so giving the illusion of the bump effect. So when my applications were ran on other systems the bump effect just did not work.

Here is and example of a custom content pipleine class for the model to be bumped
using System;
using System;
using System.IO;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Storage;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Content.Pipeline;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
using Microsoft.Xna.Framework.Content.Pipeline.Processors;
using Microsoft.Xna.Framework.Content.Pipeline.Serialization.Compiler;

namespace CustomContentPipeline
{
[ContentProcessor]
public class MyModelProcessor : ModelProcessor
{
double minX = double.MaxValue;
double minY = double.MaxValue;
double minZ = double.MaxValue;
double maxX = double.MinValue;
double maxY = double.MinValue;
double maxZ = double.MinValue;
List<BoundingBox> boxes = new List<BoundingBox>();
List<List<Vector3>> MeshVerts = new List<List<Vector3>>();
object[] ModelData = new object[2];
// Bounding Box's
private void CheckNode(NodeContent content)
{
foreach (NodeContent o in content.Children)
{
if (o is MeshContent)
{
// Get VertData
GetAllVerticies((MeshContent)o);
BoundingBox bb = new BoundingBox();
minX = double.MaxValue;
minY = double.MaxValue;
minZ = double.MaxValue;
maxX = double.MinValue;
maxY = double.MinValue;
maxZ = double.MinValue;
MeshContent mesh = (MeshContent)o;
foreach (Vector3 basev in mesh.Positions)
{
Vector3 v = basev;
if (v.X < minX)
minX = v.X;
if (v.Y < minY)
minY = v.Y;
if (v.Z < minZ)
minZ = v.Z;
if (v.X > maxX)
maxX = v.X;
if (v.Y > maxY)
maxY = v.Y;
if (v.Z > maxZ)
maxZ = v.Z;
}
double lenX = maxX - minX;
double lenZ = maxZ - minZ;
double lenY = maxY - minY;
bb.Min = new Vector3((float)minX, (float)minY, (float)minZ);
bb.Max = new Vector3((float)maxX, (float)maxY, (float)maxZ);
boxes.Add(bb);
}
else
CheckNode(o);
}
}
// Vertex positions
private void GetAllVerticies(MeshContent mesh)
{
for (int g = 0; g < mesh.Geometry.Count; g++)
{
GeometryContent geometry = mesh.Geometry[g];
List<Vector3> temp = new List<Vector3>();
for (int ind = 0; ind < geometry.Indices.Count; ind++)
{
// Transforms all of my verticies to local space.
Vector3 position = Vector3.Transform(geometry.Vertices.Positions[geometry.Indices[ind]], mesh.AbsoluteTransform);
temp.Add(position);
}
MeshVerts.Add(temp);
}
}
// Tangents.
private void GenerateTangents(NodeContent input, ContentProcessorContext context)
{
MeshContent mesh = input as MeshContent;
if (mesh != null)
{
MeshHelper.CalculateTangentFrames(mesh,
VertexChannelNames.TextureCoordinate(0),
VertexChannelNames.Tangent(0),
VertexChannelNames.Binormal(0));
}
foreach (NodeContent child in input.Children)
{
GenerateTangents(child, context);
}
}
// Normals
private void GenerateNormals(NodeContent input, ContentProcessorContext context)
{
MeshContent mesh = input as MeshContent;
if (mesh != null)
{
MeshHelper.CalculateNormals(mesh, true);
}
foreach (NodeContent child in input.Children)
{
GenerateNormals(child, context);
}
}
public override ModelContent Process(NodeContent input, ContentProcessorContext context)
{
// Calculate Mesh Tangents.
GenerateNormals(input, context);
// Calculate Mesh Normals.
GenerateTangents(input, context);
// Setup bounding box data.
CheckNode(input);
ModelData[0] = boxes;
ModelData[1] = MeshVerts;
ModelContent basemodel = base.Process(input, context);
basemodel.Tag = ModelData;
return basemodel;
}
}
}

The relevant method here is my GenerateTangent method:
// Tangents.
private void GenerateTangents(NodeContent input, ContentProcessorContext context)
{
MeshContent mesh = input as MeshContent;
if (mesh != null)
{
MeshHelper.CalculateTangentFrames(mesh,
VertexChannelNames.TextureCoordinate(0),
VertexChannelNames.Tangent(0),
VertexChannelNames.Binormal(0));
}
foreach (NodeContent child in input.Children)
{
GenerateTangents(child, context);
}
}

GenericExampleBumpNormalMapping.zip

No comments:

Post a Comment