Skip to content

Instantly share code, notes, and snippets.

@aras-p
Created May 31, 2012 15:01
Show Gist options
  • Star 27 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save aras-p/2843984 to your computer and use it in GitHub Desktop.
Save aras-p/2843984 to your computer and use it in GitHub Desktop.
Unity tangent space calculation
// THIS IS ONLY A PORTION OF THE FILE
// WILL NOT COMPILE OUT OF THE BOX!
void OrthogonalizeTangent (TangentInfo& tangentInfo, Vector3f normalf, Vector4f& outputTangent)
{
TangentInfo::Vector3d normal = { normalf.x, normalf.y, normalf.z };
TangentInfo::Vector3d tangent = tangentInfo.tangent;
TangentInfo::Vector3d binormal = tangentInfo.binormal;
// Try Gram-Schmidt orthonormalize.
// This might fail in degenerate cases which we all handle seperately.
double NdotT = TangentInfo::Vector3d::Dot (normal, tangent);
TangentInfo::Vector3d newTangent =
{
tangent.x - NdotT * normal.x, tangent.y - NdotT * normal.y, tangent.z - NdotT * normal.z
};
double magT = TangentInfo::Vector3d::Magnitude (newTangent);
newTangent = TangentInfo::Vector3d::Normalize (newTangent, magT);
double NdotB = TangentInfo::Vector3d::Dot (normal, binormal);
double TdotB = TangentInfo::Vector3d::Dot (newTangent, binormal) * magT;
TangentInfo::Vector3d newBinormal =
{
binormal.x - NdotB * normal.x - TdotB * newTangent.x,
binormal.y - NdotB * normal.y - TdotB * newTangent.y,
binormal.z - NdotB * normal.z - TdotB * newTangent.z
};
double magB = TangentInfo::Vector3d::Magnitude (newBinormal);
newBinormal = TangentInfo::Vector3d::Normalize (newBinormal, magB);
Vector3f tangentf = Vector3f( (float)newTangent.x, (float)newTangent.y, (float)newTangent.z );
Vector3f binormalf = Vector3f( (float)newBinormal.x, (float)newBinormal.y, (float)newBinormal.z );
const double kNormalizeEpsilon = 1e-6;
if (magT <= kNormalizeEpsilon || magB <= kNormalizeEpsilon)
{
// Create tangent basis from scratch - we can safely use Vector3f here - no computations ;-)
Vector3f axis1, axis2;
float dpXN = Abs( Dot( Vector3f::xAxis, normalf ) );
float dpYN = Abs( Dot( Vector3f::yAxis, normalf ) );
float dpZN = Abs( Dot( Vector3f::zAxis, normalf ) );
if ( dpXN <= dpYN && dpXN <= dpZN )
{
axis1 = Vector3f::xAxis;
if( dpYN <= dpZN ) axis2 = Vector3f::yAxis;
else axis2 = Vector3f::zAxis;
}
else if ( dpYN <= dpXN && dpYN <= dpZN )
{
axis1 = Vector3f::yAxis;
if( dpXN <= dpZN ) axis2 = Vector3f::xAxis;
else axis2 = Vector3f::zAxis;
}
else
{
axis1 = Vector3f::zAxis;
if( dpXN <= dpYN ) axis2 = Vector3f::xAxis;
else axis2 = Vector3f::yAxis;
}
tangentf = axis1 - Dot (normalf, axis1) * normalf;
binormalf = axis2 - Dot (normalf, axis2) * normalf - Dot(tangentf,axis2)*NormalizeSafe(tangentf);
tangentf = NormalizeSafe(tangentf);
binormalf = NormalizeSafe(binormalf);
}
outputTangent = Vector4f( tangentf.x, tangentf.y, tangentf.z, 0.0F);
float dp = Dot (Cross (normalf, tangentf), binormalf);
if ( dp > 0.0F)
outputTangent.w = 1.0F;
else
outputTangent.w = -1.0F;
}
void CreateTangentSpaceTangentsUnsplit (const ImportMesh& mesh, Vector4f* outTangents, float normalSmoothingAngle)
{
float smoothingAngle = max(89.0F, normalSmoothingAngle);
const int vertexCount = mesh.vertices.size();
const int faceCount = mesh.polygonSizes.size();
const int indexCount = mesh.polygons.size();
const Vector3f* vertices = &mesh.vertices[0];
const Vector3f* normals = &mesh.normals[0];
const Vector2f* tex = &mesh.uvs[0][0];
const UInt32* indices = &mesh.polygons[0];
// Calculate the tangent and binormal vectors of each face,
// also compute face start offsets in the index buffer
vector<TangentInfo> faceTangents(indexCount);
vector<UInt32> faceOffsets(faceCount);
for (int i = 0, idx = 0; i < faceCount; ++i)
{
const int fs = mesh.polygonSizes[i];
ComputeTriangleTangentBasis (vertices, tex + idx, indices + idx, &faceTangents[idx]);
///@TODO: is this good enough?
if (fs == 4)
ComputeTriangleTangentBasis (vertices, tex + idx+1, indices + idx+1, &faceTangents[idx+1]);
faceOffsets[i] = idx;
idx += fs;
}
// Average the tangents/binormals but only if they are within the smoothing angle
vector<TangentInfo> avgTangents(indexCount);
ConnectedMesh meshConnection (vertexCount, indices, mesh.polygons.size(), &mesh.polygonSizes[0], faceCount);
float hardDot = cos (Deg2Rad (smoothingAngle)) - 0.001F; // NOTE: subtract is for more consistent results across platforms
// Go through all faces, average with the connected faces
for (int fi = 0, idx = 0; fi < faceCount; ++fi)
{
const int fs = mesh.polygonSizes[fi];
for (int e = 0; e < fs; ++e)
{
TangentInfo::Vector3d faceTangent = TangentInfo::Vector3d::Normalize (faceTangents[idx+e].tangent);
TangentInfo::Vector3d faceBinormal = TangentInfo::Vector3d::Normalize (faceTangents[idx+e].binormal);
TangentInfo::Vector3d averagedTangent = {0,0,0};
TangentInfo::Vector3d averagedBinormal = {0,0,0};
ConnectedMesh::Vertex connected = meshConnection.vertices[indices[idx + e]];
for (int i=0;i<connected.faceCount;i++)
{
int faceI = -1;
const UInt32 connectedFaceOffset = faceOffsets[connected.faces[i]];
const int connectedFaceSize = mesh.polygonSizes[connected.faces[i]];
for (int k = 0; k < connectedFaceSize; ++k)
{
if (indices[connectedFaceOffset+k] == indices[idx+e])
{
faceI = connectedFaceOffset+k;
break;
}
}
Assert(faceI != -1);
TangentInfo::Vector3d connectedTangent = faceTangents[faceI].tangent;
TangentInfo::Vector3d connectedBinormal = faceTangents[faceI].binormal;
if ( TangentInfo::Vector3d::Dot(TangentInfo::Vector3d::Normalize(connectedTangent), faceTangent) > (double)hardDot )
{
averagedTangent.x += connectedTangent.x;
averagedTangent.y += connectedTangent.y;
averagedTangent.z += connectedTangent.z;
}
if ( TangentInfo::Vector3d::Dot(TangentInfo::Vector3d::Normalize(connectedBinormal), faceBinormal) > (double)hardDot )
{
averagedBinormal.x += connectedBinormal.x;
averagedBinormal.y += connectedBinormal.y;
averagedBinormal.z += connectedBinormal.z;
}
}
avgTangents[idx+e].tangent = TangentInfo::Vector3d::Normalize (averagedTangent);
avgTangents[idx+e].binormal = TangentInfo::Vector3d::Normalize (averagedBinormal);
}
idx += fs;
}
// Orthogonalize the tangents/binormals and output them into the tangents array
for (int i=0;i<avgTangents.size();i++)
{
OrthogonalizeTangent (avgTangents[i], normals[i], outTangents[i]);
}
}
// THIS IS ONLY A PORTION OF THE FILE
// WILL NOT COMPILE OUT OF THE BOX!
In shaders, binormal is always computed from normal & tangent:
float3 binormal = cross(normal, tangent.xyz ) * tangent.w;
tangent is float4, xyz=tangent, w=direction of binormal (+1 or -1)
#include "UnityPrefix.h"
#include "TangentSpace.h"
#include "Runtime/Math/Vector2.h"
#include "Runtime/Math/Vector3.h"
#include "Runtime/Utilities/Utility.h"
#include <cmath>
//------------------------------------------------------------------------------
static const double kEpsilon = 1e-10;
double
TangentInfo::Vector3d::Dot(Vector3d v1, Vector3d v2)
{
return v1.x*v2.x + v1.y*v2.y + v1.z*v2.z;
}
double
TangentInfo::Vector3d::Magnitude(Vector3d v)
{
return std::sqrt(Dot(v,v));
}
TangentInfo::Vector3d
TangentInfo::Vector3d::Normalize(Vector3d v, double mag)
{
Vector3d ret = {0,0,0};
if( mag > kEpsilon )
{
ret.x = v.x/mag;
ret.y = v.y/mag;
ret.z = v.z/mag;
}
return ret;
}
TangentInfo::Vector3d
TangentInfo::Vector3d::Normalize(Vector3d v)
{
double mag = Magnitude(v);
return Normalize(v,mag);
}
//------------------------------------------------------------------------------
void ComputeTriangleTangentBasis (const Vector3f* vertices, const Vector2f* uvs, const UInt32* indices, TangentInfo out[3])
{
// Using Eric Lengyel's approach with a few modifications
// From Mathematics for 3D Game Programming and Computer Graphics
// want to be able to transform a vector in Object Space to Tangent Space
// such that the x-axis corresponds to the 's' direction and the
// y-axis corresponds to the 't' direction, and the z-axis corresponds
// to <0,0,1>, straight up out of the texture map
const Vector3f triVertex[] = { vertices[indices[0]], vertices[indices[1]], vertices[indices[2]] };
const Vector2f triUV[] = { uvs[0], uvs[1], uvs[2] };
double p[] = { triVertex[1].x - triVertex[0].x, triVertex[1].y - triVertex[0].y, triVertex[1].z - triVertex[0].z };
double q[] = { triVertex[2].x - triVertex[0].x, triVertex[2].y - triVertex[0].y, triVertex[2].z - triVertex[0].z };
double s[] = { triUV[1].x - triUV[0].x, triUV[2].x - triUV[0].x };
double t[] = { triUV[1].y - triUV[0].y, triUV[2].y - triUV[0].y };
// we need to solve the equation
// P = s1*T + t1*B
// Q = s2*T + t2*B
// for T and B
// this is a linear system with six unknowns and six equations, for TxTyTz BxByBz
// [px,py,pz] = [s1,t1] * [Tx,Ty,Tz]
// qx,qy,qz s2,t2 Bx,By,Bz
// multiplying both sides by the inverse of the s,t matrix gives
// [Tx,Ty,Tz] = 1/(s1t2-s2t1) * [t2,-t1] * [px,py,pz]
// Bx,By,Bz -s2,s1 qx,qy,qz
TangentInfo faceInfo;
faceInfo.tangent.x = faceInfo.tangent.y = faceInfo.tangent.z = 0.0;
faceInfo.binormal.x = faceInfo.binormal.y = faceInfo.binormal.z = 0.0;
double div = s[0]*t[1] - s[1]*t[0];
double areaMult = std::abs(div);
if( areaMult >= 1e-8 )
{
double r = 1.0 / div;
s[0] *= r; t[0] *= r;
s[1] *= r; t[1] *= r;
faceInfo.tangent.x = (t[1] * p[0] - t[0] * q[0]);
faceInfo.tangent.y = (t[1] * p[1] - t[0] * q[1]);
faceInfo.tangent.z = (t[1] * p[2] - t[0] * q[2]);
faceInfo.binormal.x = (s[0] * q[0] - s[1] * p[0]);
faceInfo.binormal.y = (s[0] * q[1] - s[1] * p[1]);
faceInfo.binormal.z = (s[0] * q[2] - s[1] * p[2]);
// weight by area
faceInfo.tangent = TangentInfo::Vector3d::Normalize(faceInfo.tangent);
faceInfo.tangent.x *= areaMult;
faceInfo.tangent.y *= areaMult;
faceInfo.tangent.z *= areaMult;
faceInfo.binormal = TangentInfo::Vector3d::Normalize(faceInfo.binormal);
faceInfo.binormal.x *= areaMult;
faceInfo.binormal.y *= areaMult;
faceInfo.binormal.z *= areaMult;
}
for( unsigned v = 0 ; v < 3 ; ++v )
{
static const unsigned kNextIndex[][2] = { {2,1}, {0,2}, {1,0} };
TangentInfo::Vector3d edge1 =
{
triVertex[ kNextIndex[v][0] ].x - triVertex[v].x,
triVertex[ kNextIndex[v][0] ].y - triVertex[v].y,
triVertex[ kNextIndex[v][0] ].z - triVertex[v].z
};
/*
edge1.x = triVertex[ kNextIndex[v][0] ].x - triVertex[v].x;
edge1.y = triVertex[ kNextIndex[v][0] ].y - triVertex[v].y;
edge1.z = triVertex[ kNextIndex[v][0] ].z - triVertex[v].z;
*/
TangentInfo::Vector3d edge2 =
{
triVertex[ kNextIndex[v][1] ].x - triVertex[v].x,
triVertex[ kNextIndex[v][1] ].y - triVertex[v].y,
triVertex[ kNextIndex[v][1] ].z - triVertex[v].z
};
/*
edge2.x = triVertex[ kNextIndex[v][1] ].x - triVertex[v].x;
edge2.y = triVertex[ kNextIndex[v][1] ].y - triVertex[v].y;
edge2.z = triVertex[ kNextIndex[v][1] ].z - triVertex[v].z;
*/
// weight by angle
double angle = TangentInfo::Vector3d::Dot(TangentInfo::Vector3d::Normalize(edge1), TangentInfo::Vector3d::Normalize(edge2));
double w = std::acos( clamp(angle, -1.0, 1.0) );
out[v].tangent.x = w * faceInfo.tangent.x;
out[v].tangent.y = w * faceInfo.tangent.y;
out[v].tangent.z = w * faceInfo.tangent.z;
out[v].binormal.x = w * faceInfo.binormal.x;
out[v].binormal.y = w * faceInfo.binormal.y;
out[v].binormal.z = w * faceInfo.binormal.z;
}
}
#ifndef TANGENT_SPACE_H
#define TANGENT_SPACE_H
class Vector2f;
class Vector3f;
struct
TangentInfo
{
struct Vector3d
{
double x, y, z;
static double Dot(Vector3d v1, Vector3d v2);
static double Magnitude(Vector3d v);
static Vector3d Normalize(Vector3d v, double mag);
static Vector3d Normalize(Vector3d v);
};
Vector3d tangent;
Vector3d binormal;
};
void ComputeTriangleTangentBasis (const Vector3f* vertices, const Vector2f* uvs, const UInt32* indices, TangentInfo out[3]);
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment