Улучшение производительности VBO в OpenGL

Так что в настоящее время я пытаюсь визуализировать сложные модели с приличной скоростью, и у меня возникают некоторые проблемы; рендеринг одной модели приводит к тому, что моя частота кадров становится напряженной, без какой-либо дополнительной работы в программе. Моя модель (из которых только одна на сцене) кажется слишком большой. В массиве вершин, который я загружаю в буфер, содержится 444384 числа с плавающей точкой (в модели 24688 треугольников).

//Create vertex buffers
glGenBuffers(1, &m_Buffer);
glBindBuffer(GL_ARRAY_BUFFER, m_Buffer);
int SizeInBytes = m_ArraySize * 6 * sizeof(float);
glBufferData(GL_ARRAY_BUFFER, SizeInBytes, NULL, GL_DYNAMIC_DRAW);

//Upload buffer data
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(float) * VertArray.size(), &VertArray[0]);

Я знаю, что размер VBO имеет значение, потому что A) уменьшение размера улучшает производительность, и B) комментирование кода рендеринга:

glPushMatrix();

//Translate
glTranslatef(m_Position.x, m_Position.y, m_Position.z);

glMultMatrixf(m_RotationMatrix);

//Bind buffers for vertex and index arrays
glBindBuffer(GL_ARRAY_BUFFER, m_Buffer);

glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(3, GL_FLOAT, 6 * sizeof(float), 0);
glEnableClientState(GL_NORMAL_ARRAY);
glNormalPointer(GL_FLOAT, 6 * sizeof(float), (void*)12);

//Draw
glDrawArrays(GL_TRIANGLES, 0, m_ArraySize);

glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_NORMAL_ARRAY);

//Unbind the buffers
glBindBuffer(GL_ARRAY_BUFFER, 0);

glPopMatrix();

оставляет мне около 2000-2500 кадров в секунду, тогда как раскомментирование этого кода опускает меня примерно до 130 кадров в секунду, или 8 мс / кадр (одного этого более чем достаточно, но мне нужно иметь возможность выполнять и другие действия в программе, некоторые из который может быть интенсивным процессором). Более сложная модель с 85k треугольников снижает скорость до 50 кадров в секунду или около 20 мс / кадр, и в этот момент программа заметно заикается.

Одна пара шейдеров, которую я использую, на данный момент очень минимальна, я сомневаюсь, что это проблема. Вот они, на всякий случай; Сначала вершинный шейдер:

void main()
{
vec3 normal, lightDir;
vec4 diffuse;
float NdotL;
/* first transform the normal into eye space and normalize the result */

normal = normalize(gl_NormalMatrix * gl_Normal);
/* now normalize the light's direction. Note that according to the

OpenGL specification, the light is stored in eye space. Also since
we're talking about a directional light, the position field is actually
direction */
lightDir = normalize(vec3(gl_LightSource[0].position));
/* compute the cos of the angle between the normal and lights direction.

The light is directional so the direction is constant for every vertex.
Since these two are normalized the cosine is the dot product. We also
need to clamp the result to the [0,1] range. */
NdotL = max(dot(normal, lightDir), 0.0);
/* Compute the diffuse term */

diffuse = gl_FrontMaterial.diffuse * gl_LightSource[0].diffuse;
gl_FrontColor =  NdotL * diffuse;

gl_Position = ftransform();
}

И фрагмент шейдера:

void main()
{
gl_FragColor = gl_Color;
}

Я запускаю программу, используя GTX 660M как моя видеокарта.

Насколько я знаю, VBO — это самый быстрый способ рендеринга большого количества полигонов в OpenGL, и Интернет, кажется, предполагает, что многие машины могут рассчитывать и отображать миллионы полигонов одновременно, поэтому я уверен, что должен быть способ оптимизировать рендеринг моих сравнительно жалких 27k треугольников. Я бы предпочел сделать это сейчас, чем переписывать и реструктурировать большие объемы кода в будущем.

Я включил отбраковку спины; Я не уверен, что выборка fustrum поможет, потому что иногда вся или большая часть модели отображается на экране (в настоящее время я отбрасываю объекты, но не треугольники внутри отдельных объектов). Отрезка граней в области просмотра, которые не обращены к камере, может немного помочь, но я не уверен, как это сделать. Кроме того, я не уверен, что делать, чтобы оптимизировать рендеринг. Я еще не реализовал буфер вершин, но я читал, что это может увеличить скорость только на 10%.

Как люди достигают десятков или сотен тысяч треугольников на экране одновременно с приемлемой частотой кадров и другими вещами? Что я могу сделать, чтобы улучшить производительность моего рендеринга VBO?

ОБНОВИТЬ: Согласно комментариям ниже, я нарисовал только половину массива следующим образом:

glDrawArrays (GL_TRIANGLES, 0, m_ArraySize / 2);

А затем четверть массива:

glDrawArrays (GL_TRIANGLES, 0, m_ArraySize / 4);

