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>