xjcl
xjcl

Reputation: 15309

Issue when rendering fonts with libGDX at small (<0.5) scales

I have created a BitmapFont for my libGDX game and want to render it in front of the player (in a constant position in space, i.e. like a sign and not like a HUD). Sadly the rendering behaves weird at small scales, so much that the text becomes unreadable. What's going on and is there any way to prevent this?

At scale 0.5F (ok): enter image description here

At scale 0.2F (???): enter image description here

At scale 0.1F (???): enter image description here

Full minimal reproducible example (minus build/project files):

package xjcl.extracredits2020

import com.badlogic.gdx.*
import com.badlogic.gdx.graphics.*
import com.badlogic.gdx.graphics.g2d.*
import com.badlogic.gdx.math.Vector3

class RangeAnxietyGame : ApplicationAdapter() {
    lateinit var cam: PerspectiveCamera
    lateinit var spriteBatch: SpriteBatch
    lateinit var font: BitmapFont

    override fun create() {
        Gdx.input.apply { isCursorCatched = true }
        spriteBatch = SpriteBatch()
        font = BitmapFont()
        cam = PerspectiveCamera(67F, Gdx.graphics.width.toFloat(), Gdx.graphics.height.toFloat()).apply {
            position.set(Vector3(0f, 1.8f, 0f))
            lookAt(0f, 1.8f, 1f)
            update()
        }
    }

    override fun render() {
        Gdx.gl.glViewport(0, 0, Gdx.graphics.width, Gdx.graphics.height)
        Gdx.gl.glClearColor(0f, 0f, 0f, 1f)
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT or GL20.GL_DEPTH_BUFFER_BIT)

        if (Gdx.input.isKeyPressed(Input.Keys.F))
            Gdx.graphics.setFullscreenMode(Gdx.graphics.displayMode)
        if (Gdx.input.isKeyPressed(Input.Keys.G))
            Gdx.graphics.setWindowedMode(1280, 720)
        if (Gdx.input.isKeyPressed(Input.Keys.ESCAPE))
            Gdx.app.exit()

        val dt = Gdx.graphics.deltaTime  // seconds
        cam.apply {
            rotate(Gdx.input.deltaX * -20*dt, 0F, 1F, 0F)
            rotate(up.cpy().crs(direction), Gdx.input.deltaY * 10*dt)
            Gdx.input.setCursorPosition(Gdx.graphics.width/2, Gdx.graphics.height/2)
            update()
        }

        spriteBatch.apply {  // goal text -- bad and hacky
            begin()
            projectionMatrix = cam.combined.cpy().translate(0F, 0F, 50F)
            font.data.setScale(-0.5F, 0.5F)
            font.draw(this, "${cam.position.z.toInt()}m/50m\n${Gdx.graphics.width}x${Gdx.graphics.height} " +
                    "${(1 / dt).toInt()}fps\nat scale (-0.5F, 0.5F)", 0F, 0F)
            end()
        }
    }
}

Additionally, capturing the cursor works at first but then breaks (If I enter fullscreen with F and then exit with G, the cursor can escape on all sides. I can also escape in fullscreen to my second monitor. setCursorPosition does not make a difference.) Should I post a separate question for this?

Upvotes: 2

Views: 421

Answers (1)

Tenfour04
Tenfour04

Reputation: 93591

You need to use

font.setUseIntegerPositions(false)

so it doesn't round off each character's position to an integer coordinate. It doesn't make sense to do that in 3D space, and it's causing your letters to fall on top of each other. (The reason libGDX does this by default is that it causes text rendered in 2D at pixel perfect scale to look crisper.)

enter image description here


Background: Your font appears to use Nearest as the minification filter. Nearest filtering means for each screen pixel, it picks the nearest coordinate from the texture and shows that as the color of the screen pixel. When a texture is drawn smaller than it's source image size relative to screen pixel size and you're using Nearest filtering, you will have ugly artifacts like your screenshots show.

It is best to use either MipMapLinearNearest or MipMapLinearLinear as your minifcation filter if you are drawing your text in 3D space. This will allow OpenGL to interpolate between source image pixels so the text will look crisp. MipMapLinearLinear has the better appearance of these two choices, at the cost of some GPU. I always use MipMapLinearLinear and have never had text covering enough of the screen that the performance impact would ever be significant.

To fix this in your bitmap font file, find this line:

filter: Nearest,Nearest

and change it to

filter: MipMapLinearLinear,Linear

The last Linear is to set the magnification filter to interpolate, so the text won't look as pixelated when you get close to it.

Also, there should be a setting for this in Heiro or BMFont or whatever app you're using to create your font, so you shouldn't have to manually edit the font file's text.

Upvotes: 3

Related Questions