为什么 XNA Write/ReadObject<Model>() 不保留 Vertex/Index 缓冲区数据?

Why is XNA Write/ReadObject<Model>() not preserving Vertex/Index Buffer data?

我正在使用 XNA 4.0 内容管道构建一些自定义内容类型。

我的 classes TerrainModelSetContentTerrainModelSet 分别有自定义的 ContentTypeWriterContentTypeReader,分别是构建时间和 运行 时间 classes.

当然是地形模型集 includes/is 用于关卡地形部分的模型集合,因此有一个或多个模型按顺序序列化为单个 .xnb 内容文件。

无论如何,几乎所有我能找到的 XNA 文档或教程(来自 Microsoft 或其他地方)都清楚地表明 XNA 已经带有开箱即用的 Writer 和 Reader对于模型。

所以我的问题是,为什么它不保留写入和读取之间的任何实际模型数据?它只保留顶点或索引缓冲区中实际几何体的 BoundingSpheresPrimitiveCounts--none 等琐碎的额外内容,为了远程使用,它必须保留这些内容.

在构建时,TerrainModelSet 通过此 class 的 Write(...) 函数序列化为 .xnb 文件:

[ContentTypeWriter]
public class TerrainModelSetWriter : ContentTypeWriter<TerrainModelSetContent>
{
    protected override void Write(ContentWriter output, TerrainModelSetContent value) {
        //Write starting TerrainModelSet data...
        output.Write(value.GraphicsMeshes.Count); //value.GraphicsMeshes is a dictionary of string-keys and ModelContent-values.
        foreach (KeyValuePair<string, ModelContent> item in value.GraphicsMeshes) {
            output.Write(item.Key);
            //At this point, all geometry data is present in the Vertex and
            //Index buffers of the Model item.Value's ModelMesh's
            //ModelMeshParts, nice and neat how we would expect it. I made
            //sure if this with the debugger.
            output.WriteObject<ModelContent>(item.Value);
        }
    }
    //GetRuntimeReader(...) and GetRuntimeType(...) functions are overridden here as well.
}

并且 TerrainModelSet 当然在 运行 时间反序列化在 class:

Read(...) 方法中
public class TerrainModelSetReader : ContentTypeReader<TerrainModelSet>
{
    protected override TerrainModelSet Read(ContentReader input, TerrainModelSet existingInstance) {
        if (existingInstance == null)
            existingInstance = new TerrainModelSet();
        //Read starting TerrainModelSet data...
        int numItems = input.ReadInt32();
        for (int i = 0; i < numItems; i++) {
            string itemName = input.ReadString();
            Model m = input.ReadObject<Model>();
            //Here, we use the debugger again to check the state of m, and
            //find that the XNA Framework Content Pipeline has UTTERLY
            //FAILED to preserve ANY of the geomentry data.
            //All Vertex and Index buffers in any ModelMeshParts of any
            //ModelMeshes of m are null. Not even empty, just null. WTF?
            existingInstance.GraphicsMeshes.Add(itemName, m.Meshes[0]);
            existingInstance.CollisionMeshes.Add(itemName, CollisionMesh.FromModelMesh(m.Meshes[0]));
        }
    }
}

几何图形在 Write 调用之前就已经存在了——在其适当的缓冲区中非常整洁。我已经用调试器检查过了。但是,在 Read 调用之后,m 的网格中的所有缓冲区都为空。它们甚至不是空的——只是空的。这里发生了什么?谁能赐教一下?

好吧,我终于找到了我的答案,它说了一些关于 XNA Game Studio 的丑闻。

首先,我从文档中了解到任何给定类型 T 只能有一个 ContentTypeWriter<T>ContentTypeReader<T>。据推测,为 T 创建第二个编写器或 reader 将导致 InvalidOperationException 被抛出,因为管道无法决定使用哪个。这是有道理的。

这也意味着我们可以通过尝试写入一个来检查 Writer 或 Reader 是否已经存在。所以我做了。我添加了继承自以下内容的 classes:

  • ContentTypeWriter<Model>
  • ContentTypeReader<Model>
  • ContentTypeWriter<ModelMesh>
  • ContentTypeReader<ModelMesh>

一切 运行 都没有错误,表明内置类型编写器和 Reader 对 output.WriteObject<T>(value) 上的用户调用不可用。如果我们想在我们自己的custom/extended内容管道中序列化这些class,我们必须自己重新编写那些writers/readers并重新发明轮子。很蠢,我知道。

无论如何,这给我们带来了另一个问题。当我咬紧牙关开始实现写入和读取功能时,我发现所有内置的 XNA 图形 classes--ModelModelMeshModelMeshPartBone,等等,以及它们的构建时间 Content* classes-- 是只读的并且 sealed 带有 internal 构造函数,使任何自定义用户实例化和后续这些 class 的反序列化是不可能的。

该框架的设计目的是强制用户完全重写他们自己的图形组件框架。为什么在 EARTH 上有人会编写一个工具,然后使该工具的用户应该自己重新编写该工具,即使现有工具无需修改即可满足他们的需求?愚蠢的。只是愚蠢。

好的,够了运行。我正在引导我的答案。我知道有问题的 Writers 和 Readers 存在于某个地方。毕竟,如果您使用默认 XImporterModelProcessor 向项目添加模型资产而不进行任何管道自定义,(例如,我们让 XNA 自行序列化和反序列化我们的内容而不使用 WriteObject<Model>(model) 在单个文件中将多个项目一起内联序列化)它会起作用。显然,这些 writers/readers 是内部的,不用于自定义操作。

我会说这是有道理的,这样用户就可以编写自己的 classes 而不会产生冲突,除了如前所述,无论如何都不可能做到这一点。

所以答案是这样的:我认为愚蠢到不可能是真的,是真的。开箱即用的图形内容管道中唯一有任何实际用途的部分是 XImporter。如果您想以任何方式自定义其功能,则所有其余部分都必须由用户完全重写。

  • 您必须编写自己的 Model class,即使它只是内置的副本。
  • 你必须自己写 ModelMesh class.
  • 你必须自己写 ModelMeshPart class.
  • 你必须自己写 ModelContent class.
  • 你必须自己写 ModelMeshContent class.
  • 你必须自己写 ModelMeshPartContent class.
  • 您必须编写自己的自定义类型编写器和 Reader 每个。
  • 您还必须为任何您正在使用的可以实例化的内置 classes 编写自定义编写器和 Readers,例如:
    • Texture2DContentTexture2D
    • VertexBufferContentVertexBuffer
    • IndexCollectionIndexBuffer
    • EffectContentEffect/BasicEffect
    • 等...
  • 您必须编写自己的自定义 ModelProcessor 以将 XImporterNodeContent 输出转换为您自己的 ModelContent 及其子内容 class es.
  • 我是不是忘记了什么?哦是的。您必须编写自己的 Draw(...) 函数才能使用这些 classes。 XNA 模型的内置 Draw(GameTime) 显然对您没用。

截至撰写本 post 时,我已经差不多完成了这些任务,但在我测试此方法是否有效之前还有很长的路要走。 (需要制作一些内容来测试一个,因为编写这个新系统使我的很多旧内容无法使用)。当(如果)我让它工作时,我将用结果编辑这个答案。

如果有人希望我 post 最终工作 classes 在某处作为教程,当他们准备好时,请发表评论。我会非常乐意。

P.S. -- 在我尝试编写自定义 writers/readers 之前框架能够 write/read 模型的原因它们,是因为框架在第一次调用 WriteObject<T>(tObj)ReadObject<T>() 时使用反射为未知类型生成 writer/reader。然而,这只会处理属性,并且会丢失通过 VertexBuffer.GetData(...) 等方法调用获得的数据。此外,VertexBufferIndexBuffer 没有默认构造函数,这使得基于反射的反序列化器无法知道如何创建它们。这就是为什么我的 VertexBuffersIndexBuffers 都是空的(我很确定这就是原因)。