Need a better way of drawing pixellated polygons

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.
User avatar
Bobble68
Party member
Posts: 162
Joined: Wed Nov 30, 2022 9:16 pm
Contact:

Need a better way of drawing pixellated polygons

Post by Bobble68 »

Ok so I'm starting a new thread for this to keep it separate from the thread I made in Games and Creations, even though there were some suggestions there. (viewtopic.php?f=14&t=94562)

So in my game, there are these polygons that make up the bulk of the level. However, it is clear that these aren't working well on a lot of people's computers, so I'm looking for a way to improve performance without sacrificing the way they look.

Image

Requirements:
- Must have pixelated edges (not just the texture itself)
- Must be able to rotate

My current way of doing this is as follows:

1) Polygon vertices are loaded or drawn
2) A lower resolution canvas is created
3) The image is then drawn on to the canvas (how isn't really important)
4) The resulting canvas is stored
5) The stored canvas is then scaled up and drawn, resulting in the pixellated look

This method seriously seems to be causing some slowdown for some people, and I knew from the start it wasn't ideal. Is there a better way of acheiving this? Using a textured mesh directly loses the pixellated edges, so I tried generating more points using the Bresenham Line-Drawing Algorithm to pixellate it, but I've been unsuccessful in getting it to work properly, and I'm worried the number of triangles needed to be drawn for this could be worse. I'm not a very experienced developer, so any ideas will be appreciated.
Dragon
User avatar
darkfrei
Party member
Posts: 1216
Joined: Sat Feb 08, 2020 11:09 pm

Re: Need a better way of drawing pixellated polygons

Post by darkfrei »

1) We have vertices of polygon, also dimensions of it.
2) Create canvas with size of polygon and dpi = 1/scaleSize https://love2d.org/wiki/love.graphics.newCanvas
3) Draw polygon on this canvas
4) Use stencil to draw texture with stencil https://love2d.org/wiki/love.graphics.stencil
:awesome: in Lua we Löve
:awesome: Platformer Guide
:awesome: freebies
User avatar
Bobble68
Party member
Posts: 162
Joined: Wed Nov 30, 2022 9:16 pm
Contact:

Re: Need a better way of drawing pixellated polygons

Post by Bobble68 »

darkfrei wrote: Fri May 19, 2023 8:43 pm 1) We have vertices of polygon, also dimensions of it.
2) Create canvas with size of polygon and dpi = 1/scaleSize https://love2d.org/wiki/love.graphics.newCanvas
3) Draw polygon on this canvas
4) Use stencil to draw texture with stencil https://love2d.org/wiki/love.graphics.stencil
Ah this is what I'm already doing - the idea is to not do this because some of the polygons are too big.
One of the best suggestions I've had so far is to divide them up into chunks so that not the whole thing needs to drawn at any given time, but that'll increase the loading time due to needing to call newCanvas more times
Dragon
User avatar
darkfrei
Party member
Posts: 1216
Joined: Sat Feb 08, 2020 11:09 pm

Re: Need a better way of drawing pixellated polygons

Post by darkfrei »

Bobble68 wrote: Fri May 19, 2023 8:51 pm
darkfrei wrote: Fri May 19, 2023 8:43 pm 1) We have vertices of polygon, also dimensions of it.
2) Create canvas with size of polygon and dpi = 1/scaleSize https://love2d.org/wiki/love.graphics.newCanvas
3) Draw polygon on this canvas
4) Use stencil to draw texture with stencil https://love2d.org/wiki/love.graphics.stencil
Ah this is what I'm already doing - the idea is to not do this because some of the polygons are too big.
One of the best suggestions I've had so far is to divide them up into chunks so that not the whole thing needs to drawn at any given time, but that'll increase the loading time due to needing to call newCanvas more times
You are right, we are need the canvas with the size of screen and low-dpi, all polygons that are not on the screen changes nothing.
:awesome: in Lua we Löve
:awesome: Platformer Guide
:awesome: freebies
User avatar
pgimeno
Party member
Posts: 3691
Joined: Sun Oct 18, 2015 2:58 pm

Re: Need a better way of drawing pixellated polygons

Post by pgimeno »

A mesh made of long rectangles with one rectangle per pixel row?
User avatar
darkfrei
Party member
Posts: 1216
Joined: Sat Feb 08, 2020 11:09 pm

Re: Need a better way of drawing pixellated polygons

Post by darkfrei »

For example the checkered texture with white polygon:

Code: Select all

