• Advertisement
Sign in to follow this  

problem generating texture coordinates for sphere

This topic is 2351 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I've been trying to generate texture coordinates for a sphere created using D3DXCreateSphere in order to be able to add tangent/binormal data to the mesh however I've run into some problems. The tangents appear to be being calculated correctly except for one strip of the sphere where z~0 and x <=0

heres the problem with tangents shown in RGB

[attachment=5420:problem.jpg]

The code I'm using to create the sphere and generate the texture coordinates is as follows


HR(D3DXCreateSphere(gd3dDevice, 1.0, 20,20, &mSceneMesh, 0));

// Get the vertex declaration for the NMapVertex.
D3DVERTEXELEMENT9 elems[MAX_FVF_DECL_SIZE];
UINT numElems = 0;
HR(NMapVertex::Decl->GetDeclaration(elems, &numElems));

// Clone the mesh to the NMapVertex format.
ID3DXMesh* clonedMesh = 0;
HR(mSceneMesh->CloneMesh(mSceneMesh->GetOptions(), elems, gd3dDevice, &clonedMesh));

// lock the vertex buffer
NMapVertex *pVerts;
HR(clonedMesh->LockVertexBuffer(0,(void **)&pVerts));

// get vertex count
int numVerts=clonedMesh->GetNumVertices();

// compute texture coordinates
for (int i=0;i<numVerts;++i) {
pVerts->tex0.x = (atan2(pVerts->pos.z,pVerts->pos.x)/(D3DX_PI* 2.0f));
pVerts->tex0.y = (asin(pVerts->pos.y)/D3DX_PI);
++pVerts;
}

// unlock the vertex buffer
clonedMesh->UnlockVertexBuffer();

// Now use D3DXComputeTangentFrameEx to build the TNB-basis for each vertex
// in the mesh.
HR(D3DXComputeTangentFrameEx(
clonedMesh, // Input mesh
D3DDECLUSAGE_TEXCOORD, 0, // Vertex element of input tex-coords.
D3DDECLUSAGE_BINORMAL, 0, // Vertex element to output binormal.
D3DDECLUSAGE_TANGENT, 0, // Vertex element to output tangent.
D3DDECLUSAGE_NORMAL, 0, // Vertex element to output normal.
clonedMesh->GetOptions(), // Options
0, // Adjacency
0.01f, 0.25f, 0.01f, // Thresholds for handling errors
&mSceneMesh, // Output mesh
0)); // Vertex Remapping


Anyone have any ideas?

Share this post


Link to post
Share on other sites
Advertisement
I made my own method for calculating the tangent space by deriving the position offsets in tangent space.


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
Thanks Dawoodoz,I assume that code works with arbitrary geometry and as such is a bit too complex for me to comprehend given my lack of experience in 3d graphics :)

I ended up solving the problem, and I'll post my solution here just in case anyone else ever has this issue. For a sphere you can calculate the tangent and binormal given a normal and a position (x,y,z) as follows


//calculate TBN per pixel instead of per vertex to get rid of artifacts at the poles of spheres
float theta = acos(input.LocalPosition.z/Radius);
float phi = atan2(input.LocalPosition.y/Radius,input.LocalPosition.x/Radius);
theta+=D3DX_PI/2;
D3DXVECTOR3 tangent(sin(theta)*cos(phi),sin(theta)*sin(phi),cos(theta));
D3DXVECTOR3 binormal;
D3DXVec3Cross(&binormal,&normal,&tangent);


However given the geometry of the D3DX sphere you will always get tangent and binormal interpolation artifacts at the two poles on the Z axis, the only way I found to get around this was to calculate the TBN in the pixel shader i.e. per pixel instead of per vector in the mesh geometry, the hlsl for this is as follows


//calculate TBN per pixel instead of per vertex to get rid of artifacts at the poles of spheres
float theta = acos(input.LocalPosition.z/Radius);
float phi = atan2(input.LocalPosition.y/Radius,input.LocalPosition.x/Radius);
theta+=PI/2;
float3 tangent = float3(sin(theta)*cos(phi),sin(theta)*sin(phi),cos(theta));
float3 binormal = cross(input.Normal,tangent);


using this technique generates pixel perfect tangents and binormals across the entire sphere, so my bumpmapping code finally works correctly!

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement