Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

I'm in the process of learning Open GL and am having issues with lighting on my Terrian, I don't know if the issue is related to how I calculate my normals or the shader itself (I am using the shader from the Open GL wikibook - Lighting Arc tutorial 1). I calculate the normals like this:

void Terrian::construct_normals()  {
    terrian_normals.reserve(width * height);

    for (int i = 0; i < terrian_elements.size() / 3; i++)  {
         int index1 = terrian_elements.at(i * 3);
         int index2 = terrian_elements.at(i * 3 + 1);
         int index3 = terrian_elements.at(i * 3 + 2);

         glm::vec3 side1 = terrian_vertices.at(index1) - terrian_vertices.at(index3);
         glm::vec3 side2 = terrian_vertices.at(index1) - terrian_vertices.at(index2);
         glm::vec3 normal = glm::cross(side1, side2);
         normal = glm::normalize(normal);

         terrian_normals.emplace_back(normal);
         terrian_normals.emplace_back(normal);
         terrian_normals.emplace_back(normal);
    }


    std::cout << "Terrian Vertices: " << terrian_vertices.size() << "\nTerrian Normals: " << terrian_normals.size();
}

And the vertex shader can be viewed here:

http://pastebin.com/3vU8SHvK

Here's what the terrain looks like:

enter image description here

If you would like me to post any more of my code I would be happy to do so.

Edit:

Vertices:

void Terrian::construct_vertices()  {
    terrian_vertices.reserve(width * height);
    terrian_colors.reserve(width * height);

    std::cout << "Generating data\n";

    for (int y = 0; y < height; y++)  {
        for (int x = 0; x < width; x++)  {
            int i = x + y * width;

            terrian_vertices.emplace_back(glm::vec3(x, terrian_heightmap[y][x], -y));
            terrian_colors.emplace_back(Color(0.0, 1.0, 0.0, true));
        }
    }
}

and elements:

void Terrian::construct_elements()  {
    terrian_elements.reserve((width - 1) * (height - 1) * 6);

    for (int y = 0; y < height - 1; y++)  {
        for (int x = 0; x < width - 1; x++)  {
            GLushort bottom_left = x + y * width;
            GLushort bottom_right = (x + 1) + y * width;
            GLushort top_left = x + (y + 1) * width;
            GLushort top_right = (x + 1) + (y + 1) * width;

            terrian_elements.emplace_back(top_left);
            terrian_elements.emplace_back(bottom_right);
            terrian_elements.emplace_back(bottom_left);

            terrian_elements.emplace_back(top_left);
            terrian_elements.emplace_back(top_right);
            terrian_elements.emplace_back(bottom_right);
        }
    }
}
share|improve this question

2 Answers 2

up vote 2 down vote accepted
+50

Try this instead:

void Terrian::construct_normals()  {
    terrian_normals.resize(width * height, glm::vec3(0.f)); // Resize and initialize to zero

    for (int i = 0; i < terrian_elements.size() / 3; i++)  {
         int index1 = terrian_elements.at(i * 3);
         int index2 = terrian_elements.at(i * 3 + 1);
         int index3 = terrian_elements.at(i * 3 + 2);

         glm::vec3 side1 = terrian_vertices.at(index1) - terrian_vertices.at(index3);
         glm::vec3 side2 = terrian_vertices.at(index1) - terrian_vertices.at(index2);
         glm::vec3 normal = glm::cross(side1, side2);
         normal = glm::normalize(normal); // <- this is optional

         // accumulate normals for each triangle
         terrian_normals.at(index1) += normal;
         terrian_normals.at(index2) += normal;
         terrian_normals.at(index3) += normal;
    }

    // normalize all the accumulated normals
    for (std::size_t i=0; i<terrian_normals.size(); ++i)
        terrian_normals[i]=glm::normalize(terrian_normals[i]);

    std::cout << "Terrian Vertices: " << terrian_vertices.size() << "\nTerrian Normals: " << terrian_normals.size();
}

This code generates point normals by "averaging" the triangle normals of the adjacent triangles. I assumed this is what you wanted anyways. Note that this is an arbitrary method to generate point normals. Strictly speaking, points to not have normals. But for lighting, it is usually fine to generate them like this - it "fakes" a smoother surface. I normally leave out the normalization of the triangle normals - it biases the final normals to the directions with greater surface (on a terrain, these would be the slopes). Just experiment with it a little.

Alternatively, you can also derive the normals by constructing partial derivatives of your heightmap. This is a lot faster, but needs special treatment on the broders.

share|improve this answer
    
Great answer! Thanks! Are there any tutorials you could recommend regarding lighting techniques and such? –  Darestium Aug 6 '12 at 10:48

I assume you are using glDrawElements, and not the (deprecated) glVertex* functions. Then you have to decide, if

  • you want to have 1 normal per vertex, which means you have to average the normals of all faces adjacent to the vertex, or
  • you want to have 3 normals per triangle, which means you have to duplicate the vertices and store 3 normals for each face.

Currently your code looks like a mixture of both. You're adding 3 normals per terrain triangle, but your terrian_normals.reserve() call only allocates one normal for each vertex. I suspect your terrian_vertices and terrian_normals arrays have different size, and you're not properly aligning vertices with their normals during rendering.

share|improve this answer
    
Yes, I am using this: "glDrawElements(GL_TRIANGLES, size / sizeof(GLushort), GL_UNSIGNED_SHORT, 0);" to draw my terrain. –  Darestium Jul 29 '12 at 0:50
    
How do I go about implementing your first suggestion? –  Darestium Jul 29 '12 at 2:19

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.