sdl-gpu icon indicating copy to clipboard operation
sdl-gpu copied to clipboard

Texel coordinates causing bleeding of texture atlas when scaled

Open Wizzard033 opened this issue 6 years ago • 9 comments

When blitting, the texel coordinates are not properly attained https://github.com/grimfang4/sdl-gpu/blob/e3d350b325a0e0d0b3007f69ede62313df46c6ef/src/renderer_GL_common.inl#L4535-L4539


The above code assumes texels are at the following u, v coordinates


However, the correct u, v coordinate are as shown below

function get_texel_coords(x, y, tex_width, tex_height)
    u = (x + 0.5) / tex_width
    v = (y + 0.5) / tex_height
    return u, v
end

This is called the "half pixel correction". In this case, we are always sampling the center of each texel, which prevents bleeding of adjacent textures within a texture atlas. You can read more about it here https://docs.microsoft.com/en-us/windows/desktop/direct3d9/directly-mapping-texels-to-pixels


I noticed this issue when attempting to scale a sprite that was using a texture atlas with no padding between textures. My sprite was having the adjacent textures bleed on to it. In order to correct the issue currently, I add 0.5 to the x and y of the source rect for blitting, and subtract 1 from the width and height of the source rect. It'd be nicer if it was just fixed internally instead of using this hack.

Wizzard033 avatar Feb 23 '19 01:02 Wizzard033

Can confirm. I noticed a tiny 1 pixel offset from what I was expecting. Effect gets huge if you blow up a small bitmap to a much larger size (like in pixel-art games). Seems exactly half the original pixel off.

s9w avatar May 31 '19 18:05 s9w

Does anyone have a working fix for this?

pmiddend avatar Jun 21 '19 11:06 pmiddend

The original post contains a work around that I'm still using to this day. It works great.

I add 0.5 to the x and y of the source rect for blitting, and subtract 1 from the width and height of the source rect.

If done correctly, there should be no bleeding of textures.

Wizzard033 avatar Jun 21 '19 11:06 Wizzard033

Note that this type of thing would be really simple to fix internally too

Wizzard033 avatar Jun 21 '19 11:06 Wizzard033

@Wizzard033 Yes, fixing this in sdl-gpu was my intention.

pmiddend avatar Jun 21 '19 11:06 pmiddend

Replacing the lines I referenced in the first post with the following ought to do the trick

 // Scale src_rect tex coords according to actual texture dims 
 x1 = (src_rect->x + 0.5)/(float)tex_w; 
 y1 = (src_rect->y + 0.5)/(float)tex_h; 
 x2 = (src_rect->x + src_rect->w - 0.5)/(float)tex_w; 
 y2 = (src_rect->y + src_rect->h - 0.5)/(float)tex_h; 

Wizzard033 avatar Jun 21 '19 12:06 Wizzard033

I think there's some improvement to be made for when scaling up, but I wasn't able to get these changes suggested here to work well in the pixel-perfect test program which doesn't use scaling.

Have you adjusted the image snapping settings for your program? SDL_gpu tries to make the default behavior align images to the pixel grid, but this may not be what you need for your texture atlas (or, of course, there could be a bug here). If you haven't yet, try GPU_SetSnapMode(image, GPU_SNAP_NONE); to see if you get the results you're expecting.

If you're still having an issue, can you provide a simple program to demonstrate the texel bleeding? I'd love to get that fixed.

grimfang4 avatar Jun 21 '19 15:06 grimfang4

I don't have a setup to compile C code using SDL_gpu quickly, and so I just wrote a Lua script for my game engine so that I could show this off faster. The below code should be easy to replicate in C.


I expect to have no texture bleeding in the following code

local TILESET_TILE_SIZE = 8
local _, image
local scale = C.floatv(1, 1)
game.onStart:Add("TEST", function()
    _, image = resources.LoadImage("default_tileset.png")
    gpu.SetSnapMode(image, gpu.SNAP_NONE)
end)
game.onGUI:Add("TEST", function()
    im.SliderFloat("Scale", scale, 0.1, 10, nil, 1)
end)
game.onRender:Add("TEST", function(screen)
    local w, h = scale[0] * TILESET_TILE_SIZE, scale[0] * TILESET_TILE_SIZE
    local tw, th = TILESET_TILE_SIZE, TILESET_TILE_SIZE
    for i = 0, 15 do
        if i % 2 == 0 then
            for j = 0, 15 do
                if j % 2 == 0 then
                    local x, y = j * scale[0] * TILESET_TILE_SIZE, i * scale[0] * TILESET_TILE_SIZE
                    local tx, ty = j * TILESET_TILE_SIZE, i * TILESET_TILE_SIZE
                    gpu.BlitRect(image, gpu.MakeRect(tx, ty, tw, th), screen, gpu.MakeRect(x, y, w, h))
                end
            end
        end
    end
end)

Result of above code (note the grass tiles bleeding into rock tiles)

test_fail


After testing my "fix" a bit, it seems that it doesn't work quite right. It removes the bleeding, but the tiles then become zoomed in, where as they should be pixel-perfect.

Here's the default_tileset.png I was testing with default_tileset

Wizzard033 avatar Jun 22 '19 11:06 Wizzard033

In my experience, when one pixel bleeds in into a sprite from a sprite next to it in the atlas, on the opposite side of the sprite one pixel is cut out. Here's a visual of the problem:

Screen Shot 2020-08-24 at 10 33 59

Should be: Screen Shot 2020-08-24 at 11 57 07

Note that not only a white line is bleeding in from below, but also a pixel is missing from above (the hand is missing the topmost line of pixels).

Replacing the lines I referenced in the first post with the following ought to do the trick

 // Scale src_rect tex coords according to actual texture dims 
 x1 = (src_rect->x + 0.5)/(float)tex_w; 
 y1 = (src_rect->y + 0.5)/(float)tex_h; 
 x2 = (src_rect->x + src_rect->w - 0.5)/(float)tex_w; 
 y2 = (src_rect->y + src_rect->h - 0.5)/(float)tex_h; 

This solution fixes the bleeding, but it accentuates cutting pixels out and causes lines to be drawn twice within the sprite. Note this is all using a NEAREST filter. See the mustache in this image as an example (while the hand is still cut):

Screen Shot 2020-08-24 at 11 55 38

Edit: This seems to happen even without scaling when I render to a texture (?). If you think my issue is different from the OP, let me know and I'll move this to a separate issue.

Edit2: I'm pretty sure at this point this is a different issue. I've opened a new issue (204) explaining my problem.

albertvaka avatar Aug 24 '20 09:08 albertvaka