试图在运行时不知道它们将是啥类型的情况下创建一个 DirectX 顶点数组

Posted

技术标签:

【中文标题】试图在运行时不知道它们将是啥类型的情况下创建一个 DirectX 顶点数组【英文标题】:Trying to make an array of DirectX vertex with out knowing until run time what type they will be试图在运行时不知道它们将是什么类型的情况下创建一个 DirectX 顶点数组 【发布时间】:2010-10-29 23:54:51 【问题描述】:

为不了解 DirectX 的人提供一些背景知识。顶点不仅仅是一个 XYZ 位置,它可以包含其他数据。 DirectX 使用称为灵活顶点格式 (FVF) 的系统,让您定义希望顶点采用的格式。您可以通过将数字传递给使用按位或构建它的 DirectX 来定义这些格式,例如 (D3DFVF_XYZ | D3DFVF_DIFFUSE)表示您将开始使用(从您告诉 DirectX 时开始)具有 XYZ(三个浮点数)和 RGB 分量(DWORD / unsigned long)的顶点。

为了将您的顶点传递给显卡,您基本上将内存锁定在缓冲区所在的显卡中,并使用 memcpy 将您的数组传输过来。

您的数组是您定义自己的结构的数组,因此在这种情况下,您将创建一个类似...的结构

struct CUSTOMVERTEX 
    FLOAT X, Y, Z;
    DWORD COLOR;
;

然后创建一个 CUSTOMVERTEX 类型的数组并填写数据字段。

我认为我最好的方法是让我的班级为每个组件类型建立一个数组,所以一个 struct pos flaot x,y,z;; 的数组struct colour DWROD colour;; 的数组等等。

但是我需要将它们合并在一起,这样我就有了一个像 CUSTOMVERTEX 这样的数组结构。

现在,我想我已经创建了一个可以将数组合并到一起的函数,但我不确定它是否会按预期工作,就在这里(目前缺少实际返回这个“交错”数组的能力)

void Utils::MergeArrays(char *ArrayA, char *ArrayB, int elementSizeA, int elementSizeB, int numElements)

    char *packedElements = (char*)malloc(numElements* (elementSizeA, elementSizeB));
    char *nextElement = packedElements;
    for(int i = 0; i < numElements; ++i)
    
        memcpy(nextElement, (void*)ArrayA[i], elementSizeA);
        nextElement += elementSizeA;
        memcpy(nextElement, (void*)ArrayB[i], elementSizeB);
        nextElement += elementSizeB;
    

调用此函数时,您将传入要合并的两个数组,以及每个数组中元素的大小和数组中的元素个数。

我在聊天中询问了这个问题,当时 SO 很失望。有几件事要说。

我正在处理相当小的数据集,比如 100 个顶部,而这(理论上)更像是一个初始化任务,所以应该只完成一次,所以我可以花一点时间。

我希望能够使用 memcpy 传输到显卡的最终数组需要没有填充,它必须是连续数据。

EDIT 顶点数据的组合数组将被传输到 GPU,这首先通过请求 GPU 将 void* 设置为我可以访问的内存的开头并请求空间来完成我的 customVertex * NumOfVertex 的大小。因此,如果我的 mergeArray 函数确实松散了其中的类型,那没关系,只要我让我的单个组合数组在一个块中传输 /EDIT

最后,他们是一个很好的机会,我用这个来找错树了,所以他们可能是一个更简单的方法来解决这个问题,但我的一部分已经挖掘了我的治疗并且想让这个系统正常工作,所以我希望知道如何让这样一个系统工作(交错阵列的事情)

非常感谢...我现在需要冷静一下,所以我期待听到有关此问题的任何想法。

【问题讨论】:

