Files
2026-07-13 13:13:17 +08:00

1110 lines
44 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#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;
/// <summary>
/// Uses the GltfSharp library to load a gltf file and convert it into a <see cref="SceneSetup"/>.
/// </summary>
[Guid("00618c91-f39a-44ea-b9d8-175c996460dc")]
public class LoadGltfScene : Instance<LoadGltfScene>
, IDescriptiveFilename, IStatusProvider
{
[Output(Guid = "C2D28EBE-E235-4234-8234-EB56E87ED9AF")]
public readonly Slot<SceneSetup> ResultSetup = new();
[Output(Guid = "9891EA8D-7DE7-4DD1-A61E-337ADC569EF3")]
public readonly Slot<MeshBuffers> Mesh = new();
[Output(Guid = "F33EC4C1-F07B-46B3-BEC7-34E91B8312EF")]
public readonly Slot<PbrMaterial> Material = new();
public LoadGltfScene()
{
_resource = new Resource<SceneSetup>(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<MeshPrimitive> 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<MeshChunkDef>(),
};
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<MeshChunkDef>? ChunksDefs;
}
private struct MeshDataCounts
{
public int ChunkCount;
public int VertexCount;
public int FaceCount;
}
private void ComputeTotalMeshCounts(IEnumerable<Node?> visualChildren, ref MeshDataCounts totalCounts, ref HashSet<MeshPrimitive> 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<Node?> 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<indexBufferData.Length; faceIndex++)
{
meshData.IndexBufferData[faceIndex + counters.FaceCount].X = indexBufferData[faceIndex].X + counters.VertexCount;
meshData.IndexBufferData[faceIndex + counters.FaceCount].Y = indexBufferData[faceIndex].Y + counters.VertexCount;
meshData.IndexBufferData[faceIndex + counters.FaceCount].Z = indexBufferData[faceIndex].Z + counters.VertexCount;
}
var currentChunkIndex = meshData.ChunksDefs.Count;
meshData.ChunksDefs.Add(new MeshChunkDef()
{
StartFaceIndex = counters.FaceCount,
FaceCount = indexBufferData.Length,
StartVertexIndex = counters.VertexCount,
VertexCount = vertexBufferData.Length,
});
_chunkDefIndicesForPrimitives[meshPrimitive] = currentChunkIndex;
counters.VertexCount += vertexBufferData.Length;
counters.FaceCount += indexBufferData.Length;
chunkDefIndex = currentChunkIndex;
}
var materialDef = GetOrCreateMaterialDefinition(meshPrimitive.Material);
if (useStructureNodeForMesh)
{
structureNode.MeshBuffers = meshBufferReference;
structureNode.MeshChunkIndex = chunkDefIndex;
structureNode.Material = materialDef;
useStructureNodeForMesh = false;
continue;
}
var meshNode = new SceneSetup.SceneNode()
{
Name = child.Name,
MeshName = child.Mesh?.Name,
MeshBuffers = meshBufferReference,
MeshChunkIndex = chunkDefIndex,
Transform = transform,
CombinedTransform = child.WorldMatrix,
Material = materialDef,
};
parentNode.ChildNodes.Add(meshNode);
}
}
ParseChildrenIntoChunks(child.VisualChildren, structureNode, ref counters, ref meshData, ref meshBufferReference);
}
}
private void ParseChildren(IEnumerable<Node?> 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<SceneSetup> _resource;
#region Asset extraction
/// <summary>
/// Extract gltf material definitions so we can later create a PbrMaterial from this data.
/// </summary>
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;
}
/// <summary>
/// Setup shaders and textures required for combining roughness, metallic and occlusion textures.
/// </summary>
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<ComputeShader>(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);
}
}
/// <summary>
/// Fall back to default texture if nothing set. Otherwise use shader to create new texture.
/// </summary>
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<T3.Core.DataTypes.ComputeShader>? _combineChannelsComputeShaderResource;
/// <summary>
/// Tries to create a texture from a gltf material channel.
/// </summary>
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<PbrVertex>();
indexBufferData = Array.Empty<Int3>();
// 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<string, SceneSetup.SceneMaterial> _sceneMaterialsByName = new();
private readonly Dictionary<MeshPrimitive, MeshBuffers> _meshBuffersForPrimitives = new();
private readonly Dictionary<MeshPrimitive, int> _chunkDefIndicesForPrimitives = new();
private bool _combineBuffer;
private float _offsetRoughness;
private float _offsetMetallic;
private static SamplerState? _combineChannelsSampler;
private bool _updateTriggered;
#region implement graph node interfaces
InputSlot<string> 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<string> Path = new();
[Input(Guid = "D02F41A6-1A6B-4A6E-8D6C-A28873C79F2C")]
public readonly InputSlot<SceneSetup> Setup = new();
[Input(Guid = "57F129AE-0B2E-465D-8C0E-F6259FDD37CE")]
public readonly InputSlot<bool> CombineBuffer = new();
[Input(Guid = "EF7075E9-4BC2-442E-8E0C-E03667FF2E0A")]
public readonly InputSlot<bool> TriggerUpdate = new();
[Input(Guid = "D7AE3173-C490-47CF-8D4B-4C25102F6904")]
public readonly InputSlot<float> OffsetRoughness = new();
[Input(Guid = "49237499-9B1E-4371-AFAC-4E3394868370")]
public readonly InputSlot<float> OffsetMetallic = new();
[Input(Guid = "FB325383-754A-4702-AFF2-C19E16363460")]
public readonly InputSlot<int> MeshChildIndex = new();
}