Scalable Game Graphics

Questions about the LÖVE API, installing LÖVE and other support related questions go here.
Forum rules
Before you make a thread asking for help, read this.
Post Reply
dvdfu
Prole
Posts: 7
Joined: Sun Sep 27, 2015 6:22 pm

Scalable Game Graphics

Post by dvdfu »

Hey all, I'm making a pixelized game with low game dimensions (288 x 180). The game window can be scaled 1x, 2x, and so on. The game is using love.graphics.scale(x) to render at different scales.

As a side effect, higher window scales have "smoother" graphics resolution, because they're able to render "in-between pixels". For example, if an object is located at coordinate 100.5, it will render at a rounded screen coordinate 100 (or 101). However, at 2x resolution it will render nicely at screen coordinate 201.

This is really good, but there is a problem with rendering at decimal pixels, which is explained quite nicely here. The tearing occurs and is a really big problem for me, but the only solution seems like rounding drawing coordinates (which gets rid of the smoothness I mentioned).

How do games get around this problem? The only solution I can think of is manually multiplying all draw coordinates and rounding them, and not using love.graphics.scale(x). But this seems like a horribly contrived, messy solution. I've also looked at this thread, but the solutions involve true upscaling, which doesn't preserve smoothness.

Screenshot for reference, at 3x resolution (the horizontal black rows in the background):
Screenshot from 2016-11-15 23-55-17.png
Screenshot from 2016-11-15 23-55-17.png (14.22 KiB) Viewed 6563 times
User avatar
raidho36
Party member
Posts: 2063
Joined: Mon Jun 17, 2013 12:00 pm

Re: Scalable Game Graphics

Post by raidho36 »

Consider rendering to canvas at native resolution and then upscale to fit the screen using nearest filter. This way you don't get mid-pixel positions though, but you're not supposed to with this kind of graphics anyway. You also should use nearest filter for all other rendering to avoid non-integer coordinates filtering, which effectively locks graphics to pixel grid.
Last edited by raidho36 on Wed Nov 16, 2016 5:17 am, edited 2 times in total.
dvdfu
Prole
Posts: 7
Joined: Sun Sep 27, 2015 6:22 pm

Re: Scalable Game Graphics

Post by dvdfu »

