feat(ufbx): switch to native ufbx_vec/quat/matrix types

Replaces all Misaki.HighPerformance.Mathematics vector, quaternion, and matrix types in Ghost.Ufbx bindings with new native ufbx_vec2, ufbx_vec3, ufbx_vec4, ufbx_quat, and ufbx_matrix structs. Updates all interop code, struct fields, and API signatures accordingly. Adds struct definitions for the new types and provides matrix operations as struct methods. Removes unnecessary math package reference. Also includes minor fixes to system attributes, meshlet LOD logic, and mesh utility.

BREAKING CHANGE: All Ufbx-related APIs now use ufbx_* types instead of Misaki.HighPerformance.Mathematics types. Existing code using the old types will require updates.
This commit is contained in:
2026-04-07 23:50:55 +09:00
parent a5c10cfe5a
commit 0fc449bc78
60 changed files with 694 additions and 415 deletions

View File

@@ -25,7 +25,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Misaki.HighPerformance.Mathematics" Version="1.3.1" />
<PackageReference Include="Misaki.HighPerformance.Mathematics" Version="1.3.3" />
<PackageReference Include="System.IO.Hashing" Version="10.0.5" />
<PackageReference Include="TerraFX.Interop.Windows" Version="10.0.26100.6" />
</ItemGroup>

View File

@@ -19,9 +19,13 @@ public static class MathUtility
{
var R = new float3x3(rotation);
return new float4x4(
R[0][0] * scale.x, R[0][1] * scale.y, R[0][2] * scale.z, position.x,
R[1][0] * scale.x, R[1][1] * scale.y, R[1][2] * scale.z, position.y,
R[2][0] * scale.x, R[2][1] * scale.y, R[2][2] * scale.z, position.z,
// Row 0: Right.x, Up.x, Forward.x, Pos.x
R[0][0] * scale.x, R[1][0] * scale.y, R[2][0] * scale.z, position.x,
// Row 1: Right.y, Up.y, Forward.y, Pos.y
R[0][1] * scale.x, R[1][1] * scale.y, R[2][1] * scale.z, position.y,
// Row 2: Right.z, Up.z, Forward.z, Pos.z
R[0][2] * scale.x, R[1][2] * scale.y, R[2][2] * scale.z, position.z,
// Row 3: 0, 0, 0, 1
0f, 0f, 0f, 1f
);
}

View File

@@ -113,26 +113,26 @@ public abstract class SystemBase : ISystem
}
}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = true)]
public class UpdateAfterAttribute : Attribute
public abstract class UpdateAfterAttribute : Attribute
{
public Type SystemType { get; }
public abstract Type SystemType { get; }
}
public UpdateAfterAttribute(Type systemType)
{
SystemType = systemType;
}
public abstract class UpdateBeforeAttribute : Attribute
{
public abstract Type SystemType { get; }
}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = true)]
public class UpdateBeforeAttribute : Attribute
public class UpdateAfterAttribute<T> : UpdateAfterAttribute
{
public Type SystemType { get; }
public override Type SystemType => typeof(T);
}
public UpdateBeforeAttribute(Type systemType)
{
SystemType = systemType;
}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = true)]
public class UpdateBeforeAttribute<T> : UpdateBeforeAttribute
{
public override Type SystemType => typeof(T);
}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false)]

View File

@@ -26,7 +26,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="TerraFX.Interop.D3D12MemoryAllocator" Version="3.0.1" />
<PackageReference Include="TerraFX.Interop.D3D12MemoryAllocator" Version="3.1.0" />
</ItemGroup>
<ItemGroup>

View File

