SpriteBatch, TextureRegions и Sprite

Эта статья дает краткий обзор того, как выводить изображения используя OpenGL и как libgdx упрощает и оптимизирует задачу через SpriteBatch класс.

Отрисовка изображений

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

Для фактической отрисовки, сначала привязывается текстура (текстура делается текущей), затем геометрия передается в OpenGL для рисования. Размер и позиция нарисованной текстуры на экране определяется геометрией и как настроено окно просмотра OpenGL. Многие 2D игры настраивают окно просмотра так, чтобы оно совпадало с разрешением экрана. Это означает что геометрия задается в пикселях, что делает легким рисование текстуры в соответствии с размерам и позицией на экране.

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

Класс SpriteBatch предоставляет текстуру и координаты для каждого рисования прямоугольника. Он делает сбор геометрии без передачи ее в графический процессор. Если он получает текстуру отличную от последней, то он привязывает последнею текстуру, передавая собранную геометрию для рисования, и начинает сбор геометрии для новой текстуры.

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

SpriteBatch

Использование SpriteBatch в приложении выглядит следующим образом:

public class Game implements ApplicationListener {
    private SpriteBatch batch;

    public void create() {
        batch = new SpriteBatch();
    }

    public void render() {
        // Очистка экрана
        Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT); 

        batch.begin();
        // Здесь происходит рисование!
        batch.end();
    }

    public void resize(int width, int height) {
    }

    public void pause() {
    }

    public void resume() {
    }

    public void dispose() {
    }
}

Все вызовы рисования SpriteBatch должны быть сделаны между begin() и end() методами. Рисование не принадлежащие к SpriteBatch не может быть между begin() и end() методами.

Текстура

Класс Texture декодирует файл изображения и загружает его в память графического процессора. Файл изображения должен располагаться в assets директории, как описано в ручной настройки проекта. Размер изображения должен быть степенью двух (16x16, 64x256 и так далее).

private Texture texture;
// ...
texture = new Texture(Gdx.files.internal("image.png"));
// ...
batch.begin();
batch.draw(texture, 10, 10);
batch.end();

Созданная текстура передается в SpriteBatch для отрисовки. Текстура будет нарисована в прямоугольнике с позицией 10,10 и шириной и высотой разными размеру текстуры. SpriteBatch имеет много методов для отрисовки текстур:

Методы и описание
draw(Texture texture, float x, float y)

Рисует текстуру используя ширину и высоту текстуры.

draw(Texture texture, float x, float y,
int srcX, int srcY, int srcWidth, int srcHeight)

Рисует част текстуры.

draw(Texture texture, float x, float y,
float width, float height, int srcX, int srcY,
int srcWidth, int srcHeight, boolean flipX, boolean flipY)

Рисует часть текстуры, растягивает до ширины width и высоты height, с возможностью переворачивания.

draw(Texture texture, float x, float y,
float originX, float originY, float width, float height,
float scaleX, float scaleY, float rotation,
int srcX, int srcY, int srcWidth, int srcHeight,
boolean flipX, boolean flipY)

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

draw(Texture texture, float x, float y,
float width, float height, float u,
float v, float u2, float v2)

Этот метод рисует часть текстуры, растягивает до ширины width и высоты height. Это несколько продвинутый метод, так как он использует текстурные координаты от 0 до 1, вместо координат пикселей.

draw(Texture texture, float[] spriteVertices, int offset, int length)

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

TextureRegion

Класс TextureRegion описывает прямоугольник внутри текстуры и полезен при рисовании только части текстуры.

private TextureRegion region;
// ...
texture = new Texture(Gdx.files.internal("image.png"));
region = new TextureRegion(texture, 20, 20, 50, 50);
// ...
batch.begin();
batch.draw(region, 10, 10);
batch.end();

Здесь 20, 20, 50, 50 описывают часть текстуры, которая замет рисуется как 10,10. Тоже самое может быть достигнуто путем передачи Texture и других параметров в SpriteBatch, но TextureRegion делает это удобнее, имея только один объект, который описывает то и другое.

