【问题标题】:Why is XNA Write/ReadObject<Model>() not preserving Vertex/Index Buffer data?为什么 XNA Write/ReadObject<Model>() 不保留顶点/索引缓冲区数据?
【发布时间】:2015-07-03 11:23:28
【问题描述】:

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

我的类TerrainModelSetContentTerrainModelSet 分别有一个自定义ContentTypeWriterContentTypeReader,它们分别是构建时间和运行时间类。

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

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

所以我的问题是,为什么它在写入和读取之间不保留任何实际模型数据?它只保留了诸如BoundingSpheresPrimitiveCounts 之类的琐碎的额外内容——在顶点或索引缓冲区中没有任何实际几何图形,它必须保留这些东西才能远程使用。

在构建时,TerrainModelSet 通过此类的 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 当然会在运行时在相应类的 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 的网格中的所有缓冲区都为空。它们甚至不是空的——只是空的。这里发生了什么?谁能赐教?

【问题讨论】:

    标签: c# xna xna-4.0


    【解决方案1】:

    嗯,我终于找到了答案,它说明了 XNA Game Studio 的一些丑陋之处。

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

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

    • ContentTypeWriter&lt;Model&gt;
    • ContentTypeReader&lt;Model&gt;
    • ContentTypeWriter&lt;ModelMesh&gt;
    • ContentTypeReader&lt;ModelMesh&gt;

    一切运行都没有错误,表明内置的类型写入器和读取器不适用于output.WriteObject&lt;T&gt;(value) 上的用户调用。如果我们想在我们自己的自定义/扩展内容管道中序列化这些类,我们必须自己重新编写这些写入器/读取器并重新发明轮子。非常愚蠢,我知道。

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

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

    好吧,废话不多说。我正在回答我的问题。我知道有问题的作家和读者存在——某处。毕竟,如果您使用默认的XImporterModelProcessor 将模型资产添加到项目中而不进行任何管道定制,(例如,我们让XNA 自行序列化和反序列化我们的内容,而不使用WriteObject&lt;Model&gt;(model) 序列化多个项目一起内联在一个文件中)它将起作用。显然,这些写入器/读取器是内部的,不用于自定义操作。

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

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

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

    截至本文发布时,我刚刚完成了这些任务,但在我测试这种方法是否有效之前还有很长的路要走。 (需要制作一些内容进行测试,因为编写这个新系统使我的很多旧内容无法使用)。当(如果)我让它工作时,我会用结果编辑这个答案。

    如果有人希望我在他们准备好后将最终的工作课程作为教程发布在某个地方,请发表评论。我会非常乐意。

    PS -- 在我尝试为模型编写自定义写入器/读取器之前,框架能够写入/读取模型的原因是框架使用反射来生成写入器/读取器对于未知类型,第一次调用 WriteObject&lt;T&gt;(tObj)ReadObject&lt;T&gt;()。然而,这仅处理属性,并丢失通过方法调用获得的数据,例如VertexBuffer.GetData(...)。此外,VertexBufferIndexBuffer 没有默认构造函数,这使得基于反射的反序列化器无法知道如何制作它们。这就是为什么我的VertexBuffersIndexBuffers 都清盘了(我很确定这就是为什么)。

    【讨论】:

    • 我很想看看您是如何在 Writer 中编写 Texture2DContent 对象并在 Reader 中以 Texture2D 的形式读回的。目前,我正在尝试做同样的事情,但在阅读时,我遇到了一个我无法利用的异常。
    猜你喜欢
    • 1970-01-01
    • 2018-11-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多