Skip to content

Commit a55c68d

Browse files
authored
Fix texture coordinate offsets (#2355)
* Remove half pixel offset from uvdata * Attempt to fix border uv offsets * Fix ninepatch
1 parent 0eb7643 commit a55c68d

File tree

8 files changed

+80
-29
lines changed

8 files changed

+80
-29
lines changed

arcade/draw/rect.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,18 +56,20 @@ def draw_texture_rect(
5656
ctx.disable(ctx.BLEND)
5757

5858
atlas = atlas or ctx.default_atlas
59+
program = ctx.sprite_program_single
5960

6061
texture_id, _ = atlas.add(texture)
6162
if pixelated:
6263
atlas.texture.filter = gl.NEAREST, gl.NEAREST
64+
program.set_uniform_safe("uv_offset_bias", 0.0)
6365
else:
6466
atlas.texture.filter = gl.LINEAR, gl.LINEAR
67+
program.set_uniform_safe("uv_offset_bias", 1.0)
6568

6669
atlas.texture.use(unit=0)
6770
atlas.use_uv_texture(unit=1)
6871

6972
geometry = ctx.geometry_empty
70-
program = ctx.sprite_program_single
7173
program["pos"] = rect.center_x, rect.center_y, 0
7274
program["color"] = color.normalized
7375
program["size"] = rect.width, rect.height

arcade/resources/system/shaders/gui/nine_patch_gs.glsl

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,6 @@ void main() {
6767
vec2 uv0, uv1, uv2, uv3;
6868
vec2 atlas_size = vec2(textureSize(sprite_texture, 0));
6969
getSpriteUVs(uv_texture, int(texture_id), uv0, uv1, uv2, uv3);
70-
// TODO: Do center pixel interpolation. Revert by 0.5 pixels for now
71-
vec2 half_px = 0.5 / atlas_size;
72-
uv0 -= half_px;
73-
uv1 += vec2(half_px.x, -half_px.y);
74-
uv2 += vec2(-half_px.x, half_px.y);
75-
uv3 += half_px;
7670

7771
// Local corner offsets in pixels
7872
float left = start.x;

arcade/resources/system/shaders/sprites/sprite_list_geometry_cull_geo.glsl

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,13 @@ uniform WindowBlock {
1010
mat4 view;
1111
} window;
1212

13+
// Texture atlas
14+
uniform sampler2D sprite_texture;
15+
// Texture containing UVs for the entire atlas
1316
uniform sampler2D uv_texture;
17+
// How much half-pixel offset to apply to the UVs.
18+
// 0.0 is no offset, 1.0 is half a pixel offset
19+
uniform float uv_offset_bias;
1420

1521
in float v_angle[];
1622
in vec4 v_color[];
@@ -49,11 +55,26 @@ void main() {
4955
vec2 uv0, uv1, uv2, uv3;
5056
getSpriteUVs(uv_texture, int(v_texture[0]), uv0, uv1, uv2, uv3);
5157

58+
// Apply half pixel offset modified by bias.
59+
// What bias to set depends on the texture filtering mode.
60+
// Linear can have 1.0 bias while nearest should have 0.0 (unless scale is 1:1)
61+
// uvs (
62+
// 0.0, 0.0,
63+
// 1.0, 0.0,
64+
// 0.0, 1.0,
65+
// 1.0, 1.0
66+
// )
67+
vec2 hp = 0.5 / textureSize(sprite_texture, 0) * uv_offset_bias;
68+
uv0 += hp;
69+
uv1 += vec2(-hp.x, hp.y);
70+
uv2 += vec2(hp.x, -hp.y);
71+
uv3 += -hp;
72+
5273
// Set the out color for all vertices
5374
gs_color = v_color[0];
5475
// Upper left
5576
gl_Position = mvp * vec4(rot * vec2(-hsize.x, hsize.y) + center.xy, center.z, 1.0);
56-
gs_uv = uv0;
77+
gs_uv = uv0;
5778
EmitVertex();
5879

5980
// lower left

arcade/resources/system/shaders/sprites/sprite_list_geometry_fs.glsl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#version 330
22

3+
// Texture atlas
34
uniform sampler2D sprite_texture;
5+
// Global color set on the sprite list
46
uniform vec4 spritelist_color;
57

68
in vec2 gs_uv;
@@ -11,6 +13,7 @@ out vec4 f_color;
1113
void main() {
1214
vec4 basecolor = texture(sprite_texture, gs_uv);
1315
basecolor *= gs_color * spritelist_color;
16+
// Alpha test
1417
if (basecolor.a == 0.0) {
1518
discard;
1619
}

arcade/resources/system/shaders/sprites/sprite_list_geometry_no_cull_geo.glsl

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,13 @@ uniform WindowBlock {
1010
mat4 view;
1111
} window;
1212

13+
// Texture atlas
14+
uniform sampler2D sprite_texture;
15+
// Texture containing UVs for the entire atlas
1316
uniform sampler2D uv_texture;
17+
// How much half-pixel offset to apply to the UVs.
18+
// 0.0 is no offset, 1.0 is half a pixel offset
19+
uniform float uv_offset_bias;
1420

1521
in float v_angle[];
1622
in vec4 v_color[];
@@ -36,6 +42,21 @@ void main() {
3642
vec2 uv0, uv1, uv2, uv3;
3743
getSpriteUVs(uv_texture, int(v_texture[0]), uv0, uv1, uv2, uv3);
3844

45+
// Apply half pixel offset modified by bias.
46+
// What bias to set depends on the texture filtering mode.
47+
// Linear can have 1.0 bias while nearest should have 0.0 (unless scale is 1:1)
48+
// uvs (
49+
// 0.0, 0.0,
50+
// 1.0, 0.0,
51+
// 0.0, 1.0,
52+
// 1.0, 1.0
53+
// )
54+
vec2 hp = 0.5 / textureSize(sprite_texture, 0) * uv_offset_bias;
55+
uv0 += hp;
56+
uv1 += vec2(-hp.x, hp.y);
57+
uv2 += vec2(hp.x, -hp.y);
58+
uv3 += -hp;
59+
3960
// Set the out color for all vertices
4061
gs_color = v_color[0];
4162
// Upper left

arcade/sprite_list/sprite_list.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1103,6 +1103,14 @@ def draw(
11031103

11041104
self.program["spritelist_color"] = self._color
11051105

1106+
# Control center pixel interpolation:
1107+
# 0.0 = raw interpolation using texture corners
1108+
# 1.0 = center pixel interpolation
1109+
if self.ctx.NEAREST in atlas_texture.filter:
1110+
self.program.set_uniform_safe("uv_offset_bias", 0.0)
1111+
else:
1112+
self.program.set_uniform_safe("uv_offset_bias", 1.0)
1113+
11061114
atlas_texture.use(0)
11071115
atlas.use_uv_texture(1)
11081116
if not self._geometry:

arcade/texture_atlas/region.py

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -89,26 +89,25 @@ def __init__(
8989
# Width and height
9090
_width = self.width / atlas.width
9191
_height = self.height / atlas.height
92-
# Half pixel correction
93-
hp_x, hp_y = 0.5 / atlas.width, 0.5 / atlas.height
92+
9493
# Upper left corner. Note that are mapping the texture upside down and corners
9594
# are named as from the vertex position point of view.
9695
ul_x, ul_y = self.x / atlas.width, self.y / atlas.height
9796

9897
# upper_left, upper_right, lower_left, lower_right
9998
self.texture_coordinates = (
10099
# upper_left
101-
ul_x + hp_x,
102-
ul_y + hp_y,
100+
ul_x,
101+
ul_y,
103102
# upper_right
104-
ul_x + _width - hp_x,
105-
ul_y + hp_y,
103+
ul_x + _width,
104+
ul_y,
106105
# lower_left
107-
ul_x + hp_x,
108-
ul_y + _height - hp_y,
106+
ul_x,
107+
ul_y + _height,
109108
# lower_right
110-
ul_x + _width - hp_x,
111-
ul_y + _height - hp_y,
109+
ul_x + _width,
110+
ul_y + _height,
112111
)
113112

114113
def verify_image_size(self, image_data: ImageData):

tests/unit/atlas/test_region.py

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,34 +7,37 @@
77
from arcade.texture_atlas.atlas_default import DefaultTextureAtlas
88

99

10-
def test_region_coordinates(ctx):
11-
"""Test region class."""
10+
def test_region_coordinates_simple(ctx):
11+
"""Basic region test."""
1212
atlas = DefaultTextureAtlas(size=(8, 8), border=0, auto_resize=False)
1313
region = AtlasRegion(atlas=atlas, x=0, y=0, width=8, height=8)
1414
assert region.x == 0
1515
assert region.y == 0
1616
assert region.width == 8
1717
assert region.height == 8
1818
# Simulate the half pixel location
19-
a, b = 0.5 / 8, 1 - 0.5 / 8
19+
a, b = 0, 1.0
2020
assert region.texture_coordinates == (
2121
a, a,
2222
b, a,
2323
a, b,
2424
b, b,
2525
)
26-
# Above raw values:
27-
# (
28-
# 0.0625, 0.0625,
29-
# 0.9375, 0.0625,
30-
# 0.0625, 0.9375,
31-
# 0.9375, 0.9375)
32-
# )
26+
27+
28+
def test_region_coordinates_complex(ctx):
29+
"""A more complex region test."""
30+
atlas = DefaultTextureAtlas(size=(16, 16), border=0, auto_resize=False)
31+
region = AtlasRegion(atlas=atlas, x=1, y=2, width=8, height=6)
32+
assert region.x == 1
33+
assert region.y == 2
34+
assert region.width == 8
35+
assert region.height == 6
36+
assert region.texture_coordinates == (0.0625, 0.125, 0.5625, 0.125, 0.0625, 0.5, 0.5625, 0.5)
3337

3438

3539
def test_verify_size(ctx):
3640
im_data = ImageData(PIL.Image.new("RGBA", (8, 8)))
37-
texture = Texture(im_data)
3841
region = AtlasRegion(atlas=ctx.default_atlas, x=0, y=0, width=8, height=8)
3942

4043
region.verify_image_size(im_data)

0 commit comments

Comments
 (0)