feat(mesh): update Vertex layout, add mesh loader
Refactored Vertex to use float3 position/normal, float2 uv, float4 tangent, and Color128 color, updating all mesh generation and HLSL code accordingly. Added MeshUtility for loading .obj/.fbx meshes with deduplication and normal/tangent computation. Updated GraphicsTestWindow to use the new loader and improved resource management. Fixed D3D12ResourceAllocator resource creation logic, improved camera projection math, and simplified RenderingLayerMask. Updated package references and app display name. BREAKING CHANGE: Vertex struct layout changed; all mesh code and shaders must use the new format.
This commit is contained in:
@@ -9,19 +9,20 @@
|
|||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||||
<IsAotCompatible>True</IsAotCompatible>
|
<IsAotCompatible>True</IsAotCompatible>
|
||||||
<DefineConstants>$(DefineConstants);MHP_ENABLE_SAFETY_CHECKS</DefineConstants>
|
<DefineConstants>$(DefineConstants);MHP_ENABLE_SAFETY_CHECKS;MHP_ENABLE_STACKTRACE</DefineConstants>
|
||||||
<IsTrimmable>True</IsTrimmable>
|
<IsTrimmable>True</IsTrimmable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||||
<IsAotCompatible>True</IsAotCompatible>
|
<IsAotCompatible>True</IsAotCompatible>
|
||||||
|
<!--<DefineConstants>$(DefineConstants);MHP_ENABLE_SAFETY_CHECKS</DefineConstants>-->
|
||||||
<IsTrimmable>True</IsTrimmable>
|
<IsTrimmable>True</IsTrimmable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Misaki.HighPerformance" Version="1.0.6" />
|
<PackageReference Include="Misaki.HighPerformance" Version="1.0.6" />
|
||||||
<PackageReference Include="Misaki.HighPerformance.Jobs" Version="1.5.5" />
|
<PackageReference Include="Misaki.HighPerformance.Jobs" Version="1.5.6" />
|
||||||
<PackageReference Include="Misaki.HighPerformance.LowLevel" Version="1.6.1">
|
<PackageReference Include="Misaki.HighPerformance.LowLevel" Version="1.6.3">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
|||||||
@@ -740,7 +740,10 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
hr = CreateResource(&allocationDesc, &resourceDesc, initialState, options, null, (void**)&pAllocation);
|
hr = CreateResource(&allocationDesc, &resourceDesc, initialState, options, null, (void**)&pAllocation);
|
||||||
pResource = pAllocation->GetResource();
|
if (hr.SUCCEEDED)
|
||||||
|
{
|
||||||
|
pResource = pAllocation->GetResource();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hr.FAILED)
|
if (hr.FAILED)
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ public struct Color128 : IEquatable<Color128>
|
|||||||
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
public struct Vertex
|
public record struct Vertex
|
||||||
{
|
{
|
||||||
public static class Semantic
|
public static class Semantic
|
||||||
{
|
{
|
||||||
@@ -145,11 +145,11 @@ public struct Vertex
|
|||||||
public static readonly FixedText32 Color = new("COLOR"u8);
|
public static readonly FixedText32 Color = new("COLOR"u8);
|
||||||
}
|
}
|
||||||
|
|
||||||
public float4 position;
|
|
||||||
public float4 normal;
|
|
||||||
public float4 tangent;
|
|
||||||
public float4 uv;
|
|
||||||
public Color128 color;
|
public Color128 color;
|
||||||
|
public float4 tangent;
|
||||||
|
public float3 position;
|
||||||
|
public float3 normal;
|
||||||
|
public float2 uv;
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly struct ShaderVariant;
|
public readonly struct ShaderVariant;
|
||||||
|
|||||||
@@ -210,18 +210,20 @@ public struct Mesh : IResourceReleasable
|
|||||||
maxVertices = 64,
|
maxVertices = 64,
|
||||||
minTriangles = 32,
|
minTriangles = 32,
|
||||||
maxTriangles = 124,
|
maxTriangles = 124,
|
||||||
partitionSize = 128,
|
partitionSpatial = true,
|
||||||
clusterSpatial = true,
|
partitionSize = 16,
|
||||||
|
clusterSpatial = false,
|
||||||
|
clusterSplitFactor = 2.0f,
|
||||||
clusterFillWeight = 1.0f,
|
clusterFillWeight = 1.0f,
|
||||||
clusterSplitFactor = 1.0f,
|
|
||||||
simplifyRatio = 0.5f,
|
simplifyRatio = 0.5f,
|
||||||
simplifyThreshold = 0.5f,
|
simplifyThreshold = 0.85f,
|
||||||
simplifyErrorMergePrevious = 0.5f,
|
simplifyErrorMergePrevious = 1.0f,
|
||||||
simplifyErrorMergeAdditive = 0.5f,
|
simplifyErrorFactorSloppy = 2.0f,
|
||||||
simplifyErrorFactorSloppy = 1.0f,
|
simplifyPermissive = true,
|
||||||
simplifyErrorEdgeLimit = 1.0f,
|
simplifyFallbackPermissive = false,
|
||||||
optimizeBounds = true,
|
simplifyFallbackSloppy = true,
|
||||||
optimizeClusters = true
|
//optimizeBounds = true,
|
||||||
|
//optimizeClusters = true
|
||||||
};
|
};
|
||||||
|
|
||||||
// 2. Map Mesh to ClodMesh
|
// 2. Map Mesh to ClodMesh
|
||||||
|
|||||||
@@ -1,31 +1,7 @@
|
|||||||
using System.Diagnostics;
|
|
||||||
|
|
||||||
namespace Ghost.Graphics.Core;
|
namespace Ghost.Graphics.Core;
|
||||||
|
|
||||||
public readonly struct RenderingLayerMask : IEquatable<RenderingLayerMask>
|
public readonly struct RenderingLayerMask : IEquatable<RenderingLayerMask>
|
||||||
{
|
{
|
||||||
private static readonly Dictionary<string, uint> s_layerNameToBit = new(32);
|
|
||||||
private static readonly Dictionary<uint, string> s_bitToLayerName = new(32);
|
|
||||||
|
|
||||||
internal static void SetLayerName(int layerIndex, string name)
|
|
||||||
{
|
|
||||||
Debug.Assert(layerIndex >= 0 && layerIndex < 32, "Layer index must be between 0 and 31.");
|
|
||||||
|
|
||||||
var bit = 1u << layerIndex;
|
|
||||||
s_layerNameToBit[name] = bit;
|
|
||||||
s_bitToLayerName[bit] = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static uint GetLayerBit(string name)
|
|
||||||
{
|
|
||||||
if (s_layerNameToBit.TryGetValue(name, out var bit))
|
|
||||||
{
|
|
||||||
return bit;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0u;
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly uint _value;
|
private readonly uint _value;
|
||||||
|
|
||||||
public static readonly RenderingLayerMask Empty = new(0);
|
public static readonly RenderingLayerMask Empty = new(0);
|
||||||
|
|||||||
@@ -3,11 +3,11 @@
|
|||||||
|
|
||||||
struct Vertex
|
struct Vertex
|
||||||
{
|
{
|
||||||
float4 position;
|
|
||||||
float4 normal;
|
|
||||||
float4 tangent;
|
|
||||||
float4 uv;
|
|
||||||
float4 color;
|
float4 color;
|
||||||
|
float4 tangent;
|
||||||
|
float3 position;
|
||||||
|
float3 normal;
|
||||||
|
float2 uv;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Meshlet
|
struct Meshlet
|
||||||
|
|||||||
@@ -17,12 +17,12 @@ public static unsafe class MeshBuilder
|
|||||||
vertices = new UnsafeList<Vertex>(24, allocator);
|
vertices = new UnsafeList<Vertex>(24, allocator);
|
||||||
indices = new UnsafeList<uint>(36, allocator);
|
indices = new UnsafeList<uint>(36, allocator);
|
||||||
|
|
||||||
var corners = new float4[]
|
var corners = new float3[]
|
||||||
{
|
{
|
||||||
new (-half, -half, -half, 1.0f), new (half, -half, -half, 1.0f),
|
new (-half, -half, -half), new (half, -half, -half),
|
||||||
new (half, half, -half, 1.0f), new (-half, half, -half, 1.0f),
|
new (half, half, -half), new (-half, half, -half),
|
||||||
new (-half, -half, half, 1.0f), new (half, -half, half, 1.0f),
|
new (-half, -half, half), new (half, -half, half),
|
||||||
new (half, half, half, 1.0f), new (-half, half, half, 1.0f)
|
new (half, half, half), new (-half, half, half)
|
||||||
};
|
};
|
||||||
|
|
||||||
var faces = stackalloc int[]
|
var faces = stackalloc int[]
|
||||||
@@ -46,10 +46,10 @@ public static unsafe class MeshBuilder
|
|||||||
var vertex = new Vertex
|
var vertex = new Vertex
|
||||||
{
|
{
|
||||||
position = corners[face[i]],
|
position = corners[face[i]],
|
||||||
normal = float4.zero,
|
normal = float3.zero,
|
||||||
tangent = float4.zero,
|
tangent = float4.zero,
|
||||||
color = color,
|
color = color,
|
||||||
uv = new(uvs[i], 0.0f, 0.0f)
|
uv = uvs[i]
|
||||||
};
|
};
|
||||||
|
|
||||||
vertices.Add(vertex);
|
vertices.Add(vertex);
|
||||||
@@ -81,38 +81,38 @@ public static unsafe class MeshBuilder
|
|||||||
|
|
||||||
vertices.Add(new Vertex()
|
vertices.Add(new Vertex()
|
||||||
{
|
{
|
||||||
position = new(-hw, 0.0f, -hd, 0.0f),
|
position = new float3(-hw, 0.0f, -hd),
|
||||||
normal = float4.zero,
|
normal = float3.zero,
|
||||||
tangent = float4.zero,
|
tangent = float4.zero,
|
||||||
color = color,
|
color = color,
|
||||||
uv = new(0.0f)
|
uv = float2.zero
|
||||||
});
|
});
|
||||||
|
|
||||||
vertices.Add(new Vertex()
|
vertices.Add(new Vertex()
|
||||||
{
|
{
|
||||||
position = new(hw, 0.0f, -hd, 0.0f),
|
position = new float3(hw, 0.0f, -hd),
|
||||||
normal = float4.zero,
|
normal = float3.zero,
|
||||||
tangent = float4.zero,
|
tangent = float4.zero,
|
||||||
color = color,
|
color = color,
|
||||||
uv = new(1.0f, 0.0f, 0.0f, 0.0f)
|
uv = new float2(1.0f, 0.0f)
|
||||||
});
|
});
|
||||||
|
|
||||||
vertices.Add(new Vertex()
|
vertices.Add(new Vertex()
|
||||||
{
|
{
|
||||||
position = new(hw, 0.0f, hd, 0.0f),
|
position = new float3(hw, 0.0f, hd),
|
||||||
normal = float4.zero,
|
normal = float3.zero,
|
||||||
tangent = float4.zero,
|
tangent = float4.zero,
|
||||||
color = color,
|
color = color,
|
||||||
uv = new(1.0f, 1.0f, 0.0f, 0.0f)
|
uv = new float2(1.0f, 1.0f)
|
||||||
});
|
});
|
||||||
|
|
||||||
vertices.Add(new Vertex()
|
vertices.Add(new Vertex()
|
||||||
{
|
{
|
||||||
position = new(-hw, 0.0f, hd, 0.0f),
|
position = new float3(-hw, 0.0f, hd),
|
||||||
normal = float4.zero,
|
normal = float3.zero,
|
||||||
tangent = float4.zero,
|
tangent = float4.zero,
|
||||||
color = color,
|
color = color,
|
||||||
uv = new(0.0f, 1.0f, 0.0f, 0.0f)
|
uv = new float2(0.0f, 1.0f)
|
||||||
});
|
});
|
||||||
|
|
||||||
indices.Add(0);
|
indices.Add(0);
|
||||||
@@ -153,11 +153,11 @@ public static unsafe class MeshBuilder
|
|||||||
|
|
||||||
vertices.Add(new Vertex
|
vertices.Add(new Vertex
|
||||||
{
|
{
|
||||||
position = new float4(x, y, z, 0.0f) * radius,
|
position = new float3(x, y, z) * radius,
|
||||||
normal = float4.zero,
|
normal = float3.zero,
|
||||||
tangent = float4.zero,
|
tangent = float4.zero,
|
||||||
color = color,
|
color = color,
|
||||||
uv = new float4((float)lon / longitudeSegments, (float)lat / latitudeSegments, 0.0f, 0.0f)
|
uv = new float2((float)lon / longitudeSegments, (float)lat / latitudeSegments)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -231,7 +231,7 @@ public static unsafe class MeshBuilder
|
|||||||
public static void ComputeTangents(UnsafeList<Vertex> vertices, UnsafeList<uint> indices)
|
public static void ComputeTangents(UnsafeList<Vertex> vertices, UnsafeList<uint> indices)
|
||||||
{
|
{
|
||||||
using var scope = AllocationManager.CreateStackScope();
|
using var scope = AllocationManager.CreateStackScope();
|
||||||
var bitangents = new UnsafeArray<float4>(vertices.Count, scope.AllocationHandle);
|
var bitangents = new UnsafeArray<float3>(vertices.Count, scope.AllocationHandle, AllocationOption.Clear);
|
||||||
|
|
||||||
for (var i = 0; i < indices.Count; i += 3)
|
for (var i = 0; i < indices.Count; i += 3)
|
||||||
{
|
{
|
||||||
@@ -269,7 +269,7 @@ public static unsafe class MeshBuilder
|
|||||||
for (var i = 0; i < vertices.Count; i++)
|
for (var i = 0; i < vertices.Count; i++)
|
||||||
{
|
{
|
||||||
var n = vertices[i].normal;
|
var n = vertices[i].normal;
|
||||||
var t = vertices[i].tangent;
|
var t = vertices[i].tangent.xyz;
|
||||||
|
|
||||||
var proj = n * math.dot(n, t);
|
var proj = n * math.dot(n, t);
|
||||||
t = math.normalize(t - proj);
|
t = math.normalize(t - proj);
|
||||||
@@ -277,7 +277,7 @@ public static unsafe class MeshBuilder
|
|||||||
var b = bitangents[i];
|
var b = bitangents[i];
|
||||||
var w = math.dot(math.cross(n.xyz, t.xyz), b.xyz) < 0.0f ? -1.0f : 1.0f;
|
var w = math.dot(math.cross(n.xyz, t.xyz), b.xyz) < 0.0f ? -1.0f : 1.0f;
|
||||||
|
|
||||||
vertices[i].tangent = new float4(t.x, t.y, t.z, w);
|
vertices[i].tangent = new float4(t.xyz, w);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -582,7 +582,7 @@ public static unsafe class MeshletUtility
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var targetSize = ((nuint)merged.Count / 3) * (nuint)config.simplifyRatio * 3;
|
var targetSize = (nuint)(merged.Count / 3 * config.simplifyRatio * 3.0f);
|
||||||
var bounds = MergeBounds(clusters, groups[i]);
|
var bounds = MergeBounds(clusters, groups[i]);
|
||||||
|
|
||||||
var error = 0.0f;
|
var error = 0.0f;
|
||||||
|
|||||||
@@ -29,11 +29,12 @@ shader "MyShader/Standard"
|
|||||||
|
|
||||||
hlsl
|
hlsl
|
||||||
{
|
{
|
||||||
|
#line 31 "MyShader_Standard_Forward_hlsl_block"
|
||||||
struct PixelInput
|
struct PixelInput
|
||||||
{
|
{
|
||||||
float4 position : SV_POSITION;
|
float4 position : SV_POSITION;
|
||||||
float4 color : COLOR;
|
float4 color : COLOR;
|
||||||
float4 uv : TEXCOORD0;
|
float2 uv : TEXCOORD0;
|
||||||
nointerpolation uint meshletID : MESHLET_ID;
|
nointerpolation uint meshletID : MESHLET_ID;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -72,9 +73,9 @@ shader "MyShader/Standard"
|
|||||||
float4 worldPos = mul(instanceData.localToWorld, float4(v.position.xyz, 1.0f));
|
float4 worldPos = mul(instanceData.localToWorld, float4(v.position.xyz, 1.0f));
|
||||||
float4 viewPos = mul(viewData.viewMatrix, worldPos);
|
float4 viewPos = mul(viewData.viewMatrix, worldPos);
|
||||||
|
|
||||||
// outVerts[groupThreadID.x].position = mul(viewData.projectionMatrix, viewPos);
|
outVerts[groupThreadID.x].position = mul(viewData.projectionMatrix, viewPos);
|
||||||
// For testing.
|
// For testing.
|
||||||
outVerts[groupThreadID.x].position = float4(v.position.xyz, 1.0f);
|
// outVerts[groupThreadID.x].position = v.position.xyz;
|
||||||
|
|
||||||
outVerts[groupThreadID.x].color = v.color;
|
outVerts[groupThreadID.x].color = v.color;
|
||||||
outVerts[groupThreadID.x].uv = v.uv;
|
outVerts[groupThreadID.x].uv = v.uv;
|
||||||
|
|||||||
@@ -53,6 +53,7 @@
|
|||||||
<ProjectReference Include="..\..\Test\Ghost.Test.Core\Ghost.Test.Core.csproj" />
|
<ProjectReference Include="..\..\Test\Ghost.Test.Core\Ghost.Test.Core.csproj" />
|
||||||
<ProjectReference Include="..\..\Runtime\Ghost.Graphics\Ghost.Graphics.csproj" />
|
<ProjectReference Include="..\..\Runtime\Ghost.Graphics\Ghost.Graphics.csproj" />
|
||||||
<ProjectReference Include="..\..\Editor\Ghost.DSL\Ghost.DSL.csproj" />
|
<ProjectReference Include="..\..\Editor\Ghost.DSL\Ghost.DSL.csproj" />
|
||||||
|
<ProjectReference Include="..\..\ThridParty\Ghost.Ufbx\Ghost.Ufbx.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
Executable="$targetnametoken$.exe"
|
Executable="$targetnametoken$.exe"
|
||||||
EntryPoint="$targetentrypoint$">
|
EntryPoint="$targetentrypoint$">
|
||||||
<uap:VisualElements
|
<uap:VisualElements
|
||||||
DisplayName="Ghost.UnitTest"
|
DisplayName="Ghost.Graphics.Test"
|
||||||
Description="Ghost.UnitTest"
|
Description="Ghost.UnitTest"
|
||||||
BackgroundColor="transparent"
|
BackgroundColor="transparent"
|
||||||
Square150x150Logo="Assets\Square150x150Logo.png"
|
Square150x150Logo="Assets\Square150x150Logo.png"
|
||||||
|
|||||||
@@ -159,8 +159,8 @@ public unsafe partial class TestRenderPipeline : IRenderPipeline
|
|||||||
// NOTE: We assume camera's scale is always (1, 1, 1). Otherwise fastinverse will fail and we need to use regular inverse which is more expensive.
|
// NOTE: We assume camera's scale is always (1, 1, 1). Otherwise fastinverse will fail and we need to use regular inverse which is more expensive.
|
||||||
var viewMatrix = math.fastinverse(request.view.localToWorld);
|
var viewMatrix = math.fastinverse(request.view.localToWorld);
|
||||||
|
|
||||||
var vfov = 2.0f * math.atan(request.view.sensorSize.y / 2.0f * request.view.focalLength);
|
var vfov = 2.0f * math.atan(request.view.sensorSize.y / (2.0f * request.view.focalLength));
|
||||||
var hfov = 2.0f * math.atan(request.view.sensorSize.x / 2.0f * request.view.focalLength);
|
var hfov = 2.0f * math.atan(request.view.sensorSize.x / (2.0f * request.view.focalLength));
|
||||||
var aspectSensor = request.view.sensorSize.x / request.view.sensorSize.y;
|
var aspectSensor = request.view.sensorSize.x / request.view.sensorSize.y;
|
||||||
|
|
||||||
float vfovF;
|
float vfovF;
|
||||||
@@ -200,10 +200,10 @@ public unsafe partial class TestRenderPipeline : IRenderPipeline
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
var m_00 = 1.0f / aspectScreen * math.tan(vfovF * 0.5f);
|
|
||||||
var m_11 = 1.0f / math.tan(vfovF * 0.5f);
|
var m_11 = 1.0f / math.tan(vfovF * 0.5f);
|
||||||
var m_22 = -(request.view.farClipPlane + request.view.nearClipPlane) / (request.view.farClipPlane - request.view.nearClipPlane);
|
var m_00 = m_11 / aspectScreen;
|
||||||
var m_23 = -(2.0f * request.view.farClipPlane * request.view.nearClipPlane) / (request.view.farClipPlane - request.view.nearClipPlane);
|
var m_22 = -request.view.farClipPlane / (request.view.farClipPlane - request.view.nearClipPlane);
|
||||||
|
var m_23 = -(request.view.farClipPlane * request.view.nearClipPlane) / (request.view.farClipPlane - request.view.nearClipPlane);
|
||||||
|
|
||||||
var projectionMatrix = new float4x4
|
var projectionMatrix = new float4x4
|
||||||
(
|
(
|
||||||
|
|||||||
@@ -59,15 +59,6 @@ public partial class UnitTestApp : Application
|
|||||||
{
|
{
|
||||||
LoadDll();
|
LoadDll();
|
||||||
|
|
||||||
var opts = new AllocationManagerInitOpts
|
|
||||||
{
|
|
||||||
ArenaCapacity = 1024 * 1024 * 1024, // 1GB
|
|
||||||
StackCapacity = 1024 * 1024 * 32, // 32MB
|
|
||||||
FreeListConcurrencyLevel = Environment.ProcessorCount,
|
|
||||||
};
|
|
||||||
|
|
||||||
AllocationManager.Initialize(opts);
|
|
||||||
|
|
||||||
_window = new GraphicsTestWindow();
|
_window = new GraphicsTestWindow();
|
||||||
_window.Activate();
|
_window.Activate();
|
||||||
|
|
||||||
@@ -77,6 +68,7 @@ public partial class UnitTestApp : Application
|
|||||||
#if DEBUG
|
#if DEBUG
|
||||||
System.Diagnostics.Debugger.Break();
|
System.Diagnostics.Debugger.Break();
|
||||||
#endif
|
#endif
|
||||||
|
Environment.FailFast("Unhandled exception", e.Exception);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
176
src/Test/Ghost.Graphics.Test/Utilities/MeshUtility.cs
Normal file
176
src/Test/Ghost.Graphics.Test/Utilities/MeshUtility.cs
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
using Ghost.Core;
|
||||||
|
using Ghost.Graphics.RHI;
|
||||||
|
using Ghost.Graphics.Utilities;
|
||||||
|
using Ghost.MeshOptimizer;
|
||||||
|
using Ghost.Ufbx;
|
||||||
|
using Misaki.HighPerformance.LowLevel;
|
||||||
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
|
using Misaki.HighPerformance.LowLevel.Collections;
|
||||||
|
using Misaki.HighPerformance.LowLevel.Utilities;
|
||||||
|
using Misaki.HighPerformance.Mathematics;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Ghost.Graphics.Test.Utilities;
|
||||||
|
|
||||||
|
internal static class MeshUtility
|
||||||
|
{
|
||||||
|
private static float4 ComputeTangent(float3 t, float3 n, float3 b)
|
||||||
|
{
|
||||||
|
var proj = n * math.dot(n, t);
|
||||||
|
t = math.normalize(t - proj);
|
||||||
|
var w = math.dot(math.cross(n.xyz, t.xyz), b.xyz) < 0.0f ? -1.0f : 1.0f;
|
||||||
|
return new float4(t.xyz, w);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static unsafe Result LoadMesh(string filePath, Allocator allocator, out UnsafeList<Vertex> vertices, out UnsafeList<uint> indices)
|
||||||
|
{
|
||||||
|
vertices = default;
|
||||||
|
indices = default;
|
||||||
|
|
||||||
|
if (!File.Exists(filePath))
|
||||||
|
{
|
||||||
|
return Result.Failure("Invalid file path.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Path.GetExtension(filePath).Equals(".obj", StringComparison.OrdinalIgnoreCase)
|
||||||
|
&& !Path.GetExtension(filePath).Equals(".fbx", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return Result.Failure("Unsupported file format. Only .obj and .fbx are supported.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var load_Opts = new ufbx_load_opts();
|
||||||
|
var error = new ufbx_error();
|
||||||
|
|
||||||
|
using var pool = new MemoryPool<VirtualStack, VirtualStack.CreationOpts>(new VirtualStack.CreationOpts
|
||||||
|
{
|
||||||
|
reserveCapacity = 256 * 1024 * 1024 // 256 MB should be enough for most models, adjust as needed. Note that this use virtual memory and does not actually consume physical memory until allocations are made.
|
||||||
|
});
|
||||||
|
|
||||||
|
using var scope0 = pool.Allocator.CreateScope(pool.AllocationHandle);
|
||||||
|
using var str = new UnsafeArray<byte>(Encoding.UTF8.GetByteCount(filePath) + 1, scope0.AllocationHandle);
|
||||||
|
var count = Encoding.UTF8.GetBytes(filePath, str.AsSpan());
|
||||||
|
str[count] = 0;
|
||||||
|
|
||||||
|
using var scene = new DisposablePtr<ufbx_scene>(ufbx_scene.LoadFile((sbyte*)str.GetUnsafePtr(), &load_Opts, &error));
|
||||||
|
if (scene.Get() == null)
|
||||||
|
{
|
||||||
|
return Result.Failure(error.description.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
using var flatVertices = new UnsafeList<Vertex>(1024, scope0.AllocationHandle);
|
||||||
|
//using var flatIndices = new UnsafeList<uint>(1024, scope0.AllocationHandle);
|
||||||
|
|
||||||
|
var needComputeNormals = false;
|
||||||
|
|
||||||
|
for (var i = 0u; i < scene.Get()->nodes.count; i++)
|
||||||
|
{
|
||||||
|
var node = scene.Get()->nodes.data[i];
|
||||||
|
if (node->is_root)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
using var scope1 = pool.Allocator.CreateScope(pool.AllocationHandle);
|
||||||
|
|
||||||
|
if (node->mesh != null)
|
||||||
|
{
|
||||||
|
var pMesh = node->mesh;
|
||||||
|
if (pMesh->num_faces == 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var maxScratchIndices = (uint)(pMesh->max_face_triangles * 3u);
|
||||||
|
|
||||||
|
using var triIndicesArray = new UnsafeArray<uint>((int)maxScratchIndices, scope1.AllocationHandle);
|
||||||
|
|
||||||
|
for (var j = 0u; j < pMesh->num_faces; j++)
|
||||||
|
{
|
||||||
|
var face = pMesh->faces.data[j];
|
||||||
|
|
||||||
|
var numTris = UfbxApi.TriangulateFace((uint*)triIndicesArray.GetUnsafePtr(), maxScratchIndices, pMesh, face);
|
||||||
|
|
||||||
|
var totalIndices = numTris * 3;
|
||||||
|
for (var k = 0; k < totalIndices; k++)
|
||||||
|
{
|
||||||
|
var ufbxTopologyIndex = triIndicesArray[k];
|
||||||
|
|
||||||
|
// TODO: Check if normals/UVs exist before accessing .flatIndices.data
|
||||||
|
// If it does not exist, we leave uv as (0,0) and compute normals/tangents later
|
||||||
|
|
||||||
|
var posIdx = pMesh->vertex_position.indices.data[ufbxTopologyIndex];
|
||||||
|
var normIdx = pMesh->vertex_normal.exists ? pMesh->vertex_normal.indices.data[ufbxTopologyIndex] : uint.MaxValue;
|
||||||
|
var tanIdx = pMesh->vertex_tangent.exists ? pMesh->vertex_tangent.indices.data[ufbxTopologyIndex] : uint.MaxValue;
|
||||||
|
var uvIdx = pMesh->vertex_uv.exists ? pMesh->vertex_uv.indices.data[ufbxTopologyIndex] : uint.MaxValue;
|
||||||
|
var colIdx = pMesh->vertex_color.exists ? pMesh->vertex_color.indices.data[ufbxTopologyIndex] : uint.MaxValue;
|
||||||
|
var btanIdx = pMesh->vertex_bitangent.exists ? pMesh->vertex_bitangent.indices.data[ufbxTopologyIndex] : uint.MaxValue;
|
||||||
|
|
||||||
|
var vertex = new Vertex
|
||||||
|
{
|
||||||
|
position = pMesh->vertex_position.values.data[posIdx],
|
||||||
|
normal = normIdx != uint.MaxValue ? pMesh->vertex_normal.values.data[normIdx] : default,
|
||||||
|
uv = uvIdx != uint.MaxValue ? pMesh->vertex_uv.values.data[uvIdx] : default,
|
||||||
|
color = colIdx != uint.MaxValue ? new Color128(pMesh->vertex_color.values.data[colIdx]) : default,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (tanIdx != uint.MaxValue)
|
||||||
|
{
|
||||||
|
var t = pMesh->vertex_tangent.values.data[tanIdx];
|
||||||
|
var n = vertex.normal;
|
||||||
|
var b = btanIdx != uint.MaxValue ? pMesh->vertex_bitangent.values.data[btanIdx] : math.cross(n, t);
|
||||||
|
vertex.tangent = ComputeTangent(t, n, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
var newIndex = (uint)flatVertices.Count;
|
||||||
|
|
||||||
|
flatVertices.Add(vertex);
|
||||||
|
//flatIndices.Add(newIndex);
|
||||||
|
|
||||||
|
if (!needComputeNormals)
|
||||||
|
{
|
||||||
|
needComputeNormals = normIdx == uint.MaxValue || tanIdx == uint.MaxValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var numIndices = (uint)flatVertices.Count;
|
||||||
|
|
||||||
|
using var weldedIndices = new UnsafeArray<uint>((int)numIndices, scope0.AllocationHandle);
|
||||||
|
using var cachedIndices = new UnsafeArray<uint>((int)numIndices, scope0.AllocationHandle);
|
||||||
|
|
||||||
|
var stream = new ufbx_vertex_stream
|
||||||
|
{
|
||||||
|
data = flatVertices.GetUnsafePtr(),
|
||||||
|
vertex_count = numIndices,
|
||||||
|
vertex_size = (nuint)sizeof(Vertex)
|
||||||
|
};
|
||||||
|
|
||||||
|
var numUniqueVertices = stream.GenerateIndices(1, (uint*)weldedIndices.GetUnsafePtr(), (nuint)weldedIndices.Count, null, &error);
|
||||||
|
if (numUniqueVertices == 0 && error.type != ufbx_error_type.UFBX_ERROR_NONE)
|
||||||
|
{
|
||||||
|
return Result.Failure($"Welding failed: {error.description}");
|
||||||
|
}
|
||||||
|
|
||||||
|
MeshOptApi.OptimizeVertexCache((uint*)cachedIndices.GetUnsafePtr(), (uint*)weldedIndices.GetUnsafePtr(), numIndices, numIndices);
|
||||||
|
|
||||||
|
vertices = new UnsafeList<Vertex>((int)numUniqueVertices, allocator);
|
||||||
|
indices = new UnsafeList<uint>((int)numIndices, allocator);
|
||||||
|
|
||||||
|
var finalVertexCount = MeshOptApi.OptimizeVertexFetch(vertices.GetUnsafePtr(), (uint*)cachedIndices.GetUnsafePtr(), numIndices, flatVertices.GetUnsafePtr(), numIndices, (nuint)sizeof(Vertex));
|
||||||
|
|
||||||
|
vertices.UnsafeSetCount((int)finalVertexCount);
|
||||||
|
|
||||||
|
MemoryUtility.MemCpy(indices.GetUnsafePtr(), cachedIndices.GetUnsafePtr(), numIndices * sizeof(uint));
|
||||||
|
indices.UnsafeSetCount((int)numIndices);
|
||||||
|
|
||||||
|
if (needComputeNormals)
|
||||||
|
{
|
||||||
|
MeshBuilder.ComputeNormal(vertices, indices);
|
||||||
|
MeshBuilder.ComputeTangents(vertices, indices);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Success();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ using Ghost.Engine.Utilities;
|
|||||||
using Ghost.Entities;
|
using Ghost.Entities;
|
||||||
using Ghost.Graphics.Core;
|
using Ghost.Graphics.Core;
|
||||||
using Ghost.Graphics.RHI;
|
using Ghost.Graphics.RHI;
|
||||||
|
using Ghost.Graphics.Test.Utilities;
|
||||||
using Ghost.Graphics.Utilities;
|
using Ghost.Graphics.Utilities;
|
||||||
using Microsoft.UI.Xaml;
|
using Microsoft.UI.Xaml;
|
||||||
using Microsoft.UI.Xaml.Controls;
|
using Microsoft.UI.Xaml.Controls;
|
||||||
@@ -33,6 +34,15 @@ public sealed partial class GraphicsTestWindow : Window
|
|||||||
|
|
||||||
Panel.SizeChanged += SwapChainPanel_SizeChanged;
|
Panel.SizeChanged += SwapChainPanel_SizeChanged;
|
||||||
Panel.CompositionScaleChanged += SwapChainPanel_CompositionScaleChanged;
|
Panel.CompositionScaleChanged += SwapChainPanel_CompositionScaleChanged;
|
||||||
|
|
||||||
|
var opts = new AllocationManagerInitOpts
|
||||||
|
{
|
||||||
|
ArenaCapacity = 1024 * 1024 * 1024, // 1GB
|
||||||
|
StackCapacity = 1024 * 1024 * 32, // 32MB
|
||||||
|
FreeListConcurrencyLevel = Environment.ProcessorCount,
|
||||||
|
};
|
||||||
|
|
||||||
|
AllocationManager.Initialize(opts);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GraphicsTestWindow_Activated(object sender, WindowActivatedEventArgs e)
|
private void GraphicsTestWindow_Activated(object sender, WindowActivatedEventArgs e)
|
||||||
@@ -42,6 +52,9 @@ public sealed partial class GraphicsTestWindow : Window
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
e.Handled = true;
|
||||||
|
_isFirstActivationHandled = true;
|
||||||
|
|
||||||
_renderSystem = new RenderSystem(new RenderSystemDesc()
|
_renderSystem = new RenderSystem(new RenderSystemDesc()
|
||||||
{
|
{
|
||||||
FrameBufferCount = 2,
|
FrameBufferCount = 2,
|
||||||
@@ -92,11 +105,12 @@ public sealed partial class GraphicsTestWindow : Window
|
|||||||
|
|
||||||
_world.EntityManager.SetComponent(cameraEntity, new LocalToWorld
|
_world.EntityManager.SetComponent(cameraEntity, new LocalToWorld
|
||||||
{
|
{
|
||||||
matrix = float4x4.TRS(new float3(0.0f, 0.0f, -5.0f), quaternion.identity, new float3(1.0f, 1.0f, 1.0f))
|
matrix = float4x4.TRS(new float3(0.0f, 1.0f, 5.0f), quaternion.EulerXYZ(new float3(0, 0, 0)), new float3(1.0f, 1.0f, 1.0f))
|
||||||
});
|
});
|
||||||
|
|
||||||
// Create Mesh Entity
|
// Create Mesh Entity
|
||||||
MeshBuilder.CreateCube(0.75f, default, Allocator.Persistent, out var vertices, out var indices);
|
//MeshBuilder.CreateCube(0.75f, default, Allocator.Persistent, out var vertices, out var indices);
|
||||||
|
MeshUtility.LoadMesh("F:/c/SimpleRayTracer/native/assets/bunny.obj", Allocator.Persistent, out var vertices, out var indices).ThrowIfFailed();
|
||||||
|
|
||||||
// TODO: Put this to the beginning of the frame without createing another command buffer?
|
// TODO: Put this to the beginning of the frame without createing another command buffer?
|
||||||
using var directCmd = _renderSystem.GraphicsEngine.CreateCommandBuffer(CommandBufferType.Graphics);
|
using var directCmd = _renderSystem.GraphicsEngine.CreateCommandBuffer(CommandBufferType.Graphics);
|
||||||
@@ -127,27 +141,34 @@ public sealed partial class GraphicsTestWindow : Window
|
|||||||
});
|
});
|
||||||
|
|
||||||
CompositionTarget.Rendering += OnRendering;
|
CompositionTarget.Rendering += OnRendering;
|
||||||
|
|
||||||
e.Handled = true;
|
|
||||||
_isFirstActivationHandled = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GraphicsTestWindow_Closed(object sender, WindowEventArgs e)
|
private void GraphicsTestWindow_Closed(object sender, WindowEventArgs e)
|
||||||
{
|
{
|
||||||
CompositionTarget.Rendering -= OnRendering;
|
try
|
||||||
_renderSystem?.Stop();
|
{
|
||||||
|
CompositionTarget.Rendering -= OnRendering;
|
||||||
if (_world != null)
|
_renderSystem?.Stop();
|
||||||
|
|
||||||
|
if (_world != null)
|
||||||
|
{
|
||||||
|
World.Destroy(_world.ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
_renderSystem?.ResourceManager.ReleaseMesh(_meshHandle);
|
||||||
|
|
||||||
|
_swapChain?.Dispose();
|
||||||
|
_renderSystem?.Dispose();
|
||||||
|
|
||||||
|
AllocationManager.Dispose();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Environment.FailFast("Failed to close the window properly.", ex);
|
||||||
|
}
|
||||||
|
finally
|
||||||
{
|
{
|
||||||
World.Destroy(_world.ID);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_renderSystem?.ResourceManager.ReleaseMesh(_meshHandle);
|
|
||||||
|
|
||||||
_swapChain?.Dispose();
|
|
||||||
_renderSystem?.Dispose();
|
|
||||||
|
|
||||||
AllocationManager.Dispose();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SwapChainPanel_SizeChanged(object sender, SizeChangedEventArgs e)
|
private void SwapChainPanel_SizeChanged(object sender, SizeChangedEventArgs e)
|
||||||
|
|||||||
Reference in New Issue
Block a user