CUSTOMERVERTEX 数组您面临什么问题?为什么要考虑将我们的 VERTEX 和 COLOR 分开? 我不想将它们分开,我想获取组件数组并将它们合并在一起,以便我拥有一个 customvertex 类型的数组,除了,我不知道到底是什么,直到运行时我的 FVF(因此我的自定义顶点中的数据)将是。 你知道从 DX8 开始就不需要 FVF 格式了吧? @jalf 等什么?我知道我可以加载 .x 文件 检查顶点缓冲区的 D3D 文档。假设您不是在查看 DX8 版本,他们提到您可以创建 FVF 缓冲区,但还描述了如何使用顶点声明来实现大致相同的目标,但具有更大的灵活性。 FVF 必要 的唯一情况是如果您想使用ProcessVertices 写入顶点缓冲区。阅读msdn.microsoft.com/en-us/library/bb206332%28VS.85%29.aspx、msdn.microsoft.com/en-us/library/bb204869%28VS.85%29.aspx和msdn.microsoft.com/en-us/library/bb173428%28v=VS.85%29.aspx 【参考方案1】:

不,不,不。 FVF 系统已被弃用多年,甚至在 D3D10 或更高版本中都不可用。 D3D9 使用 VertexElement 系统。示例代码:

D3DVERTEXELEMENT9 VertexColElements[] =

    0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0,
    0, 12, D3DDECLTYPE_D3DCOLOR, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_COLOR, 0,
    D3DDECL_END(),
;

FVF 系统有许多基本缺陷 - 例如,字节进入的顺序。

最重要的是,如果你想制作一个运行时变体的顶点数据格式,那么你需要为你可能想要拥有的每个可能的变体编写一个着色器,并编译它们,然后花费你的生命来交换他们周围。而且,对最终产品的影响将是疯狂的——例如,如果你决定取出 Phong 着色所需的光照数据,你怎么可能编写一个有竞争力的渲染引擎?

现实情况是,运行时变体的顶点格式有点疯狂。

不过,我想我最好伸出援手。你真正需要的是一个多态函数对象和一些普通的内存——D3D 需要 void*s 或类似的东西,所以这没什么大不了的。当你调用函数对象时,它会添加到 FVF 声明中并将数据复制到内存中。

class FunctionBase 
public:
    virtual ~FunctionBase() 
    virtual void Call(std::vector<std::vector<char>>& vertices, std::vector<D3DVERTEXELEMENT9>& vertexdecl, int& offset) = 0;
