Generating tangent frames for models

Started by
9 comments, last by eduwushu 13 years, 6 months ago
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
Advertisement
Nobody has ever experimented this problem??
Well, we are not far seers.
Did you upgrade your home systems to XNA 4.0? This may be causing the problem. 4.0 broke some things from 3.1.

Just a guess without code.
my blog contains ramblings and what I am up to programming wise.
I will try it
Thanks!!
szecs

I was not expecting you to gues what was happening at first glance. I was only asking if anybody has experimented something like this before and if he has solved it.
I was thinking that if you posted some code, you would have bigger chance to get help.
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);}
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);                    }[...]}
code for calculating tangents

http://www.terathon.com/code/tangent.html
Wisdom is knowing when to shut up, so try it.
--Game Development http://nolimitsdesigns.com: Reliable UDP library, Threading library, Math Library, UI Library. Take a look, its all free.

This topic is closed to new replies.

Advertisement