Как повторно использовать вершины через примитивы в OpenGL - PullRequest
1 голос
/ 16 октября 2019

Я использую OpenGL в C ++ (технически EGL, на Jetson Nano.)

Допустим, я хочу нарисовать N Quads. Представьте себе список цветных прямоугольников. В кадре может быть несколько тысяч таких прямоугольников.

Я хочу использовать два буфера вершин:

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

Первый буфер вершин должен определять геометрию каждого четырехугольника. В нем должно быть только 4 вершины, а его данные будут просто углами четырехугольника. Что-то вроде:

0, 0, // top left
1, 0, // top right
0, 1, // bottom left
1, 1, // bottom right

Тогда второй буфер вершин должен иметь только x, y, width, height всех прямоугольников.

x1, y1, width1, height1, color1,
x2, y2, width2, height2, color2,
x3, y3, width3, height3, color3,
x4, y4, width4, height4, color4,
x5, y5, width5, height5, color5,
x6, y6, width6, height6, color6,
... etc.

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

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

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

Как мне это настроить?

Что я делаю сейчас:

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

0, 0, // (1) top left
1, 0, // 
0, 1, // 
1, 1  // 
0, 0, // (2) top left
1, 0, // 
0, 1, // 
1, 1, // 
0, 0, // (3) top left
1, 0, // 
0, 1, // 
1, 1, // 
... etc

И мой второй буфер дублирует свои данные для каждой вершины:

x1, y1, width1, height1, color1,
x1, y1, width1, height1, color1,
x1, y1, width1, height1, color1,
x1, y1, width1, height1, color1,
x2, y2, width2, height2, color2,
x2, y2, width2, height2, color2,
x2, y2, width2, height2, color2,
x2, y2, width2, height2, color2,
x3, y3, width3, height3, color3,
x3, y3, width3, height3, color3,
x3, y3, width3, height3, color3,
x3, y3, width3, height3, color3,
x4, y4, width4, height4, color4,
x4, y4, width4, height4, color4,
x4, y4, width4, height4, color4,
x4, y4, width4, height4, color4,
x5, y5, width5, height5, color5,
x5, y5, width5, height5, color5,
x5, y5, width5, height5, color5,
x5, y5, width5, height5, color5,
x6, y6, width6, height6, color6,
x6, y6, width6, height6, color6,
x6, y6, width6, height6, color6,
x6, y6, width6, height6, color6,
... etc.

Это кажется действительно неэффективно, и я просто хочу указать первые 4 вершины один раз, и он будет продолжать их как-то повторно, вместо того, чтобы дублировать эти 4 вершины N раз, чтобы в моем первом буфере было в общей сложности 4 * N вершин. И я хочу указать атрибуты x, y, width, height, color только один раз для каждого четырехугольника для общего количества N вершин, и , а не один раз для каждой общей вершины для в общей сложности 4 * N вершин.

Что мне делать?

1 Ответ

1 голос
/ 17 октября 2019

Вообще говоря, самый эффективный способ рендерить серию квадов - это ... рендерить серию квадов. Вы не отправляете ширину / высоту или другую информацию для каждого экземпляра;Вы вычисляете фактические позиции 4 вершин в CPU и записываете их в память GPU, используя соответствующие методы потоковой передачи объекта буфера . В частности, избегайте попыток изменить только несколько четырехугольников;если ваши данные не статичны, вероятно, будет лучше перегрузить их все (в другой / недействительный буфер), а не изменять только несколько байтов на месте.

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

Вы можете решить эту проблему, уменьшив размер данных вершин. Поскольку мы говорим о двумерных четырехугольниках, вы можете очень хорошо использовать шорты для положения XY каждой вершины. Или 16-битные поплавки. В любом случае, это означает, что каждая вершина (позиция + цвет) занимает всего 8 байтов, что означает, что квад - это всего 32 байта данных. Очевидно, что 12 байтов меньше 32 (12 - это цена за экземпляр, если вы используете аналогичное сжатие), но это все равно на 33% меньше, чем 48 байтов, которые будут использовать полные позиции float.


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

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

Входное значение gl_VertexID сообщает вам, какой индекс вершины визуализируется. Учитывая, что вы отрисовываете квады, текущий экземпляр будет gl_VertexID / 4. А текущая вершина в квадре gl_VertexID % 4. Таким образом, ваш VS будет выглядеть примерно так:

struct instance
{
  vec2 position;
  vec2 size;
  uint color; //Packed as 4 bytes; unpack with unpackUnorm4x8
  uint padding; //Padding needed due to alignment/stride of 8 bytes.
};

layout(binding = 0, std430) buffer instance_data
{
  instance instances[];
};

vec2[4] vertex_table =
{
  vec2{0, 0},
  vec2{1, 0},
  vec2{0, 1},
  vec2{1, 1},
};

void main()
{
    instance curr_instance = instances[gl_VertexID / 4];
    vec2 vertex = vertex_table[gl_VertexID % 4];

    vertex = curr_instance.position + (curr_instance.size * vertex);
    gl_Position = vec4(vertex.xy, 0.0, 1.0);
}

Как быстро будут происходить подобные вещи, полностью зависит от того, насколько хорошо ваш графический процессор обрабатывает такие виды чтения глобальной памяти. Обратите внимание, что по крайней мере гипотетически возможно уменьшить размер данных для каждого экземпляра до 12. Вы можете упаковать положение и размер в два 16-разрядных шорта или полураспуска, используя unpackUnorm2x16 или unpackHalf2x16 для распаковки. эти значения соответственно. Если вы сделаете это, то ваша instance структура будет иметь только 3 uint значения, и заполнение не потребуется.

...