Closed morew4rd closed 1 year ago
Forgot to say: I'm open to helping out with this and similar issues if you're OK with taking contributions. Thanks.
See the flags in texture.lobster
, you want to set both texture_format_nearest_mag
and texture_format_nearest_min
when you call gl_load_texture
, then when rendering with e.g. gl_rect
make sure the screen size in pixels is an integer multiple of the texture res if possible.
If you're actually using font files.. well, those would contain vector representations of your pixellated font. If you get the linear look, you are using gl_scale
on font output instead of correctly setting gl_set_font_size
relative to the native pixels of the screen.
Re: gl_load_texture
sounds good. Looks like the same flags can be set while creating a framebuffer too so I should be OK on that front.
However, with fonts I want to push back a little. You're presumably caching the glyphs in a texture, no? (Otherwise it'd be very expensive to render text.) Would it not be possible to set the min/mag filters to neareset on that texture? See I scaled the font in the OP "incorrectly" and the pixels are still crisp (though probably a bit rectanglish 😄) (*)
(*) Done with my own framework (Lyte2D: Lua + C, similar to Lobster in that Lua code is the driver, calling leaf C APIs.) (I'm using a library for font caching)
Yes, it be possible to specify those flags on the font texture too, but that is typically not what you want, as you'd likely get deformed characters.
The font contains a vector definition, which likely doesn't line up with pixels directly. It will then clamp those to pixels when generating the font texture. Then you scale it as a texture and "nearest" clamps it again. That's 2x clamping that will have side effects if you're not careful. Also, there's not just the texture filtering to take into account, but FreeType's anti-aliasing, depending on how the vectors match the pixels.
Meanwhile, when using gl_set_font_size
directly for the right size, you'll get crisp font rendering no matter the discrepancy between how the font was specified and the screen pixels.
If you want to be using nearest filtering directly, you are better off with a font that comes as a texture, giving you full control over pixels.
I see, so perhaps the approach I am taking in Lyte2D is wrong. I was taking inspiration from some other frameworks (and using a library), but it makes sense that scaling bitmaps twice can easily create artifacts.
I'll do a bit more experimentation, and will report back if I see any other issues on this area. I think this one can be closed (unless you want to use it to track some issue).
Just a FYI. TIL, thanks to you.
I made the double clamping issue visible using Lyte2D. I didn't even think about this until you brought it up. Red is texture scaling and blue is font scaling. Placements are clearly wrong on the red one.
I'll fix my samples (And think a design where perhaps my scale
and set_font_size
/draw_text
APIs aware of each other)
Cool :)
As for them being aware of eachother, I actually have some pretty tricky code in ui_rendertextatlocalsize
whose sole purpose is that after a game has applied an arbitrary scale with gl_scale
to get back to pixel scale so the font rendering can be 1:1 with what FreeType renders :) So yeah, it's a tricky problem, but worth getting right for accurate rendering.
// helper for rendering text at arbitrary scaled locations with maximum precision
// normal font rendering is specified in pixels, and is only rendered at precise pixel boundaries (which looks best)
// when gl_scale(1) and gl_translate in integer amounts
// if you use arbitrary scaling it will look more blurry than necessary
// using this function, it will find the appropriate font size given a size relative to the current scale (not pixels),
// clamp to pixel boundaries,
// then execute any text rendering contained in f
def ui_rendertextatpixelsize(f):
// Translate to nearest pixel:
gl_translate(-fraction(gl_origin() + 0.5) + 0.5):
f()
def ui_rendertextatlocalsize(localsize:float, f):
let oldfontsize = gl_get_font_size()
let oldoutlinesize = gl_get_outline_size()
let sc = gl_scaling().x
let localfontsizef = sc * localsize
let localfontsize = round(localfontsizef) // font size to nearest pixel
gl_set_font_size(localfontsize, oldoutlinesize * (localfontsizef / oldfontsize))
gl_scale xy_1 / gl_scaling(): // get back to pixel scale
ui_rendertextatpixelsize(f)
gl_set_font_size(oldfontsize, oldoutlinesize)
def ui_text_pixel(t):
ui_rendertextatpixelsize():
gl_text(t)
def ui_text_local(t):
ui_rendertextatlocalsize():
gl_text(t)
Awesome, and thank you so much for the pointer! I was thinking something similar, but there're some gotchas for sure.
(OpenGL moment: moving by [0.5, 0.5] 😄)
(This is more of a question/feature request so if this doesn't fit here, please feel free to move to the discussions area or close.)
I'm playing with lobster codebase & capabilities, currently trying to see if it works well for "pixel-perfect" style rendering. For pixel 2D games, devs usually use a very small resolution (say 320*200) and small fonts. As an example, one I like is this: https://managore.itch.io/m5x7
So everything gets scaled to be visible with the modern machines and high resolutions and high DPIs.
The problem is with scaling filters. I'd like to be able to use
nearest neighbor
for font scaling so that font pixels can look like big clean squares. My guess is fonts are currently scaled withlinear
algorithm.I'm expecting this to look like this:
Note: This is also required for image/texture scaling as well. But I haven't played with those parts of the lobster engine yet. I'll amend this issue if I find similar problems there as well.