| name | libgdx-2d-rendering |
| description | Use when writing libGDX Java/Kotlin code involving 2D rendering — SpriteBatch, ShapeRenderer, Texture, TextureRegion, TextureAtlas, Camera, Viewport, draw ordering, blending, or screen clearing. Use when debugging rendering artifacts, missing sprites, stretched graphics, or begin/end errors. |
libGDX 2D Rendering
Reference for SpriteBatch, ShapeRenderer, Texture, TextureRegion, TextureAtlas, Camera/Viewport integration, draw ordering, blending, and screen clearing.
Texture
Texture tex = new Texture(Gdx.files.internal("image.png"));
Texture tex = new Texture(Gdx.files.internal("image.png"));
tex.setFilter(TextureFilter.Nearest, TextureFilter.Nearest);
tex.setFilter(TextureFilter.Linear, TextureFilter.Linear);
tex.setWrap(TextureWrap.Repeat, TextureWrap.Repeat);
TextureFilter — min/mag:
| Filter | Use for | Result |
|---|
Nearest | Pixel art, retro games | Sharp pixels, no blurring |
Linear | Smooth artwork, UI | Bilinear interpolation |
MipMapLinearLinear | Large textures drawn small | Smooth mipmapped (min filter only) |
Default filter is Nearest. For smooth-scaled artwork, you must explicitly set Linear.
TextureWrap: ClampToEdge (default), Repeat, MirroredRepeat. Only Repeat/MirroredRepeat require power-of-two texture dimensions.
Power-of-two: Not required for general use on modern GPUs. Only required when using Repeat/MirroredRepeat wrap modes or mipmaps. Non-POT textures work fine with ClampToEdge (the default).
Disposal: Textures hold GPU memory — you MUST call dispose() when done. See Disposal section below.
TextureRegion
A rectangular sub-area of a Texture. Used for sprite sheets, tilesets, and atlas regions. Drawing a TextureRegion does NOT copy pixels — it references the parent Texture with UV coordinates.
Texture sheet = new Texture(Gdx.files.internal("spritesheet.png"));
TextureRegion region = new TextureRegion(sheet, 0, 0, 32, 32);
TextureRegion[][] frames = TextureRegion.split(sheet, 32, 32);
TextureRegion firstFrame = frames[0][0];
region.flip(false, true);
Do NOT dispose a TextureRegion. Dispose the parent Texture only. Disposing the Texture invalidates all regions referencing it.
TextureAtlas
Packs multiple images into one or more textures with a .atlas descriptor file (created by libGDX TexturePacker or tools like gdx-texture-packer-gui).
TextureAtlas atlas = new TextureAtlas(Gdx.files.internal("pack.atlas"));
AtlasRegion hero = atlas.findRegion("hero");
Array<AtlasRegion> runFrames = atlas.findRegions("hero_run");
Animation<TextureRegion> runAnim = new Animation<>(0.1f, runFrames, PlayMode.LOOP);
atlas.dispose();
Do NOT separately dispose Textures obtained from an atlas. The atlas owns them — atlas.dispose() handles it. In non-trivial projects, prefer loading via AssetManager for lifecycle management.
SpriteBatch
Batches textured quads into a single draw call for GPU efficiency. All draw calls MUST be between begin() and end(). Calling draw() outside throws IllegalStateException.
SpriteBatch batch = new SpriteBatch();
batch.setProjectionMatrix(camera.combined);
batch.begin();
batch.draw(texture, x, y);
batch.draw(texture, x, y, width, height);
batch.draw(region, x, y);
batch.draw(region, x, y, width, height);
batch.draw(region, x, y, originX, originY, width, height, scaleX, scaleY, rotation);
batch.end();
setProjectionMatrix — REQUIRED with Camera
If you use an OrthographicCamera or Viewport, you MUST call batch.setProjectionMatrix(camera.combined) before batch.begin(). Without it, SpriteBatch draws in a default pixel-coordinate projection that ignores your camera's position, zoom, and viewport.
camera.update();
batch.setProjectionMatrix(camera.combined);
batch.begin();
batch.end();
Call setProjectionMatrix() BEFORE begin(), not between begin()/end() — calling it mid-batch forces a flush (performance hit).
Draw Overloads
| Method | Use |
|---|
draw(Texture, x, y) | Full texture at position, original size |
draw(Texture, x, y, w, h) | Full texture, scaled to w×h |
draw(TextureRegion, x, y) | Region at position, original size |
draw(TextureRegion, x, y, w, h) | Region, scaled |
draw(TextureRegion, x, y, originX, originY, w, h, scaleX, scaleY, rotation) | Full transform |
originX/originY are relative to position (x, y), not to the texture. Rotation is in degrees, counter-clockwise.
setColor / Tinting
batch.setColor(r, g, b, a) tints all subsequent draws by multiplying texture colors by the given color. White (1,1,1,1) = no tint. The tint persists until you change it again — the most common bug is forgetting to reset after tinting one sprite, which causes everything drawn after it to be tinted.
batch.begin();
batch.draw(background, 0, 0);
batch.setColor(1, 0, 0, 1);
batch.draw(hitEnemy, ex, ey);
batch.setColor(Color.WHITE);
batch.draw(player, px, py);
batch.end();
Pattern: setColor(tint) → draw() → setColor(Color.WHITE). Always reset to Color.WHITE after tinting.
Batching Efficiency
SpriteBatch has a default capacity of 1000 quads. A texture switch (drawing from a different Texture object) forces a flush — a new draw call to the GPU. This is why TextureAtlas packing matters: drawing many sprites from one atlas texture avoids flushes. Minimizing texture switches is the single most impactful batching optimization.
Sprite
Sprite extends TextureRegion and bundles position, size, rotation, origin, color, and flip into one object. Call sprite.draw(batch) instead of passing all parameters to batch.draw() manually.
Use Sprite when you need per-entity transforms (position, rotation, scale, tint) that change each frame. Use raw TextureRegion draws when you're just blitting from an atlas with no per-sprite state (tiles, static UI).
Camera & Viewport
Default coordinate system: OrthographicCamera centers at (0,0), so the visible area spans (-halfWidth, -halfHeight) to (+halfWidth, +halfHeight). Many users expect (0,0) to be the bottom-left corner. Use viewport.update(width, height, true) in resize() to center the camera at (worldWidth/2, worldHeight/2), which shifts (0,0) to the bottom-left.
OrthographicCamera camera = new OrthographicCamera();
Viewport viewport = new FitViewport(worldWidth, worldHeight, camera);
viewport.update(width, height, true);
camera.update();
batch.setProjectionMatrix(camera.combined);
viewport.update(width, height, true) — the true parameter centers the camera at (worldWidth/2, worldHeight/2). Without it, the camera stays at its current position — which defaults to (0,0), meaning the visible area is centered at the origin. This causes the classic "everything renders in the top-right corner" bug when you place sprites at positive coordinates expecting (0,0) to be bottom-left.
Common Viewport types:
| Viewport | Behavior |
|---|
FitViewport | Maintains aspect ratio, letterboxes/pillarboxes |
FillViewport | Maintains aspect ratio, crops edges |
StretchViewport | Fills screen, may distort |
ExtendViewport | Extends world to fill screen, no distortion |
ScreenViewport | 1:1 pixel mapping (commonly used with Scene2D Stage for UI) |
viewport.update(width, height) must be called in resize() — without it, the viewport won't adapt to window size changes.
Draw Ordering
libGDX has NO automatic z-sorting or depth testing for 2D. SpriteBatch uses the painter's algorithm: sprites are drawn in submission order. Later draws appear on top of earlier draws.
batch.begin();
batch.draw(background, 0, 0);
batch.draw(player, px, py);
batch.draw(hud, hx, hy);
batch.end();
For y-sorting (top-down games where lower entities appear in front):
entities.sort((a, b) -> Float.compare(b.y, a.y));
batch.begin();
for (Entity e : entities) {
batch.draw(e.region, e.x, e.y);
}
batch.end();
Blending
SpriteBatch enables GL blending by default (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA). This is correct for transparent sprites.
batch.disableBlending();
batch.begin();
batch.draw(opaqueBackground, 0, 0);
batch.end();
batch.enableBlending();
batch.begin();
batch.draw(transparentSprite, x, y);
batch.end();
Disable blending only when drawing fully opaque textures where you're certain no alpha is needed. The performance gain is minor in most games.
ShapeRenderer
Draws geometric primitives (rectangles, circles, lines). Same begin/end contract as SpriteBatch.
ShapeRenderer shapes = new ShapeRenderer();
shapes.setProjectionMatrix(camera.combined);
shapes.begin(ShapeType.Filled);
shapes.setColor(Color.RED);
shapes.rect(x, y, width, height);
shapes.circle(cx, cy, radius);
shapes.end();
shapes.begin(ShapeType.Line);
shapes.setColor(Color.WHITE);
shapes.line(x1, y1, x2, y2);
shapes.rectLine(x1, y1, x2, y2, lineWidth);
shapes.end();
ShapeType: Filled (solid), Line (outlines), Point (dots). You cannot change ShapeType between begin() and end() — you must end and begin again.
ShapeRenderer Does NOT Enable Blending
Unlike SpriteBatch, ShapeRenderer does NOT enable GL blending by default. Alpha values in setColor() are silently ignored — shapes render fully opaque. To draw semi-transparent shapes, you must enable blending manually:
Gdx.gl.glEnable(GL20.GL_BLEND);
Gdx.gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA);
shapes.begin(ShapeType.Filled);
shapes.setColor(0, 0, 0, 0.5f);
shapes.rect(0, 0, worldWidth, worldHeight);
shapes.end();
Gdx.gl.glDisable(GL20.GL_BLEND);
NEVER Intermix SpriteBatch and ShapeRenderer
You CANNOT have SpriteBatch and ShapeRenderer open at the same time. Each maintains its own GL state. Nesting or interleaving their begin/end blocks causes rendering corruption or crashes.
batch.begin();
batch.draw(texture, x, y);
shapes.begin(ShapeType.Filled);
shapes.rect(x, y, w, h);
shapes.end();
batch.end();
batch.setProjectionMatrix(camera.combined);
batch.begin();
batch.draw(texture, x, y);
batch.end();
shapes.setProjectionMatrix(camera.combined);
shapes.begin(ShapeType.Filled);
shapes.rect(x, y, w, h);
shapes.end();
Order tip: Draw all SpriteBatch content first, then all ShapeRenderer content (or vice versa) to minimize begin/end switches.
ScreenUtils.clear()
Use ScreenUtils.clear() (since libGDX 1.10.0) instead of the verbose Gdx.gl.glClearColor() + Gdx.gl.glClear() pattern.
ScreenUtils.clear(0.15f, 0.15f, 0.2f, 1f);
ScreenUtils.clear(Color.SKY);
Gdx.gl.glClearColor(0, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
Both are functionally identical. Always use ScreenUtils.clear() in new code. Do not present the glClearColor+glClear pattern as an alternative — it is the legacy approach replaced by ScreenUtils.clear() in 1.10.0.
Disposal
These types hold GPU/native resources and MUST be disposed:
| Type | What it holds |
|---|
Texture | GPU texture memory |
SpriteBatch | Mesh + shader |
ShapeRenderer | Mesh + shader |
TextureAtlas | All backing Texture(s) |
BitmapFont | Texture(s) |
FrameBuffer | GL framebuffer + texture |
Skin | Textures, fonts |
Do NOT dispose:
TextureRegion — does not own the Texture
AtlasRegion — owned by the TextureAtlas
- Textures obtained from a
TextureAtlas — the atlas disposes them
Dispose in reverse creation order when possible. In non-trivial projects, use AssetManager to handle disposal via unload() and clear().
Minimal Working Example
import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.utils.ScreenUtils;
import com.badlogic.gdx.utils.viewport.FitViewport;
public class MyGame extends ApplicationAdapter {
SpriteBatch batch;
Texture img;
OrthographicCamera camera;
FitViewport viewport;
@Override
public void create() {
camera = new OrthographicCamera();
viewport = new FitViewport(800, 480, camera);
batch = new SpriteBatch();
img = new Texture(Gdx.files.internal("player.png"));
}
@Override
public void render() {
ScreenUtils.clear(0.15f, 0.15f, 0.2f, 1f);
camera.update();
batch.setProjectionMatrix(camera.combined);
batch.begin();
batch.draw(img, 100, 100);
batch.end();
}
@Override
public void resize(int width, int height) {
viewport.update(width, height, true);
}
@Override
public void dispose() {
batch.dispose();
img.dispose();
}
}
Common Mistakes
- Nesting SpriteBatch and ShapeRenderer begin/end blocks — Never open one while the other is still open. End the first before beginning the second.
- Forgetting
batch.setProjectionMatrix(camera.combined) — Without this, SpriteBatch uses default pixel coordinates and ignores your camera entirely. Must be called BEFORE begin().
- Not disposing Textures — Textures hold GPU memory. Forgetting
dispose() leaks VRAM. Use AssetManager for non-trivial projects.
- Disposing TextureRegion or atlas-owned Textures — TextureRegion doesn't own the texture. Atlas-owned textures are disposed by
atlas.dispose(). Only dispose what you directly created.
- Using old
Gdx.gl.glClearColor() + glClear() pattern — Use ScreenUtils.clear() instead (since 1.10.0). Same result, less boilerplate.
- Expecting automatic z-sorting — libGDX draws in submission order. There is no z-buffer for 2D. Sort entities yourself before drawing.
- Calling
setProjectionMatrix() between begin/end — This forces a batch flush. Call it BEFORE begin().
- Not calling
viewport.update() in resize() — Viewport won't adapt to window changes, causing stretched or misplaced graphics.
- Using Nearest filter for smooth artwork (or Linear for pixel art) — Nearest preserves sharp pixels; Linear smooths. Default is Nearest. Set explicitly based on art style.
- Forgetting
camera.update() before drawing — Camera matrix won't reflect position/zoom changes made since last update.
- Forgetting to reset
batch.setColor() to Color.WHITE after tinting — All subsequent draws remain tinted. Always reset: setColor(tint) → draw() → setColor(Color.WHITE).
- Drawing semi-transparent shapes with ShapeRenderer without enabling GL blending — Alpha is ignored, shapes render opaque. Call
Gdx.gl.glEnable(GL20.GL_BLEND) before the ShapeRenderer block.
- Not passing
true to viewport.update(width, height, true) and wondering why content appears offset — Without centerCamera, the camera stays at (0,0) center, not bottom-left origin. Sprites at positive coordinates appear in the top-right corner.