Когда я должен использовать индексированные массивы вершин OpenGL? - PullRequest
40 голосов
/ 02 июня 2010

Я пытаюсь получить четкое представление о том, когда мне следует использовать индексированные массивы вершин OpenGL, нарисованные с помощью gl [Multi] DrawElements и т. П., В отличие от того, когда я должен просто использовать смежные массивы вершин, нарисованные с помощью gl [ Мульти] DrawArrays.

( Обновление: Консенсус в ответах, которые я получил, заключается в том, что всегда следует использовать проиндексированные вершины.)

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

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

Это приводит меня к выводу, что:

1. Для геометрии с небольшим количеством швов индексированные массивы - большая победа.

Всегда следуйте правилу 1, кроме:

Для геометрии, которая является очень «блочной», в которой каждое ребро представляет шов, преимущество индексированных массивов менее очевидно. Чтобы взять простой куб в качестве примера, хотя каждая вершина используется в трех разных гранях, мы не можем разделить вершины между ними, потому что для одной вершины нормали поверхности (и, возможно, другие вещи, такие как координаты цвета и текстуры) ) будет отличаться на каждом лице. Следовательно, нам нужно явно ввести избыточные позиции вершин в наш массив, чтобы одна и та же позиция могла использоваться несколько раз с разными нормалями и т. Д. Это означает, что индексированные массивы используются реже.

например. При рендеринге одного лица куба:

 0     1
  o---o
  |\  |
  | \ |
  |  \|
  o---o
 3     2

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

если рендеринг с использованием GL_TRIANGLE_FAN (или _STRIP), то каждая грань куба может быть отрисована таким образом:

verts  = [v0, v1, v2, v3]
colors = [c0, c0, c0, c0]
normal = [n0, n0, n0, n0]

Добавление индексов не позволяет нам упростить это.

Из этого я заключаю, что:

2. При рендеринге геометрии, которая представляет собой все швы или в основном швы, при использовании GL_TRIANGLE_STRIP или _FAN, я никогда не должен использовать индексированные массивы, а вместо этого всегда должен использовать gl [Multi] DrawArrays.

( Обновление: Ответы указывают на то, что этот вывод неверен. Хотя индексы не позволяют нам уменьшить размер массивов здесь, они все равно должны использоваться из-за других преимуществ производительности, как обсуждалось в комментариях)

Единственное исключение из правила 2:

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

 0     1
  o---o
  |\  |
  | \ |
  |  \|
  o---o
 3     2

Без индексов, используя GL_TRIANGLES, массивы будут выглядеть примерно так:

verts =   [v0, v1, v2,  v2, v3, v0]
normals = [n0, n0, n0,  n0, n0, n0]
colors =  [c0, c0, c0,  c0, c0, c0]

Поскольку вершина и нормаль часто имеют по 3 числа с плавающей запятой, а цвет часто составляет 3 байта, то для каждой грани куба получается:

verts   = 6 * 3 floats = 18 floats
normals = 6 * 3 floats = 18 floats
colors  = 6 * 3 bytes  = 18 bytes

= 36 floats and 18 bytes per cube face.

(Я понимаю, что число байтов может измениться, если используются разные типы, точные цифры приведены только для иллюстрации.)

С помощью индексов мы можем немного упростить это, получив:

verts   = [v0, v1, v2, v3]     (4 * 3 = 12 floats)
normals = [n0, n0, n0, n0]     (4 * 3 = 12 floats)
colors  = [c0, c0, c0, c0]     (4 * 3 = 12 bytes)
indices = [0, 1, 2,  2, 3, 0]  (6 shorts)

= 24 floats + 12 bytes, and maybe 6 shorts, per cube face.

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

Это приводит меня к выводу, что:

3. При использовании GL_TRIANGLES всегда следует использовать индексированные массивы, даже для геометрии, которая представляет собой все швы.

Пожалуйста, исправьте мои выводы жирным шрифтом, если они ошибочны.

1 Ответ

33 голосов
/ 02 июня 2010

Из этого я заключаю, что при рендеринге геометрии, которая представляет собой все швы или в основном швы, при использовании GL_TRIANGLE_STRIP или _FAN, я никогда не должен использовать индексированные массивы, а вместо этого должен всегда использовать gl [Multi] DrawArrays.

Нет, и причина довольно проста.

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

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

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


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

Обмен информацией позволяет сэкономить пропускную способность передачи информации, но это не единственное преимущество. Фактически индексированные массивы позволяют:

  • Избегать синхронизации информации, принадлежащей к одной и той же «концептуальной» вершине, заданной много раз
  • Позволяет выполнять одну и ту же операцию шейдера для одной вершины, вместо того, чтобы выполнять ее много раз, по одному для каждого дублирования вершины.
  • Кроме того, сочетание использования треугольных полос / вентиляторов и индексов позволяет приложению сжимать буфер индексов, поскольку для спецификации полосы / вентилятора требуется меньше индексов (для треугольника всегда требуется 3 индекса для каждой грани).

Индексированный массив не может использоваться, как вы указали, всякий раз, когда вершина не может поделиться всей информацией, связанной с ней (цвет, координаты текстуры и т. Д.) С другой совпадающей вершиной.


Просто для полноты размер информации, необходимой для спецификации геометрии, не является единственным фактором, определяющим оптимальную операцию рендеринга.

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

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

...