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