0

I am trying to create a program that shows a wave-like animation using Perlin Noise by creating many triangles.

This is the important part of my program:

class OGLT9_NOISE
{
    //class for Perlin Noise (noise3d()) and Fractional Brownian Motion (fmb()) generaion
};

glm::vec3 OGLT9_GRAPHICS::getNormal(glm::vec3 a, glm::vec3 b, glm::vec3 c)
{
    return glm::normalize(glm::cross(c-a, b-a));
}

void generateTerrain(OGLT9_SHADER *oglt9Shader)
{
    static OGLT9_NOISE noise;
    static float yValue = 0;
    int terrainRes = 7;             //terrain's resolution
    float terrainSpacing = 10.0f;
    vector<glm::vec3> vertexData;
    vector<glm::vec3> normalData;
    multi_array<float, 2> terrain;

    terrain.resize(extents[1<<terrainRes][1<<terrainRes]);

    for(long z=-(1<<(terrainRes-1)); z<(1<<(terrainRes-1)); z++)
        for(long x=-(1<<(terrainRes-1)); x<(1<<(terrainRes-1)); x++)
            terrain[z+(1<<(terrainRes-1))][x+(1<<(terrainRes-1))] = (noise.fbm((double)x/16.0, yValue, (double)z/16.0, 2, 0.4, 1.2, 2.9, 1.1)/2.0+0.5)*100.0;

    for(long z=0; z<(1<<terrainRes)-1; z++)
    {
        for(long x=0; x<(1<<terrainRes)-1; x++)
        {
            vertexData.push_back(glm::vec3((float)x*terrainSpacing, terrain[z][x], (float)z*terrainSpacing));
            vertexData.push_back(glm::vec3(((float)x+1.0f)*terrainSpacing, terrain[z+1][x+1], ((float)z+1.0f)*terrainSpacing));
            vertexData.push_back(glm::vec3(((float)x+1.0f)*terrainSpacing, terrain[z][x+1], (float)z*terrainSpacing));
            vertexData.push_back(glm::vec3((float)x*terrainSpacing, terrain[z][x], (float)z*terrainSpacing));
            vertexData.push_back(glm::vec3((float)x*terrainSpacing, terrain[z+1][x], ((float)z+1.0f)*terrainSpacing));
            vertexData.push_back(glm::vec3(((float)x+1.0f)*terrainSpacing, terrain[z+1][x+1], ((float)z+1.0f)*terrainSpacing));

            normalData.push_back(getNormal(vertexData[vertexData.size()-6], vertexData[vertexData.size()-5], vertexData[vertexData.size()-4]));
            normalData.push_back(normalData[normalData.size()-1]);
            normalData.push_back(normalData[normalData.size()-2]);
            normalData.push_back(getNormal(vertexData[vertexData.size()-3], vertexData[vertexData.size()-2], vertexData[vertexData.size()-1]));
            normalData.push_back(normalData[normalData.size()-1]);
            normalData.push_back(normalData[normalData.size()-2]);
        }
    }

    glUseProgram(oglt9Shader->program);
    glGenBuffers(1, &vbo);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, vertexData.size()*3*sizeof(float), vertexData.data(), GL_STATIC_DRAW);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, NULL);
    glEnableVertexAttribArray(0);

    glGenBuffers(1, &nbo);
    glBindBuffer(GL_ARRAY_BUFFER, nbo);
    glBufferData(GL_ARRAY_BUFFER, normalData.size()*3*sizeof(float), normalData.data(), GL_STATIC_DRAW);
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, NULL);
    glEnableVertexAttribArray(1);
    glBindBuffer(GL_ARRAY_BUFFER, 0);

    numVertices = vertexData.size()*3;
    yValue += 0.01f;
}

void render()
{
    //Clear screen and enable depth buffer
    //Create and transmit matrices and light direction to shaders

    generateTerrain(oglt9Shader);

    glDrawArrays(GL_TRIANGLES, 0, numVertices);

    glDeleteBuffers(1, &vbo);
    glDeleteBuffers(1, &nbo);

    //Swap buffers to window
}

And my vertex shader...

#version 430 core

layout (location = 0) in vec3 vPosition;
layout (location = 1) in vec3 vNormal;

uniform mat4 mMatrix;
uniform mat4 vMatrix;
uniform mat4 pMatrix;

out vec3 fPosition;
out vec3 fNormal;

void main(void)
{
    gl_Position = pMatrix * vMatrix * mMatrix * vec4(vPosition, 1.0);
    fPosition = vPosition;
    fNormal = normalize(transpose(inverse(mat3(mMatrix))) * vNormal);
}

#version 430 core

in  vec3 fPosition;
in  vec3 fNormal;
out vec4 outColor;
uniform vec3 lightDirection;

...and fragment shader.

void main(void)
{
    vec3 rawColor = vec3(1.0);
    vec3 ambientColor = vec3(1.0, 1.0, 1.0);
    float diffuseIntensity = max(0.0, dot(fNormal, lightDirection));
    vec3 diffuseColor = diffuseIntensity * vec3(0.9, 0.9, 0.9);

    outColor = vec4(rawColor*ambientColor*diffuseColor, 1.0);
}

This is the final image:

The not so smooth image

So, what can I do to make the triangles smooth so you can't see these hard edges anymore?

sigalor
  • 901
  • 11
  • 24

1 Answers1

4

You're using the same normal for all 3 vertices of each triangle. This will essentially result in flat shading, meaning that the color of each triangle is constant.

What you need is normals that better approximate the actual normals of the surface, instead of calculating the normal of each triangle separately. To get a smooth looking surface, you need to have one normal per vertex, and then use that normal when specifying the vertex for all the triangles that share the vertex.

The most efficient way of doing this is that you really store each vertex/normal of your grid in the VBO only once. You can then use an index buffer to reference the vertices when defining the triangles. This means that you have an additional buffer of type GL_ELEMENT_ARRAY_BUFFER containing indices, and then draw with glDrawElements(). You should be able to find reference information and tutorials on how to do that.

To actually obtain the normals, one common approach is that you average the triangle normals of all adjacent triangles to calculate the normal at a vertex.

Reto Koradi
  • 53,228
  • 8
  • 93
  • 133