Width, Height = love.graphics.getDimensions ()
PixelSize = 16
love.graphics.setDefaultFilter ('nearest', 'nearest')

-- prepare checker texture
local checker = love.graphics.newCanvas (Width, Height, {dpiscale=1/PixelSize})

love.graphics.setCanvas (checker)
for y = 0, Height-1*PixelSize, PixelSize do
	for x = 0, Width-1*PixelSize, PixelSize do
		if (x/PixelSize+y/PixelSize)%(2) == 0 then 
			love.graphics.setColor (0.2,0.2,0.2)
		else
			love.graphics.setColor (0.4,0.4,0.4)
		end
		love.graphics.rectangle ('fill', x,y, PixelSize, PixelSize)
	end
end
Polygon = {10, 300, 400,30, 700,200, 200,500}
love.graphics.setColor (1,1,1)
love.graphics.polygon ('fill', Polygon)
love.graphics.setCanvas ()

function love.draw()
	love.graphics.draw (checker)
end
Screenshot 2023-05-20 005838.png
Screenshot 2023-05-20 005838.png (8.48 KiB) Viewed 1321 times
-------------------------------
And the pixelated polygon (move mouse to change geometry) with texture on it (of coarse here can be any texture, not checkered or low-dpi):

Code: Select all

Width, Height = love.graphics.getDimensions ()
PixelSize = 16
love.graphics.setDefaultFilter ('nearest', 'nearest')

-- prepare checker texture
local checker = love.graphics.newCanvas (Width, Height, {dpiscale=1/PixelSize})

love.graphics.setCanvas (checker)
for y = 0, Height-1*PixelSize, PixelSize do
	for x = 0, Width-1*PixelSize, PixelSize do
		if (x/PixelSize+y/PixelSize)%(2) == 0 then 
			love.graphics.setColor (0.2,0.2,0.2)
		else
			love.graphics.setColor (0.4,0.4,0.4)
		end
		love.graphics.rectangle ('fill', x,y, PixelSize, PixelSize)
	end
end

love.graphics.setCanvas ()

local function getMask (polygon)
	local mask = love.graphics.newCanvas (Width, Height, {dpiscale=1/PixelSize})
	love.graphics.setCanvas (mask)
		love.graphics.setColor (1,1,1)
		love.graphics.polygon ('fill', polygon)
	love.graphics.setCanvas ()
	return mask
end

local mask_shader = love.graphics.newShader[[
   vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 screen_coords) {
      if (Texel(texture, texture_coords).rgb == vec3(0.0)) {
         // a discarded pixel wont be applied as the stencil.
         discard;
      }
      return vec4(1.0);
   }
]]

local function myStencilFunction()
   love.graphics.setShader(mask_shader)
   love.graphics.draw(Mask, 0, 0)
   love.graphics.setShader()
end


function love.load()
	Polygon = {10, 300, 400,30, 700,200, 200,500}
	Mask = getMask (Polygon)
	ResultCanvas = love.graphics.newCanvas (Width, Height, {dpiscale=1/PixelSize})
	love.graphics.setCanvas ({ResultCanvas, stencil=true})
    love.graphics.stencil(myStencilFunction, "replace", 1)
    love.graphics.setStencilTest("greater", 0)
    love.graphics.draw(checker)
    love.graphics.setStencilTest()
	love.graphics.setCanvas ()
end


function love.draw()
--	love.graphics.draw (checker)
	love.graphics.draw (ResultCanvas)
end

