Page 1 of 1

Trying to understand why text rendering differently with a background in a canvas

Posted: Wed May 09, 2018 8:35 pm
by ryanzec
So I have this code (bear in mind this is just something I threw together as I am evaluating Love2D as an alternative to Unity3d for my current project so the structure is probably not great):

Code: Select all

local inventoryCanvasVertices = {}
local inventoryScreenVertices = {}

local inventoryWidth = 340
local contentHeight = 16000
local viewportHeight = 150
local inventoryContentCanvas = love.graphics.newCanvas(inventoryWidth, contentHeight)
local inventoryViewportCanvas = love.graphics.newCanvas(inventoryWidth, viewportHeight)

function drawInventory()
  love.graphics.setCanvas(inventoryContentCanvas)

  local topOffset = 180

  -- important line 1
  love.graphics.setColor(0, 0, 0, 1)

  inventoryCanvasVertices = getBoxVertices(0, 0, inventoryWidth, contentHeight)
  inventoryScreenVertices =
    getBoxVertices(
    uiManager.sideInfo.points.topLeft.x + uiManager.inventory.positionOffset.x + uiManager.sideInfo.padding,
    uiManager.sideInfo.points.topLeft.y + uiManager.inventory.positionOffset.y + uiManager.sideInfo.padding + topOffset,
    inventoryWidth,
    contentHeight
  )

  -- important line 2
  love.graphics.polygon("fill", inventoryCanvasVertices)

  love.graphics.setColor(1, 1, 1, 1)

  drawItem()

  love.graphics.setCanvas(inventoryViewportCanvas)
  love.graphics.draw(inventoryContentCanvas, 0, 0, 0, 1, 1, 0, uiManager.inventory.scrollOffset)

  love.graphics.setCanvas()

  love.graphics.draw(
    inventoryViewportCanvas,
    uiManager.sideInfo.points.topLeft.x + uiManager.inventory.positionOffset.x + uiManager.sideInfo.padding,
    uiManager.sideInfo.points.topLeft.y + uiManager.inventory.positionOffset.y + uiManager.sideInfo.padding + topOffset
  )
end
Just for context, the basic idea is that I wanted to see how easy it was to create a scrollable ui content area. From my limited knowledge of Love2D, I decide to create 2 canvases, one for the content and 1 for the scrollable viewport and just set an offset when drawing the content in the scrollable viewport (and then just draw the scrollable viewport to the "main canvas"). While I am not sure if this a the best way to do this, lets just ignore that for the time being (unless it is directly related to the text rendering issue).

Now I am drawing the scrollable viewport in an area where I am already drawing a black background however I am drawing another black background in the content canvas which allows me to get the following results:
Screen Shot 2018-05-09 at 4.02.29 PM.png
Screen Shot 2018-05-09 at 4.02.29 PM.png (8.74 KiB) Viewed 5832 times
Initial I did not do this because I think I did not need to however if I remove the black background in the content canvas (the lines I commented as important 1 / 2), I get the following results:
Screen Shot 2018-05-09 at 4.02.42 PM.png
Screen Shot 2018-05-09 at 4.02.42 PM.png (6.72 KiB) Viewed 5832 times
So the main part of my question here is why would the text render different based on the background being nothing (or transparent) vs it explicitly being set as black when it is being drawing on top (a.k.a. after) a black box anyways (though on a different canvas)?

Now I am also opened to any suggestions / critiques people might have about my implementation of the scrollable content outside of you can using library X / Y / Z (part of the allure of using Love2D is that fact it's low level API is much simpler and I think, maybe naively, it is easier to do exactly want you want and nothing more than with something like Unity).

Re: Trying to understand why text rendering differently a background in a canvas

Posted: Wed May 09, 2018 9:31 pm
by zorg
At first glance, you're not clearing the canvases, which is probably your issue; if you don't do that each frame, you'll accumulate the text color near the edges of the text where the font rasterizer anti-aliases the text needed to be drawn.

0.1+0.1+0.1+ ... will tend towards infinity, or if clamped to a maximum of 1.0, then 1.0.

On the other hand, i'd say that using only one canvas should be enough, you shouldn't need two; just either offset the inventory content canvas by how much you need to, as dictated by your viewport... you could also set up a scissor (although in screen-space, transforms have no effect on that) to basically clip the area you should draw your canvas to.

Re: Trying to understand why text rendering differently a background in a canvas

Posted: Wed May 09, 2018 9:59 pm
by ryanzec
zorg wrote: Wed May 09, 2018 9:31 pm At first glance, you're not clearing the canvases, which is probably your issue; if you don't do that each frame, you'll accumulate the text color near the edges of the text where the font rasterizer anti-aliases the text needed to be drawn.

0.1+0.1+0.1+ ... will tend towards infinity, or if clamped to a maximum of 1.0, then 1.0.

On the other hand, i'd say that using only one canvas should be enough, you shouldn't need two; just either offset the inventory content canvas by how much you need to, as dictated by your viewport... you could also set up a scissor (although in screen-space, transforms have no effect on that) to basically clip the area you should draw your canvas to.
Interesting, I though that was the case however when I tested clearing both canvases, I got text that was a lot thinner:

Screen Shot 2018-05-09 at 5.51.48 PM.png
Screen Shot 2018-05-09 at 5.51.48 PM.png (7.47 KiB) Viewed 5812 times

I tried it again but this time only clearing the content canvas but not the viewport canvas and than gave me the expected results. Do you know why clearing the viewport canvas would effect that text like that out of curiosity?

It might not be that big of a deal to figure out as I am going to try to refactor the code to just use 1 canvas like you suggested as it did not dawn on me that I can render stuff outside the the size of the canvas and I just need to offset it (hopefully these things with dawn on my more as I work at this level of code more :)).

