# Wrong UV calculation

For a personal project, I'm trying to implement a tilemap. My goal is to obtain this

But all I got is this

I am calculating the UV coordinates inside my shader, with this formula. 8 and 13 is the width and height of my tileset, in tile.

vs_uv = (1.0 / vec2(8, 13)) * (vec2(tile / 8, tile % 13) + vertex);

Where tile is one element of this array

GLint map[] = {
0,  1,  2,  3,  4,  5,  6,  7,
8,  9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 23,
};


and where vertex is line of this array

GLfloat vertices[] = {
0.0f, 1.0f,
1.0f, 0.0f,
0.0f, 0.0f,

0.0f, 1.0f,
1.0f, 0.0f,
1.0f, 1.0f
};


Here's the full code of the example :

#include <algorithm>
#include <memory>
#include <cstring>

#include <boost/filesystem.hpp>
#include <boost/multi_array.hpp>
#define GLEW_STATIC
#include <GL/glew.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <SDL2/SDL.h>

#include <pck/global.hpp>
#include <pck/program.hpp>
#include <pck/utils/stb_image.h>
#include <pck/window.hpp>

namespace fs = boost::filesystem;

const int WIDTH = 800, HEIGHT = 600;

GLint map[] = {
0,  1,  2,  3,  4,  5,  6,  7,
8,  9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 23,
};

GLuint map_w = 8;
GLuint map_h = 3;

int main()
{
// Wrapper around SDL2 window, also initializing OpenGL
pck::Global::window.reset(new pck::Window("Test_gl", WIDTH, HEIGHT));

// Vertices buffer
GLfloat vertices[] = {
0.0f, 1.0f,
1.0f, 0.0f,
0.0f, 0.0f,

0.0f, 1.0f,
1.0f, 0.0f,
1.0f, 1.0f
};

// Positions buffer
glm::vec2 positions[24];

int index = 0;
for(size_t j = 0; j < map_h; ++j)
{
for(size_t i = 0; i < map_w; ++i)
{
positions[index++] = glm::vec2(i, j);
}
}

// Texture declaration
GLuint tex_ID;
glGenTextures(1, &tex_ID);

int w(0), h(0), c(0);
unsigned char* data = stbi_load("tileset.png", &w, &h, &c, STBI_rgb_alpha);

if(data == nullptr)
{
std::cout << "Failed to load texture\n";
stbi_image_free(data);
return 1;
}

glBindTexture(GL_TEXTURE_2D, tex_ID);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

if(c == 3)
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
else if(c == 4)
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);

glGenerateMipmap(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, 0);

stbi_image_free(data);

GLuint VAO;
glGenVertexArrays(1, &VAO);

glBindVertexArray(VAO);
GLuint vertex_VBO;
glGenBuffers(1, &vertex_VBO);

glBindBuffer(GL_ARRAY_BUFFER, vertex_VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), (GLvoid*)0);
glBindBuffer(GL_ARRAY_BUFFER, 0);

// Also set instance data
GLuint positions_VBO;
glGenBuffers(1, &positions_VBO);

glBindBuffer(GL_ARRAY_BUFFER, positions_VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(glm::vec2) * 24, &positions[0], GL_STATIC_DRAW);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), (GLvoid*)0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glVertexAttribDivisor(1, 1);

GLuint tiles_VBO;
glGenBuffers(1, &tiles_VBO);

glBindBuffer(GL_ARRAY_BUFFER, tiles_VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(map), map, GL_STATIC_DRAW);
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 1, GL_INT, GL_FALSE, sizeof(GLint), (GLvoid*)0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glVertexAttribDivisor(2, 1);
glBindVertexArray(0);

#version 330 core
layout (location = 0) in vec2 vertex;
layout (location = 1) in vec2 position;
layout (location = 2) in int tile;

uniform mat4 model;
uniform mat4 projection;
uniform ivec2 tileset_tile_size;

out vec2 vs_uv;

void main()
{
gl_Position = projection * model * vec4(vertex + position, 0.0f, 1.0f);

vs_uv = (1.0 / vec2(8, 13)) * (vec2(tile / 8, tile % 13) + vertex);
}
)"));

