12

目前正在使用 C# 开发 3D 媒体引擎,我遇到了一个小难题。我已经弄清楚了我的渲染循环,我得到了一个很棒的插件架构和内容管理系统,甚至还有一个材料管道都计划好了。然后引擎计划使用 DirectX 和 OpenGL(通过“渲染器”插件),以及两个 API 的可编程管道。

无论如何,本周初我开始研究用于处理顶点的引擎抽象层(我已经为此担心了好几个星期了)。正如你们中的一些人所知,图形 API 之间的顶点处理完全不相关或相同。好吧,有点相关;),但不一样。在 OpenGL 中处理顶点非常简单,您创建自定义顶点结构,将其发送到 GPU,然后让着色器处理其余部分。这对于灵活的图形管道来说是完美的,OpenGL 不需要知道每个顶点包含哪些元素。另一方面,DirectX 要求我们为每个顶点结构创建声明,然后将它们发送到 GPU。

问题是我不知道正在传递什么类型的顶点结构,我绝对希望避免创建涉及通过枚举和一些抽象的“VertexDeclaration”类声明顶点的每个元素的抽象层;这会导致一些问题:

1) 至少可以说获取顶点元素会很痛苦。我可以使用一些'VertexSemantic'并询问顶点'a - z'的位置,但是当为骨骼动画之类的东西处理大量顶点时,它可能会有很多开销。

2) 考虑到引擎的主要关注点是“新手”,用户不太友好。我希望用户能够创建自定义顶点和网格缓冲区,而无需声明大量对象,从而消耗宝贵的开发时间。

3)更多?

现在我可以对属性做一些事情,然后在 DirectX 渲染器中为顶点结构创建声明。例如,继续创建一些枚举:

// for getting the format layout of the element
public enum ElementFormat
{
    Float, Float2, Float3, Byte, etc, etc
}
// for determining the 'usage' 
// (here is 'another' where DirectX limits vertex structures ><)
public enum ElementUsage
{
    Position, Normal, Color, TextureCoord, etc, etc
}

现在我可以创建一个属性,用户可以将其应用于顶点结构中每个元素的“字段”:

    public class VertexElementAttribute : Attribute
    {
        #region Properties
        /// <summary>
        /// Gets the total size (in bytes) of the element.
        /// </summary>
        public int Size
        {
            get;
            set;
        }
        /// <summary>
        /// Gets the number of values contained with-in the element.
        /// </summary>
        public int Count
        {
            get;
            set;
        }
        /// <summary>
        /// Gets the type semantic of the element.
        /// </summary>
        public ElementType Type
        {
            get;
            set;
        }
        /// <summary>
        /// Gets the usage semantic of the element.
        /// </summary>
        public ElementUsage Usage
        {
            get;
            set;
        }
        #endregion

        #region Init
        /// <summary>
        /// Creates a new vertex element attribute.
        /// </summary>
        /// <param name="count">The number of values contained within the element.</param>
        /// <param name="size">The total size (in bytes) of the element.</param>
        /// <param name="type">The type semantic of the element.</param>
        /// <param name="usage">The usage semantic of the element.</param>
        public VertexElementAttribute(int count, int size, ElementType type, ElementUsage usage)
        {
            Count = count;
            Size = size;
            Type = type;
            Usage = usage;
        }
        #endregion
    }

自定义顶点结构的示例:

public struct VertexPositionColor
{
    [VertexElement(3, sizeof(Vector3), ElementType.FLOAT3, ElementUsage.POSITION)]
    public Vector3 Xyz;
    [VertexElement(4, sizeof(Color), ElementType.FLOAT4, ElementUsage.COLOR)]
    public Color Rgba; 

    ... etc
}

这会很好。在 DirectX 插件(渲染器)中,我可以只创建一个实用程序类,该类可以为每种结构类型创建语义,然后缓存数据,因此不必为每个顶点重新创建声明。

我什至可以向 ELementUsage 添加一个 NONE 枚举值,以便可以将自定义值用于任何含义......但它们只能在 OpenGL 中工作,因为 DirectX 要求您标记每个顶点......除非我有什么失踪。

我的问题:

有没有更好的方法来解决这个问题(除了使用属性)?有没有办法避免在 DirectX 中使用 VertexDeclarations?关于“我的”问题,您有什么不明白的地方吗?

编辑:

使用属性的一个问题是从每个顶点获取元素数据。假设我想获取网格缓冲区中每个顶点的位置。由于我使用属性,我不能只做'vertex.Position',我必须创建一个可以从顶点结构中提取字段引用的实用程序方法,例如'Utility.GetElement(vertex,ElementUsage.POSITION)' . 此方法需要使用反射首先找到属性,然后返回对字段值的引用。甚至(我认为)不可能设置该值?

另一种方法是创建一个 IElement 接口并实现每个元素(Positon、Normal 等)。该接口可以具有 Name 属性,我可以直接在继承的元素结构中返回它,就像 PositionElements 的 Name 属性只会返回“Positon”。

接下来,我可以将 IElement 数组保存在 Vertex 结构中,该结构包含 AddElement(IElement)、GetElement(string name)、GetElement(int index)、Insert、Replace 等方法。我将实现 DirectX 已知的所有元素,以便渲染器插件可以解析一个顶点结构来创建一个顶点声明数组。

问题是我不确定数组“[]”是否可以用作顶点元素数据。比如,数组包含哪些其他字节(如果有)会阻碍我将 Vertex 结构(包含 IElement 数组)直接传递给 DirectX,然后传递给 GPU?

以这种方式实现它对于我需要它来说绝对是完美的。另一个问题是 IElement(元素)的继承类型可以是一个类,还是元素值必须是值类型?

4

2 回答 2

1

自从我做过任何directx或opengl以来已经很长时间了,所以请接受我的建议,但我记得不久前做过类似的事情

我想我做了这样的事情:

var graphicsStream = new GraphicsStream();

var elements = graphicsStream.Create<Vector3>(Usage.Position);
graphicsStream.Create<Color>(Usage.Color);
graphicsStream.Create<Quaternion>(Usage.Fribble);

elements.SetData(new[] { new Vector3(), new Vector3() });

var vertexFormat = graphicsStream.GetFormat();
graphicsStream.Validate();  // ensure all the streams have the same length

// get a particular element by type
var p = graphicsStream.GetData(Usage.Position, 1);

您将有一个图形流,它是一组应用了用法的类型化数据集。由此您可以生成适当的顶点格式。该 API 可以让您一次性更改单个元素或上传和替换整个顶点缓冲区。

最大的缺点是您没有一个结构可以代表顶点结构中的“列”。

我不知道这是否是你正在寻找的那种东西。为什么这样的设计不合适?

于 2011-03-03T17:20:08.633 回答
1

你可以看看OGRE如何处理这个问题。特别是关于 VertexElement 以及顶点缓冲区类的渲染系统文档。OGRE 是 C++,但我相信您将能够轻松地将设计模式转换为 C#。

于 2011-03-22T10:52:57.110 回答