Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 158 additions & 0 deletions sources/tools/Stride.Importer.3D/MeshConverter.RuntimeMaterials.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.

using System;
using System.Collections.Generic;
using System.IO;
using Silk.NET.Assimp;
using Stride.Importer.ThreeD.Material;
using Stride.Core.Mathematics;
using Stride.Rendering.Materials;
using Stride.Rendering.Materials.ComputeColors;
using Texture = Stride.Graphics.Texture;

namespace Stride.Importer.ThreeD;

public partial class MeshConverter
{
internal unsafe List<Rendering.Material> BuildRuntimeMaterials(string pathToFile)
{
uint importFlags = 0;
var scene = Initialize(pathToFile, null, importFlags, 0);

if (scene == null)
{
var error = assimp.GetErrorStringS();
if (error.Length > 0)
{
Logger.Error($"Assimp: {error}");
}

return null;
}

var materials = ExtractMaterials(scene);
return materials;
}

private unsafe List<Rendering.Material> ExtractMaterials(Scene* scene)
{
var materials = new List<Rendering.Material>();
for (uint i = 0; i < scene->MNumMaterials; i++)
{
var pMaterial = scene->MMaterials[i];
var descriptor = new MaterialDescriptor
{
Attributes = new MaterialAttributes()
};

// Diffuse
var diffuseStack = Materials.ConvertAssimpStackCppToCs(assimp, scene, pMaterial, TextureType.Diffuse, Logger);
var diffuseNode = BuildRuntimeComputeColor(diffuseStack, scene);
if (diffuseNode != null)
{
descriptor.Attributes.Diffuse = new MaterialDiffuseMapFeature(diffuseNode);
descriptor.Attributes.DiffuseModel = new MaterialDiffuseLambertModelFeature();
}

// Surface/Normals
var normalStack = Materials.ConvertAssimpStackCppToCs(assimp, scene, pMaterial, TextureType.Normals, Logger);
var normalNode = BuildRuntimeComputeColor(normalStack, scene);
if (normalNode != null)
descriptor.Attributes.Surface = new MaterialNormalMapFeature(normalNode);

// Specular
// TODO: Metallness
var specularStack = Materials.ConvertAssimpStackCppToCs(assimp, scene, pMaterial, TextureType.Specular, Logger);
var specularNode = BuildRuntimeComputeColor(specularStack, scene);
if (specularNode != null)
{
descriptor.Attributes.Specular = new MaterialSpecularMapFeature { SpecularMap = specularNode };
descriptor.Attributes.SpecularModel = new MaterialSpecularMicrofacetModelFeature
{
Fresnel = new MaterialSpecularMicrofacetFresnelSchlick(),
Visibility = new MaterialSpecularMicrofacetVisibilityImplicit(),
NormalDistribution = new MaterialSpecularMicrofacetNormalDistributionBlinnPhong()
};
}

// TODO: Microsurface/Gloss
/*
var glossStack = Materials.ConvertAssimpStackCppToCs(assimp, scene, pMaterial, TextureType.Shininess, Logger);
var glossNode = BuildRuntimeComputeColor(glossStack, scene);
if (glossNode != null)
{
descriptor.Attributes.MicroSurface = new MaterialGlossinessMapFeature { GlossinessMap = };
}
*/

var material = Rendering.Material.New(graphicsDevice, descriptor);
materials.Add(material);
}

return materials;
}

private unsafe IComputeColor BuildRuntimeComputeColor(MaterialStack stack, Scene* scene)
{
if (stack.IsEmpty) return null;

var top = stack.Pop();

return top switch
{
StackColor c => new ComputeColor(
new Color4(c.Color.R, c.Color.G, c.Color.B, c.Alpha)
),

StackTexture t => LoadRuntimeTexture(t.TexturePath, scene) is { } texture
? new ComputeTextureColor(texture)
{
AddressModeU = ConvertTextureMode(t.MappingModeU),
AddressModeV = ConvertTextureMode(t.MappingModeV),
TexcoordIndex = t.Channel == 0
? TextureCoordinate.Texcoord0
: TextureCoordinate.Texcoord1
}
: null,

_ => null
};
}

private unsafe Texture LoadRuntimeTexture(string texturePath, Scene* scene)
{
try
{
// Embedded texture (*0, *1, ...)
if (texturePath.StartsWith("*") && int.TryParse(texturePath.Substring(1), out int texIndex))
{
var tex = scene->MTextures[texIndex];
var bytes = new byte[tex->MWidth];
fixed (byte* dst = bytes)
System.Buffer.MemoryCopy(tex->PcData, dst, bytes.Length, bytes.Length);

using var ms = new MemoryStream(bytes);
var image = Graphics.Image.Load(ms);
return Texture.New(graphicsDevice, image);
}

var dir = Path.GetDirectoryName(vfsInputFilename);
var fullPath = Path.Combine(dir, texturePath);
if (System.IO.File.Exists(fullPath))
{
using var fs = System.IO.File.OpenRead(fullPath);
var image = Graphics.Image.Load(fs);
return Texture.New(graphicsDevice, image);
}

Logger.Warning($"Texture not found: {fullPath}");
return null;
}
catch (Exception ex)
{
Logger.Error($"Failed to load texture '{texturePath}': {ex.Message}");
return null;
}
}
}
101 changes: 95 additions & 6 deletions sources/tools/Stride.Importer.3D/MeshConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@

