When drawing a 2D scene (ortho) I have a bunch of quads that read from one texture atlas to get their texture, I calculate the uv coordinates using this code:
float x = (uv.x * (_atlasRect.y - _atlasRect.x) + _atlasRect.x) / atlasSize.x;
float y = (uv.y * (_atlasRect.w - _atlasRect.z) + _atlasRect.z) / atlasSize.y;
This works perfectly, however, I want to also tile my textures using a vec2 _scaleFactor
. This is the number of times it should repeat. For horizontally stretched objects it is equal to (scale.x/scale.y, 1.0f)
and for vertically stretched objects it is equal to (1.0f, scale.y/scale.x)
. I multiply the u
v by the _scaleFactor
and then mod it by 1.0
in the fragment shader:
vec2 uv = _uv * _scaleFactor;
uv.x = mod(uv.x, 1.0);
uv.y = mod(uv.y, 1.0);
Which makes the uv look like this (before the atlas calculation).
This is great and works perfectly. Then it is scaled by the atlas calculation, which also works and gives an max x UV of 0.5 for the player (the atlas is 256x128 currently) with a min x uv of 0.5 for the ground, so this also works.
Then when I finally put the atlas UV coords into a texture sampler:
color = texture(tex, vec2(x, y));
It has a black artefact around the texture.
It appears on all sides on the player (droplet) with a scale factor of (1.000, 1.000) and only on top and bottom of the platform (dirt) with a scale factor of (6.000, 1.000), and dissapears at some x positions.
- If I don’t use mod() the artefact goes away (although the textures are wrong).
- If I don’t multiply by scale factor and don’t mod() there’s no artefact.
- If I use uv.x - fract(uv.x) (any uv.y) instead of mod(uv.x, 1.0) the artefact only appears.
- If I use uv.x - floor(uv.x) the artefact still appears.
I tried rewriting the mod function to use doubles, no change. I tried changing the texture wrap parameters from clamp to repeat, no change. I have tried clamping from 0.0 to 1.0, no change. The UVs look perfect when I output them as the colour, so how can this thin border appear when sampling the texture? It doesn’t appear to be a pixel from anywhere in the texture.
I have tried casting the floored coord to a float, no change. I have tried multiplying by scale factor in the vertex shader instead, no change. The artefact only appears when using mod() on the uv, or perhaps floor().
Using GTX 1080Ti driver 560.94 OpenGL core 4.5 (although the issue persists when using opengl 3.2 in the shader with #version) with SDL2. The scale factor, matrix and atlas rect are passed in via an instanced array buffer, the atlasSize is a uniform. All the sprites are drawn with one call to glDrawArraysInstanced(). The instanced array buffer is passed in properly (verified through nsight graphics 2022.4.1).Transparency is enabled with glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
. Face culling is disabled.
Everything was working perfectly until I tried to mod the uv coord.
Vertex Shader code:
#version 450 core
layout(location = 0) in vec3 position;
layout(location = 1) in vec2 uv;
layout(location = 2) in mat4 pvmMatrix;
layout(location = 6) in vec4 atlasRect;
layout(location = 7) in vec2 scaleFactor;
out vec2 _uv;
out vec4 _atlasRect;
out vec2 _scaleFactor;
void main()
{
gl_Position = pvmMatrix * vec4(position, 1.0);
_uv = uv;
_atlasRect = atlasRect;
_scaleFactor = scaleFactor;
}
Fragment Shader Code:
#version 450 core
in vec2 _uv;
in vec4 _atlasRect;
in vec2 _scaleFactor;
out vec4 color;
uniform sampler2D tex;
uniform vec2 atlasSize;
void main()
{
//convert 0-1 uv coords to coords relative to atlas
vec2 uv = _uv * _scaleFactor;
uv.x = mod(uv.x, 1.0);
uv.y = mod(uv.y, 1.0);
float x = (uv.x * (_atlasRect.y - _atlasRect.x) + _atlasRect.x) / atlasSize.x;
float y = (uv.y * (_atlasRect.w - _atlasRect.z) + _atlasRect.z) / atlasSize.y;
color = texture(tex, vec2(x, y));
}
Texture Creation:
glActiveTexture(GL_TEXTURE0); //select the default texture unit (to avoid messing up another texture unit's texture)
glGenTextures(1, &texture); //gen empty tex
glBindTexture(GL_TEXTURE_2D, texture); //bind it
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); //set wrapping values
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); //..
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR); //set filter values
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); //..
glTexImage2D(GL_TEXTURE_2D, 0, glType, width, height, 0, glType, GL_UNSIGNED_BYTE, data); //populate texture
glGenerateMipmap(GL_TEXTURE_2D); //generate mipmap
Originally I had these embedded seperately (not allowed), then as imgur links (not allowed) so here are my images all as one: