Sign in to follow this  
eduwushu

Generating tangent frames for models

Recommended Posts

Hi all!!

I'm developing a project in XNA and I made some time ago a custom model processor that generates for meshes the tangent, normals and binormals of the model. It worked for some time but now depending on the computer I'm on, it will generate or not this data.

I have been developing thsi project ins everal computers, but using the same configuration for the project (x86). Now tangent frames are only generated correctly on my work computer. When I execute the project in my home PC or in my laptop this data will be initialized to zero.

Anybody knows why is this happening?

A lot of thanks

Share this post


Link to post
Share on other sites
I have written my own normal and tangent space generation with smoothing that works on my non instanced triangle list representation. The smoothing is using the angles of the corners as a bias to the average to make sure that quads looks good. The U and V axis vectors are a scaled tangent space to handle deep raytraced bump mapping even if the texture has twisted UV coordinates.
AxisU is how much you move in XYZ to move (1,0) in texture space.
AxisV is how much you move in XYZ to move (0,1) in texture space.
The vertex shader does normalizing and some cross products to extract all the data needed.
Here is a part of my model class taken out of context.


#define LoopForward(min,var,max) for(var=min;var<=max;var++)
#define LoopBackward(min,var,max) for(var=max;var>=min;var--)
#define LoopForwardStartAndLength(var,start,length) LoopForward(start,var,((start)+(length))-1)
#define LoopBackwardStartAndLength(var,start,length) LoopBackward(start,var,((start)+(length))-1)
#define LoopForwardLengthFromZero(var,length) LoopForward(0,var,(length)-1)
#define LoopBackwardLengthFromZero(var,length) LoopBackward(0,var,(length)-1)
#define Square(x) ((x)*(x))
#define ForAllParts(var) LoopForward(0,var,PL_UsedElements-1)
#define ForAllVerticesInPart(var,part) LoopForward(PL_Array[part].StartVertice,var,(PL_Array[part].StartVertice+PL_Array[part].Vertices)-1)

void ModelClass::GenerateFacetedNormals( void ) {
int partIndex;
int vertA; int vertB; int vertC;
D3DXVECTOR3 Pos1; D3DXVECTOR3 Pos2; D3DXVECTOR3 Pos3;
D3DXVECTOR3 VectorA; D3DXVECTOR3 VectorB; D3DXVECTOR3 CrossResult; D3DXVECTOR3 Normal;
ForAllParts(partIndex) {
ForAllVerticesInPart(vertA, partIndex) { vertB = vertA + 1; vertC = vertA + 2;
Pos1 = HLVB_Array[vertA].Position;
Pos2 = HLVB_Array[vertB].Position;
Pos3 = HLVB_Array[vertC].Position;
D3DXVec3Subtract(&VectorA, &Pos2, &Pos1);
D3DXVec3Subtract(&VectorB, &Pos3, &Pos1);
D3DXVec3Cross(&CrossResult, &VectorA, &VectorB);
D3DXVec3Normalize(&Normal, &CrossResult);
HLVB_Array[vertA].Normal = Normal;
HLVB_Array[vertB].Normal = Normal;
HLVB_Array[vertC].Normal = Normal;
vertA = vertA + 2; // Skip 2 vertices
}
}
bNeedGeometryUpdate = true;
}

void ModelClass::GenerateTangentSpace( void ) {
int partIndex;
int vertA; int vertB; int vertC;
D3DXVECTOR3 Pos1; D3DXVECTOR3 Pos2; D3DXVECTOR3 Pos3; D3DXVECTOR2 UV1; D3DXVECTOR2 UV2; D3DXVECTOR2 UV3;
D3DXVECTOR3 AxisU; D3DXVECTOR3 AxisV; D3DXVECTOR2 AX; D3DXVECTOR2 AY; D3DXVECTOR2 AZ;
ForAllParts(partIndex) {
ForAllVerticesInPart(vertA, partIndex) { vertB = vertA + 1; vertC = vertA + 2;
Pos1 = HLVB_Array[vertA].Position;
UV1 = HLVB_Array[vertA].UV;
Pos2 = HLVB_Array[vertB].Position;
UV2 = HLVB_Array[vertB].UV;
Pos3 = HLVB_Array[vertC].Position;
UV3 = HLVB_Array[vertC].UV;

// Calculate (DX/DU, DX/DV), (DY/DU, DY/DV), (DZ/DU, DZ/DV)
AX = ZDerativesFromPoints(D3DXVECTOR3(UV1.x, UV1.y, Pos1.x), D3DXVECTOR3(UV2.x, UV2.y, Pos2.x), D3DXVECTOR3(UV3.x, UV3.y, Pos3.x));
AY = ZDerativesFromPoints(D3DXVECTOR3(UV1.x, UV1.y, Pos1.y), D3DXVECTOR3(UV2.x, UV2.y, Pos2.y), D3DXVECTOR3(UV3.x, UV3.y, Pos3.y));
AZ = ZDerativesFromPoints(D3DXVECTOR3(UV1.x, UV1.y, Pos1.z), D3DXVECTOR3(UV2.x, UV2.y, Pos2.z), D3DXVECTOR3(UV3.x, UV3.y, Pos3.z));

// Transpose (AX, AY, AZ) to (AxisU, AxisV)
AxisU = D3DXVECTOR3(AX.x, AY.x, AZ.x);
AxisV = D3DXVECTOR3(AX.y, AY.y, AZ.y);

HLVB_Array[vertA].AxisU = AxisU;
HLVB_Array[vertA].AxisV = AxisV;
HLVB_Array[vertB].AxisU = AxisU;
HLVB_Array[vertB].AxisV = AxisV;
HLVB_Array[vertC].AxisU = AxisU;
HLVB_Array[vertC].AxisV = AxisV;
vertA = vertA + 2; // Skip 2 vertices
}
}
bNeedGeometryUpdate = true;
}

void ModelClass::CalculateTriangleAngles( int FirstVerticeIndex ) {
int vertA; int vertB; int vertC;
vertA = FirstVerticeIndex;
vertB = FirstVerticeIndex + 1;
vertC = FirstVerticeIndex + 2;
D3DXVECTOR3 VecAToB; D3DXVECTOR3 VecBToC; D3DXVECTOR3 VecCToA;
float DistAToB; float DistBToC; float DistCToA;
// Subtract positions to make vectors between the vertices
D3DXVec3Subtract(&VecAToB, &HLVB_Array[vertA].Position, &HLVB_Array[vertB].Position);
D3DXVec3Subtract(&VecBToC, &HLVB_Array[vertB].Position, &HLVB_Array[vertC].Position);
D3DXVec3Subtract(&VecCToA, &HLVB_Array[vertC].Position, &HLVB_Array[vertA].Position);
// Measure the distances
DistAToB = D3DXVec3Length(&VecAToB);
DistBToC = D3DXVec3Length(&VecBToC);
DistCToA = D3DXVec3Length(&VecCToA);
// Use a well known formula to calculate the angles
EVB_Array[vertA].Angle = acos(((Square(DistCToA) + Square(DistAToB)) - Square(DistBToC)) / (2 * DistCToA * DistAToB));
EVB_Array[vertB].Angle = acos(((Square(DistBToC) + Square(DistAToB)) - Square(DistCToA)) / (2 * DistBToC * DistAToB));
EVB_Array[vertC].Angle = acos(((Square(DistBToC) + Square(DistCToA)) - Square(DistAToB)) / (2 * DistBToC * DistCToA));
}

float ModelClass::DistanceFromDegrees( float Angle ) {
D3DXVECTOR2 AngleDiff;
float MaxSmoothingAngleInRadians;
MaxSmoothingAngleInRadians = Angle * 0.01745329251994329576923690768489f; // 1 degree = 2PI/360 radians
AngleDiff = D3DXVECTOR2((float)cos((double)MaxSmoothingAngleInRadians) - 1.0f, (float)sin((double)MaxSmoothingAngleInRadians));
return D3DXVec2Length(&AngleDiff);
}

