Created
May 31, 2012 15:01
-
-
Save aras-p/2843984 to your computer and use it in GitHub Desktop.
Unity tangent space calculation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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! |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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