#version 330 core

uniform sampler2D image;

in vec2 vs_uv;

out vec4 fs_color;

void main()
{
//fs_color = vec4(vs_uv, 0.0f, 1.0f);
fs_color = texture(image, vs_uv);
}
)"));

std::shared_ptr<pck::Program> program(new pck::Program(vs, fs));

// Uniforms
program->use();

glm::mat4 model;
model = glm::rotate(model, 0.0f, glm::vec3(0.0f, 0.0f, 1.0f));
model = glm::scale(model, glm::vec3(16.0f, 16.0f, 1.0f));

pck::Global::zoom = 2;
glm::mat4 projection = glm::ortho(0.0f, static_cast<GLfloat>(pck::Global::width / pck::Global::zoom),
static_cast<GLfloat>(pck::Global::height / pck::Global::zoom), 0.0f, -1.0f, 1.0f);

glm::ivec2 tileset_tile_size(8, 13);

glUniform2iv(glGetUniformLocation(program->ID(), "tileset_tile_size"), 1, glm::value_ptr(tileset_tile_size));

glUniformMatrix4fv(glGetUniformLocation(program->ID(), "projection"), 1, GL_FALSE, glm::value_ptr(projection));
glUniformMatrix4fv(glGetUniformLocation(program->ID(), "model"), 1, GL_FALSE, glm::value_ptr(model));

//glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

while(pck::Global::window->is_closed())
{
while(pck::Global::window->poll_event() != 0)
{
if(pck::Global::event->type == SDL_QUIT)
{
pck::Global::window->close();
}
else if(pck::Global::event->type == SDL_KEYDOWN)
{
if(pck::Global::event->key.keysym.sym == SDLK_ESCAPE)
pck::Global::window->close();
}
}

glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);

glBindTexture(GL_TEXTURE_2D, tex_ID);

program->use();

glBindVertexArray(VAO);
glDrawArraysInstanced(GL_TRIANGLES, 0, 6, 24);
glBindVertexArray(0);

pck::Global::window->update();
}

return 0;
}


I think the line you've highlighted is the problem:

vs_uv = (1.0 / vec2(8, 13)) * (vec2(tile / 8, tile % 13) + vertex);


Your vertices have coordinates that are between 0 and 1. Because your using GL_TEXTURE_2D, your texture coordinates need to be between 0 and 1. So the above calculation will always give values greater than 1, except for the first tile.

I would think that you'd need the width and height of your texture atlas to calculate this properly. Without seeing your texture atlas, it's hard to tell you the right thing to do, but if your tiles are laid out in a single row in the texture atlas, it should be something like this:

vs_uv = tile * tileSize;


where tileSize is the width and height in normalized coordinates of 1 tile of the texture atlas. So if you have 5 tiles that are 8 x 13 pixels laid out in a single row, your texture atlas will be 40 x 13. Normalizing that, 1 tile is 1/5 width and 1 height, so tileSize would be (0.2, 1.0).

• I don´t know if the formula you gave me is totally right, but the fact that the vertex value will give a value greater than 1 make sense. I´ll give you an update in a few hours. Thanks Commented Apr 5, 2017 at 16:37
• I changed the formula to vs_uv = (1.0 / vec2(8, 13)) * (vec2(tile / 8, tile % 13) + vec2(vertex.x / 8, vertex.y / 13));, and here's the result. I don't know what's wrong this time. Commented Apr 8, 2017 at 0:33
• If you'd post a picture of your texture atlas, that would help. Commented Apr 8, 2017 at 1:59
• First picture of the post is what I am trying to get on my screen. The full tileset is here Commented Apr 8, 2017 at 2:05

I finally found the right formula, where (8.0, 13.0) is the number of tiles in my tileset, in width and height.

vs_uv = (1.0 / vec2(8.0, 13.0)) * vec2(mod(tile, 8.0), floor(tile / 8.0)) + (vertex / vec2(8.0, 13.0));


Giving me this