试图在运行时不知道它们将是啥类型的情况下创建一个 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 顶点数组的主要内容,如果未能解决你的问题,请参考以下文章
处理 requestAttributionDetailsWithBlock 和 NSDictionary。在不知道它到底是啥的情况下将任何类型转换为 NSString