Re: Trying to understand why text rendering differently with a background in a canvas

Posted: Wed May 09, 2018 10:12 pm
by ryanzec
Actually, if I don't clear the viewport canvas, then the old text never goes away, which make sense, which means I am left with to thin text.

Also when I try to use just one canvas, I get 2 issues:

Screen Shot 2018-05-09 at 6.08.59 PM.png
Screen Shot 2018-05-09 at 6.08.59 PM.png (70.36 KiB) Viewed 5811 times

1. when it is scrolled, it over with the content above (not sure if scissor would help based on your previous comment)
2. The content that should be shown below does not show (there should be a Berry 4 / Berry 5 / etc).

The code I am using is:

Code: Select all

love.graphics.draw(
  inventoryContentCanvas,
  uiManager.sideInfo.vertices[1] + uiManager.sideInfo.padding,
  uiManager.sideInfo.vertices[2] + uiManager.sideInfo.padding + topOffset,
  0,
  1,
  1,
  0,
  uiManager.inventory.scrollOffset
)
Code you maybe provide some pseudo code that might lead to me the solution for using 1 canvas?

Re: Trying to understand why text rendering differently with a background in a canvas

Posted: Thu May 10, 2018 9:15 am
by bartbes
The second problem is the blend mode. Love uses alpha blending by default, basically using the alpha channel to determine how to blend with the background. If you do this twice, you'll see that transparent parts (like the edges of characters) are blended into the background twice, leading to the thin text you see. When drawing the canvas to the screen you want to prevent blending from happening again, and you can do this by changing the blend mode to be a so-called "premultiplied" blend mode.

Re: Trying to understand why text rendering differently with a background in a canvas

Posted: Thu May 10, 2018 6:51 pm
by pgimeno
bartbes wrote: Thu May 10, 2018 9:15 am The second problem is the blend mode. Love uses alpha blending by default, basically using the alpha channel to determine how to blend with the background. If you do this twice, you'll see that transparent parts (like the edges of characters) are blended into the background twice, leading to the thin text you see. When drawing the canvas to the screen you want to prevent blending from happening again, and you can do this by changing the blend mode to be a so-called "premultiplied" blend mode.
That is something that always bothers me. Why is LÖVE not doing associative alpha blending? Is it not possible or too expensive in OpenGL? This forum thread in the OpenGL site has no responses: https://www.opengl.org/discussion_board ... a-blending so I guess that may be the reason, but it would be a relief to get a confirmation.

Edit: I think I've found the answer: https://gamedev.stackexchange.com/quest ... link-82996

It seems that working with premultiplied alpha is the best approach, but it's not too easy. Text must be drawn with "alphamultiply" mode on a *black* transparent canvas for it to work, if you want to obtain a premultiplied result. Black is the premultiplied value for a transparent background; clearing the canvas to some other colour will give wrong results, even if fully transparent.

Edit 2: OK, I've done the math now. Bartbes was right: drawing in alphamultiply mode to a canvas with premultiplied alpha gets you a canvas with premultiplied alpha, which you can draw to the screen in premultiplied mode to get the same effect as if you drew the objects directly to the screen. The only catch is that if you clear the canvas with a transparent colour other than black, you don't get a premultiplied canvas as a starting point, and the result won't be right. But the point of this second edit is to note that it's not necessary to work with premultiplied everything. Fixed my first edit.

Re: Trying to understand why text rendering differently with a background in a canvas

Posted: Thu May 10, 2018 10:43 pm
by raidho36
You can run a loop over the image pixels when you load it, multiplying color channels by the alpha channel. You can also convert any standard graphics to premultiplied graphics right within the shader:

Code: Select all

vec4 color = texel ( image, coords );
color.rgb *= color.a;
It's one extra operation in the shader, but the performance hit is pretty marginal, so it's an OK substitute. It's still best that you don't do this in real time, instead doing it at export or import stage where the time spent doing it is basically irrelevant.

It kinda baffles me that people think GPUs should bend backwards over their misunderstanding of rendering mechanisms.