Попытка создать массив вершин DirectX, не зная до времени выполнения, какого типа они будут - PullRequest
0 голосов
/ 30 октября 2010

Немного фона для тех, кто не знает DirectX. Вершина - это не просто позиция XYZ, она может содержать и другие данные. DirectX использует систему, известную как Flexible Vertex Format, 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 для передачи на видеокарту, не должен содержать отступов, это должны быть смежные данные.

РЕДАКТИРОВАТЬ Объединенный массив данных вершин будет передан в GPU, это сначала делается с помощью запроса GPU установить void * в начало памяти, к которой у меня есть доступ, и запроса пространства размер моего customVertex * NumOfVertex. Так что, если моя функция mergeArray теряет типы внутри нее, это нормально, только если я получу свой единственный объединенный массив для передачи в одном блоке / EDIT

Наконец, это хорошая возможность для плотины. Я лаю не то дерево с этим, так что, может быть, это гораздо более простой способ просто не иметь этой проблемы, но часть меня вырыла мои исцеления и хочет, чтобы эта система работала, поэтому я был бы признателен, зная, как заставить такую ​​систему работать (вещь с чередующимися массивами)

Большое вам спасибо ... Мне нужно успокоить голову, так что я с нетерпением жду каких-либо идей по этой проблеме.

Ответы [ 4 ]

1 голос
/ 30 октября 2010

Нет, нет, нет.Система 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 занимает пустые места или что-то подобное, так что это не имеет большого значения.Когда вы вызываете объект функции, он добавляет к объявлению 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());

Извините, я использую лямбда-выражения, я использую компилятор C ++ 0x.

1 голос
/ 30 октября 2010

Ваше решение выглядит хорошо.Но если вы хотите что-то немного больше C ++, вы можете попробовать что-то вроде этого:

Edit Мое предыдущее решение в основном воссоздало что-то, что уже существовало, std :: pair.Я не знаю, о чем я думал, вот еще более простое решение C ++:

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;
}
0 голосов
/ 30 октября 2010

Изменяя ваш код, это должно сделать это:

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;
        }
    }
}
0 голосов
/ 30 октября 2010

Я не знаю DirectX, но такая же концепция существует в OpenGL, а в OpenGL вы можете указать местоположение и шаг каждого атрибута вершины. У вас могут быть чередующиеся атрибуты (например, ваша первая структура) или вы можете сканировать их в разных блоках. В OpenGL вы используете glVertexPointer, чтобы настроить эти вещи. Учитывая, что DirectX в конечном счете работает на том же оборудовании, я подозреваю, что есть какой-то способ сделать то же самое в DirectX, но я не знаю, что это.

Некоторое поиск в Google с DirectX и glVertexPointer в качестве ключевых слов вызывает SetFVF и SetVertexDeclaration

MSDN на SetFVF , обсуждение gamedev, сравнивая их

...