Сокращение количества выводимого массива каждый раз буквально удваивало скорость (с 12 мс до 6 и 3 мс соответственно), однако модель была полностью исправна — ничего не пропало. Это говорит о том, что я делаю что-то не так в другом месте, но я не знаю что; Я вполне уверен, что я не добавляю одни и те же треугольники 4+ раза при составлении модели, так что еще это может быть? Могу ли я как-то загружать буфер несколько раз?

4

Решение

glDrawArrays() В качестве третьего аргумента принимает число индексы рисовать. Вы передаете число с плавающей точкой в ​​вашей чередующейся вершине и обычном массиве, которое в 6 раз превышает число индексов. Графический процессор отстает, потому что вы говорите ему, чтобы получить доступ к данным за пределами вашего буфера — современные графические процессоры могут вызвать сбой, когда это произойдет, старые могут просто сбить вашу систему 🙂

Рассмотрим следующий чередующийся массив:

vx0 vy0 vz0 nx0 ny0 nz0 vx1 vy1 vz1 nx1 ny1 nz1 vx2 vy2 vz2 nx2 ny2 nz2

Этот массив содержит три вершины и три нормали (один треугольник). Для рисования треугольника требуется три вершины, поэтому вам нужно три индекса для их выбора. Чтобы нарисовать вышеуказанный треугольник, вы должны использовать:

glDrawArrays(GL_TRIANGLES, 0, 3);

Как работают атрибуты (вершины, нормали, цвета, текстуры и т. Д.), Один индекс выбирает значение из КАЖДОГО из атрибутов. Если бы вы добавили атрибуты цвета к треугольнику выше, вы все равно использовали бы только 3 индекса.

3

Другие решения

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

Из того, что я могу сказать, есть два вопроса с вашим текущим подходом.

  1. Огромное количество данных, которые должны быть обработаны (хотя
    это может быть проблемой и с индексированными треугольниками).

  2. При использовании glDrawArrays () в отличие от glDrawElements, графический процессор
    не может использовать кэш после преобразования, который используется для уменьшения
    количество обработки вершин.

Если возможно, переставьте ваши данные, чтобы использовать индексированные треугольники.

Я просто добавлю предостережение о том, что если вы используете индексированные треугольники, вы должны убедиться, что вы разделяете данные вершин между треугольниками, чтобы получить максимальную производительность. Это действительно о том, как хорошо вы организовываете свои данные.

3

редактировать: прочитайте некоторые комментарии; ответы ниже.


Пара случайных вещей, чтобы попробовать.

glBufferData(GL_ARRAY_BUFFER, SizeInBytes, NULL, GL_DYNAMIC_DRAW);

Пытаться GL_STATIC_DRAW, Вероятно, это не поможет в установившемся режиме (поскольку драйвер должен заметить, что нет необходимости в повторной загрузке, так как нет никакой модификации данных буфера вершин), но это стоит попробовать.

glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_NORMAL_ARRAY);

//Unbind the buffers
glBindBuffer(GL_ARRAY_BUFFER, 0);

Не меняйте состояние буфера вершин после каждого рисования, если это не нужно. Это всего лишь один буфер, оставьте его связанным.

   normal = normalize(gl_NormalMatrix * gl_Normal);
/* now normalize the light's direction. Note that according to the

OpenGL specification, the light is stored in eye space. Also since
we're talking about a directional light, the position field is actually
direction */
lightDir = normalize(vec3(gl_LightSource[0].position));
/* compute the cos of the angle between the normal and lights direction.

The light is directional so the direction is constant for every vertex.
Since these two are normalized the cosine is the dot product. We also
need to clamp the result to the [0,1] range. */
NdotL = max(dot(normal, lightDir), 0.0);

Вы можете оптимизировать это немного и сохранить normalize() (и, следовательно, полудороже invsqrt). Обратите внимание, что для векторов v1 а также v2и скаляры s1 а также s2:

dot(v1 * s1, v2 * s2) == s1 * s2 * dot(v1, v2);

Так что если v1 а также v2 являются ненормализованными, вы можете вычесть их квадратичную величину, умножить их вместе и умножить на объединенные invsqrt один раз, чтобы уменьшить их точечное произведение вниз.


85 тысяч треугольников, примерно 50 кадров в секунду? С GTX660M я бы сказал, что у вас все хорошо. Я не ожидал бы получить значительно более высокие цифры на оборудовании, которое вы используете.

Что касается конвейера с фиксированной функциональностью — все крутые ребята в наше время используют полностью программируемый конвейер. Но FF не собирается вас терять — внутренне драйвер компилирует состояние FF в набор шейдеров, поэтому он в любом случае выполняется на GPU как шейдер.

Как упоминает @JamesSteele, индексированные треугольники — это хорошая идея, если вы можете сохранить хорошее местоположение в ваших данных вершин. Это может потребовать перекомпиляции или повторной обработки ваших входных данных.

3
По вопросам рекламы [email protected]