void ModelClass::SmoothNormals( float WeldingTreshold, float MaxSmoothingAngleInDegrees ) {
int partIndex;
int vertA; int vertB; int vertP;
float NormalDistTreshold;
NormalDistTreshold = DistanceFromDegrees(MaxSmoothingAngleInDegrees);
// Work on one part at a time to save time
ForAllParts(partIndex) {
// Initialize triangles
ForAllVerticesInPart(vertA, partIndex) { CalculateTriangleAngles(vertA); vertA = vertA + 2; }
// Initialize vertices
ForAllVerticesInPart(vertA, partIndex) {
EVB_Array[vertA].Parent_Index = -1;
EVB_Array[vertA].NormalSum = D3DXVECTOR3(0.0f, 0.0f, 0.0f);
}
// Find connections and group by pointing to the first member of each group.
LoopForward(PL_Array[partIndex].StartVertice, vertA, PL_Array[partIndex].StartVertice + PL_Array[partIndex].Vertices - 2) {
LoopForward(vertA + 1, vertB, PL_Array[partIndex].StartVertice + PL_Array[partIndex].Vertices - 1) {
D3DXVECTOR3 PosAToB; float PosDiffAToB;
D3DXVECTOR3 NorAToB; float NorDiffAToB;
D3DXVec3Subtract(&PosAToB, &HLVB_Array[vertA].Position, &HLVB_Array[vertB].Position);
D3DXVec3Subtract(&NorAToB, &HLVB_Array[vertA].Normal, &HLVB_Array[vertB].Normal);
PosDiffAToB = D3DXVec3Length(&PosAToB);
NorDiffAToB = D3DXVec3Length(&NorAToB);
if (PosDiffAToB < WeldingTreshold && NorDiffAToB < NormalDistTreshold) {
if (EVB_Array[vertB].Parent_Index == -1) {
EVB_Array[vertA].Parent_Index = vertB;
} else {
EVB_Array[vertA].Parent_Index = EVB_Array[vertB].Parent_Index;
}
}
}
}
// Add properties using the angle
ForAllVerticesInPart(vertA, partIndex) {
if (EVB_Array[vertA].Parent_Index == -1) { vertP = vertA; } else { vertP = EVB_Array[vertA].Parent_Index; }
EVB_Array[vertP].NormalSum = EVB_Array[vertP].NormalSum + (HLVB_Array[vertA].Normal * EVB_Array[vertA].Angle);
}
// Scale the result
ForAllVerticesInPart(vertA, partIndex) {
// Only parent's properties will be used
if (EVB_Array[vertA].Parent_Index == -1) {
D3DXVec3Normalize(&EVB_Array[vertA].NormalSum, &EVB_Array[vertA].NormalSum);
}
}
// Write properties to the real vertex buffer
ForAllVerticesInPart(vertA, partIndex) {
int Parent_Index; Parent_Index = EVB_Array[vertA].Parent_Index; if (Parent_Index == -1) { Parent_Index = vertA; }
HLVB_Array[vertA].Normal = EVB_Array[Parent_Index].NormalSum;
}
}
// Tell the engine that the high level vertex buffer has been modified
bNeedGeometryUpdate = true;
}