@@ -15,10 +15,12 @@ namespace Ghost.Graphics.Core;
public struct Meshlet
{
public SphereBounds boundingSphere; // 16 bytes
public SphereBounds parentBoundingSphere; // 16 bytes
public AABB boundingBox; // 24 bytes
public uint vertexOffset; // offset into meshlet vertex index array
public uint triangleOffset; // offset into packed triangle array
public uint groupIndex; // owning group
public float clusterError; // geometric error of this meshlet/cluster
public float parentError; // geometric refinement error carried into runtime LOD tests
public byte vertexCount; // max 64
public byte triangleCount; // max 124
@@ -319,13 +321,15 @@ public struct Mesh : IResourceReleasable
var meshlet = new Meshlet
{
boundingSphere = new SphereBounds(cluster.bounds.center, cluster.bounds.radius),
parentBoundingSphere = new SphereBounds(group.simplified.center, group.simplified.radius),
boundingBox = new AABB(cluster.bounds.center - cluster.bounds.radius, cluster.bounds.center + cluster.bounds.radius),
vertexCount = (byte)cluster.vertexCount,
triangleCount = (byte)(cluster.localIndexCount / 3),
vertexOffset = (uint)data.meshletVertices.Count,
triangleOffset = (uint)data.meshletTriangles.Count,
groupIndex = (uint)data.groups.Count - 1,
parentError = cluster.bounds.error,
clusterError = cluster.bounds.error,
parentError = group.simplified.error,
localMaterialIndex = 0, // TODO: support multiple materials
lodLevel = (byte)group.depth,
};

View File

@@ -13,11 +13,13 @@ struct Vertex
struct Meshlet
{
float4 boundingSphere;
float4 parentBoundingSphere;
float3 boundingBoxMin;
float3 boundingBoxMax;
uint vertexOffset;
uint triangleOffset;
uint groupIndex;
float clusterError;
float parentError;
uint packedCounts; // byte vertexCount, byte triangleCount, byte localMaterialIndex, byte lodLevel
};

View File

@@ -46,19 +46,59 @@ shader "MyShader/Standard"
groupshared ASPayload s_Payload;
// computes approximate (perspective) projection error of a cluster in screen space
float BoundsError(float3 center, float radius, float error, float3 cameraPos, float cameraProj, float cameraZNear)
{
float d = length(center - cameraPos) - radius;
return error / max(d, cameraZNear) * (cameraProj * 0.5f);
}
[numthreads(1, 1, 1)]
void ASMain(uint3 groupID : SV_GroupID)
{
InstanceData instanceData = LoadData<InstanceData>(g_PushConstantData.instanceBuffer, g_PushConstantData.instanceIndex);
MeshData meshData = LoadData<MeshData>(instanceData.meshBuffer, 0);
ByteAddressBuffer meshletBuffer = GET_BUFFER(meshData.meshletBuffer);
Meshlet meshlet = meshletBuffer.Load<Meshlet>(groupID.x * sizeof(Meshlet));
uint meshletIndex = groupID.x;
s_Payload.meshletIndex = groupID.x;
ByteAddressBuffer meshletBuffer = GET_BUFFER(meshData.meshletBuffer);
Meshlet meshlet = meshletBuffer.Load<Meshlet>(meshletIndex * sizeof(Meshlet));
ViewData viewData = LoadData<ViewData>(g_PushConstantData.viewBuffer, 0);
// Assume uniform scale
float scale = length(float3(instanceData.localToWorld[0][0], instanceData.localToWorld[1][0], instanceData.localToWorld[2][0]));
// Nanite projection metric: cot(fovy / 2) -> projectionMatrix[1][1]
// Multiply by screenSize.y to get error in pixels.
float cameraProj = viewData.projectionMatrix[1][1] * viewData.screenSize.y;
float threshold = 1.0f;
// Calculate THIS cluster's error using ITS OWN bounding sphere
float3 clusterCenter = mul(instanceData.localToWorld, float4(meshlet.boundingSphere.xyz, 1.0f)).xyz;
float clusterRadius = meshlet.boundingSphere.w * scale;
float clusterError = meshlet.clusterError * scale;
float clusterProjError = BoundsError(clusterCenter, clusterRadius, clusterError, viewData.cameraPosition, cameraProj, viewData.nearClip);
// Calculate the PARENT'S error using the PARENT'S bounding sphere
float3 parentCenter = mul(instanceData.localToWorld, float4(meshlet.parentBoundingSphere.xyz, 1.0f)).xyz;
float parentRadius = meshlet.parentBoundingSphere.w * scale;
float parentError = meshlet.parentError * scale;
float parentProjError = BoundsError(parentCenter, parentRadius, parentError, viewData.cameraPosition, cameraProj, viewData.nearClip);
bool isRoot = meshlet.parentError >= 3.402823466e+38F; // FLT_MAX
uint emitMeshlet = ((parentProjError > threshold || isRoot) && clusterProjError <= threshold) ? 1u : 0u;
// Failsafe: if we are at the finest LOD (LOD 0) and the error is STILL too high,
// we must render it anyway because there is no more detailed geometry to fall back on.
uint lodLevel = (meshlet.packedCounts >> 24) & 0xFFu;
uint emitMeshlet = lodLevel == 0u ? 1u : 0u;
if (lodLevel == 0u && parentProjError > threshold)
{
emitMeshlet = 1u;
}
DispatchMesh(emitMeshlet, 1u, 1u, s_Payload);
}
@@ -140,6 +180,9 @@ shader "MyShader/Standard"
float b = float((hash & 0x0000FFu)) / 255.0;
return float4(r, g, b, 1.0);
// InstanceData instanceData = LoadData<InstanceData>(g_PushConstantData.instanceBuffer, g_PushConstantData.instanceIndex);
// return mul(instanceData.localToWorld, float4(input.normal, 1.0f));
}
}