function love.mousemoved (x, y)
	Polygon[#Polygon-1], Polygon[#Polygon] = x, y
	Mask = getMask (Polygon)
	ResultCanvas = love.graphics.newCanvas (Width, Height, {dpiscale=1/PixelSize})
	love.graphics.setCanvas ({ResultCanvas, stencil=true})
    love.graphics.stencil(myStencilFunction, "replace", 1)
    love.graphics.setStencilTest("greater", 0)
    love.graphics.draw(checker)
    love.graphics.setStencilTest()
	love.graphics.setCanvas ()
end
Screenshot 2023-05-20 011543.png
Screenshot 2023-05-20 011543.png (8.01 KiB) Viewed 1320 times
pixelated-polzgons-01.love
(831 Bytes) Downloaded 56 times
-------------------------------------------------
And the version with rotating polygon:

Code: Select all

Width, Height = love.graphics.getDimensions ()
PixelSize = 16
love.graphics.setDefaultFilter ('nearest', 'nearest')

-- prepare checker texture
local checker = love.graphics.newCanvas (Width, Height, {dpiscale=1/PixelSize})

love.graphics.setCanvas (checker)
for y = 0, Height-1*PixelSize, PixelSize do
	for x = 0, Width-1*PixelSize, PixelSize do
		if (x/PixelSize+y/PixelSize)%(2) == 0 then 
			love.graphics.setColor (0.2,0.2,0.2)
		else
			love.graphics.setColor (0.4,0.4,0.4)
		end
		love.graphics.rectangle ('fill', x,y, PixelSize, PixelSize)
	end
end

love.graphics.setCanvas ()

local function getMask (polygon)
	local cx, cy, angle = polygon.cx, polygon.cy, polygon.angle
	local mask = love.graphics.newCanvas (Width, Height, {dpiscale=1/PixelSize}) -- change this solution to not create new canvas on each frame!  :x 
	love.graphics.setCanvas (mask)
		love.graphics.setColor (1,1,1)
		love.graphics.push ()
		love.graphics.translate (cx, cy)
		love.graphics.rotate (angle)
		love.graphics.translate (-cx, -cy)
		love.graphics.polygon ('fill', polygon)
		
		love.graphics.pop ()
	love.graphics.setCanvas ()
	return mask
end

local mask_shader = love.graphics.newShader[[
   vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 screen_coords) {
      if (Texel(texture, texture_coords).rgb == vec3(0.0)) {
         // a discarded pixel wont be applied as the stencil.
         discard;
      }
      return vec4(1.0);
   }
]]

local function myStencilFunction()
   love.graphics.setShader(mask_shader)
   love.graphics.draw(Mask, 0, 0)
   love.graphics.setShader()
end


function love.load()
	Polygon = {10, 300, 400,30, 700,200, 200,500}
	Polygon.cx, Polygon.cy = 400, 250
	Polygon.angle, Polygon.omega = 0, 0.3
end


function love.update(dt)
	Polygon.angle = Polygon.angle + dt*Polygon.omega
	
	Mask = getMask (Polygon)
	ResultCanvas = love.graphics.newCanvas (Width, Height, {dpiscale=1/PixelSize})
	love.graphics.setCanvas ({ResultCanvas, stencil=true})
    love.graphics.stencil(myStencilFunction, "replace", 1)
    love.graphics.setStencilTest("greater", 0)
    love.graphics.draw(checker)
    love.graphics.setStencilTest()
	love.graphics.setCanvas ()
end

function love.draw()
--	love.graphics.draw (checker)
	love.graphics.draw (ResultCanvas)
end

function love.mousemoved (x, y)
	Polygon[#Polygon-1], Polygon[#Polygon] = x, y

end
Attachments
pixelated-polygons-02.love
(936 Bytes) Downloaded 92 times
Last edited by darkfrei on Sat May 20, 2023 8:13 am, edited 2 times in total.
:awesome: in Lua we Löve
:awesome: Platformer Guide
:awesome: freebies
User avatar
zorg
Party member
Posts: 3470
Joined: Thu Dec 13, 2012 2:55 pm
Location: Absurdistan, Hungary
Contact:

Re: Need a better way of drawing pixellated polygons

Post by zorg »

Bobble68 wrote: Fri May 19, 2023 6:40 pm My current way of doing this is as follows:

1) Polygon vertices are loaded or drawn
2) A lower resolution canvas is created
3) The image is then drawn on to the canvas (how isn't really important)
4) The resulting canvas is stored
5) The stored canvas is then scaled up and drawn, resulting in the pixellated look

This method seriously seems to be causing some slowdown for some people, and I knew from the start it wasn't ideal.
I hope you didn't forget to read the wiki's warning about creating stuff, like canvases for example.
You are not creating a new canvas every frame, right?
If you are, that might be your issue, considering none of the other things seem too bad on performance.
Me and my stuff :3True Neutral Aspirant. Why, yes, i do indeed enjoy sarcastically correcting others when they make the most blatant of spelling mistakes. No bullying or trolling the innocent tho.
User avatar
Bobble68
Party member
Posts: 162
Joined: Wed Nov 30, 2022 9:16 pm
Contact:

Re: Need a better way of drawing pixellated polygons

Post by Bobble68 »

darkfrei wrote: Fri May 19, 2023 11:00 pm And the version with rotating polygon:
This isn't what I need though - I don't just need it to rotate, I need the pixel grid itself to rotate, as I already have in the game.

