Page 1 of 2

Scaled up retro low res graphics issues.

Posted: Thu Aug 02, 2012 10:16 am
by OuTopos
This is my first post on this forum so I just want to thank Löve and it's community. Thank you all! :D

I've had this thing that always annoys me when it comes to recreating retro feeling in graphics.
Normally you just scale up some graphic using nearest neighbor interpolation so one pixel in the graphics content is more than an actual pixel on the screen. And that works well. However if you start moving layers/sprites around they will break the immersion if they don't snap to the over sized fake pixels.
retroexample.png
retroexample.png (5.59 KiB) Viewed 4864 times
In Löve when using the love.graphics.scale function you can solve this by always providing a rounded number of the actual position before drawing the sprite.

The problem I have is with the particle system. Is there a way to get the particles to snap to the fake pixels?
Maybe a better solution would be to run the game in actual low res but then it will be really small in windowed mode.

I haven't read about this anywhere so maybe most people don't care. But I do :D
How would you guys solve this?

Re: Scaled up retro low res graphics issues.

Posted: Thu Aug 02, 2012 11:32 am
by dreadkillz
Just store your layer's scale in a variable so you can have stuff snap to it. Then you multiply by (layer_tosnapto_scale/current_layer_scale) to snap to that layer's point.

Code: Select all

scale1 = 2 -- the scale of your layer 1
scale2 = 3 -- the scale of your layer 2
function love.draw()
	love.graphics.push()
		love.graphics.scale(scale1)
		... -- draw your layer 1 stuff
	love.graphics.pop()

	love.graphics.push()
		love.graphics.scale(scale2)
		... -- draw your layer 2 stuff
		-- if you want to draw something at point(1,1) in layer 1, you would multiply this layer's points by (scale1/scale2) to snap to the other layer's points
	love.graphics.pop()
end
So...

point(1,1) in layer 1 in screen coordinates: point(2,2)
point(1,1) in layer 2 in screen coordinates: point(3,3)

To snap to point(1,1) in layer 1 just draw at: point(scale1/scale2*x,scale1/scale2*y)

So in layer2, you draw at point(2/3,2/3) ---> screen coordinate: point(2,2) !!!

Re: Scaled up retro low res graphics issues.

Posted: Thu Aug 02, 2012 3:46 pm
by Lafolie
Why go to the effort of enlarging all of the images if you want them to scale like this? Just run the game at a low resolution and scale it up at runtime, that way one 'pixel' (square) in the image represents one actual pixel in the canvas/buffer/screen and not 4 or 16 or 32, whatever.

The particles might also look a little more coherent too this way, though you should consider that particle effects can distort the 'retro effect' and you should design your particles specifically for the visual theme.

Re: Scaled up retro low res graphics issues.

Posted: Thu Aug 02, 2012 3:48 pm
by Boolsheet
OuTopos wrote:The problem I have is with the particle system. Is there a way to get the particles to snap to the fake pixels?
You mean ParticleSystem? Uh, I don't think so. It's not possible to access or modify the position of the particles and it works entirely with floating-point numbers.

I don't know how it would look, but you can try rendering to a Canvas with the small resolution and then draw that (with the appropriate scale and nearest neighbor filtering) to the screen. It means, however, that the graphics driver must support OpenGL 3.0 or the framebuffer extension.

Re: Scaled up retro low res graphics issues.

Posted: Thu Aug 02, 2012 4:05 pm
by flashkot
Lafolie wrote:Just run the game at a low resolution and scale it up at runtime
I understand how to make this with Canvas. But what if Canvas is not supported? And if there are several methods to do this, which one is fastes?

Re: Scaled up retro low res graphics issues.

Posted: Thu Aug 02, 2012 4:11 pm
by Lafolie
Doesn't Love have some form of scale procedure? And also, is it not possible to simply set the display resolution to match your game's resolution, thus it will be scaled up by the display/driver/gfx card/blah.

Re: Scaled up retro low res graphics issues.

Posted: Thu Aug 02, 2012 4:32 pm
by flashkot
I think drivers will not allow me to set resolution like 480x270.

Also i want allow to run my game windowed.

Re: Scaled up retro low res graphics issues.

Posted: Thu Aug 02, 2012 10:30 pm
by Lafolie

Re: Scaled up retro low res graphics issues.

Posted: Fri Aug 03, 2012 4:20 am
by Jasoco
Here's the code I use nearly-unmodified so you can get an example of how to do it. Sorry there's no comments and you'll have to use your head to figure out some things.

