Sarah Yoo

The Secrete in Storing Many Textures

How do large games like Sea of Thieves or Minecraft store so many textures? Having over 1000 of texture files would absolutely make the games exploded. The solution is texture atlasing: squeeze as many textures as possible into one file and access each texture by their index.

For instance, this is the texture atlas I use: Texture Atlase

There are a total of 16 x 16 = 256 textures in my texture atlas. A texture atlas is a large image that contains many smaller textures packed together in a grid. This helps reduce the number of separate texture files the game needs to load.

In OpenGL, texture coordinates range as follow:

  • Top-left corner of the atlas: (0, 0)
  • Top-right corner: (1, 0)
  • Bottom-left corner: (0, 1)
  • Bottom-right corner: (1, 1)

Since my atlas is divided into a 16 × 16 grid, each texture inside it takes up 1/16 of the width and height, or 1/16 = 0.0625 in OpenGL coordinates. So, for instance, the top-left coordinate of the sand texture in the second row would be (3/16, 2/16).

How I Convert This Texture Index Into a UV Coordinate

Recall that each vertex data stores a texture UV coordinate.

I'm only drawing cubes in my voxel engine. Each face in a cube is a square made of 4 vertices. Utilizing this fact, I use the modulo of the vertex index (from 0 to 3) to determine its corner position on a texture:

  • 0: Top-left of a square face
  • 1: Top-right
  • 2: Botton-right
  • 3: Bottom-left

To convert a texture index in the atlas into the correct UV coordinate for each vertex, I wrote the following helper function:

Drop to see the code

static vec2 convert_to_uv(int index, vec2 texture_coord) {
	float x = texture_coord.x;
	float y = texture_coord.y;
	if (index == 0) { //top-left
		return vec2((x - 1)/textures_columns, y/texture_rows);
	}
	else if (index == 1) { //top-right
		return vec2(x/textures_columns, y/texture_rows);
	}
	else if (index == 2) { //bottom-right
		return vec2(x/textures_columns, (y - 1)/texture_rows);
	}
	else if (index == 3) { //bottom-left
		return vec2((x - 1)/textures_columns, (y - 1)/texture_rows);
	}
	return vec2(-1, -1); //invalid index
}

Usage example:

Drop to see the code

//texture index in the atlas
vec2 texture_coord = texture_map[type][face]; 
//assign UV based on vertex corner. i is the index of a vertex used in EBO
vertex.texture = convert_to_uv(i % 4, texture_coord);