Fix mesh tangent and bitangent vectors generation with OpenFBX backend by using MikkTSpace

This commit is contained in:
Wojtek Figat
2021-11-11 13:23:30 +01:00
parent ac347f0029
commit 6f16195b08
9 changed files with 2218 additions and 46 deletions

View File

@@ -11,6 +11,8 @@
#include "Engine/Tools/ModelTool/ModelTool.h"
#include "Engine/Tools/ModelTool/VertexTriangleAdjacency.h"
#include "Engine/Platform/Platform.h"
#define USE_MIKKTSPACE 1
#include "ThirdParty/MikkTSpace/mikktspace.h"
#if USE_ASSIMP
#define USE_SPARIAL_SORT 1
#define ASSIMP_BUILD_NO_EXPORT
@@ -279,6 +281,7 @@ void MeshData::BuildIndexBuffer()
REMAP_BUFFER(UVs);
REMAP_BUFFER(Normals);
REMAP_BUFFER(Tangents);
REMAP_BUFFER(BitangentSigns);
REMAP_BUFFER(LightmapUVs);
REMAP_BUFFER(Colors);
REMAP_BUFFER(BlendIndices);
@@ -445,6 +448,56 @@ bool MeshData::GenerateNormals(float smoothingAngle)
return false;
}
#if USE_MIKKTSPACE
namespace
{
int GetNumFaces(const SMikkTSpaceContext* pContext)
{
const auto meshData = (MeshData*)pContext->m_pUserData;
return meshData->Indices.Count() / 3;
}
int GetNumVerticesOfFace(const SMikkTSpaceContext* pContext, const int iFace)
{
return 3;
}
void GetPosition(const SMikkTSpaceContext* pContext, float fvPosOut[], const int iFace, const int iVert)
{
const auto meshData = (MeshData*)pContext->m_pUserData;
const auto e = meshData->Positions[meshData->Indices[iFace * 3 + iVert]];
fvPosOut[0] = e.X;
fvPosOut[1] = e.Y;
fvPosOut[2] = e.Z;
}
void GetNormal(const SMikkTSpaceContext* pContext, float fvNormOut[], const int iFace, const int iVert)
{
const auto meshData = (MeshData*)pContext->m_pUserData;
const auto e = meshData->Normals[meshData->Indices[iFace * 3 + iVert]];
fvNormOut[0] = e.X;
fvNormOut[1] = e.Y;
fvNormOut[2] = e.Z;
}
void GetTexCoord(const SMikkTSpaceContext* pContext, float fvTexcOut[], const int iFace, const int iVert)
{
const auto meshData = (MeshData*)pContext->m_pUserData;
const auto e = meshData->UVs[meshData->Indices[iFace * 3 + iVert]];
fvTexcOut[0] = e.X;
fvTexcOut[1] = e.Y;
}
void SetTSpaceBasic(const SMikkTSpaceContext* pContext, const float fvTangent[], const float fSign, const int iFace, const int iVert)
{
const auto meshData = (MeshData*)pContext->m_pUserData;
const auto v = meshData->Indices[iFace * 3 + iVert];
meshData->Tangents[v] = Vector3(fvTangent);
meshData->BitangentSigns[v] = fSign;
}
}
#endif
bool MeshData::GenerateTangents(float smoothingAngle)
{
if (Positions.IsEmpty() || Indices.IsEmpty())
@@ -459,16 +512,25 @@ bool MeshData::GenerateTangents(float smoothingAngle)
}
const auto startTime = Platform::GetTimeSeconds();
const int32 vertexCount = Positions.Count();
const int32 indexCount = Indices.Count();
Tangents.Resize(vertexCount, false);
smoothingAngle = Math::Clamp(smoothingAngle, 0.0f, 45.0f);
// Note: this assumes that mesh is in a verbose format
// where each triangle has its own set of vertices
// and no vertices are shared between triangles (dummy index buffer).
#if USE_MIKKTSPACE
SMikkTSpaceInterface callbacks = {
GetNumFaces,
GetNumVerticesOfFace,
GetPosition,
GetNormal,
GetTexCoord,
SetTSpaceBasic,
nullptr
};
const SMikkTSpaceContext context = { &callbacks, this };
BitangentSigns.Resize(vertexCount, false);
genTangSpace(&context, 180.0f - smoothingAngle);
#else
const float angleEpsilon = 0.9999f;
BitArray<> vertexDone;
vertexDone.Resize(vertexCount);
@@ -478,36 +540,35 @@ bool MeshData::GenerateTangents(float smoothingAngle)
const Vector2* meshTex = UVs.Get();
Vector3* meshTang = Tangents.Get();
// Calculate the tangent for every face
// Calculate the tangent per-triangle
Vector3 min, max;
min = max = Positions[0];
min = max = Positions[Indices[0]];
for (int32 i = 0; i < indexCount; i += 3)
{
const int32 p0 = Indices[i + 0], p1 = Indices[i + 1], p2 = Indices[i + 2];
const Vector3 v1 = Positions[p0];
const Vector3 v2 = Positions[p1];
const Vector3 v3 = Positions[p2];
const Vector3 v0 = Positions[p0];
const Vector3 v1 = Positions[p1];
const Vector3 v2 = Positions[p2];
Vector3::Min(min, v0, min);
Vector3::Min(min, v1, min);
Vector3::Min(min, v2, min);
Vector3::Min(min, v3, min);
Vector3::Max(max, v0, max);
Vector3::Max(max, v1, max);
Vector3::Max(max, v2, max);
Vector3::Max(max, v3, max);
// Position differences p1->p2 and p1->p3
Vector3 v = v2 - v1, w = v3 - v1;
Vector3 v = v1 - v0, w = v2 - v0;
// Texture offset p1->p2 and p1->p3
float sx = meshTex[p1].X - meshTex[p0].X, sy = meshTex[p1].Y - meshTex[p0].Y;
float tx = meshTex[p2].X - meshTex[p0].X, ty = meshTex[p2].Y - meshTex[p0].Y;
const float dirCorrection = (tx * sy - ty * sx) < 0.0f ? -1.0f : 1.0f;
// When t1, t2, t3 in same position in UV space, just use default UV direction
const float dir = (tx * sy - ty * sx) < 0.0f ? -1.0f : 1.0f;
if (sx * ty == sy * tx)
{
// Use default UV direction for invalid case
sx = 0.0;
sy = 1.0;
tx = 1.0;
@@ -517,19 +578,19 @@ bool MeshData::GenerateTangents(float smoothingAngle)
// Tangent points in the direction where to positive X axis of the texture coord's would point in model space
// Bitangent's points along the positive Y axis of the texture coord's, respectively
Vector3 tangent, bitangent;
tangent.X = (w.X * sy - v.X * ty) * dirCorrection;
tangent.Y = (w.Y * sy - v.Y * ty) * dirCorrection;
tangent.Z = (w.Z * sy - v.Z * ty) * dirCorrection;
bitangent.X = (w.X * sx - v.X * tx) * dirCorrection;
bitangent.Y = (w.Y * sx - v.Y * tx) * dirCorrection;
bitangent.Z = (w.Z * sx - v.Z * tx) * dirCorrection;
tangent.X = (w.X * sy - v.X * ty) * dir;
tangent.Y = (w.Y * sy - v.Y * ty) * dir;
tangent.Z = (w.Z * sy - v.Z * ty) * dir;
bitangent.X = (w.X * sx - v.X * tx) * dir;
bitangent.Y = (w.Y * sx - v.Y * tx) * dir;
bitangent.Z = (w.Z * sx - v.Z * tx) * dir;
// Store for every vertex of that face
// Set tangent frame for every vertex in that triangle
for (int32 b = 0; b < 3; b++)
{
const int32 p = Indices[i + b];
// Project tangent and bitangent into the plane formed by the vertex' normal
// Project tangent and bitangent into the plane formed by the normal
Vector3 localTangent = tangent - meshNorm[p] * (tangent * meshNorm[p]);
Vector3 localBitangent = bitangent - meshNorm[p] * (bitangent * meshNorm[p]);
localTangent.Normalize();
@@ -601,10 +662,11 @@ bool MeshData::GenerateTangents(float smoothingAngle)
}
// Smooth the tangents of all vertices that were found to be close enough
Vector3 smoothTangent(0, 0, 0);
Vector3 smoothTangent(0, 0, 0), smoothBitangent(0, 0, 0);
for (int32 b = 0; b < closeVertices.Count(); b++)
{
smoothTangent += meshTang[closeVertices[b]];
auto p = closeVertices[b];
smoothTangent += meshTang[p];
}
smoothTangent.Normalize();
@@ -614,10 +676,10 @@ bool MeshData::GenerateTangents(float smoothingAngle)
meshTang[closeVertices[b]] = smoothTangent;
}
}
#endif
const auto endTime = Platform::GetTimeSeconds();
LOG(Info, "Generated tangents for mesh in {0}s ({1} vertices, {2} indices)", Utilities::RoundTo2DecimalPlaces(endTime - startTime), vertexCount, indexCount);
return false;
}
@@ -646,14 +708,13 @@ void MeshData::ImproveCacheLocality()
Platform::MemoryClear(piCachingStamps, vertexCount * sizeof(uint32));
// Allocate an empty output index buffer. We store the output indices in one large array.
// Since the number of triangles won't change the input faces can be reused. This is how
// We save thousands of redundant mini allocations for aiFace::mIndices
// Since the number of triangles won't change the input triangles can be reused. This is how
const uint32 iIdxCnt = indexCount;
uint32* const piIBOutput = NewArray<uint32>(iIdxCnt);
uint32* piCSIter = piIBOutput;
// Allocate the flag array to hold the information
// Whether a face has already been emitted or not
// Whether a triangle has already been emitted or not
std::vector<bool> abEmitted(indexCount / 3, false);
// Dead-end vertex index stack