void ModelClass::SmoothTangentSpace( float WeldingTreshold, float MaxSmoothingAngleInDegrees ) {
int partIndex;
int vertA; int vertB; int vertP;
float NormalDistTreshold;
NormalDistTreshold = DistanceFromDegrees(MaxSmoothingAngleInDegrees);
// Work on one part at a time to save time
ForAllParts(partIndex) {
// Initialize triangles
ForAllVerticesInPart(vertA, partIndex) { CalculateTriangleAngles(vertA); vertA = vertA + 2; }
// Initialize vertices
ForAllVerticesInPart(vertA, partIndex) {
EVB_Array[vertA].Parent_Index = -1;
EVB_Array[vertA].AxisUSum = D3DXVECTOR3(0.0f, 0.0f, 0.0f);
EVB_Array[vertA].AxisVSum = D3DXVECTOR3(0.0f, 0.0f, 0.0f);
EVB_Array[vertA].AngleSum = 0.0f;
}
// Find connections and group by pointing to the first member of each group.
LoopForward(PL_Array[partIndex].StartVertice, vertA, PL_Array[partIndex].StartVertice + PL_Array[partIndex].Vertices - 2) {
LoopForward(vertA + 1, vertB, PL_Array[partIndex].StartVertice + PL_Array[partIndex].Vertices - 1) {
D3DXVECTOR3 PosAToB; float PosDiffAToB;
D3DXVECTOR3 NorAToB; float NorDiffAToB;
D3DXVECTOR3 AxisUAToB; float AxisUDiffAToB;
D3DXVECTOR3 AxisVAToB; float AxisVDiffAToB;
D3DXVECTOR3 AU; D3DXVECTOR3 AV;
D3DXVECTOR3 BU; D3DXVECTOR3 BV;
D3DXVec3Subtract(&PosAToB, &HLVB_Array[vertA].Position, &HLVB_Array[vertB].Position);
D3DXVec3Subtract(&NorAToB, &HLVB_Array[vertA].Normal, &HLVB_Array[vertB].Normal);
D3DXVec3Normalize(&AU, &HLVB_Array[vertA].AxisU);
D3DXVec3Normalize(&AV, &HLVB_Array[vertA].AxisV);
D3DXVec3Normalize(&BU, &HLVB_Array[vertB].AxisU);
D3DXVec3Normalize(&BV, &HLVB_Array[vertB].AxisV);
D3DXVec3Subtract(&AxisUAToB, &AU, &BU);
D3DXVec3Subtract(&AxisVAToB, &AV, &BV);
PosDiffAToB = D3DXVec3Length(&PosAToB);
NorDiffAToB = D3DXVec3Length(&NorAToB);
AxisUDiffAToB = D3DXVec3Length(&AxisUAToB);
AxisVDiffAToB = D3DXVec3Length(&AxisVAToB);
if (PosDiffAToB < WeldingTreshold && NorDiffAToB < NormalDistTreshold && AxisUDiffAToB < NormalDistTreshold && AxisVDiffAToB < NormalDistTreshold) {
if (EVB_Array[vertB].Parent_Index == -1) {
EVB_Array[vertA].Parent_Index = vertB;
} else {
EVB_Array[vertA].Parent_Index = EVB_Array[vertB].Parent_Index;
}
}
}
}
// Add properties using the angle
ForAllVerticesInPart(vertA, partIndex) {
if (EVB_Array[vertA].Parent_Index == -1) { vertP = vertA; } else { vertP = EVB_Array[vertA].Parent_Index; }
EVB_Array[vertP].AxisUSum = EVB_Array[vertP].AxisUSum + (HLVB_Array[vertA].AxisU * EVB_Array[vertA].Angle);
EVB_Array[vertP].AxisVSum = EVB_Array[vertP].AxisVSum + (HLVB_Array[vertA].AxisV * EVB_Array[vertA].Angle);
EVB_Array[vertP].AngleSum = EVB_Array[vertP].AngleSum + EVB_Array[vertA].Angle;
}
// Scale the result
ForAllVerticesInPart(vertA, partIndex) {
// Only parent's properties will be used
if (EVB_Array[vertA].Parent_Index == -1) {
if (EVB_Array[vertA].AngleSum > 0.0f) {
EVB_Array[vertA].AxisUSum = EVB_Array[vertA].AxisUSum / EVB_Array[vertA].AngleSum;
EVB_Array[vertA].AxisVSum = EVB_Array[vertA].AxisVSum / EVB_Array[vertA].AngleSum;
}
}
}
// Write properties to the real vertex buffer
ForAllVerticesInPart(vertA, partIndex) {
int Parent_Index; Parent_Index = EVB_Array[vertA].Parent_Index; if (Parent_Index == -1) { Parent_Index = vertA; }
HLVB_Array[vertA].AxisU = EVB_Array[Parent_Index].AxisUSum;
HLVB_Array[vertA].AxisV = EVB_Array[Parent_Index].AxisVSum;
}
}
// Tell the engine that the high level vertex buffer has been modified
bNeedGeometryUpdate = true;
}

//Precondition: |N| = 1
//Postconsition: The partial derivatives (DZ/DX, DZ/DY) on a plane defined by the normal N.
D3DXVECTOR2 ModelClass::ZDerivativesFromNormal(D3DXVECTOR3 N) {
return D3DXVECTOR2(-N.x / N.z, -N.y / N.z);
}

