LibGDX图形模块之纹理与纹理区域

本页简要介绍了使用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 待续

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容