View File

@@ -17,6 +17,7 @@ void MeshData::Clear()
UVs.Clear();
Normals.Clear();
Tangents.Clear();
BitangentSigns.Clear();
LightmapUVs.Clear();
Colors.Clear();
BlendIndices.Clear();
@@ -44,6 +45,7 @@ void MeshData::SwapBuffers(MeshData& other)
UVs.Swap(other.UVs);
Normals.Swap(other.Normals);
Tangents.Swap(other.Tangents);
BitangentSigns.Swap(other.BitangentSigns);
LightmapUVs.Swap(other.LightmapUVs);
Colors.Swap(other.Colors);
BlendIndices.Swap(other.BlendIndices);
@@ -59,6 +61,7 @@ void MeshData::Release()
UVs.Resize(0);
Normals.Resize(0);
Tangents.Resize(0);
BitangentSigns.Resize(0);
LightmapUVs.Resize(0);
Colors.Resize(0);
BlendIndices.Resize(0);
@@ -72,6 +75,7 @@ void MeshData::InitFromModelVertices(ModelVertex19* vertices, uint32 verticesCou
UVs.Resize(verticesCount, false);
Normals.Resize(verticesCount, false);
Tangents.Resize(verticesCount, false);
BitangentSigns.Resize(0);
LightmapUVs.Resize(verticesCount, false);
Colors.Resize(0);
BlendIndices.Resize(0);
@@ -97,6 +101,7 @@ void MeshData::InitFromModelVertices(ModelVertex18* vertices, uint32 verticesCou
UVs.Resize(verticesCount, false);
Normals.Resize(verticesCount, false);
Tangents.Resize(verticesCount, false);
BitangentSigns.Resize(0);
LightmapUVs.Resize(verticesCount, false);
Colors.Resize(0);
BlendIndices.Resize(0);
@@ -121,6 +126,7 @@ void MeshData::InitFromModelVertices(ModelVertex15* vertices, uint32 verticesCou
UVs.Resize(verticesCount, false);
Normals.Resize(verticesCount, false);
Tangents.Resize(verticesCount, false);
BitangentSigns.Resize(0);
LightmapUVs.Resize(0);
Colors.Resize(0);
BlendIndices.Resize(0);
@@ -144,6 +150,7 @@ void MeshData::InitFromModelVertices(VB0ElementType18* vb0, VB1ElementType18* vb
UVs.Resize(verticesCount, false);
Normals.Resize(verticesCount, false);
Tangents.Resize(verticesCount, false);
BitangentSigns.Resize(0);
LightmapUVs.Resize(verticesCount, false);
Colors.Resize(0);
BlendIndices.Resize(0);
@@ -169,6 +176,7 @@ void MeshData::InitFromModelVertices(VB0ElementType18* vb0, VB1ElementType18* vb
UVs.Resize(verticesCount, false);
Normals.Resize(verticesCount, false);
Tangents.Resize(verticesCount, false);
BitangentSigns.Resize(0);
LightmapUVs.Resize(verticesCount, false);
if (vb2)
{
@@ -206,6 +214,7 @@ void MeshData::InitFromModelVertices(VB0ElementType15* vb0, VB1ElementType15* vb
UVs.Resize(verticesCount, false);
Normals.Resize(verticesCount, false);
Tangents.Resize(verticesCount, false);
BitangentSigns.Resize(0);
LightmapUVs.Resize(0, false);
Colors.Resize(0);
BlendIndices.Resize(0);
@@ -283,6 +292,12 @@ bool MeshData::Pack2Model(WriteStream* stream) const
LOG(Error, "Invalid size of {0} stream.", TEXT("Tangents"));
return true;
}
bool hasBitangentSigns = BitangentSigns.HasItems();
if (hasBitangentSigns && BitangentSigns.Count() != verticiecCount)
{
LOG(Error, "Invalid size of {0} stream.", TEXT("BitangentSigns"));
return true;
}
bool hasLightmapUVs = LightmapUVs.HasItems();
if (hasLightmapUVs && LightmapUVs.Count() != verticiecCount)
{
@@ -314,15 +329,12 @@ bool MeshData::Pack2Model(WriteStream* stream) const
Vector3 normal = hasNormals ? Normals[i] : Vector3::UnitZ;
Vector3 tangent = hasTangents ? Tangents[i] : Vector3::UnitX;
Vector2 lightmapUV = hasLightmapUVs ? LightmapUVs[i] : Vector2::Zero;
// Calculate bitangent sign
Vector3 bitangent = Vector3::Normalize(Vector3::Cross(normal, tangent));
byte sign = static_cast<byte>(Vector3::Dot(Vector3::Cross(bitangent, normal), tangent) < 0.0f ? 1 : 0);
Vector3 bitangentSign = hasBitangentSigns ? BitangentSigns[i] : Vector3::Dot(Vector3::Cross(Vector3::Normalize(Vector3::Cross(normal, tangent)), normal), tangent);
// Write vertex
vb1.TexCoord = Half2(uv);
vb1.Normal = Float1010102(normal * 0.5f + 0.5f, 0);
vb1.Tangent = Float1010102(tangent * 0.5f + 0.5f, sign);
vb1.Tangent = Float1010102(tangent * 0.5f + 0.5f, static_cast<byte>(bitangentSign < 0 ? 1 : 0));
vb1.LightmapUVs = Half2(lightmapUV);
stream->Write(&vb1);
@@ -405,6 +417,12 @@ bool MeshData::Pack2SkinnedModel(WriteStream* stream) const
LOG(Error, "Invalid size of {0} stream.", TEXT("Tangents"));
return true;
}
bool hasBitangentSigns = BitangentSigns.HasItems();
if (hasBitangentSigns && BitangentSigns.Count() != verticiecCount)
{
LOG(Error, "Invalid size of {0} stream.", TEXT("BitangentSigns"));
return true;
}
if (BlendIndices.Count() != verticiecCount)
{
LOG(Error, "Invalid size of {0} stream.", TEXT("BlendIndices"));
@@ -441,18 +459,15 @@ bool MeshData::Pack2SkinnedModel(WriteStream* stream) const
Vector2 uv = hasUVs ? UVs[i] : Vector2::Zero;
Vector3 normal = hasNormals ? Normals[i] : Vector3::UnitZ;
Vector3 tangent = hasTangents ? Tangents[i] : Vector3::UnitX;
Vector3 bitangentSign = hasBitangentSigns ? BitangentSigns[i] : Vector3::Dot(Vector3::Cross(Vector3::Normalize(Vector3::Cross(normal, tangent)), normal), tangent);
Int4 blendIndices = BlendIndices[i];
Vector4 blendWeights = BlendWeights[i];
// Calculate bitangent sign
Vector3 bitangent = Vector3::Normalize(Vector3::Cross(normal, tangent));
byte sign = static_cast<byte>(Vector3::Dot(Vector3::Cross(bitangent, normal), tangent) < 0.0f ? 1 : 0);
// Write vertex
vb.Position = Positions[i];
vb.TexCoord = Half2(uv);
vb.Normal = Float1010102(normal * 0.5f + 0.5f, 0);
vb.Tangent = Float1010102(tangent * 0.5f + 0.5f, sign);
vb.Tangent = Float1010102(tangent * 0.5f + 0.5f, static_cast<byte>(bitangentSign < 0 ? 1 : 0));
vb.BlendIndices = Color32(blendIndices.X, blendIndices.Y, blendIndices.Z, blendIndices.W);
vb.BlendWeights = Half4(blendWeights);
stream->Write(&vb);
@@ -522,6 +537,7 @@ void MeshData::Merge(MeshData& other)
MERGE(UVs, Vector2::Zero);
MERGE(Normals, Vector3::Forward);
MERGE(Tangents, Vector3::Right);
MERGE(BitangentSigns, 1.0f);
MERGE(LightmapUVs, Vector2::Zero);
MERGE(Colors, Color::Black);
MERGE(BlendIndices, Int4::Zero);

View File

@@ -56,6 +56,13 @@ public:
/// </summary>
Array<Vector3> Tangents;
/// <summary>
/// Bitangents vectors signs (used for bitangent reconstruction). Can be +1 or -1.
/// bitangent = cross(normal, tangent) * sign
/// sign = dot(cross(bitangent, normal), tangent)
/// </summary>
Array<float> BitangentSigns;
/// <summary>
/// Mesh index buffer
/// </summary>

View File

@@ -61,6 +61,7 @@ public class ModelTool : EngineModule
}
options.PrivateDependencies.Add("meshoptimizer");
options.PrivateDependencies.Add("MikkTSpace");
options.PublicDefinitions.Add("COMPILE_WITH_MODEL_TOOL");
}

View File

@@ -543,11 +543,7 @@ bool ProcessMesh(OpenFbxImporterData& data, const ofbx::Mesh* aMesh, MeshData& m
// Tangents
if ((data.Options.CalculateTangents || !tangents) && mesh.UVs.HasItems())
{
if (mesh.GenerateTangents(data.Options.SmoothingTangentsAngle))
{
errorMsg = TEXT("Failed to generate tangents.");
return true;
}
// Generated after full mesh data conversion
}
else if (tangents)
{
@@ -779,6 +775,15 @@ bool ProcessMesh(OpenFbxImporterData& data, const ofbx::Mesh* aMesh, MeshData& m
}
}
if ((data.Options.CalculateTangents || !tangents) && mesh.UVs.HasItems())
{
if (mesh.GenerateTangents(data.Options.SmoothingTangentsAngle))
{
errorMsg = TEXT("Failed to generate tangents.");
return true;
}
}
if (data.Options.OptimizeMeshes)
{
mesh.ImproveCacheLocality();
@@ -918,7 +923,7 @@ void ExtractKeyframePosition(const ofbx::Object* bone, ofbx::Vec3& trans, const
void ExtractKeyframeRotation(const ofbx::Object* bone, ofbx::Vec3& trans, const Frame& localFrame, Quaternion& keyframe)
{
const Matrix frameTrans = ToMatrix(bone->evalLocal(localFrame.Translation, trans, {1.0, 1.0, 1.0 }));
const Matrix frameTrans = ToMatrix(bone->evalLocal(localFrame.Translation, trans, { 1.0, 1.0, 1.0 }));
Quaternion::RotationMatrix(frameTrans, keyframe);
}

View File

@@ -0,0 +1,21 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
using Flax.Build;
/// <summary>
/// https://github.com/mmikk/MikkTSpace
/// </summary>
public class MikkTSpace : ThirdPartyModule
{
/// <inheritdoc />
public override void Init()
{
base.Init();
LicenseType = LicenseTypes.MIT;
LicenseFilePath = "license.txt";
// Merge third-party modules into engine binary
BinaryModuleName = "FlaxEngine";
}
}

View File

@@ -0,0 +1,17 @@
Copyright (C) 2011 by Morten S. Mikkelsen
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,145 @@
/** \file mikktspace/mikktspace.h
* \ingroup mikktspace
*/
/**
* Copyright (C) 2011 by Morten S. Mikkelsen
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*/
#ifndef __MIKKTSPACE_H__
#define __MIKKTSPACE_H__
#ifdef __cplusplus
extern "C" {
#endif
/* Author: Morten S. Mikkelsen
* Version: 1.0
*
* The files mikktspace.h and mikktspace.c are designed to be
* stand-alone files and it is important that they are kept this way.
* Not having dependencies on structures/classes/libraries specific
* to the program, in which they are used, allows them to be copied
* and used as is into any tool, program or plugin.
* The code is designed to consistently generate the same
* tangent spaces, for a given mesh, in any tool in which it is used.
* This is done by performing an internal welding step and subsequently an order-independent evaluation
* of tangent space for meshes consisting of triangles and quads.
* This means faces can be received in any order and the same is true for
* the order of vertices of each face. The generated result will not be affected
* by such reordering. Additionally, whether degenerate (vertices or texture coordinates)
* primitives are present or not will not affect the generated results either.
* Once tangent space calculation is done the vertices of degenerate primitives will simply
* inherit tangent space from neighboring non degenerate primitives.
* The analysis behind this implementation can be found in my master's thesis
* which is available for download --> http://image.diku.dk/projects/media/morten.mikkelsen.08.pdf
* Note that though the tangent spaces at the vertices are generated in an order-independent way,
* by this implementation, the interpolated tangent space is still affected by which diagonal is
* chosen to split each quad. A sensible solution is to have your tools pipeline always
* split quads by the shortest diagonal. This choice is order-independent and works with mirroring.
* If these have the same length then compare the diagonals defined by the texture coordinates.
* XNormal which is a tool for baking normal maps allows you to write your own tangent space plugin
* and also quad triangulator plugin.
*/
typedef int tbool;
typedef struct SMikkTSpaceContext SMikkTSpaceContext;
typedef struct {
// Returns the number of faces (triangles/quads) on the mesh to be processed.
int (*m_getNumFaces)(const SMikkTSpaceContext * pContext);
// Returns the number of vertices on face number iFace
// iFace is a number in the range {0, 1, ..., getNumFaces()-1}
int (*m_getNumVerticesOfFace)(const SMikkTSpaceContext * pContext, const int iFace);
// returns the position/normal/texcoord of the referenced face of vertex number iVert.
// iVert is in the range {0,1,2} for triangles and {0,1,2,3} for quads.
void (*m_getPosition)(const SMikkTSpaceContext * pContext, float fvPosOut[], const int iFace, const int iVert);
void (*m_getNormal)(const SMikkTSpaceContext * pContext, float fvNormOut[], const int iFace, const int iVert);
void (*m_getTexCoord)(const SMikkTSpaceContext * pContext, float fvTexcOut[], const int iFace, const int iVert);
// either (or both) of the two setTSpace callbacks can be set.
// The call-back m_setTSpaceBasic() is sufficient for basic normal mapping.
// This function is used to return the tangent and fSign to the application.
// fvTangent is a unit length vector.
// For normal maps it is sufficient to use the following simplified version of the bitangent which is generated at pixel/vertex level.
// bitangent = fSign * cross(vN, tangent);
// Note that the results are returned unindexed. It is possible to generate a new index list
// But averaging/overwriting tangent spaces by using an already existing index list WILL produce INCRORRECT results.
// DO NOT! use an already existing index list.
void (*m_setTSpaceBasic)(const SMikkTSpaceContext * pContext, const float fvTangent[], const float fSign, const int iFace, const int iVert);
// This function is used to return tangent space results to the application.
// fvTangent and fvBiTangent are unit length vectors and fMagS and fMagT are their
// true magnitudes which can be used for relief mapping effects.
// fvBiTangent is the "real" bitangent and thus may not be perpendicular to fvTangent.
// However, both are perpendicular to the vertex normal.
// For normal maps it is sufficient to use the following simplified version of the bitangent which is generated at pixel/vertex level.
// fSign = bIsOrientationPreserving ? 1.0f : (-1.0f);
// bitangent = fSign * cross(vN, tangent);
// Note that the results are returned unindexed. It is possible to generate a new index list
// But averaging/overwriting tangent spaces by using an already existing index list WILL produce INCRORRECT results.
// DO NOT! use an already existing index list.
void (*m_setTSpace)(const SMikkTSpaceContext * pContext, const float fvTangent[], const float fvBiTangent[], const float fMagS, const float fMagT,
const tbool bIsOrientationPreserving, const int iFace, const int iVert);
} SMikkTSpaceInterface;
struct SMikkTSpaceContext
{
SMikkTSpaceInterface * m_pInterface; // initialized with callback functions
void * m_pUserData; // pointer to client side mesh data etc. (passed as the first parameter with every interface call)
};
// these are both thread safe!
tbool genTangSpaceDefault(const SMikkTSpaceContext * pContext); // Default (recommended) fAngularThreshold is 180 degrees (which means threshold disabled)
tbool genTangSpace(const SMikkTSpaceContext * pContext, const float fAngularThreshold);
// To avoid visual errors (distortions/unwanted hard edges in lighting), when using sampled normal maps, the
// normal map sampler must use the exact inverse of the pixel shader transformation.
// The most efficient transformation we can possibly do in the pixel shader is
// achieved by using, directly, the "unnormalized" interpolated tangent, bitangent and vertex normal: vT, vB and vN.
// pixel shader (fast transform out)
// vNout = normalize( vNt.x * vT + vNt.y * vB + vNt.z * vN );
// where vNt is the tangent space normal. The normal map sampler must likewise use the
// interpolated and "unnormalized" tangent, bitangent and vertex normal to be compliant with the pixel shader.
// sampler does (exact inverse of pixel shader):
// float3 row0 = cross(vB, vN);
// float3 row1 = cross(vN, vT);
// float3 row2 = cross(vT, vB);
// float fSign = dot(vT, row0)<0 ? -1 : 1;
// vNt = normalize( fSign * float3(dot(vNout,row0), dot(vNout,row1), dot(vNout,row2)) );
// where vNout is the sampled normal in some chosen 3D space.
//
// Should you choose to reconstruct the bitangent in the pixel shader instead
// of the vertex shader, as explained earlier, then be sure to do this in the normal map sampler also.
// Finally, beware of quad triangulations. If the normal map sampler doesn't use the same triangulation of
// quads as your renderer then problems will occur since the interpolated tangent spaces will differ
// eventhough the vertex level tangent spaces match. This can be solved either by triangulating before
// sampling/exporting or by using the order-independent choice of diagonal for splitting quads suggested earlier.
// However, this must be used both by the sampler and your tools/rendering pipeline.
#ifdef __cplusplus
}
#endif
#endif