Search code examples
c++opengltexturesglm-math

How to fix texturing a terrain?


I have coded a flat terrain made up of triangles, but it seems like there some mirroring occurs. I want it to imitate grass, but it is blurried in some spots. Do I need to add some params in glTexParameteri? Or maybe it is an error which is associated with the drawing code?

enter image description here

A function which reads in a texture:

GLuint Terrain::write_model_texture(const char* filename)
{

    GLuint tex;
    // Activate texture 0
    glActiveTexture(GL_TEXTURE0);

    // Read into computers memory
    std::vector<unsigned char> image;
    unsigned width, height;

    // Read the image
    unsigned error = lodepng::decode(image, width, height, filename);

    // Import to graphics card memory
    glGenTextures(1, &tex);                     //Initialize one handle
    glBindTexture(GL_TEXTURE_2D, tex); //Activate handle

    // Copy image to graphics cards memory represented by the active handle
    glTexImage2D(GL_TEXTURE_2D, 0, 4, width, height, 0,
        GL_RGBA, GL_UNSIGNED_BYTE, (unsigned char*)image.data());

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    return tex;
}

Also code which draws triangles:

this->P = glm::mat4(1.0f);
    this->V = glm::mat4(1.0f);
    this->M = glm::mat4(1.0f);

    P = in.P;
    V = in.V;

    
    //M = glm::rotate(M, glm::radians(90.0f), glm::vec3(1.0f, 0.0f, 0.0f));
    M = glm::translate(M, glm::vec3(-5.0f, -5.0f, 0.0f));
    //M = glm::scale(M, glm::vec3(10.0f, 10.0f, 10.0f));
    
    for (int row = 0; row < terrain_height; row++)
    {
        int col;
        // adding a row of vertices
        for (col = 0; col < terrain_width; col++) {
            // x, y, z, 1
            
            //std::cout << random_num << std::endl;
            terrain_verts.emplace_back(col, row, 0, 1);
            terrain_norms.emplace_back(0.0f, 0.0f, 1.0f, 0);
        }

        // adding a row of indices
        for (col = 0; col < terrain_width; col++)
        {
            terrain_indices.emplace_back(col + row * terrain_width);
            terrain_indices.emplace_back(col + row * terrain_width + 1);
            terrain_indices.emplace_back(col + terrain_width * (row + 1) - 1);
        }

        for (col = terrain_width - 1; col >= 0; col--)
        {
            terrain_indices.emplace_back(col + row * terrain_width);
            terrain_indices.emplace_back(col + terrain_width * (row + 1) - 1);
            terrain_indices.emplace_back(col + terrain_width * (row + 1));
        }

        // adding a row of texture coordinates
        if (row % 2 == 0)
        {
            for (col = 0; col < terrain_width; col += 2)
            {
                terrain_texture_coordinates.emplace_back(0, 0);
                terrain_texture_coordinates.emplace_back(1, 0);
            }
            
        }
        else
        {
            for (col = 0; col < terrain_width; col += 2)
            {
                terrain_texture_coordinates.emplace_back(0, 1);
                terrain_texture_coordinates.emplace_back(1, 1);

            }
        }
    }

    spTextured->use();
    glUniformMatrix4fv(spTextured->u("P"), 1, false, glm::value_ptr(P));
    glUniformMatrix4fv(spTextured->u("V"), 1, false, glm::value_ptr(V));
    glEnableVertexAttribArray(spTextured->a("vertex"));
    glEnableVertexAttribArray(spTextured->a("texCoord"));
    glEnableVertexAttribArray(spTextured->a("normal"));

    glUniformMatrix4fv(spTextured->u("M"), 1, false, glm::value_ptr(M));

    glVertexAttribPointer(spTextured->a("vertex"), 4, GL_FLOAT, false, 0, terrain_verts.data());
    glVertexAttribPointer(spTextured->a("texCoord"), 2, GL_FLOAT, false, 0, terrain_texture_coordinates.data());
    glVertexAttribPointer(spTextured->a("normal"), 4, GL_FLOAT, false, 0, terrain_norms.data());

    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, tex);
    glUniform1i(spTextured->u("tex"), 0);

    glDrawElements(GL_TRIANGLES, terrain_indices_count(), GL_UNSIGNED_INT, terrain_indices.data());

    
    glDisableVertexAttribArray(spTextured->a("vertex"));
    glDisableVertexAttribArray(spTextured->a("color"));
    glDisableVertexAttribArray(spTextured->a("normal"));

Solution

  • Your indices do not make the slightest sense:

    for (col = 0; col < terrain_width; col++)
    {
        terrain_indices.emplace_back(col + row * terrain_width);
        terrain_indices.emplace_back(col + row * terrain_width + 1);
        terrain_indices.emplace_back(col + terrain_width * (row + 1) - 1);
    }
    
    for (col = terrain_width - 1; col >= 0; col--)
    {
        terrain_indices.emplace_back(col + row * terrain_width);
        terrain_indices.emplace_back(col + terrain_width * (row + 1) - 1);
        terrain_indices.emplace_back(col + terrain_width * (row + 1));
    }
    

    If you look at your grid of data:

    row = 0:  0 --- 1 --- 2 --- 3
              |     |     |     |
              |     |     |     |
    row = 1:  4 --- 5 --- 6 ----7
    
              ^     ^     ^     ^
             col=0  1     2     3  
    

    For row 0, column 0, you generate two triangles with the following vertices:

    1. 0, 1, 3 which is a deformed triangle with zero area
    2. 0, 3, 4 which goes completely across your grid:

    For inner cells like row=0, col=1, you get:

    1. 1, 2, 4 which crosses 2 grid cells
    2. 1, 4, 5 which is at least belonging to a grid cell (although not the one it should)

    You can actually see these patterns in your screenshot, you just need to take into account that you draw lots of weirdly overlapping triangles.

    Your tex coords also won't work that way. You generate tex coords for alternating rows like this:

    row = 0:  (0,0) --- (1,0) 
                |         |
                |         |
    row = 1:  (0,1) --- (1,1)
                |         |
                |         |
    row = 2:  (0,0) --- (1,0)
    

    If you map texcoords that way, the cells between row 1 and row 2 will have the image vertically mirrored to those between row 0 and 1. You can't share the vertices of a row for the grid cella below and above it that way, you would have to duplicate the row vertices with different texcoords to make that work.

    However, that is not necessary as you can use GL_REPEAT texture wrap mode and simply use texoords outside the [0,1] range like:

    row = 0:  (0,0) --- (1,0) --- (2,0)
                |         |         |
                |         |         |
    row = 1:  (0,1) --- (1,1) --- (2,1)
                |         |         |
                |         |         |
    row = 2:  (0,2) --- (1,2) --- (2,2)