You'll still need to snap all the objects to the grid if you really want to keep it faithful. But using my method below, you won't need to snap if you use Canvas mode, but will need to snap of not using canvas. Personally, as much as I also hate retro games with off-pixel graphics and malformed scaling, I think it's a bit too much work to have to snap every single X and Y position in the code while drawing. Just so much extra work. As I mentioned, if your computer supports canvases (app.canvas below is retrieved by using the love.graphics.isSupported() call.), the snapping will happen automatically. But in non-canvas mode, images will sometimes be partially between pixels like you pointed out above. So it all comes down to whether you want to go to the trouble of snapping the images to the grid or not.

Note: The app and preferences tables are methods I use for organizing this stuff. You will obviously want to modify this to fit your own needs.

Code: Select all

--CHANGE RESOLUTION OR SCALE OR SETUP CANVAS SUPPORT
function setupScreen()
    if preferences.fullscreen then
        love.graphics.setMode(app.screen_width, app.screen_height, true, preferences.vsync, preferences.fsaa)
    else
        love.graphics.setMode(app.draw_width * preferences.pixelscale + dbuff, app.draw_height * preferences.pixelscale, false, preferences.vsync, preferences.fsaa)
    end

    local scale = love.graphics.getHeight() / app.draw_height
    if preferences.stretch == false then scale = math.floor(scale) end
    if preferences.stretchfill and preferences.stretch then
    	app.scale_vert = scale
    	app.scale_horiz = (love.graphics.getWidth()-dbuff) / app.draw_width
    else
    	app.scale_vert = scale
    	app.scale_horiz = scale
    end

    app.draw_ox = (love.graphics.getWidth() - (app.draw_width * app.scale_horiz)) / 2
    app.draw_oy = (love.graphics.getHeight() - (app.draw_height * app.scale_vert)) / 2

    if app.usecanvas then
        if love.graphics.isSupported("canvas") then
            app.canvas = love.graphics.newCanvas(app.draw_width, app.draw_height)
            app.canvas:setFilter("linear", "nearest")
        else
            app.usecanvas = false
            app.canvas = nil
        end
    else
        app.canvas = nil
    end

    _dbg:updateLineCount()
end

--HANDLE SCREEN PREPARATION
function screenPreparation()
    if app.usecanvas == true then
        love.graphics.setCanvas(app.canvas)
        app.canvas:clear(255,255,255,0)
    elseif app.usecanvas == false then
		local xx, yy = app.draw_ox, app.draw_oy
        if _dbg.on then
			xx, yy = app.draw_ox * 2, app.draw_oy * 2
        end
        love.graphics.push()
        love.graphics.translate(xx, yy)
        love.graphics.scale(app.scale_horiz, app.scale_vert)
        love.graphics.setScissor(xx, yy, app.draw_width * app.scale_horiz, app.draw_height * app.scale_vert)
    end
end

--HANDLE SCREEN PRESENTATION
function screenPresentation()
    if app.usecanvas == true then
		local xx, yy = app.draw_ox, app.draw_oy
        if _dbg.on then
			xx, yy = app.draw_ox * 2, app.draw_oy * 2
        end
        love.graphics.setCanvas()
        love.graphics.setColor(255,255,255)
        love.graphics.draw(app.canvas, xx, yy, 0, app.scale_horiz, app.scale_vert)
    elseif app.usecanvas == false then
        love.graphics.scale(1)
        love.graphics.pop()
        love.graphics.setScissor()
    end
end
Call setupScreen() when you change any settings like fullscreen or window size.
Call screenPreparation() before drawing and screenPresentation() after drawing game related stuff. You can also use more push(), pop(), translate(), scale(), setScissor() calls nested too. As long as you end up closing what you open. i.e. close every push with a pop, every setScissor(x, y, w, h) with an empty setScissor().

Create a function to snap the value:

Code: Select all

function snap(v)
  if app.usecanvas then
    return v
  else
    return math.floor(v / scale) * scale
  end
end
Or something.

Re: Scaled up retro low res graphics issues.

Posted: Mon Aug 06, 2012 6:28 am
by OuTopos
Thanks for the suggestions. I will experiment a bit when I have more time.
But for now it seems like I will avoid the particle system all together and just round the sprites positions to whole pixels.
Most of the sprites are already correctly placed and will not move since they are just tiles. It's just the moving objects that cause some problem.

Thanks.