Repeat Tile From Texture Atlas

#1

I’ve got a question regarding Texture Atlases and tiling specific tiles from that atlas. Currently I have my project set up so that I calculate the tile UV texture coordinates for each vertex before passing into glsl shader. I was wondering if it was possible to instead having one large texture of the tile on my quad that I may have it smaller(Preferably I would like to pass in a uniform to indicate the amount of times to tile) and tiled multiple times instead. I’ve looked around at some other similar questions and I have not found a working solution as of yet. Right now my fragment shader does not contain any calculations besides retrieving the color of the pixel from the texture() function. I’ve also heard using texture arrays is another solution, but I am unfamiliar with them and their performance. Can someone help point me in the right direction? Thanks.

#2

So you want the same effect as setting the wrap mode to GL_REPEAT, but for a tile rather than the entire texture, right?

You need to pass in the size of the tile and its position within the atlas, both as fractions of the texture’s size (i.e. in the range [0,1]). E.g.

uniform vec2 position;
uniform vec2 size;
...
texcoord = position + size * frac(texcoord);

If you’re using mipmaps, there’s the added complication that you can’t use the implicit derivatives generated by texture(), as those will be incorrect when you wrap around the edge of a tile. Instead, you need to calculate derivatives from the original texture coordinates and use textureGrad(), e.g.:

uniform vec2 position;
uniform vec2 size;
...
vec2 dx = dFdx(texcoord);
vec2 dy = dFdy(texcoord);
texcoord = position + size * frac(texcoord);
vec4 color = textureGrad(tex, texcoord, dx, dy);

A 2D texture array is similar to a 3D texture in that it has a third dimension. But unlike a 3D texture, mipmaps are only in 2 dimensions (so for e.g. a 64x64x16 2D texture array, the next mipmap level would be 32x32x16, not 32x32x8), filtering is only performed in 2D dimensions, and the third texture coordinate is unnormalised (it’s just rounded to the nearest integer to select the layer). 2D texture arrays are created using glTexImage3D with the target GL_TEXTURE_2D_ARRAY and accessed via a uniform of type sampler2DArray in the shader.

If you’re using a 2D texture array, each tile would be the full width and height of the texture so you can just use GL_REPEAT. Texture arrays require OpenGL 3.0 or the EXT_texture_array extension.

#3

Thanks for the reply, I’ll try this out and let you know how it works when I return home.

#4

Ok so I tried your initial suggestion, I am just getting a white cube. I dont think I am understanding corretly though. Here is my current shader…

#version 330

// INPUT
in vec2 TexCoords;

// OUTPUT
out vec4 color;

// UNIFORM
uniform sampler2D diffuse;

void main(void)
{
	// Hardcoded Vars (Atlas contains 16x16 tiles)
	vec2 position = vec2(1/16, 1/16);	// I assume gets tile at (1, 1) since the size of the tiles are 1/16
	vec2 size = vec2(1/16, 1/16);		// Each tile is 1/16 of the atlas

	// Result
	vec2 coord = position + size * fract(TexCoord);

	// Output
	color = texture(diffuse, coord);
}

Before I was calculating the UV coordinates for the specific tile outside the shader like so (index being location in atlas)…

	float col = index % 16;
	float x = col / 16;

	float row = index / 16;
	float y = row / 16;

        // Coord 1
	uv.push_back((1.0f / 16.0f) + x);
	uv.push_back((0.0f / 16.0f) + y);
        // Coord 2
	uv.push_back((0.0f / 16.0f) + x);
	uv.push_back((0.0f / 16.0f) + y);
        // Coord 3
	uv.push_back((0.0f / 16.0f) + x);
	uv.push_back((1.0f / 16.0f) + y);
        // Coord 4
	uv.push_back((1.0f / 16.0f) + x);
	uv.push_back((1.0f / 16.0f) + y);

Before the changes my shader just looked like (which results in one large tile from the atlas)…

#version 330

// INPUT
in vec2 TexCoords;

// OUTPUT
out vec4 color;

// UNIFORM
uniform sampler2D diffuse;

void main(void)
{

	// Output
	color = texture(diffuse, TexCoords);
}

Hopefully Im not being confusing, if you need anymore context or code just ask and Ill provide. Thanks for the help.

#5

1/16 is zero. Use 1.0/16. Or vec2(1,1)/16.

#6

I cant believe I forgot that! It works great now! Thank you, you have saved me a great deal of time.