mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 18:27:24 +01:00
Calculate missing tangents on import, convert all to bitangents for use
This commit is contained in:
parent
b53a2f1def
commit
9fae88934d
2 changed files with 122 additions and 7 deletions
|
|
@ -140,11 +140,15 @@ public class SubMeshImporter
|
|||
|
||||
private void BuildIndices()
|
||||
{
|
||||
// TODO: glTF supports a bunch of primitive types, ref. Schema2.PrimitiveType. All this code is currently assuming that it's using plain triangles (4). It should probably be generalised to other formats - I _suspect_ we should be able to get away with evaulating the indices to triangles with GetTriangleIndices, but will need investigation.
|
||||
_indices = _primitive.GetIndices().Select(idx => (ushort)idx).ToArray();
|
||||
}
|
||||
|
||||
private void BuildVertexAttributes()
|
||||
{
|
||||
// Tangent calculation requires indices if missing.
|
||||
ArgumentNullException.ThrowIfNull(_indices);
|
||||
|
||||
var accessors = _primitive.VertexAccessors;
|
||||
|
||||
var morphAccessors = Enumerable.Range(0, _primitive.MorphTargetsCount)
|
||||
|
|
@ -158,7 +162,7 @@ public class SubMeshImporter
|
|||
VertexAttribute.BlendWeight(accessors),
|
||||
VertexAttribute.BlendIndex(accessors, _nodeBoneMap),
|
||||
VertexAttribute.Normal(accessors, morphAccessors),
|
||||
VertexAttribute.Tangent1(accessors, morphAccessors),
|
||||
VertexAttribute.Tangent1(accessors, morphAccessors, _indices),
|
||||
VertexAttribute.Color(accessors),
|
||||
VertexAttribute.Uv(accessors),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -242,10 +242,24 @@ public class VertexAttribute
|
|||
);
|
||||
}
|
||||
|
||||
public static VertexAttribute? Tangent1(Accessors accessors, IEnumerable<Accessors> morphAccessors)
|
||||
public static VertexAttribute? Tangent1(Accessors accessors, IEnumerable<Accessors> morphAccessors, ushort[] indices)
|
||||
{
|
||||
if (!accessors.TryGetValue("TANGENT", out var accessor))
|
||||
if (!accessors.TryGetValue("NORMAL", out var normalAccessor))
|
||||
{
|
||||
Penumbra.Log.Warning("Normals are required to facilitate import or calculation of tangents.");
|
||||
return null;
|
||||
}
|
||||
|
||||
var normals = normalAccessor.AsVector3Array();
|
||||
var values = accessors.TryGetValue("TANGENT", out var accessor)
|
||||
? accessor.AsVector4Array()
|
||||
: CalculateTangents(accessors, indices, normals);
|
||||
|
||||
if (values == null)
|
||||
{
|
||||
Penumbra.Log.Warning("No tangents available for sub-mesh. This could lead to incorrect lighting, or mismatched vertex attributes.");
|
||||
return null;
|
||||
}
|
||||
|
||||
var element = new MdlStructs.VertexElement()
|
||||
{
|
||||
|
|
@ -254,8 +268,6 @@ public class VertexAttribute
|
|||
Usage = (byte)MdlFile.VertexUsage.Tangent1,
|
||||
};
|
||||
|
||||
var values = accessor.AsVector4Array();
|
||||
|
||||
// Per glTF specification, TANGENT morph values are stored as vec3, with the W component always considered to be 0.
|
||||
var morphValues = morphAccessors
|
||||
.Select(a => a.GetValueOrDefault("TANGENT")?.AsVector3Array())
|
||||
|
|
@ -263,7 +275,7 @@ public class VertexAttribute
|
|||
|
||||
return new VertexAttribute(
|
||||
element,
|
||||
index => BuildByteFloat4(values[index]),
|
||||
index => BuildBitangent(values[index], normals[index]),
|
||||
buildMorph: (morphIndex, vertexIndex) =>
|
||||
{
|
||||
var value = values[vertexIndex];
|
||||
|
|
@ -272,11 +284,110 @@ public class VertexAttribute
|
|||
if (delta != null)
|
||||
value += new Vector4(delta.Value, 0);
|
||||
|
||||
return BuildByteFloat4(value);
|
||||
return BuildBitangent(value, normals[vertexIndex]);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary> Build a byte array representing bitagent data computed from the provided tangent and normal. </summary>
|
||||
/// <remarks> XIV primarily stores bitangents, rather than tangents as with most other software, so we calculate on import. </remarks>
|
||||
private static byte[] BuildBitangent(Vector4 tangent, Vector3 normal)
|
||||
{
|
||||
var handedness = tangent.W;
|
||||
var tangent3 = new Vector3(tangent.X, tangent.Y, tangent.Z);
|
||||
var bitangent = Vector3.Normalize(Vector3.Cross(normal, tangent3));
|
||||
bitangent *= handedness;
|
||||
|
||||
// Byte floats encode 0..1, and bitangents are stored as -1..1. Convert.
|
||||
bitangent = (bitangent + Vector3.One) / 2;
|
||||
return BuildByteFloat4(new Vector4(bitangent, handedness));
|
||||
}
|
||||
|
||||
/// <summary> Attempt to calculate tangent values based on other pre-existing data. </summary>
|
||||
private static Vector4[]? CalculateTangents(Accessors accessors, ushort[] indices, IList<Vector3> normals)
|
||||
{
|
||||
// To calculate tangents, we will also need access to uv data.
|
||||
if (!accessors.TryGetValue("TEXCOORD_0", out var uvAccessor))
|
||||
return null;
|
||||
|
||||
var positions = accessors["POSITION"].AsVector3Array();
|
||||
var uvs = uvAccessor.AsVector2Array();
|
||||
|
||||
// TODO: Surface this in the UI.
|
||||
Penumbra.Log.Warning("Calculating tangents, this may result in degraded light interaction. For best results, ensure tangents are caculated or retained during export from 3D modelling tools.");
|
||||
|
||||
var vertexCount = positions.Count;
|
||||
|
||||
// https://github.com/TexTools/xivModdingFramework/blob/master/xivModdingFramework/Models/Helpers/ModelModifiers.cs#L1569
|
||||
// https://gamedev.stackexchange.com/a/68617
|
||||
// https://marti.works/posts/post-calculating-tangents-for-your-mesh/post/
|
||||
var tangents = new Vector3[vertexCount];
|
||||
var bitangents = new Vector3[vertexCount];
|
||||
|
||||
// Iterate over triangles, calculating tangents relative to the UVs.
|
||||
for (var index = 0; index < indices.Length; index += 3)
|
||||
{
|
||||
// Collect information for this triangle.
|
||||
var vertexIndex1 = indices[index];
|
||||
var vertexIndex2 = indices[index + 1];
|
||||
var vertexIndex3 = indices[index + 2];
|
||||
|
||||
var position1 = positions[vertexIndex1];
|
||||
var position2 = positions[vertexIndex2];
|
||||
var position3 = positions[vertexIndex3];
|
||||
|
||||
var texcoord1 = uvs[vertexIndex1];
|
||||
var texcoord2 = uvs[vertexIndex2];
|
||||
var texcoord3 = uvs[vertexIndex3];
|
||||
|
||||
// Calculate deltas for the position XYZ, and texcoord UV.
|
||||
var edge1 = position2 - position1;
|
||||
var edge2 = position3 - position1;
|
||||
|
||||
var uv1 = texcoord2 - texcoord1;
|
||||
var uv2 = texcoord3 - texcoord1;
|
||||
|
||||
// Solve.
|
||||
var r = 1.0f / (uv1.X * uv2.Y - uv1.Y * uv2.X);
|
||||
var tangent = new Vector3(
|
||||
(edge1.X * uv2.Y - edge2.X * uv1.Y) * r,
|
||||
(edge1.Y * uv2.Y - edge2.Y * uv1.Y) * r,
|
||||
(edge1.Z * uv2.Y - edge2.Z * uv1.Y) * r
|
||||
);
|
||||
var bitangent = new Vector3(
|
||||
(edge1.X * uv2.X - edge2.X * uv1.X) * r,
|
||||
(edge1.Y * uv2.X - edge2.Y * uv1.X) * r,
|
||||
(edge1.Z * uv2.X - edge2.Z * uv1.X) * r
|
||||
);
|
||||
|
||||
// Update vertex values.
|
||||
tangents[vertexIndex1] += tangent;
|
||||
tangents[vertexIndex2] += tangent;
|
||||
tangents[vertexIndex3] += tangent;
|
||||
|
||||
bitangents[vertexIndex1] += bitangent;
|
||||
bitangents[vertexIndex2] += bitangent;
|
||||
bitangents[vertexIndex3] += bitangent;
|
||||
}
|
||||
|
||||
// All the triangles have been calcualted, normalise the results for each vertex.
|
||||
var result = new Vector4[vertexCount];
|
||||
for (var vertexIndex = 0; vertexIndex < vertexCount; vertexIndex++)
|
||||
{
|
||||
var n = normals[vertexIndex];
|
||||
var t = tangents[vertexIndex];
|
||||
var b = bitangents[vertexIndex];
|
||||
|
||||
// Gram-Schmidt orthogonalize and calculate handedness.
|
||||
var tangent = Vector3.Normalize(t - n * Vector3.Dot(n, t));
|
||||
var handedness = Vector3.Dot(Vector3.Cross(t, b), n) > 0 ? 1 : -1;
|
||||
|
||||
result[vertexIndex] = new Vector4(tangent, handedness);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static VertexAttribute? Color(Accessors accessors)
|
||||
{
|
||||
if (!accessors.TryGetValue("COLOR_0", out var accessor))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue