/// <summary>
/// Generate normals and tangents if not present.
/// This method is very important for using shaders, most
/// shader techniques will expect the TangentVertex format!
/// </summary>
/// <param name="someMesh">Mesh we are going to manipulate</param>
public static void GenerateNormalsAndTangentsIfNotPresent(
ref Mesh someMesh)
{
if (someMesh == null)
throw new ArgumentNullException
("someMesh",
"Can't generate normals and tangents without valid mesh.");
// Quick check if vertex declaration of mesh already fits.
VertexElement[] decl = someMesh.Declaration;
if (TangentVertex.IsTangentVertexDeclaration(decl))
// Everything looks fine, leave it that way.
return;
bool hadNormals = false;
bool hadTangents = false;
// Check the first couple of declaration usages
for (int i = 0; i < 6 && i < decl.Length; i++)
{
if (decl[i].DeclarationUsage == DeclarationUsage.Normal)
{
hadNormals = true;
break;
} // if (decl[i].DeclarationUsage)
if (decl[i].DeclarationUsage == DeclarationUsage.Tangent)
{
hadTangents = true;
break;
} // if (decl[i].DeclarationUsage)
} // for (int)
// Create new mesh and clone everything
Mesh tempMesh = someMesh.Clone(
someMesh.Options.Value,
TangentVertex.VertexElements,
Graphics.GetDirectXDevice());
// Destroy current mesh, use the new one
someMesh.Dispose();
someMesh = tempMesh;
// Check if we got texture coordinates, if not, generate them!
bool gotMilkErmTexCoords = false;
bool gotValidNormals = true;
bool gotValidTangents = true;
TangentVertex[] verts =
(TangentVertex[])someMesh.LockVertexBuffer(
LockFlags.None,
new int[1] { someMesh.
NumberVertices });
// Check all vertices
for (int num = 0; num < verts.Length; num++)
{
// We only need at least 1 texture coordinate different from (0, 0)
if (verts[num].u != 0.0f ||
verts[num].v != 0.0f)
gotMilkErmTexCoords = true;
// All normals and tangents must be valid, else generate them below.
if (verts[num].normal.IsZero())
gotValidNormals = false;
if (verts[num].tangent.IsZero())
gotValidTangents = false;
// If we found valid texture coordinates and no normals or tangents,
// there isn't anything left to check here.
if (gotMilkErmTexCoords == true &&
gotValidNormals == false &&
gotValidTangents == false)
break;
} // for (num, <, ++)
// If declaration had normals, but we found no valid normals,
// set hadNormals to false and generate valid normals (see below).
if (gotValidNormals == false)
hadNormals = false;
// Same check for tangents
if (gotValidTangents == false)
hadTangents = false;
// Generate dummy texture coordinates, not only useful for tangent
// generation, but also unit tests display better visual meshes.
if (gotMilkErmTexCoords == false)
{
for (int num = 0; num < verts.Length; num++)
{
// Similar stuff as in GenerateTextureCoordinates, very simple and
// dummy way to generate texture coordinates from object position.
// Usually only test objects don't have texture coordinates.
verts[num].u = -0.75f + verts[num].pos.x / 2.0f;
verts[num].v = +0.75f - verts[num].pos.y / 2.0f +
verts[num].pos.z / 2.0f;
} // for (num, <, ++)
} // if (gotMilkErmTexCoords)
someMesh.UnlockVertexBuffer();
// Generate normals if this mesh hadn't any.
if (hadNormals == false)
someMesh.ComputeNormals();
// Ok, first weld vertices which should be together anyway.
// This optimizes rendering and enables us to do correct tangent
// calculations below. For example a mesh using around 100 faces might
// have around 300 vertices, if each face has its own 3 vertices. But
// when collapsing same vertices together we can get this down to 100-150
// vertices (which saves half of the bandwidth and vertex memory).
WeldEpsilons weldEpsilons =
new WeldEpsilons
();
// Position and normal should be the same (or nearly the same)
weldEpsilons.Position = 0.0001f;
weldEpsilons.Normal = 0.0001f;
// Rest of the weldEpsilons values can stay 0, we don't use them
// or if they are used (like texture coord or already generated tangent
// data, they must be the same for vertices we want to collapse).
someMesh.WeldVertices(
// Don't collapse faces that are not smoothend together.
WeldEpsilonsFlags.WeldPartialMatches,
// Use the epsilon values defined above
weldEpsilons,
// Let WeldVertices generate the adjacency
null);
// Need to generate tangents because mesh doesn't provide any yet?
if (hadTangents == false)
{
// Huston, we might have a problem!
// If the vertices for a smoothend point exist several times the
// DirectX ComputeTangent method is not able to threat them all the
// same way (see Screenshots on my post on abi.exdream.com).
// To circumvent this, we collapse all vertices in a cloned mesh
// even if the texture coordinates don't fit. Then we copy the
// generated tangents back to the original mesh vertices (duplicating
// the tangents for vertices at the same point with the same normals
// if required). This happens usually with models exported from 3DSMax.
// Clone mesh just for tangent generation
Mesh dummyTangentGenerationMesh = someMesh.Clone(
someMesh.Options.Value,
someMesh.Declaration,
Graphics.GetDirectXDevice());
// Reuse weldEpsilons, just change the TextureCoordinates, which we
// don't care about anymore. TextureCoordinate expects 8 float values.
weldEpsilons.TextureCoordinate =
new float[] { 1,
1,
1,
1,
1,
1,
1,
1 };
// Rest of the weldEpsilons values can stay 0, we don't use them.
dummyTangentGenerationMesh.WeldVertices(
// Don't collapse faces that are not smoothend together.
WeldEpsilonsFlags.WeldPartialMatches,
// Use the defined epsilon values
weldEpsilons,
// Let WeldVertices generate the adjacency
null);
// Compute tangents with texture channel 0,
// tangents are in stream 0, binormals are not required
// and wrapping doesn't help or work anyways (last 0 parameter).
dummyTangentGenerationMesh.ComputeTangent(0, 0, D3DX.Default, 0);
// Ok, time to copy the smoothly generated tangents back :)
TangentVertex[] tangentVerts =
(TangentVertex[])dummyTangentGenerationMesh.LockVertexBuffer(
LockFlags.None,
new int[1] { dummyTangentGenerationMesh.
NumberVertices });
verts =
(TangentVertex[])someMesh.LockVertexBuffer(
LockFlags.None,
new int[1] { someMesh.
NumberVertices });
for (int num = 0; num < verts.Length; num++)
{
// Search for tangent vertex with the exact same position and normal.
for (int tangentVertexNum = 0; tangentVertexNum <
tangentVerts.Length; tangentVertexNum++)
if (verts[num].pos == tangentVerts[tangentVertexNum].pos &&
verts[num].normal == tangentVerts[tangentVertexNum].normal)
{
// Copy the tangent over
verts[num].tangent = tangentVerts[tangentVertexNum].tangent;
// No more checks required, proceed with next vertex
break;
} // for if (verts[num].pos)
} // for (num)
someMesh.UnlockVertexBuffer();
dummyTangentGenerationMesh.UnlockVertexBuffer();
} // if (hadTangents)
// Finally optimize the mesh for the current graphics cards vertex cache.
int[] adj = someMesh.ConvertPointRepsToAdjacency((GraphicsStream)null);
someMesh.OptimizeInPlace(MeshFlags.OptimizeVertexCache, adj);
} // GenerateNormalsAndTangentsIfNotPresent(someMesh)