;
// Example implementation
class Position : public FunctionBase 
    virtual void Call(std::vector<std::vector<char>>& vertices, std::vector<D3DVERTEXELEMENT9>& vertexdecl, int& offset) 
        std::for_each(vertices.begin(), vertices.end(), [&](std::vector<char>& data) 
            float x[3] = 0;
            char* ptr = (char*)x;
            for(int i = 0; i < sizeof(x); i++) 
                data.push_back(ptr[i]);
            
        
        vertexdecl.push_back(0, offset, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0);
        offset += sizeof(x);
    
;

std::vector<std::vector<char>> vertices;
std::vector<D3DVERTEXELEMENT9> vertexdecl;
vertices.resize(vertex_count);
std::vector<std::shared_ptr<FunctionBase>> functions;
// add to functions here
int offset = 0;
std::for_each(functions.begin(), functions.end(), [&](std::shared_ptr<FunctionBase>& ref) 
    ref->Call(vertices, vertexdecl, offset);
);
vertexdecl.push_back(D3DDECL_END());

请原谅我使用 lambda,我使用 C++0x 编译器。

【讨论】:

好吧,我可能应该说,我被 DirectX9 困住了,我不会为这门课程的工作而烦恼着色器。我不知道旅游代码如何让我制作一个顶点数组,直到运行时我才知道它们的样子,然后复制到 GPU @thecoshman:因为顶点和顶点格式是动态构建的?您所要做的就是用对象填充函数库向量,然后它会自行构建。【参考方案2】:

您的解决方案看起来不错。但如果你想要更多 C++ish 的东西,你可以试试这样:

编辑 我之前的解决方案基本上是重新创建了一些已经存在的东西,std::pair。我不知道我在想什么,这是更 C++ish 的解决方案:

template<typename InIt_A, typename InIt_B, typename OutIt>
void MergeArrays(InIt_A ia, InIt_B ib, OutIt out, std::size_t size)

    for(std::size_t i=0; i<size; i++)
    
        *out = make_pair(*ia,*ib);
        ++out;
        ++ia;
        ++ib;
    


int main()

    pos p[100];
    color c[100];
    typedef pair<pos,color> CustomVertex;
    CustomVertex cv[100];

    MergeArrays(p,c,cv,100);

您不必担心填充,因为 D3D 顶点中的所有元素都是 32 位浮点数或 32 位整数。

编辑

这是一个可行的解决方案。它会一次完成所有的合并,而且你不必担心传递大小:

// declare a different struct for each possible vertex element
struct Position  FLOAT x,y,z; ;
struct Normal  FLOAT x,y,z; ;
struct Diffuse  BYTE a,r,g,b; ;
struct TextureCoordinates  FLOAT u,v; ;
// etc...

// I'm not all too sure about all the different elements you can have in a vertex
// But you would want a parameter for each one in this function.  Any element that
// you didn't use, you would just pass in a null pointer.  Since it's properly
// typed, you won't be able to pass in an array of the wrong type without casting.
std::vector<char> MergeArrays(Position * ppos, Normal * pnorm, Diffuse * pdif, TextureCoordinates * ptex, int size)

    int element_size = 0;
    if(ppos) element_size += sizeof(Position);
    if(pnorm) element_size += sizeof(Normal);
    if(pdif) element_size += sizeof(Diffuse);
    if(ptex) element_size += sizeof(TextureCoordinates);

    vector<char> packed(element_size * size);

    vector<char>::iterator it = packed.begin();
    while(it != packed.end())
    
        if(ppos)
        
            it = std::copy_n(reinterpret_cast<char*>(ppos), sizeof(Position), it);
            ppos++;
        

        if(pnorm)
        
            it = std::copy_n(reinterpret_cast<char*>(pnorm), sizeof(Normal), it);
            pnorm++;
        

        if(pdif)
        
            it = std::copy_n(reinterpret_cast<char*>(pdif), sizeof(Diffuse), it);
            pdif++;
        

        if(ptex)
        
            it = std::copy_n(reinterpret_cast<char*>(ptex), sizeof(TextureCoordinates), it);
            ptex++;
        

    

    return packed;


// Testing it out.  We'll create an array of 10 each of some of the elements.
// We'll use Position, Normal, and Texture Coordinates.  We'll pass in a NULL
// for Diffuse.
int main() 

    Position p[10];
    Normal n[10];
    TextureCoordinates tc[10];

    // Fill in the arrays with dummy data that we can easily read.  In this
    // case, what we'll do is cast each array to a char*, and fill in each
    // successive element with an incrementing value.
    for(int i=0; i<10*sizeof(Position); i++)
    
        reinterpret_cast<char*>(p)[i] = i;
    

    for(int i=0; i<10*sizeof(Normal); i++)
    
        reinterpret_cast<char*>(n)[i] = i;
    

    for(int i=0; i<10*sizeof(TextureCoordinates); i++)
    
        reinterpret_cast<char*>(tc)[i] = i;
    

    vector<char> v = MergeArrays(p,n,NULL,tc,10);

    // Output the vector.  It should be interlaced:
    // Position-Normal-TexCoordinates-Position-Normal-TexCoordinates-etc...
    for_each(v.begin(), v.end(),
        [](const char & c)  cout << (int)c << endl; );
    cout << endl;

【讨论】:

这个解决方案的问题是,在编译时我需要知道我想要合并位置和颜色,但我根本不需要。直到运行时我才知道要合并哪些元素。因此,我需要合并数组函数才能工作,这样它就不会关心各个元素的大小。我的“输出数组”是否松散了数据的类型并不重要,只要它被正确打包,因为当我将它传输到 GPU 时,它会变成一个 void* 或 size custom vertex * number of vertices .【参考方案3】:

修改你的代码,应该这样做:

void* Utils::MergeArrays(char *ArrayA, char *ArrayB, int elementSizeA, int elementSizeB, int numElements)

    char *packedElements = (char*)malloc(numElements* (elementSizeA + elementSizeB));
    char *nextElement = packedElements;
    for(int i = 0; i < numElements; ++i)
    
        memcpy(nextElement, ArrayA + i*elementSizeA, elementSizeA);
        nextElement += elementSizeA;
        memcpy(nextElement, ArrayB + i*elementSizeB, elementSizeB);
        nextElement += elementSizeB;
    
    return packedElements;

请注意,您可能需要一些代码一次合并所有属性,而不是一次合并 2 个(想想位置+法线+纹理坐标+颜色+...)。另请注意,您可以在填充顶点缓冲区时进行合并,这样您就不需要分配 packedElements

类似:

//pass the Locked buffer in as destArray
void Utils::MergeArrays(char* destArray, char **Arrays, int* elementSizes, int numArrays, int numElements)

    char* nextElement = destArray;
    for(int i = 0; i < numElements; ++i)
    
        for (int array=0; array<numArrays; ++array)
        
            int elementSize = elementSizes[array];
            memcpy(nextElement, Arrays[array] + i*elementSize, elementSize);
            nextElement += elementSize;
        
    

【讨论】:

哦,我明白了,所以不是一次合并两个数组,而是将函数传递给内存中的空间,我希望它将数组合并到数组和数组中,数组的数量和数组的大小。但是如何让这个函数知道每个数组中元素的大小? @thecoshman:elementSizes 存储每个数组中元素的大小。 “数组的大小”是从 elementSizes 和 numElements 推断出来的。 在实际解决问题之后,我接受了这个答案。据我所知,这可以解决我的问题。尽管它可能需要对这个函数或我周围的代码进行一些修改。【参考方案4】:

我不知道 DirectX,但 OpenGL 中存在完全相同的概念,并且在 OpenGL 中,您可以指定每个顶点属性的位置和步幅。您可以拥有交替的属性(例如您的第一个结构),或者您可以将它们扫描存储在不同的块中。在 OpenGL 中,您使用 glVertexPointer 来设置这些东西。考虑到 DirectX 最终运行在相同的硬件上,我怀疑在 DirectX 中有一些方法可以做同样的事情,但我不知道它是什么。

使用 DirectX 和 glVertexPointer 作为关键字进行一些谷歌搜索会出现 SetFVF 和 SetVertexDeclaration

MSDN on SetFVF, gamedev discussion comparing them

【讨论】:

您说的没错,但是当您设置 FVF 时,您是说您将传入由这些组件组成的顶点。您传入的顶点数组需要是 struct A 类型,struct A 中的字段由您要使用的 FVF 确定,因此如果您尝试传输具有额外组件的顶点数据数组,你只会把你的程序搞得一团糟,因为第一个顶点的最后几个字段将成为第二个顶点的第一个,依此类推。 我认为这描述了 DX 方式来做我试图在 GL 中描述的事情:gamedev.net/community/forums/topic.asp?topic_id=431127

以上是关于试图在运行时不知道它们将是啥类型的情况下创建一个 DirectX 顶点数组的主要内容,如果未能解决你的问题,请参考以下文章

跨域是啥意思,JSONP 在这种情况下代表啥?

在不知道类型是啥的情况下调用返回泛型集合的泛型方法? C#

gopl 反射1

在这种简单的情况下,PHP 拒绝接受返回类型的原因是啥?

编译器如何在编译时不知道大小的情况下分配内存?

处理 requestAttributionDetailsWithBlock 和 NSDictionary。在不知道它到底是啥的情况下将任何类型转换为 NSString