Pull to refresh

Перевод туториалов по libGDX — часть 2 (рисование изображений)

Reading time7 min
Views32K
Представляю вашему вниманию перевод второго туториала по libGDX. Оригинал находится здесь. Первая часть находится здесь.

Этот туториал дает представление как рисовать изображения используя OpenGL и как libGDX упрощает и оптимизирует этот процесс с помощью класса SpriteBatch.

Рисование изображений.



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


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

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

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

Изменение текстуры каждые несколько прямоугольников, которые должны отрисовываться, мешает 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 (текстура)


Класс Texture получает изображение из файла, и загружает его в GPU. Файл изображения должен быть размещен в директории «assets», как описано в (ССЫЛКА) Project Setup. Размеры изображения должны быть степенью двойки (16х16, 64х256 и т. д.).

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)
Этот монструозный метод рисует часть текстуры с возможностью сжатия (растяжения), вращения вокруг точки и возможностью отражения.

draw(Texture texture, float x, float y, float width, float height, float u, float v, float u2, float v2)
Рисует часть текстуры, растянутую до размеров width*height. Это более продвинутый метод. Координаты указываються не в пикселях, а в действительных числах.

draw(Texture texture, float[] spriteVertices, int offset, int length)
Рисует часть текстуры, «натягивая» ее на фигуру, указанную в spriteVerticles

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). Это же действие может быть сделано передачей текстуры и дополнительных параметров классу SpriteBatch, но класс TextureRegion делает это удобнее, поскольку проще определить отдельный обьект и работать с ним, чем помнить про кучу дополнительных параметров.

SpriteBatch имеет много методов для рисования TextureRegion:

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, с возможностью масштабирования и вращения относительно точки originX, originY.

Sprite (спрайт)


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

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). Это же может быть сделано передачей текстуры или ее части в SpriteBatch и передачей других параметров, но класс Sprite делает это удобней, поскольку вы храните все параметры в одном месте. Также, благодаря тому, что класс Sprite хранит геометрию внутри себя и пересчитывет ее лишь в случае необходимости, это слегка улучшает производительность в операциях масштабирования, вращения или других свойствах, которые не меняются между кадрами.

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

Tinting (окраска (затенение) цветом )


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

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. Альфа-канал игнорируется, если смешивание (blending) отключено.

Blending (cмешивание )


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

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

Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT); // Эта строчка очищает экран
batch.begin();
batch.disableBlending();
backgroundSprite.draw(batch);
batch.enableBlending();
// Здесь рисуются другие части
batch.end();


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

Viewport (область просмотра)


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

Примечание. Как только появится более подробная документация по области просмотра, она будет размещена здесь.

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


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

SpriteBatch имеет public поле, которое называется maxSpritesInBatch. Оно указывает на максимальное число спрайтов, которое может быть отослано на отрисовку видеоускорителю за один раз на протяжении жизненного цикла SpriteBatch. Можно выставить это число очень большим и проверять его. Это поможет подобрать оптимальный размер SpriteBatch. Размер SpriteBatch (число, которое вы передаете в конструктор) должен слегка превышать число maxSpritesInBatch. Вы всегда можете установить maxSpritesInBatch в ноль — то есть, сбросить этот счетчик.

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

SpriteBatch имеет дополнительный конструктор который принимает число и размеры буферов. Это продвинутая фишка, которая приказывает работать с VBO (vertex buffer objects) вместо обычного VA (vertex arrays). SpriteBatch хранит список буферов и при каждом очередном цикле рендеринга использует следующий буфер. Когда maxSpritesInBatch маленькое и renderCalls большое, это фишка может дать небольшой прирост производительности.
Tags:
Hubs:
Total votes 17: ↑16 and ↓1+15
Comments2

Articles