raidho36 wrote:Consider rendering to canvas at native resolution and then upscale to fit the screen using nearest filter. This way you don't get mid-pixel positions though, but you're not supposed to with this kind of graphics anyway. You also should use nearest filter for all other rendering to avoid non-integer coordinates filtering, which effectively locks graphics to pixel grid.
I actually have the native-render-and-upscale implemented, but I still get the bleeding issue (this time, it's even more noticeable when upscaled):
Screenshot from 2016-11-16 00-22-47.png
Screenshot from 2016-11-16 00-22-47.png (13.93 KiB) Viewed 6556 times
Also, nearest is set as the default using love.graphics.setDefaultFilter.

I'm curious as to why graphics shouldn't be used this way. What's the standard way for drawing upscaled sprites at pixel coordinates of finer resolution?
User avatar
raidho36
Party member
Posts: 2063
Joined: Mon Jun 17, 2013 12:00 pm

Re: Scalable Game Graphics

Post by raidho36 »

Your aliasing problem is clearly that the sprite polygon samples outside of sprite texture when it's shifted by sub-pixel amounts, i.e. where polygon fragment is still present as renderable pixel but where actual texture slips away. You should pad your textures with 1 pixel strip same as on the edge, or from opposite edge if the sprite is to be tiled. Or maybe it's a bug in your code and you render it 1 pixel higher than it actually is.

It shouldn't be this way because that's the whole point. Back in the day the hardware was extremely limited, most notably that rotating, scaling and god forbid filtering were prohibitively expensive, so they weren't used. Sprites were rendered 1:1 to the screen, small display resolution meant that visuals had to be represented by tiny amounts of pixels so easily recognizable and high contrast shapes had to be used, and harsh color palette limitations made them use very few colors. Pixellated graphics is not good of and in itself, getting extremely low fidelity graphics to look good anyway is what gives it its charm. If you're not going for the same effect, might as well leverage modern GPU power and use high fidelity graphics.
User avatar
s-ol
Party member
Posts: 1077
Joined: Mon Sep 15, 2014 7:41 pm
Location: Cologne, Germany
Contact:

Re: Scalable Game Graphics

Post by s-ol »

Why don't you floor coordinates to thirds? just do love.graphics.draw(quad, img, math.floor(x * 3)/3, math.floor(y * 3/3)).

Or write a function f2(x, y) return math.floor(x*3)/3, math.floor(y*3)/3 end, or monkeypatch the love.graphics functions to do it for you.

s-ol.nu /blog  -  p.s-ol.be /st8.lua  -  g.s-ol.be /gtglg /curcur

Code: Select all

print( type(love) )
if false then
  baby:hurt(me)
end
User avatar
raidho36
Party member
Posts: 2063
Joined: Mon Jun 17, 2013 12:00 pm

Re: Scalable Game Graphics

Post by raidho36 »

That's what he originally suggested doing but wrote off as excessive.
User avatar
pgimeno
Party member
Posts: 3656
Joined: Sun Oct 18, 2015 2:58 pm

Re: Scalable Game Graphics

Post by pgimeno »

dvdfu wrote:I actually have the native-render-and-upscale implemented, but I still get the bleeding issue (this time, it's even more noticeable when upscaled):
Screenshot from 2016-11-16 00-22-47.png
When you draw at 1:1 on a canvas, you should round the drawing coordinates of the stuff you draw. Also, if you have any graphics that are not at 1:1, you can run into that problem.

You should get a pixel-perfect screen when you draw on a canvas at 1:1. If you don't, you need to fix it, rounding coordinates of the stuff you draw until you do. Once you have it, you can upscale. There are ways to get pixel-smooth movements when upscaling; the zoomed pixels don't need to be at coordinates that are multiples of one zoomed pixel. But that's a different issue that needs to be dealt with separately. First step is to get the 1:1 pixel-perfect screen drawn on a canvas.

That's if you don't want to handle bleeding by extending the sprites (which sounds like a bad hack to me, as it generates visually incorrect graphics).

(Edited to clarify I meant on a canvas)
User avatar
D0NM
Party member
Posts: 250
Joined: Mon Feb 08, 2016 10:35 am
Location: Zabuyaki
Contact:

Re: Scalable Game Graphics

Post by D0NM »

dvdfu wrote:Hey all, I'm making a pixelized game with low game dimensions (288 x 180). The game window can be scaled 1x, 2x, and so on. The game is using love.graphics.scale(x) to render at different scales.
you can add extra outline pattern around your tiles.

or overlap tiles vertically / horizontally by 1 pixel.
Like they do with real tiles on roofs.

Image

It might be useful if you want more smooth scrolling
instead of rounding all the coordinates to integers.

Good luck with your nice looking game!!
Our LÖVE Gamedev blog Zabuyaki (an open source retro beat 'em up game). Twitter: @Zabuyaki.
:joker: LÖVE & Lua Video Lessons in Russian / Видео уроки по LÖVE и Lua :joker:
dvdfu
Prole
Posts: 7
Joined: Sun Sep 27, 2015 6:22 pm

Re: Scalable Game Graphics

Post by dvdfu »

pgimeno wrote:You should get a pixel-perfect screen when you draw on a canvas at 1:1. If you don't, you need to fix it, rounding coordinates of the stuff you draw until you do. Once you have it, you can upscale.
Just realized, I was drawing at 1:1 but without rounding coordinates. Just did so and everything draws correctly now.

After considering raidho36's post, I think upscaling at 1:1 would be the best solution, and that I should focus on making the game look and feel good at its original resolution.

Thanks all for the help!
Post Reply

Who is online

Users browsing this forum: Ahrefs [Bot], Amazon [Bot], Bing [Bot], Semrush [Bot] and 5 guests