Класс SpriteBatch имеет много методов для рисования регионов текстуры.

Методы и описание
draw(TextureRegion region, float x, float y)

Рисует регион используя ширину и высоту региона.

draw(TextureRegion region, float x, float y,
float width, float height)

Рисует регион, растягивая до ширины width и высоты height.

draw(TextureRegion region, float x, float y,
float originX, float originY, float width, float height,
float scaleX, float scaleY, float rotation)

Рисует регион, растягивая до ширины width и высоты height, масштабирует и вращает вокруг оригинала.

Спрайт

Класс Sprite регион текстуры и геометрию, где он будет рисоваться и цвет для рисования.

private Sprite sprite;
// ...
texture = new Texture(Gdx.files.internal("image.png"));
sprite = new Sprite(texture, 20, 20, 50, 50);
sprite.setPosition(10, 10);
sprite.setRotation(45);
// ...
batch.begin();
sprite.draw(batch);
batch.end();

Здесь 20, 20, 50, 50 описывают часть текстуры, которая повернута на 45 градусов и рисуется в позиции 10,10. Тоже самое можно достигнуть передачей Texture или TextureRegion и других параметров в SpriteBatch, но класс Sprite делает это более удобным, имея один объект, которые описывает все. Кроме того, поскольку Sprite содержит геометрию и пересчитывает ее только когда необходимо, он немного более эффективный, если масштаб, поворот или другие свойства остаются неизменными между кадрами.

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

Тонирование

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

private Texture texture;
private TextureRegion region;
private Sprite sprite;
// ...
texture = new Texture(Gdx.files.internal("image.png"));
region = new TextureRegion(texture, 20, 20, 50, 50);
sprite = new Sprite(texture, 20, 20, 50, 50);
sprite.setPosition(100, 10);
sprite.setColor(0, 0, 1, 1);
// ...
batch.begin();
batch.setColor(1, 0, 0, 1);
batch.draw(texture, 10, 10);
batch.setColor(0, 1, 0, 1);
batch.draw(region, 50, 10);
sprite.draw(batch);
batch.end();

Этот код показывает как рисовать текстуру, регион и спрайт с тонированием. Значения цвета описываются используя RGBA формат, со значения между 0 и 1. Альфа значение игнорируется при отключеным смешивании.

Смешивание

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

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

Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT); // Эта строка очищает экран
batch.begin();
batch.disableBlending();
backgroundSprite.draw(batch);
batch.enableBlending();
// Рисование чего-либо другого.
batch.end();

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

Окно просмотра

SpriteBatch управляет собственными матрицами проекции и преобразования. Когда создан SpriteBatch, он использует текущий размер приложения для установки ортографической проекции, используя Y систему координат. Когда вызван метод begin(), он устанавливает окно просмотра.

Настройка производительности

Класс SpriteBatch имеет конструктор, который устанавливает максимальное число спрайтов, которые могут быть помещены в буфер перед оправкой графическому процессору. Если число слишком мало, это приведет к дополнительным обращениям к графическому процессору. Если оно слишком велико, то SpriteBatch будет использовать больше памяти, чем это необходимо.

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

SpriteBatch имеет публичное поле с именем renderCalls. После вызова end() метода, это поле показывает сколько раз группа геометрии, была передана графическому процессору между последними вызовами begin() и end(). Это происходит, когда должна быть привязана другая текстура или когда SpriteBatch имеет в кэше достаточно спрайтов и становиться полностью заполненным. Соответственно, если для SpriteBatch изменен размер и renderCalls имеет большое значение (возможно больше чем 15-20), то это указывает, что происходит множественная привязка текстур.

SpriteBatch имеет дополнительный конструктор, который принимает размер и количество буферов. Это дополнительная особенность, которая позволяет использовать VBO, а не обычный массив вершин. Таким образом, есть список буферов и при каждом вызове визуализации, используется следующий в списке буфер (меняя по кругу). Когда значение maxSpritesInBatch велико и renderCalls мало, то это может дать небольшой прирост производительность.