//Precondition: The result is only valid if there is a solution.
//Postconsition: The partial derivatives (DZ/DX, DZ/DY) on a plane defined by the normal N.
D3DXVECTOR2 ModelClass::ZDerativesFromPoints(D3DXVECTOR3 A, D3DXVECTOR3 B, D3DXVECTOR3 C) {
D3DXVECTOR3 CrossResult; D3DXVECTOR3 NormalResult; D3DXVECTOR3 AToB; D3DXVECTOR3 AToC;
D3DXVec3Subtract(&AToB, &B, &A);
D3DXVec3Subtract(&AToC, &C, &A);
D3DXVec3Cross(&CrossResult, &AToC, &AToB);
D3DXVec3Normalize(&NormalResult, &CrossResult);
return ZDerivativesFromNormal(NormalResult);
}

Share this post


Link to post
Share on other sites
Well I didn't post any code because I thought it could be a project configuration problem or maybe a XNA version problem. But just in case here it is the relevant part of my model processor code. I took it from the web the code fragments needed to instruct XNA to compute tangent, normal and binormal data for me.

As I said, this works perfectly on my work computer but in the computer of my home tangent and binormal data will not be computed.

I'm trying tu uninstall XNA and reinstall it, just to be sure.



[ContentProcessor]
public class CBModelProcessor : ModelProcessor
{
// Maximum number of bone matrices we can render using shader 2.0
// in a single pass. If you change this, update SkinnedModelfx to match.
const int MaxBones = 59;

List<Vector3> m_adVertices = new List<Vector3>();
List<BoneWeightCollection> m_adVertexWeights = new List<BoneWeightCollection>();
Dictionary<string, int> m_dBoneNameMap = new Dictionary<string, int>();

Vector3 m_v3Min,m_v3Max;
string sMaterialName;

// acceptableVertexChannelNames are the inputs that the normal map effect
// expects. The NormalMappingModelProcessor overrides ProcessVertexChannel
// to remove all vertex channels which don't have one of these four
// names.
static IList<string> acceptableVertexChannelNames =
new string[]
{
VertexChannelNames.TextureCoordinate(0),
VertexChannelNames.Normal(0),
VertexChannelNames.Binormal(0),
VertexChannelNames.Tangent(0)
};

[Browsable(false)]
public override bool GenerateTangentFrames
{
get { return true; }
set { }
}

protected override void ProcessVertexChannel(GeometryContent geometry, int vertexChannelIndex, ContentProcessorContext context)
{
String vertexChannelName = geometry.Vertices.Channels[vertexChannelIndex].Name;
if (vertexChannelName == VertexChannelNames.Weights())
{
foreach (BoneWeightCollection bwc in geometry.Vertices.Channels[vertexChannelIndex])
{
m_adVertexWeights.Add(bwc);
}
//Store the weights of animation for OBBox computing
//IEnumerable<BoneWeightCollection> iterator= geometry.Vertices.Channels[vertexChannelIndex].ReadConvertedContent<BoneWeightCollection>();

}
// if this vertex channel has an acceptable names, process it as normal.
if (acceptableVertexChannelNames.Contains(vertexChannelName))
{
base.ProcessVertexChannel(geometry, vertexChannelIndex, context);
}
// otherwise, remove it from the vertex channels; it's just extra data
// we don't need.
else
{
geometry.Vertices.Channels.Remove(vertexChannelName);
}
}
/// <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)
{

System.Diagnostics.Debugger.Launch();
ExtractModelVertices(input);
ExtractMaterialName(input);
if (!IsAnimatedModel(input))
return ProcessStaticModel(input, context);
else
return ProcessAnimatedModel(input, context);

}
[...]
}

Share this post


Link to post
Share on other sites
Finally I doscovered where was the error: As I'm from Spain I was using a number configuration that uses , as decimal separator (instead of .) Changing locale configuration for decimal separator solved the problem. Now my model is being processed correctly and everything is fine.

Thanks to all for your support!!!! Really!!

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