namespace Stride.Importer.ThreeD
{
public class MeshConverter
public partial class MeshConverter
{
static MeshConverter()
{
Expand All @@ -52,6 +52,7 @@ static MeshConverter()
private Matrix rootTransformInverse;
private Model modelData;

private readonly GraphicsDevice graphicsDevice = null;
private readonly List<ModelNodeDefinition> nodes = new();
private readonly Dictionary<string, int> textureNameCount = new();

Expand All @@ -60,6 +61,12 @@ public MeshConverter(Logger logger)
Logger = logger ?? GlobalLogger.GetLogger("Import Assimp");
}

public MeshConverter(Logger logger, GraphicsDevice graphicsDevice)
{
Logger = logger ?? GlobalLogger.GetLogger("Import Assimp");
this.graphicsDevice = graphicsDevice;
}

private void ResetConversionData()
{
textureNameCount.Clear();
Expand Down Expand Up @@ -150,7 +157,20 @@ public unsafe Rendering.Skeleton ConvertSkeleton(string inputFilename, string ou

return ProcessSkeleton(scene);
}


internal Model BuildRuntimeModel(string inputFilename, bool deduplicateMaterials)
{
var model = Convert(inputFilename, null, deduplicateMaterials);
// convert materials from sources to Stride
var materials = BuildRuntimeMaterials(inputFilename);
var count = materials.Count;
for (int i = 0; i < count; i++)
{
model.Materials.Add(materials[i]);
}
return model;
}

private unsafe Scene* Initialize(string inputFilename, string outputFilename, uint importFlags, aiPostProcessSteps postProcessFlags)
{
ResetConversionData();
Expand All @@ -167,6 +187,7 @@ public unsafe Rendering.Skeleton ConvertSkeleton(string inputFilename, string ou
postProcessFlags |= aiPostProcessSteps.aiProcess_CalcTangentSpace
| aiPostProcessSteps.aiProcess_Triangulate
| aiPostProcessSteps.aiProcess_GenNormals
| aiPostProcessSteps.aiProcess_LimitBoneWeights
| aiPostProcessSteps.aiProcess_SortByPType
| aiPostProcessSteps.aiProcess_FlipWindingOrder
| aiPostProcessSteps.aiProcess_FlipUVs
Expand Down Expand Up @@ -234,13 +255,69 @@ private unsafe Model ConvertAssimpScene(Scene* scene)

if (meshInfo.HasSkinningNormal && meshInfo.TotalClusterCount > 0)
nodeMeshData.Parameters.Set(MaterialKeys.HasSkinningNormal, true);


// calculate bounding box for runtime imported meshes
if (graphicsDevice != null && meshInfo.Draw.VertexBuffers?.Length > 0)
{
var vb = meshInfo.Draw.VertexBuffers[0];
var serializedData = vb.Buffer.GetSerializationData();
if (serializedData != null)
{
var bytes = serializedData.Content;
var stride = vb.Declaration.VertexStride;
var bmin = new Vector3(float.MaxValue);
var bmax = new Vector3(float.MinValue);
fixed (byte* ptr = bytes)
{
for (int vi = 0; vi < vb.Count; vi++)
{
var pos = *(Vector3*)(ptr + vi * stride);
Vector3.Min(ref bmin, ref pos, out bmin);
Vector3.Max(ref bmax, ref pos, out bmax);
}
}
nodeMeshData.BoundingBox = new BoundingBox(bmin, bmax);
BoundingSphere.FromBox(ref nodeMeshData.BoundingBox, out nodeMeshData.BoundingSphere);
}
}

modelData.Meshes.Add(nodeMeshData);
}
}

if (graphicsDevice != null)
{
foreach (var mesh in modelData.Meshes)
{
var draw = mesh.Draw;
for (int i = 0; i < draw.VertexBuffers.Length; i++)
{
var vb = draw.VertexBuffers[i];
var serializedData = vb.Buffer.GetSerializationData();
if (serializedData == null) continue;
draw.VertexBuffers[i] = new VertexBufferBinding(
Graphics.Buffer.Vertex.New(graphicsDevice, serializedData.Content),
vb.Declaration, vb.Count, vb.Offset);
}
if (draw.IndexBuffer.Buffer != null)
{
var serData = draw.IndexBuffer.Buffer.GetSerializationData();
if (serData != null)
draw.IndexBuffer = new IndexBufferBinding(
Graphics.Buffer.Index.New(graphicsDevice, serData.Content),
draw.IndexBuffer.Is32Bit, draw.IndexBuffer.Count, draw.IndexBuffer.Offset);
}
}

// single bounding box for multimesh Model
var modelBox = BoundingBox.Empty;
foreach (var mesh in modelData.Meshes)
BoundingBox.Merge(ref modelBox, ref mesh.BoundingBox, out modelBox);
modelData.BoundingBox = modelBox;

modelData.Skeleton = new Rendering.Skeleton { Nodes = [.. nodes] };
}

return modelData;
}

Expand Down Expand Up @@ -1066,10 +1143,22 @@ private unsafe MeshInfo ProcessMesh(Scene* scene, Silk.NET.Assimp.Mesh* mesh, Di
}

// Build the mesh data
var vertexDeclaration = new VertexDeclaration(vertexElements.ToArray());
var vertexBufferBinding = new VertexBufferBinding(GraphicsSerializerExtensions.ToSerializableVersion(new BufferData(BufferFlags.VertexBuffer, vertexBuffer)), vertexDeclaration, (int)mesh->MNumVertices, vertexDeclaration.VertexStride, 0);
var indexBufferBinding = new IndexBufferBinding(GraphicsSerializerExtensions.ToSerializableVersion(new BufferData(BufferFlags.IndexBuffer, indexBuffer)), is32BitIndex, (int)nbIndices, 0);
Graphics.Buffer vb, ib;
var vertexDeclaration = new VertexDeclaration([.. vertexElements]);

if (graphicsDevice == null)
{
vb = GraphicsSerializerExtensions.ToSerializableVersion(new BufferData(BufferFlags.VertexBuffer, vertexBuffer));
ib = GraphicsSerializerExtensions.ToSerializableVersion(new BufferData(BufferFlags.IndexBuffer, indexBuffer));
}
// loading meshes to the GPU if Model loading from file to runtime without asset
else
{
vb = Graphics.Buffer.Vertex.New(graphicsDevice, vertexBuffer);
ib = Graphics.Buffer.Index.New(graphicsDevice, indexBuffer);
}
var vertexBufferBinding = new VertexBufferBinding(vb, vertexDeclaration, (int)mesh->MNumVertices, vertexDeclaration.VertexStride, 0);
var indexBufferBinding = new IndexBufferBinding(ib, is32BitIndex, nbIndices, 0);

drawData.VertexBuffers = new VertexBufferBinding[] { vertexBufferBinding };
drawData.IndexBuffer = indexBufferBinding;
Expand Down
20 changes: 20 additions & 0 deletions sources/tools/Stride.Importer.3D/ModelBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.

using Stride.Core.Diagnostics;
using Stride.Graphics;
using Stride.Rendering;

namespace Stride.Importer.ThreeD;

public static class ModelBuilder
{
public static Model CreateFromFile(GraphicsDevice graphicsDevice, string pathToModel)
{
// convert model from sources to Stride
var deduplicateMaterial = false;
var log = GlobalLogger.GetLogger("Model builder");
var meshConverter = new MeshConverter(log, graphicsDevice);
return meshConverter.BuildRuntimeModel(pathToModel, deduplicateMaterial);
}
}
Loading