本页简要介绍了使用OpenGL绘制图像的方式,以及libgdx如何通过SpriteBatch类简化和优化绘制任务。
绘制图片
在LibGDX中,从其原始格式(例如PNG)解码并上传到GPU的图像称为纹理,要绘制纹理,需要指定纹理的每个顶点来应用纹理,纹理的子集称为纹理区域(Textureregions)
要进行实际的绘图,首先要告诉OpenGL所要绘制的纹理区域,绘制的纹理在屏幕上的大小及位置是由绘制的纹理区域及视口配置所决定的,许多2D游戏一般使用
StretchViewport(拉伸)视口。这样可以在屏幕上以合适的大小和位置绘制纹理.
绘制纹理的某一个特定的矩形区域或者多次绘制相同纹理/纹理的各个区域都是非常常见的情形,将同一个纹理的不同矩形区域一次性发送到GPU是SpriteBatch的工作.
SpriteBatch会收集纹理(texture)及需要被绘制的纹理区域(一般是个矩形区域)坐标.但是它并不会立即提交给GPU,当SpriteBatch收集完成后,会统一提交给GPU进行绘制处理.然后进入新一轮的收集。
绑定纹理是一个耗费内存的操作,由于这些原因,通常在更大的图像中存储许多较小的图像,然后绘制较大图像的区域,以最大限度的节约内存消耗,有关详细信息,请参阅 TexturePacker 工具.
SpriteBatch
在应用程序中使用 SpriteBatch (source) 如下所示:
public class Game implements ApplicationAdapter {
private SpriteBatch batch;
public void create () {
batch = new SpriteBatch();
}
public void render () {
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); // This cryptic line clears the screen.
batch.begin();
// Drawing goes here!
batch.end();
}
}
所有SpriteBatch绘图调用必须在begin()和end() 方法之间进行。 在begin()和end()之间不能发生非SpriteBatch绘图行为。
当您自己使用自定义着色器和绑定纹理时,SpriteBatch假定活动纹理个数为0.,您可以使用以下代码重新设置:
Gdx.gl.glActiveTexture(GL20.GL_TEXTURE0);
Texture
Texture类解码图像文件并将其加载到GPU内存中。 图像文件应放在“assets”文件夹中。 由于兼容性和性能原因,图像的尺寸应为2(16x16,64x256等)的幂次方(gl20以上不再有这个限制)。
private Texture texture;
...
texture = new Texture(Gdx.files.internal("image.png"));
...
batch.begin();
batch.draw(texture, 10, 10);
batch.end();
Method signature | Description |
---|---|
draw(Texture texture, float x, float y) | Draws the texture using the texture's width and height |
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) | 绘制纹理的一部分,拉伸到指定宽度和高度,并可选翻转 |
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) | 这会画出一部分纹理,拉伸到宽度和高度。 这是一种稍微高级的方法,因为它使用0-1的纹理坐标,而不是像素坐标。 |
draw(Texture texture, float[] spriteVertices, int offset, int length) | 这是传递原始几何,纹理坐标和颜色信息的高级方法。 这可以用来绘制任何四边形,而不仅仅是矩形。 |
这里创建一个纹理并将其传递给要SpriteBatch进行绘制,纹理将被绘制在坐上角坐标为(10,10),宽高为纹理的宽高的矩形区域内被绘制,, SpriteBatch有很多绘制纹理的方法:
Method signature | Description |
---|---|
draw(Texture texture, float x, float y) | Draws the texture using the texture's width and height |
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) | 绘制纹理的一部分,拉伸到指定宽度和高度,并可选翻转 |
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) | 这会画出一部分纹理,拉伸到宽度和高度。 这是一种稍微高级的方法,因为它使用0-1的纹理坐标,而不是像素坐标。 |
draw(Texture texture, float[] spriteVertices, int offset, int length) | 这是传递原始几何,纹理坐标和颜色信息的高级方法。 这可以用来绘制任何四边形,而不仅仅是矩形。 |
TextureRegion
TextureRegion class (source) 描述了纹理内的一个矩形区域,仅用于绘制纹理的一部分:
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();
Method signature | Description |
---|---|
draw(TextureRegion region, float x, float y) | 使用纹理区域的宽度和高度绘制区域。 |
draw(TextureRegion region, float x, float y,float width, float height) | 绘制纹理区域,拉伸到宽度和高度。 |
draw(TextureRegion region, float x, float y,float originX, float originY, float width, float height,float scaleX, float scaleY, float rotation) | 绘制纹理区域,拉伸到宽度和高度,并按原点缩放和旋转。 |
这里的20,20,50,50描述了纹理的区域部分 左上角坐标(20,20),宽高各50像素.
SpriteBatch有很多绘制纹理区域的方法:
Method signature | Description |
---|---|
draw(TextureRegion region, float x, float y) | 使用纹理区域的宽度和高度绘制区域。 |
draw(TextureRegion region, float x, float y,float width, float height) | 绘制纹理区域,拉伸到宽度和高度。 |
draw(TextureRegion region, float x, float y,float originX, float originY, float width, float height,float scaleX, float scaleY, float rotation) | 绘制纹理区域,拉伸到宽度和高度,并按原点缩放和旋转。 |
Sprite
Sprite class (source)描述了纹理区域以及将被绘制的颜色.
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不适合应用于希望将模型与视图分开的设计模式。 在这种情况下,使用纹理或纹理区域可能会更有意义。
还要注意,Sprite构造函数不会对Sprite的目标位置产生影响,调用Sprite(Texture,int,int,int,int)不会编辑位置。 必须调用Sprite#setPosition(float,float),否则sprite将被绘制在默认位置(0,0)。
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();
这显示了如何对纹理,区域和精灵进行着色,这里的颜色值使用1到0之间的RGBA值进行描述。如果禁用混合(Blending),则忽略Alpha值.
Blending
默认情况下启用混合。 这意味着当绘制纹理时,纹理的半透明部分与已经在该位置的屏幕上的像素合并。
当禁用混合时,该位置的屏幕上的任何内容都将被纹理替换。 这样更有效率,所以除非需要否则混合应该总是被禁用的。 例如,当在整个屏幕上绘制大背景图像时,首先禁用混合可以获得性能提升:
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); // This cryptic line clears the screen.
batch.begin();
batch.disableBlending();
backgroundSprite.draw(batch);
batch.enableBlending();
// Other drawing here.
batch.end();
确保每帧清除屏幕
Viewport
SpriteBatch管理自己的投影和变换矩阵,当创建一个SpriteBatch时,它使用当前应用程序大小使用y坐标系来设置正投影像。 当开始被调用时,它设置viewport.。
性能调优
SpriteBatch有一个构造函数,用于设置发送到GPU之前可以缓冲的最大精灵数量。如果这太低,它将引起额外的GPU调用。如果这太高,SpriteBatch会使用更多的内存。
SpriteBatch有一个名为maxSpritesInBatch的公共字段(int 类型),这表示在SpriteBatch的整个生命周期内一次最多可以发送到GPU的精灵数量.设置一个非常大的SpriteBatch大小,然后检查此字段可以帮助确定最佳的SpriteBatch大小。它的大小应该等于或稍大于maxSpritesInBatch。该字段可以设置为零,以便在任何时候重置它。
当end()方法被调用,此字段指示在最后一次begin()和end()调用之间,几何变换被发送到GPU的次数。这只有在必须绑定不同的纹理时,或者当SpriteBatch已经缓存了足够的精灵才会发生。 如果SpriteBatch的大小适当,并且renderCalls很大(大于15-20),则表示许多纹理绑定正在发生。
SpriteBatch有一个额外的构造函数,它具有一个大小和多个缓冲区。 这是一个高级功能,可以使用顶点缓冲对象(VBO),而不是通常的顶点数组(VAs)。 保存缓冲区列表,每个渲染调用使用列表中的下一个缓冲区(环绕)。 当maxSpritesInBatch为很小并且renderCalls很大时,这可能会提供小的性能提升。
//TODO 待续