Почему дозатор спрайтов быстрее? - PullRequest
2 голосов
/ 13 июля 2011

Я читаю Начиная игры для Android (Марио Цехнер) в данный момент.

Читая о 2D-играх с OpenGL ES 1.0, автор представляет концепцию SpriteBatcher , которая принимает для каждого спрайта визуализацию координат и угла. Затем SpriteBatcher вычисляет окончательные координаты прямоугольника спрайта и помещает их в один большой буфер.

В методе render SpriteBatcher устанавливает состояние для всех спрайтов Once (текстура, смешивание, буфер вершин, буфер текстурных координат). Все спрайты используют одну и ту же текстуру, но не одинаковые координаты текстуры.

Преимущества такого поведения:

  • Конвейер рендеринга не останавливается, поскольку при рендеринге всех спрайтов не происходит никаких изменений состояния.
  • Меньше вызовов OpenGL. (= меньше накладных расходов JNI)

Но я вижу главный недостаток:

  • Для вращения ЦПУ должен рассчитать синус и косинус и выполнить 16 умножений для каждого спрайта. Насколько я знаю, вычисление синуса и косинуса очень дорого и медленно.

Но подход SpriteBatcher намного быстрее, чем использование большого количества glRotate / glTranslate для рендеринга спрайтов один за другим.

Наконец-то мои вопросы:

  • Почему это быстрее? Изменения состояния OpenGL действительно так дороги?
  • Графический процессор оптимизирован для векторного умножения и вращения, в то время как центральный процессор - нет. Почему это не имеет значения?
  • Можно ли использовать SpriteBatcher на рабочем столе с выделенной GFX-картой?
  • Есть ли момент, когда SpriteBatcher становится неэффективным?

Ответы [ 2 ]

6 голосов
/ 13 июля 2011

Но я вижу главный недостаток:

  • Для вращения ЦПУ должен рассчитать синус и косинус и выполнить 16 умножений для каждого спрайта.Насколько я знаю, вычисление синуса и косинуса очень дорого и медленно.

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

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

/* populates the currently bound VBO with sprite geometry */
void populate_sprites_VBO(std::vector<vec3> sprite_positions)
{
    GLfloat mv[16];
    GLfloat sprite_left[3];
    GLfloat sprite_up[3];

    glGetMatrixf(GL_MODELVIEW_MATRIX, mv);

    for(int i=0; i<3; i++) {
        sprite_left[i] = mv[i*4];
        sprite_up[i]   = mv[i*4 + 4];
    }

    std::vector<GLfloat> sprite_geom;
    for(std::vector<vec3>::iterator sprite=sprite_positions.begin(), end=sprite_positions.end();
        sprite != end;
        sprite++ ){
           sprite_geom.append(sprite->x + (-sprite_left[0] - sprite_up[0])*sprite->scale);
           sprite_geom.append(sprite->y + (-sprite_left[1] - sprite_up[1])*sprite->scale);
           sprite_geom.append(sprite->z + (-sprite_left[2] - sprite_up[2])*sprite->scale);

           sprite_geom.append(sprite->x + ( sprite_left[0] - sprite_up[0])*sprite->scale);
           sprite_geom.append(sprite->y + ( sprite_left[1] - sprite_up[1])*sprite->scale);
           sprite_geom.append(sprite->z + ( sprite_left[2] - sprite_up[2])*sprite->scale);


           sprite_geom.append(sprite->x + ( sprite_left[0] + sprite_up[0])*sprite->scale);
           sprite_geom.append(sprite->y + ( sprite_left[1] + sprite_up[1])*sprite->scale);
           sprite_geom.append(sprite->z + ( sprite_left[2] + sprite_up[2])*sprite->scale);


           sprite_geom.append(sprite->x + (-sprite_left[0] + sprite_up[0])*sprite->scale);
           sprite_geom.append(sprite->y + (-sprite_left[1] + sprite_up[1])*sprite->scale);
           sprite_geom.append(sprite->z + (-sprite_left[2] + sprite_up[2])*sprite->scale);
    }
    glBufferData(GL_ARRAY_BUFFER, 
                 sprite_positions.size() * sizeof(sprite_positions[0]), &sprite_positions[0], 
                 GL_DRAW_STREAM);
}    

Если шейдеры доступны, то вместо перекомпоновки данных спрайта на ЦП в каждом кадреМожно использовать геометрический шейдер или вершинный шейдер.Геометрический шейдер будет принимать вектор положения, масштаб, текстуру и т. Д. И испускать квадраты.Используя вершинный шейдер, вы отправите много квадратов [-1,1], где каждая вершина будет содержать центральное положение спрайта, которому она принадлежит, в качестве дополнительного атрибута vec3.


Наконец мои вопросы:

  • Почему это быстрее?Действительно ли изменения состояния OpenGL настолько дороги?

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

  • Графический процессор оптимизирован для векторного умножения и вращения, а процессор - нет.Почему это не имеет значения?

Это не разница между GPU и CPU.Отличие графического процессора от центрального состоит в том, что он выполняет ту же последовательность операций с огромным количеством записей параллельно (каждый пиксель кадрового буфера отображается).ЦП, с другой стороны, запускает программу по одной записи за раз.

Но ЦП выполняют векторные операции так же хорошо, если не даже лучше, чем графические процессоры.Особенно там, где важна точность, процессоры по-прежнему предпочтительнее графических процессоров.MMX, SSE и 3DNow!такое наборы векторных математических инструкций.

  • Можно ли использовать SpriteBatcher на рабочем столе с выделенной GFX-картой?

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

  • Есть ли точка, в которой SpriteBatcher становится неэффективным?

Да, а именно узкое место передачи ЦП → ГП.Сегодня очень быстро используются геометрические шейдеры и экземпляры, чтобы делать такие вещи.

0 голосов
/ 13 июля 2011

Я не знаю о SpriteBatcher, но, глядя на информацию, которую вы предоставили здесь, я думаю:

  • Это быстрее, потому что использует меньше изменений состояния и, что более важно, меньше вызовов отрисовки. Мобильные платформы имеют особенно строгие ограничения на количество вызовов в каждом кадре.
  • Это не имеет значения, потому что, вероятно, они используют ЦП для вращения. Лично я не вижу причин не использовать для этого графический процессор, который был бы намного быстрее и сводил бы к нулю нагрузку на полосу пропускания.
  • Полагаю, это будет хорошей оптимизацией, учитывая пункт 1.
  • Я могу вспомнить два крайних случая: когда слишком мало спрайтов или когда составная текстура (содержащая все повернутые спрайты) становится слишком большой (мобильные устройства имеют меньшие ограничения по размеру).
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...