#nullable enable using SharpDX.Direct3D11; using SharpDX.WIC; using SharpGLTF.Schema2; using T3.Core.Rendering; using T3.Core.Rendering.Material; using T3.Core.Utils; using T3.Core.Utils.Geometry; using Scene = SharpGLTF.Schema2.Scene; namespace Lib.render.scene; /// /// Uses the GltfSharp library to load a gltf file and convert it into a . /// [Guid("00618c91-f39a-44ea-b9d8-175c996460dc")] public class LoadGltfScene : Instance , IDescriptiveFilename, IStatusProvider { [Output(Guid = "C2D28EBE-E235-4234-8234-EB56E87ED9AF")] public readonly Slot ResultSetup = new(); [Output(Guid = "9891EA8D-7DE7-4DD1-A61E-337ADC569EF3")] public readonly Slot Mesh = new(); [Output(Guid = "F33EC4C1-F07B-46B3-BEC7-34E91B8312EF")] public readonly Slot Material = new(); public LoadGltfScene() { _resource = new Resource(Path, OnFileChanged); _resource.AddDependentSlots(ResultSetup, Mesh, Material); ResultSetup.UpdateAction += Update; Mesh.UpdateAction += Update; } private bool OnFileChanged(FileResource file, SceneSetup? currentValue, [NotNullWhen(true)]out SceneSetup? newValue, [NotNullWhen(false)]out string? failureReason) { LoadFile(file.AbsolutePath, 0, true); // if (BmFontDescription.TryInitializeFromFile(file.AbsolutePath, out newValue)) // { failureReason = null; newValue = ResultSetup.Value!; // return true; // } return true; // failureReason = "Failed to load font from file"; // return false; } private void Update(EvaluationContext context) { _lastErrorMessage = string.Empty; var materialNeedsUpdate = OffsetRoughness.DirtyFlag.IsDirty || OffsetMetallic.DirtyFlag.IsDirty; _offsetRoughness = OffsetRoughness.GetValue(context); _offsetMetallic = OffsetMetallic.GetValue(context); var meshChildIndex = MeshChildIndex.GetValue(context); var sceneSetup = Setup.GetValue(context); if (sceneSetup == null || Setup.Input.IsDefault) { sceneSetup = new SceneSetup(); Setup.SetTypedInputValue(sceneSetup); } var filePath = Path.GetValue(context); _updateTriggered = TriggerUpdate.GetValue(context); _updateTriggered |= MathUtils.WasChanged(CombineBuffer.GetValue(context), ref _combineBuffer); TriggerUpdate.SetTypedInputValue(false); // if (filePath == null || !File.Exists(filePath)) // { // _lastErrorMessage = $"Gltf File not found: {filePath}"; // return; // } LoadFile(filePath, meshChildIndex, materialNeedsUpdate); } private void LoadFile(string? filePath, int meshChildIndex, bool materialNeedsUpdate) { if (filePath == null) return; if (LoadFileIfRequired(filePath, out var newSetup)) { ResultSetup.Value?.Dispose(); // TODO: Need to clarify of null reference types are allowed for slot values Setup.SetTypedInputValue(newSetup!); //TODO: this is weird. ResultSetup.Value = newSetup!; } if (ResultSetup?.Value?.Dispatches != null && ResultSetup.Value.Dispatches.Count > 0) { var dispatchCount = ResultSetup.Value.Dispatches.Count; var index = meshChildIndex.Mod(dispatchCount); Mesh.Value = ResultSetup.Value.Dispatches[index].MeshBuffers; Material.Value = ResultSetup.Value.Dispatches[index].Material; } if (materialNeedsUpdate && ResultSetup?.Value?.Dispatches != null && ResultSetup.Value.Dispatches.Count > 0) { foreach (var dispatch in ResultSetup.Value.Dispatches) { if (dispatch.Material == null) continue; dispatch.Material.Parameters.Roughness = _offsetRoughness; dispatch.Material.Parameters.Metal = _offsetMetallic; dispatch.Material.UpdateParameterBuffer(); } } } protected override void Dispose(bool isDisposing) { if (!isDisposing) return; ResultSetup.Value?.Dispose(); Log.Debug("Destroying LoadGltfScene"); } private bool LoadFileIfRequired(string path, out SceneSetup? sceneSetup) { sceneSetup = null; if (!_updateTriggered && path == _lastFilePath) return false; _lastFilePath = path; _sceneMaterialsByName.Clear(); _meshBuffersForPrimitives.Clear(); sceneSetup = new SceneSetup(); if (!TryGetFilePath(path, out var fullPath)) { ShowError($"Gltf File not found: {path}"); return true; } try { var model = ModelRoot.Load(fullPath); var rootNode = _combineBuffer ? ConvertToNodeStructureIntoChunks(model.DefaultScene) : ConvertToNodeStructure(model.DefaultScene); sceneSetup.RootNodes.Clear(); sceneSetup.RootNodes.Add(rootNode); sceneSetup.GenerateSceneDrawDispatches(); } catch (Exception e) { ShowError($"Failed to load gltf file: {fullPath} \n{e.Message}"); return false; } return true; } private SceneSetup.SceneNode ConvertToNodeStructure(Scene modelDefaultScene) { var rootNode = new SceneSetup.SceneNode() { Name = modelDefaultScene.Name }; ParseChildren(modelDefaultScene.VisualChildren, rootNode); return rootNode; } // Todo adjust shader implementation new stride [StructLayout(LayoutKind.Explicit, Size = Stride)] // ReSharper disable once MemberCanBePrivate.Global public struct MeshChunkDef { [FieldOffset(0)] public int StartFaceIndex; [FieldOffset(4)] public int FaceCount; [FieldOffset(8)] public int StartVertexIndex; [FieldOffset(12)] public int VertexCount; internal const int Stride = 16; } /** Flatten all meshes into a single mesh buffer seperated into chunks */ private SceneSetup.SceneNode ConvertToNodeStructureIntoChunks(Scene modelDefaultScene) { var rootNode = new SceneSetup.SceneNode() { Name = modelDefaultScene.Name }; // Count all vertices and faces var totalCounts = new MeshDataCounts(); HashSet collectedMeshPrimitives = new (); _chunkDefIndicesForPrimitives.Clear(); ComputeTotalMeshCounts(modelDefaultScene.VisualChildren, ref totalCounts, ref collectedMeshPrimitives); var meshDataSet = new MeshDataSet() { VertexBufferData = new PbrVertex[totalCounts.VertexCount], IndexBufferData = new Int3[totalCounts.FaceCount], ChunksDefs = new List(), }; var meshBufferReference = new MeshBuffers(); // empty references that will be filled later // Fill the buffers var counters = new MeshDataCounts(); ParseChildrenIntoChunks(modelDefaultScene.VisualChildren, rootNode, ref counters, ref meshDataSet, ref meshBufferReference); // Create actual mesh buffers var faceCount = meshDataSet.IndexBufferData!.Length; var indexBufferData = meshDataSet.IndexBufferData; var verticesCount = meshDataSet.VertexBufferData!.Length; var vertexBufferData = meshDataSet.VertexBufferData; var chunkCount = meshDataSet.ChunksDefs!.Count; var chunkBufferData = meshDataSet.ChunksDefs.ToArray(); ResourceManager.SetupStructuredBuffer(indexBufferData, 3 * 4 * faceCount, 3 * 4, ref meshBufferReference.IndicesBuffer.Buffer); ResourceManager.CreateStructuredBufferSrv(meshBufferReference.IndicesBuffer.Buffer, ref meshBufferReference.IndicesBuffer.Srv); ResourceManager.CreateStructuredBufferUav(meshBufferReference.IndicesBuffer.Buffer, UnorderedAccessViewBufferFlags.None, ref meshBufferReference.IndicesBuffer.Uav); ResourceManager.SetupStructuredBuffer(vertexBufferData, PbrVertex.Stride * verticesCount, PbrVertex.Stride, ref meshBufferReference.VertexBuffer.Buffer); ResourceManager.CreateStructuredBufferSrv(meshBufferReference.VertexBuffer.Buffer, ref meshBufferReference.VertexBuffer.Srv); ResourceManager.CreateStructuredBufferUav(meshBufferReference.VertexBuffer.Buffer, UnorderedAccessViewBufferFlags.None, ref meshBufferReference.VertexBuffer.Uav); ResourceManager.SetupStructuredBuffer(chunkBufferData, MeshChunkDef.Stride * chunkCount, MeshChunkDef.Stride, ref meshBufferReference.ChunkDefsBuffer.Buffer); ResourceManager.CreateStructuredBufferSrv(meshBufferReference.ChunkDefsBuffer.Buffer, ref meshBufferReference.ChunkDefsBuffer.Srv); ResourceManager.CreateStructuredBufferUav(meshBufferReference.ChunkDefsBuffer.Buffer, UnorderedAccessViewBufferFlags.None, ref meshBufferReference.ChunkDefsBuffer.Uav); // TODO: Create instance points -> See [GetPointsFromSceneDef] // TODO: Separate this into a separate operator return rootNode; } private sealed class MeshDataSet { public PbrVertex[]? VertexBufferData; public Int3[]? IndexBufferData; public List? ChunksDefs; } private struct MeshDataCounts { public int ChunkCount; public int VertexCount; public int FaceCount; } private void ComputeTotalMeshCounts(IEnumerable visualChildren, ref MeshDataCounts totalCounts, ref HashSet collectedMeshPrimitives) { foreach (var child in visualChildren) { if (child == null) continue; if (child.Mesh != null) { foreach (var meshPrimitive in child.Mesh.Primitives) { if(!collectedMeshPrimitives.Add(meshPrimitive)) continue; // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract if(meshPrimitive == null) continue; if (!meshPrimitive.VertexAccessors.TryGetValue("POSITION", out var positionAccessor)) continue; var triangleCount= meshPrimitive.GetTriangleIndices().Count(); var verticesCount = positionAccessor.Count; if(triangleCount == 0 || verticesCount == 0) continue; totalCounts.ChunkCount++; totalCounts.VertexCount += verticesCount; totalCounts.FaceCount += triangleCount; } } ComputeTotalMeshCounts(child.VisualChildren, ref totalCounts, ref collectedMeshPrimitives); } } private static string MatrixToString(Matrix4x4 m) { return $"{m.M11:0.00} {m.M12:0.00} {m.M13:0.00} {m.M13:0.00} | " + $"{m.M21:0.00} {m.M22:0.00} {m.M23:0.00} {m.M23:0.00} | " + $"{m.M31:0.00} {m.M32:0.00} {m.M33:0.00} {m.M33:0.00} | " + $"{m.M41:0.00} {m.M42:0.00} {m.M43:0.00} {m.M43:0.00} | "; } /** * TODO: This is work in progress! * This will only add the mesh data to the meshDataSet, but not generate buffers. */ private void ParseChildrenIntoChunks(IEnumerable visualChildren, SceneSetup.SceneNode parentNode, ref MeshDataCounts counters, ref MeshDataSet meshData, ref MeshBuffers meshBufferReference) { foreach (var child in visualChildren) { if (child == null || meshData.VertexBufferData == null || meshData.IndexBufferData == null || meshData.ChunksDefs == null) continue; var t = child.LocalTransform.GetDecomposed(); var transform = new SceneSetup.Transform { Translation = t.Translation, Scale = t.Scale, Rotation = t.Rotation, }; var structureNode = new SceneSetup.SceneNode { Name = child.Name, MeshName = child.Mesh?.Name, // MeshBuffers = meshBuffers, Transform = transform, CombinedTransform = child.WorldMatrix, }; parentNode.ChildNodes.Add(structureNode); var useStructureNodeForMesh = true; if (child.Mesh != null) { foreach (var meshPrimitive in child.Mesh.Primitives) { if(meshPrimitive == null) continue; if(!_chunkDefIndicesForPrimitives.TryGetValue(meshPrimitive, out var chunkDefIndex)) { if (!GetMeshDataFromPrimitive(meshPrimitive, out var vertexBufferData, out var indexBufferData, out var message)) continue; Array.Copy(vertexBufferData, 0, meshData.VertexBufferData, counters.VertexCount, vertexBufferData.Length); for(var faceIndex=0; faceIndex visualChildren, SceneSetup.SceneNode parentNode) { foreach (var child in visualChildren) { if (child == null) continue; var t = child.LocalTransform.GetDecomposed(); var transform = new SceneSetup.Transform { Translation = t.Translation, Scale = t.Scale, Rotation = t.Rotation, }; // Pure logic node var structureNode = new SceneSetup.SceneNode() { Name = child.Name, MeshName = child.Mesh?.Name, // MeshBuffers = meshBuffers, Transform = transform, CombinedTransform = child.WorldMatrix, }; parentNode.ChildNodes.Add(structureNode); var useStructureNodeForMesh = true; if (child.Mesh != null) { var meshIndex = 0; foreach (var meshPrimitive in child.Mesh.Primitives) { if(meshPrimitive == null) continue; if (!_meshBuffersForPrimitives.TryGetValue(meshPrimitive, out var meshBuffers)) { meshIndex++; if (!TryGenerateMeshBuffersFromGltfChild(meshPrimitive, out meshBuffers, out var errorMessage)) { ShowError(errorMessage); meshBuffers = null; } if (meshBuffers == null) continue; _meshBuffersForPrimitives[meshPrimitive] = meshBuffers; } Log.Debug($" mesh:{child.Name} {child.Mesh?.Name} {meshIndex}"); var materialDef = GetOrCreateMaterialDefinition(meshPrimitive.Material); //Log.Debug("Material: " + materialDef); if (useStructureNodeForMesh) { structureNode.MeshBuffers = meshBuffers; structureNode.Material = materialDef; useStructureNodeForMesh = false; continue; } var meshNode = new SceneSetup.SceneNode() { Name = child.Name, MeshName = child.Mesh?.Name, MeshBuffers = meshBuffers, Transform = transform, CombinedTransform = child.WorldMatrix, Material = materialDef, }; parentNode.ChildNodes.Add(meshNode); } } ParseChildren(child.VisualChildren, structureNode); } } private readonly Resource _resource; #region Asset extraction /// /// Extract gltf material definitions so we can later create a PbrMaterial from this data. /// private SceneSetup.SceneMaterial? GetOrCreateMaterialDefinition(Material? gltfMaterial) { if (gltfMaterial == null) return null; var name = gltfMaterial.Name; if (string.IsNullOrEmpty(name)) return null; if (_sceneMaterialsByName.TryGetValue(name, out var materialDef)) { return materialDef; } var baseColor = new System.Numerics.Vector4(1, 1, 1, 1); var baseColorSrv = PbrMaterial.DefaultAlbedoColorSrv; var emissiveColor = Vector4.Zero; var emissiveSrv = PbrMaterial.DefaultEmissiveColorSrv; var normalSrv = PbrMaterial.DefaultNormalSrv; ShaderResourceView? occlusionSrv = null; ShaderResourceView? metallicRoughnessSrv = null; Texture2D? metallicRoughnessTexture = null; Texture2D? occlusionTexture = null; var roughness = 0.5f; var metallic = 0f; foreach (var channel in gltfMaterial.Channels) { Log.Debug("channel: " + channel.Key); switch (channel.Key) { case "BaseColor": { baseColor = channel.Color; if (TryCreateTextureFromChannel(gltfMaterial, channel, out var _, out var srv, PbrMaterial.DefaultAlbedoColorSrv)) { baseColorSrv = srv; } break; } case "Emissive": { emissiveColor = channel.Color; if (TryCreateTextureFromChannel(gltfMaterial, channel, out var _, out var srv, PbrMaterial.DefaultEmissiveColorSrv)) { emissiveSrv = srv; } break; } case "MetallicRoughness": { if (TryCreateTextureFromChannel(gltfMaterial, channel, out var texture, out var srv, null)) { metallicRoughnessTexture = texture; metallicRoughnessSrv = srv; } foreach (var p in channel.Parameters) { switch (p.Name) { case "MetallicFactor": metallic = (float)p.Value; break; case "RoughnessFactor": roughness = (float)p.Value; break; } } break; } case "Occlusion": { if (TryCreateTextureFromChannel(gltfMaterial, channel, out var texture, out var srv, null)) { occlusionTexture = texture; occlusionSrv = srv; } break; } case "Normal": { if (TryCreateTextureFromChannel(gltfMaterial, channel, out var _, out var srv, PbrMaterial.DefaultNormalSrv)) { normalSrv = srv; } break; } } } TryCreateRoughnessMetallicOcclusionTexture( metallicRoughnessTexture, metallicRoughnessSrv, occlusionTexture, occlusionSrv, out var roughnessMetallicOcclusionSrv); metallicRoughnessTexture?.Dispose(); metallicRoughnessSrv?.Dispose(); occlusionTexture?.Dispose(); occlusionSrv?.Dispose(); var newMaterialDef = new SceneSetup.SceneMaterial { Name = name, // ColorTexture = baseColorTexture, // NormalTexture = normalTexture, PbrParameters = new PbrMaterial.PbrParameters { BaseColor = baseColor, EmissiveColor = emissiveColor, Roughness = roughness - 1 + _offsetRoughness, // glTF standard uses roughness factor?! Specular = 1, Metal = metallic -1 + _offsetMetallic, // glTF standard uses metallic factor?! } }; var newPbrMaterial = new PbrMaterial { Name = name, AlbedoMapSrv = baseColorSrv, EmissiveMapSrv = emissiveSrv, RoughnessMetallicOcclusionSrv = roughnessMetallicOcclusionSrv, NormalSrv = normalSrv, Parameters = newMaterialDef.PbrParameters, }; newPbrMaterial.UpdateParameterBuffer(); newMaterialDef.PbrMaterial = newPbrMaterial; _sceneMaterialsByName[name] = newMaterialDef; return newMaterialDef; } /// /// Setup shaders and textures required for combining roughness, metallic and occlusion textures. /// private static void PrepareCombineShaderResources(Instance instance, bool forceUpdate = false) { if (_combineChannelsComputeShaderResource == null) { const string sourcePath = "Lib:shaders/cs/CombineGltfChannels-cs.hlsl"; const string entryPoint = "main"; _combineChannelsComputeShaderResource = ResourceManager.CreateShaderResource(sourcePath, instance, () => entryPoint, OnShaderChanged); OnShaderChanged(_combineChannelsComputeShaderResource.Value); } if (!forceUpdate && _combineChannelsComputeShaderResource?.Value != null) return; OnShaderChanged(_combineChannelsComputeShaderResource!.Value); return; static void OnShaderChanged(ComputeShader? e) { if (e == null) { Log.Error($"Failed to initialize video conversion shader in {nameof(LoadGltfScene)}"); return; } e.Name = "CombineGltfChannels"; var samplerDesc = new SamplerStateDescription { Filter = Filter.MinMagMipLinear, AddressU = TextureAddressMode.Clamp, AddressV = TextureAddressMode.Clamp, AddressW = TextureAddressMode.Clamp, MipLodBias = 0, MaximumAnisotropy = 1, ComparisonFunction = Comparison.Never, MinimumLod = -999999, MaximumLod = 9999999 }; _combineChannelsSampler?.Dispose(); _combineChannelsSampler = new SamplerState(ResourceManager.Device, samplerDesc); } } /// /// Fall back to default texture if nothing set. Otherwise use shader to create new texture. /// private void TryCreateRoughnessMetallicOcclusionTexture(Texture2D? metallicRoughnessTexture, ShaderResourceView? metallicRoughnessSrv, Texture2D? occlusionTexture, ShaderResourceView? occlusionSrv, out ShaderResourceView? resultSrv) { if (metallicRoughnessSrv == null && occlusionSrv == null) { resultSrv = PbrMaterial.DefaultRoughnessMetallicOcclusionSrv; return; } PrepareCombineShaderResources(this, _updateTriggered); var device = ResourceManager.Device; var deviceContext = device.ImmediateContext; var csStage = deviceContext.ComputeShader; // Compute max resolution var width = 1; var height = 1; if (metallicRoughnessTexture != null) { width = Math.Max(width, metallicRoughnessTexture.Description.Width); height = Math.Max(height, metallicRoughnessTexture.Description.Width); } if (occlusionTexture != null) { width = Math.Max(width, occlusionTexture.Description.Width); height = Math.Max(height, occlusionTexture.Description.Width); } metallicRoughnessSrv ??= PbrMaterial.BlackPixelSrv; occlusionSrv ??= PbrMaterial.WhitePixelSrv; // TODO: create and test merge compute shader // Keep previous setup var prevShader = csStage.Get(); var prevUavs = csStage.GetUnorderedAccessViews(0, 1); var prevSrvs = csStage.GetShaderResources(0, 1); var prevSamplers = csStage.GetSamplers(0, 1); // Set Shader if (_combineChannelsComputeShaderResource != null) { var convertShader = _combineChannelsComputeShaderResource.Value; csStage.Set(convertShader); } var srvs = new[] { metallicRoughnessSrv, occlusionSrv }; csStage.SetShaderResources(0, srvs); csStage.SetSampler(0, _combineChannelsSampler); // Create target texture var resultTextureDescription = new Texture2DDescription { BindFlags = BindFlags.UnorderedAccess | BindFlags.RenderTarget | BindFlags.ShaderResource, Format = SharpDX.DXGI.Format.R8G8B8A8_UNorm, Width = width, Height = height, MipLevels = 1, SampleDescription = new SampleDescription(1, 0), Usage = ResourceUsage.Default, OptionFlags = ResourceOptionFlags.None | ResourceOptionFlags.GenerateMipMaps, CpuAccessFlags = CpuAccessFlags.None, ArraySize = 1 }; var resultTexture = Texture2D.CreateTexture2D(resultTextureDescription); resultSrv = new ShaderResourceView(ResourceManager.Device, resultTexture); var resultUav = new UnorderedAccessView(ResourceManager.Device, resultTexture); csStage.SetUnorderedAccessView(0, resultUav, 0); // Dispatch const int threadNumX = 16, threadNumY = 16; var dispatchCountX = (width / threadNumX) + 1; var dispatchCountY = (height / threadNumY) + 1; deviceContext.Dispatch(dispatchCountX, dispatchCountY, 1); ResourceManager.Device.ImmediateContext.GenerateMips(resultSrv); // Restore prev setup csStage.SetUnorderedAccessView(0, prevUavs[0]); csStage.SetShaderResource(0, prevSrvs[0]); csStage.SetSamplers(0, prevSamplers); csStage.Set(prevShader); } private static Resource? _combineChannelsComputeShaderResource; /// /// Tries to create a texture from a gltf material channel. /// private static bool TryCreateTextureFromChannel(Material gltfMaterial, MaterialChannel channel, out Texture2D? texture, out ShaderResourceView? srv, ShaderResourceView? fallbackSrv) { texture = null; srv = fallbackSrv; if (channel.Texture == null) { return false; } var imageContent = channel.Texture.PrimaryImage.Content.Content; try { using var memStream = new MemoryStream(imageContent.ToArray()); memStream.Position = 0; using var imagingFactory = new ImagingFactory(); using var bitmapDecoder = new BitmapDecoder(imagingFactory, memStream, DecodeOptions.CacheOnDemand); using var formatConverter = new FormatConverter(imagingFactory); using var bitmapFrameDecode = bitmapDecoder.GetFrame(0); formatConverter.Initialize(bitmapFrameDecode, SharpDX.WIC.PixelFormat.Format32bppRGBA, BitmapDitherType.None, null, 0.0, BitmapPaletteType.Custom); texture = Texture2D.CreateFromBitmap(ResourceManager.Device, formatConverter); texture.Name = channel.Key; Log.Debug($" Created {gltfMaterial.Name}.{channel.Key} with {texture.Description.Width}×{texture.Description.Height}"); // bitmapFrameDecode.Dispose(); // bitmapDecoder.Dispose(); // formatConverter.Dispose(); // imagingFactory.Dispose(); srv = new ShaderResourceView(ResourceManager.Device, texture); ResourceManager.Device.ImmediateContext.GenerateMips(srv); return true; } catch (Exception e) { Log.Error($"Failed to create texture from channel {gltfMaterial.Name}.{channel.Key} : {e.Message}"); return false; } } private static bool TryGenerateMeshBuffersFromGltfChild(MeshPrimitive meshPrimitive, out MeshBuffers newMesh, out string message) { // TODO: return cached mesh to reuse buffer newMesh = new MeshBuffers(); message = string.Empty; if (!GetMeshDataFromPrimitive(meshPrimitive, out var vertexBufferData, out var indexBufferData, out message)) return false; try { var faceCount = indexBufferData.Length; var verticesCount = vertexBufferData.Length; const int stride = 3 * 4; ResourceManager.SetupStructuredBuffer(indexBufferData, stride * faceCount, stride, ref newMesh.IndicesBuffer.Buffer); ResourceManager.CreateStructuredBufferSrv(newMesh.IndicesBuffer.Buffer, ref newMesh.IndicesBuffer.Srv); ResourceManager.CreateStructuredBufferUav(newMesh.IndicesBuffer.Buffer, UnorderedAccessViewBufferFlags.None, ref newMesh.IndicesBuffer.Uav); ResourceManager.SetupStructuredBuffer(vertexBufferData, PbrVertex.Stride * verticesCount, PbrVertex.Stride, ref newMesh.VertexBuffer.Buffer); ResourceManager.CreateStructuredBufferSrv(newMesh.VertexBuffer.Buffer, ref newMesh.VertexBuffer.Srv); ResourceManager.CreateStructuredBufferUav(newMesh.VertexBuffer.Buffer, UnorderedAccessViewBufferFlags.None, ref newMesh.VertexBuffer.Uav); } catch (Exception e) { message = $"Failed loading gltf: {e.Message}"; return false; } return true; } private static bool GetMeshDataFromPrimitive(MeshPrimitive meshPrimitive, out PbrVertex[] vertexBufferData, out Int3[] indexBufferData, out string message) { vertexBufferData = Array.Empty(); indexBufferData = Array.Empty(); // Convert vertices { // TODO: Iterate over all primitives var vertexAccessors = meshPrimitive.VertexAccessors; // Collect positions if (!vertexAccessors.TryGetValue("POSITION", out var positionAccessor)) { message = "Can't find POSITION attribute in gltf mesh"; return false; } var verticesCount = positionAccessor.Count; if (vertexBufferData.Length != verticesCount) vertexBufferData = new PbrVertex[verticesCount]; var positions = positionAccessor.AsVector3Array(); // Collect normals Vector3[]? normals = null; if (vertexAccessors.TryGetValue("NORMAL", out var normalAccess)) { normals = normalAccess.AsVector3Array().ToArray(); } // Collect texture coords Vector2[]? texCoords = null; Vector2[]? texCoords2 = null; if (vertexAccessors.TryGetValue("TEXCOORD_0", out var texAccess)) { texCoords = texAccess.AsVector2Array().ToArray(); } if (vertexAccessors.TryGetValue("TEXCOORD_1", out var texAccess2)) // Check for second texture coordinate set { texCoords2 = texAccess2.AsVector2Array().ToArray(); } // Write vertex buffer for (var vertexIndex = 0; vertexIndex < positions.Count; vertexIndex++) { var position = positions[vertexIndex]; vertexBufferData[vertexIndex] = new PbrVertex { Position = new Vector3(position.X, position.Y, position.Z), Normal = normals == null ? VectorT3.Up : normals[vertexIndex], Tangent = VectorT3.Right, Bitangent = VectorT3.ForwardLH, Texcoord = texCoords == null ? Vector2.Zero : new Vector2(texCoords[vertexIndex].X, 1 - texCoords[vertexIndex].Y), Texcoord2 = texCoords2 == null ? Vector2.Zero : new Vector2(texCoords2[vertexIndex].X, 1 - texCoords2[vertexIndex].Y), ColorRgb = Vector3.One, Selection = 1, }; } if (verticesCount == 0) { message = "No vertices found"; return false; } } // Convert indices { var indices = meshPrimitive.GetTriangleIndices().ToList(); var faceCount = indices.Count; if (indexBufferData.Length != faceCount) indexBufferData = new Int3[faceCount]; var faceIndex = 0; foreach (var (a, b, c) in indices) { indexBufferData[faceIndex] = new Int3(a, b, c); faceIndex++; // Calc TBN space var p1 = vertexBufferData[a].Position; var p2 = vertexBufferData[b].Position; var p3 = vertexBufferData[c].Position; // check for degenerated triangle if (p1 == p2 || p1 == p3 || p2 == p3) continue; var uv1 = vertexBufferData[a].Texcoord; var uv2 = vertexBufferData[b].Texcoord; var uv3 = vertexBufferData[c].Texcoord; // check for degenerated triangle if (uv1 == uv2 || uv1 == uv3 || uv2 == uv3) continue; var n1 = vertexBufferData[a].Normal; var n2 = vertexBufferData[b].Normal; var n3 = vertexBufferData[c].Normal; // Taken from https://github.com/vpenades/SharpGLTF/blob/master/examples/SharpGLTF.Runtime.MonoGame/NormalTangentFactories.cs var s = p2 - p1; var t = p3 - p1; var sUv = uv2 - uv1; var tUv = uv3 - uv1; //var tUv = uv1 - uv3; //tUv.Y = 1 - tUv.Y; var sx = sUv.X; var tx = tUv.X; var sy = sUv.Y; var ty = tUv.Y; var r = 1.0F / ((sx * ty) - (tx * sy)); if (!r._IsFinite()) continue; var sDir = new Vector3((ty * s.X) - (sy * t.X), (ty * s.Y) - (sy * t.Y), (ty * s.Z) - (sy * t.Z)) * r; var tDir = new Vector3((sx * t.X) - (tx * s.X), (sx * t.Y) - (tx * s.Y), (sx * t.Z) - (tx * s.Z)) * r; if (!sDir._IsFinite()) continue; if (!tDir._IsFinite()) continue; // Ill-fated attempt with brute force // sDir = Vector3.Cross(n1, Vector3.UnitY); // tDir = Vector3.Cross(n1, sDir); // Todo: Sadly this fill add significant artifacts to complex meshes vertexBufferData[a].Tangent = Vector3.Normalize(sDir); vertexBufferData[a].Bitangent = Vector3.Normalize(tDir); } if (faceCount == 0) { message = "No faces found"; return false; } } message = string.Empty; return true; } #endregion private readonly Dictionary _sceneMaterialsByName = new(); private readonly Dictionary _meshBuffersForPrimitives = new(); private readonly Dictionary _chunkDefIndicesForPrimitives = new(); private bool _combineBuffer; private float _offsetRoughness; private float _offsetMetallic; private static SamplerState? _combineChannelsSampler; private bool _updateTriggered; #region implement graph node interfaces InputSlot IDescriptiveFilename.SourcePathSlot => Path; private string _lastFilePath = string.Empty; IStatusProvider.StatusLevel IStatusProvider.GetStatusLevel() { return string.IsNullOrEmpty(_lastErrorMessage) ? IStatusProvider.StatusLevel.Success : IStatusProvider.StatusLevel.Warning; } string IStatusProvider.GetStatusMessage() { return _lastErrorMessage; } private void ShowError(string message) { _lastErrorMessage = message; Log.Warning(_lastErrorMessage, this); } private string _lastErrorMessage = string.Empty; #endregion [Input(Guid = "292e80cf-ba31-4a50-9bf4-83712430f811")] public readonly InputSlot Path = new(); [Input(Guid = "D02F41A6-1A6B-4A6E-8D6C-A28873C79F2C")] public readonly InputSlot Setup = new(); [Input(Guid = "57F129AE-0B2E-465D-8C0E-F6259FDD37CE")] public readonly InputSlot CombineBuffer = new(); [Input(Guid = "EF7075E9-4BC2-442E-8E0C-E03667FF2E0A")] public readonly InputSlot TriggerUpdate = new(); [Input(Guid = "D7AE3173-C490-47CF-8D4B-4C25102F6904")] public readonly InputSlot OffsetRoughness = new(); [Input(Guid = "49237499-9B1E-4371-AFAC-4E3394868370")] public readonly InputSlot OffsetMetallic = new(); [Input(Guid = "FB325383-754A-4702-AFF2-C19E16363460")] public readonly InputSlot MeshChildIndex = new(); }