Calculating texture coordinates from a heightmap
Asked Answered
H

1

5

I am currently building a height map terrain generator using OpenGL. It's a simple program that loads a height map image, iterates over the image data and generates vertices, indices and normals. At its current state it can render a height map with a single colour based on the normals.

enter image description here

My problem is generating correct UV coordinates for the diffuse map. It just comes out wrong:

enter image description here

This is the diffuse map I am trying to load:

enter image description here

Here is what I currently have:

Generate Vertices, Normals and Indices

// Generate Vertices and texture coordinates
    for (int row = 0; row <= this->imageHeight; row++)
    {
        for (int column = 0; column <= this->imageWidth; column++)
        {
            float x = (float)column / (float)this->imageWidth;
            float y = (float)row / (float)this->imageHeight;

            float pixel = this->imageData[this->imageWidth * row + column];

            float z;
            if (row == this->imageHeight || column == this->imageWidth || row == 0 || column == 0)
            {
                z = 0.0f;
            }
            else
            {
                z = float(pixel / 256.0)*this->scale; 
            }

            MeshV3 mesh;
            mesh.position = glm::vec3(x, y, z);
            mesh.normal = glm::vec3(0.0, 0.0, 0.0);
            mesh.texture = glm::vec2(x, y);

            this->mesh.push_back(mesh);

        }
    }

// Generate indices
    for (int row = 0; row < this->imageHeight; row++)
    {
        for (int column = 0; column < this->imageWidth; column++)
        {
            int row1 = row * (this->imageWidth + 1);
            int row2 = (row + 1) * (this->imageWidth + 1);

            // triangle 1
            this->indices.push_back(glm::uvec3(row1 + column, row1 + column + 1, row2 + column + 1));

            // triangle 2
            this->indices.push_back(glm::uvec3(row1 + column, row2 + column + 1, row2 + column));
        }
    }

// Generate normals
    for (int i = 0; i < this->indices.size(); i++)
    {
        glm::vec3 v1 = this->mesh[this->indices[i].x].position;
        glm::vec3 v2 = this->mesh[this->indices[i].y].position;
        glm::vec3 v3 = this->mesh[this->indices[i].z].position;

        glm::vec3 edge1 = v1 - v2;
        glm::vec3 edge2 = v1 - v3;
        glm::vec3 normal = glm::normalize(glm::cross(edge1, edge2));

        this->mesh[this->indices[i].x].normal += normal;
        this->mesh[this->indices[i].y].normal += normal;
        this->mesh[this->indices[i].z].normal += normal;
    }

I load the diffuse map with the following method

void Terrein::getDIffuseMap()
{
    glGenTextures(1, &this->texture);
    glBindTexture(GL_TEXTURE_2D, this->texture); // all upcoming GL_TEXTURE_2D operations now have effect on this texture object

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

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

    int width, height, nrChannels;

    std::string path = "assets/diffuse.jpg";
    this->diffuseData = stbi_load(path.c_str(), &width, &height, &nrChannels, 0);
    if (this->diffuseData)
    {
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, this->diffuseData);
        glGenerateMipmap(GL_TEXTURE_2D);
    }
    else
    {
        std::cout << "Failed to load diffuse texture" << std::endl;
    }
}

I can't seem to figure out what might be wrong here. Is there an issue with how I am loading the image? Or am I not calculating the texture coordinates coorectly? Please let me know if there is anything else I should provide. I have been stuck at this for a few days now. Thanks!

Harriettharrietta answered 18/4, 2019 at 4:11 Comment(3)
Not your problem, but your calculation of float pixel probably accesses outside the bounds of imageData. Since pixel is only used in the else part of the following if, move pixel into the body of the else.Strunk
where are your shaders?Platinic
@Strunk Thank you, I was getting an out of bounds error on the imageData. A stupid mistake on my part, and it fixed after taking your suggestion.Harriettharrietta
F
7

By default OpenGL assumes that the start of each row of an image is aligned to 4 bytes.

This is because the GL_UNPACK_ALIGNMENT parameter by default is 4.

Since the image has 3 color channels (GL_RGB), and is tightly packed the size of a row of the image may not be aligned to 4 bytes.

When a RGB image with 3 color channels is loaded to a texture object, then GL_UNPACK_ALIGNMENT has to be set to 1:

glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0,
             GL_RGB, GL_UNSIGNED_BYTE, this->diffuseData);

The diffuse image in the question has a dimension of 390x390. So each row of the image has a size of 390 * 3 = 1170 bytes.
Since 1170 is not divisible by 4 (1170 / 4 = 292,5), the start of a row is not aligned to 4 bytes.

Related question: Failing to map a simple unsigned byte rgb texture to a quad

Format answered 18/4, 2019 at 4:42 Comment(4)
Thank you! This completely resolved the issue and I learned something new. So in the future, I should use a texture image that is a divisible of 4. I updated the question with the working solution.Harriettharrietta
@AaronZaman You're welcome. If you use RGBA images, then this won't be an issue, because the size of each pixel is 4 and thus the length of each row is divisible by 4.Format
I actually thought about using RGBA, but then I stopped doing it as I thought I wouldn't need an alpha pass. But I do.Harriettharrietta
@AaronZaman Of course it needs some more memory, but there is nothing different in handling, if the alpha channel is 255 respectively 1.0.Format

© 2022 - 2024 — McMap. All rights reserved.