zorg wrote: Sat May 20, 2023 5:31 am I hope you didn't forget to read the wiki's warning about creating stuff, like canvases for example.
You are not creating a new canvas every frame, right?
If you are, that might be your issue, considering none of the other things seem too bad on performance.
Yeah I have, that's why I specified that the canvas is stored. The thing is it runs fine on my own computer, but a lot of people are complaining about poor performance, and I know the islands take around 50% of the rendering time so I'm looking for a way to improve them.
Dragon
User avatar
darkfrei
Party member
Posts: 1216
Joined: Sat Feb 08, 2020 11:09 pm

Re: Need a better way of drawing pixellated polygons

Post by darkfrei »

Bobble68 wrote: Sat May 20, 2023 9:15 am
darkfrei wrote: Fri May 19, 2023 11:00 pm And the version with rotating polygon:
This isn't what I need though - I don't just need it to rotate, I need the pixel grid itself to rotate, as I already have in the game.
OK, I thought that you want to go from smooth pixels to pixel perfect solution, that looks like the old game.
But you can draw the polygon without inclination, but use the tilted mask for it.

Code: Select all

Width, Height = love.graphics.getDimensions ()
PixelSize = 8
love.graphics.setDefaultFilter ('nearest', 'nearest')

-- prepare checker texture
local checker = love.graphics.newCanvas ()
love.graphics.setCanvas (checker)
for y = 0, Height-1*PixelSize, PixelSize do
	for x = 0, Width-1*PixelSize, PixelSize do
		if (x/PixelSize+y/PixelSize)%(2) == 0 then 
			love.graphics.setColor (0.6,0.6,0.6)
		else
			love.graphics.setColor (0.4,0.4,0.4)
		end
		love.graphics.rectangle ('fill', x,y, PixelSize, PixelSize)
	end
end
love.graphics.setCanvas ()

-- low-res mask canvas
Mask = love.graphics.newCanvas (Width, Height, {dpiscale=1/PixelSize})

ResultCanvas = love.graphics.newCanvas ()



local mask_shader = love.graphics.newShader[[
   vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 screen_coords) {
      if (Texel(texture, texture_coords).rgb == vec3(0.0)) {
         // a discarded pixel wont be applied as the stencil.
         discard;
      }
      return vec4(1.0);
   }
]]

local function myStencilFunction()
	love.graphics.setShader(mask_shader)
		love.graphics.draw(Mask, Polygon.cx, Polygon.cy, Polygon.angle, 1,1, Polygon.cx, Polygon.cy)
	love.graphics.setShader()
end



function love.load()
	Polygon = {10, 300, 400,30, 700,200, 200,500}
	Polygon.cx, Polygon.cy = 400, 250
	Polygon.angle, Polygon.omega = 0, 0.3
	
-- setup mask
	love.graphics.setCanvas (Mask)
		love.graphics.setColor (1,1,1)
		love.graphics.polygon ('fill', Polygon)
	love.graphics.setCanvas ()
end


function love.update(dt)
	Polygon.angle = Polygon.angle + dt*Polygon.omega

--	update result 
	love.graphics.setCanvas ({ResultCanvas, stencil=true})
		love.graphics.clear ()
    love.graphics.stencil(myStencilFunction, "replace", 1)
    love.graphics.setStencilTest("greater", 0)
    love.graphics.draw(checker)
    love.graphics.setStencilTest()
	love.graphics.setCanvas ()
end

function love.draw()
	love.graphics.draw (ResultCanvas)
end
Screenshot 2023-05-20 164927.png
Screenshot 2023-05-20 164927.png (11.33 KiB) Viewed 1176 times
:awesome: in Lua we Löve
:awesome: Platformer Guide
:awesome: freebies
User avatar
pgimeno
Party member
Posts: 3691
Joined: Sun Oct 18, 2015 2:58 pm

Re: Need a better way of drawing pixellated polygons

Post by pgimeno »

pgimeno wrote: Fri May 19, 2023 9:13 pm A mesh made of long rectangles with one rectangle per pixel row?
Here's a proof of concept for that. I took the liberty of extracting stone.png from your game for this demo, I hope you don't mind.

As I commented in the other thread, the texture is first drawn to a canvas in nearest neighbour mode with a floor(magnification factor) zoom factor, then the canvas interpolation mode is set to linear filtering. That canvas is then used as the mesh texture.
Attachments
texturedPolyPOC.love
(3.72 KiB) Downloaded 55 times
Post Reply

Who is online

Users browsing this forum: Ahrefs [Bot] and 7 guests