1

I've been trying to achieve smooth normals on a generated terrain mesh (tile grid style) to give off smooth lighting, however I have only be able to achieve flat shaded lighting and I believe this might be to do with the fact all the faces are separated (though some vertex's may have the same position).

I've looked around for a similar question, the closest would be: this question however I'm not sure about how to go about implementing his solution of taking a percentage.

Currently I have this code which is based on the typical way to calculate smooth normals - but only achieves flat shading in my case.

    // Reset Normals
    for (int i = 0; i < this->vertexCount; i++) {
        this->vertices[i].normal = glm::vec3(0.0f);
    }

    // For each face
    for (int i = 0; i < totalVerts; i += 3) {

        auto index0 = indices ? this->indices[i] : i;
        auto index1 = indices ? this->indices[i + 1] : i + 1;
        auto index2 = indices ? this->indices[i + 2] : i + 2;

        auto vertex0 = this->vertices[index0].position;
        auto vertex1 = this->vertices[index1].position;
        auto vertex2 = this->vertices[index2].position;

        auto normal = glm::cross(vertex1 - vertex0, vertex2 - vertex0);

        this->vertices[index0].normal += normal;
        this->vertices[index1].normal += normal;
        this->vertices[index2].normal += normal;

    }

    // Normalize
    for (int i = 0; i < this->vertexCount; i++) {
        this->vertices[i].normal = glm::normalize(this->vertices[i].normal);
    }

How would I go about changing this to smooth between surrounding faces? (As for why each face is separate, it's because some may have verts with specific properties which cannot be shared with other faces)

genpfault
  • 51,148
  • 11
  • 85
  • 139
Nork
  • 23
  • 5
  • Tip: `this->` is only necessary if you have variable shadowing, which you probably don't here. – tadman Apr 01 '21 at 01:01
  • It's mostly confusing here since you use it inconsistently and it implies you have variable shadowing. – tadman Apr 01 '21 at 01:06
  • Do you want `+= normal` or `= normal`? – tadman Apr 01 '21 at 01:08
  • see [How to achieve smooth tangent space normals?](https://stackoverflow.com/a/21930058/2521214) ... you simply compute per verter normal as (wighted) average of all "flat" normals of faces sharing the same vertex. however looks like your code does exactly that (appart of the weights but if your grid is uniform its ok) so the error might be elsewhere ... – Spektre Apr 01 '21 at 06:14

1 Answers1

1

For your reference, a working code to get the face normals, vertices, and vertex normals is:

void get_vertices_and_normals_from_triangles(vector<triangle> &t, vector<vec3> &fn, vector<vec3> &v, vector<vec3> &vn)
{

// Face normals
fn.clear();

// Vertices
v.clear();

// Vertex normals
vn.clear();

if(0 == t.size())
    return;

cout << "Triangles: " << t.size() << endl;

cout << "Welding vertices" << endl;

// Insert unique vertices into set.
set<indexed_vertex_3> vertex_set;

for(vector<triangle>::const_iterator i = t.begin(); i != t.end(); i++)
{
    vertex_set.insert(i->vertex[0]);
    vertex_set.insert(i->vertex[1]);
    vertex_set.insert(i->vertex[2]);
}

cout << "Vertices: " << vertex_set.size() << endl;

cout << "Generating vertex indices" << endl;

vector<indexed_vertex_3> vv;

// Add indices to the vertices.
for(set<indexed_vertex_3>::const_iterator i = vertex_set.begin(); i != vertex_set.end(); i++)
{
    size_t index = vv.size();
    vv.push_back(*i);
    vv[index].index = index;
}

for (size_t i = 0; i < vv.size(); i++)
{
    vec3 vv_element(vv[i].x, vv[i].y, vv[i].z);
    v.push_back(vv_element);
}

vertex_set.clear();

// Re-insert modifies vertices into set.
for(vector<indexed_vertex_3>::const_iterator i = vv.begin(); i != vv.end(); i++)
    vertex_set.insert(*i);

cout << "Assigning vertex indices to triangles" << endl;

// Find the three vertices for each triangle, by index.
set<indexed_vertex_3>::iterator find_iter;

for(vector<triangle>::iterator i = t.begin(); i != t.end(); i++)
{
    find_iter = vertex_set.find(i->vertex[0]);
    i->vertex[0].index = find_iter->index;

    find_iter = vertex_set.find(i->vertex[1]);
    i->vertex[1].index = find_iter->index;

    find_iter = vertex_set.find(i->vertex[2]);
    i->vertex[2].index = find_iter->index;
}

vertex_set.clear();

cout << "Calculating normals" << endl;
fn.resize(t.size());
vn.resize(v.size());

for(size_t i = 0; i < t.size(); i++)
{
    vec3 v0;// = t[i].vertex[1] - t[i].vertex[0];
    v0.x = t[i].vertex[1].x - t[i].vertex[0].x;
    v0.y = t[i].vertex[1].y - t[i].vertex[0].y;
    v0.z = t[i].vertex[1].z - t[i].vertex[0].z;

    vec3 v1;// = t[i].vertex[2] - t[i].vertex[0];
    v1.x = t[i].vertex[2].x - t[i].vertex[0].x;
    v1.y = t[i].vertex[2].y - t[i].vertex[0].y;
    v1.z = t[i].vertex[2].z - t[i].vertex[0].z;

    fn[i] = cross(v0, v1);
    fn[i] = normalize(fn[i]);

    vn[t[i].vertex[0].index] = vn[t[i].vertex[0].index] + fn[i];
    vn[t[i].vertex[1].index] = vn[t[i].vertex[1].index] + fn[i];
    vn[t[i].vertex[2].index] = vn[t[i].vertex[2].index] + fn[i];
}

for (size_t i = 0; i < vn.size(); i++)
    vn[i] = normalize(vn[i]);
}

The code to stuff the vertex data into a vector is as follows. Note that the unwelded vertices of the triangles are reconstructed in the following calls to vertex_data.push_back(v0.x);, etc.

void draw_mesh(void)
{
glUseProgram(render.get_program());


glUniformMatrix4fv(uniforms.render.proj_matrix, 1, GL_FALSE, &main_camera.projection_mat[0][0]);
glUniformMatrix4fv(uniforms.render.mv_matrix, 1, GL_FALSE, &main_camera.view_mat[0][0]);
glUniform1f(uniforms.render.shading_level, 1.0f);

vector<float> vertex_data;

for (size_t i = 0; i < triangles.size(); i++)
{
    vec3 colour(0.0f, 0.8f, 1.0f);

    size_t v0_index = triangles[i].vertex[0].index;
    size_t v1_index = triangles[i].vertex[1].index;
    size_t v2_index = triangles[i].vertex[2].index;

    vec3 v0_fn(vertex_normals[v0_index].x, vertex_normals[v0_index].y, vertex_normals[v0_index].z);
    vec3 v1_fn(vertex_normals[v1_index].x, vertex_normals[v1_index].y, vertex_normals[v1_index].z);
    vec3 v2_fn(vertex_normals[v2_index].x, vertex_normals[v2_index].y, vertex_normals[v2_index].z);

    vec3 v0(triangles[i].vertex[0].x, triangles[i].vertex[0].y, triangles[i].vertex[0].z);
    vec3 v1(triangles[i].vertex[1].x, triangles[i].vertex[1].y, triangles[i].vertex[1].z);
    vec3 v2(triangles[i].vertex[2].x, triangles[i].vertex[2].y, triangles[i].vertex[2].z);

    vertex_data.push_back(v0.x);
    vertex_data.push_back(v0.y);
    vertex_data.push_back(v0.z);
    vertex_data.push_back(v0_fn.x);
    vertex_data.push_back(v0_fn.y);
    vertex_data.push_back(v0_fn.z);
    vertex_data.push_back(colour.x);
    vertex_data.push_back(colour.y);
    vertex_data.push_back(colour.z);

    vertex_data.push_back(v1.x);
    vertex_data.push_back(v1.y);
    vertex_data.push_back(v1.z);
    vertex_data.push_back(v1_fn.x);
    vertex_data.push_back(v1_fn.y);
    vertex_data.push_back(v1_fn.z);
    vertex_data.push_back(colour.x);
    vertex_data.push_back(colour.y);
    vertex_data.push_back(colour.z);

    vertex_data.push_back(v2.x);
    vertex_data.push_back(v2.y);
    vertex_data.push_back(v2.z);
    vertex_data.push_back(v2_fn.x);
    vertex_data.push_back(v2_fn.y);
    vertex_data.push_back(v2_fn.z);
    vertex_data.push_back(colour.x);
    vertex_data.push_back(colour.y);
    vertex_data.push_back(colour.z);
}


GLuint components_per_vertex = 9;
const GLuint components_per_normal = 3;
GLuint components_per_position = 3;
const GLuint components_per_colour = 3;

GLuint triangle_buffer;

glGenBuffers(1, &triangle_buffer);

GLuint num_vertices = static_cast<GLuint>(vertex_data.size()) / components_per_vertex;

glBindBuffer(GL_ARRAY_BUFFER, triangle_buffer);
glBufferData(GL_ARRAY_BUFFER, vertex_data.size() * sizeof(GLfloat), &vertex_data[0], GL_DYNAMIC_DRAW);

glEnableVertexAttribArray(glGetAttribLocation(render.get_program(), "position"));
glVertexAttribPointer(glGetAttribLocation(render.get_program(), "position"),
    components_per_position,
    GL_FLOAT,
    GL_FALSE,
    components_per_vertex * sizeof(GLfloat),
    NULL);

glEnableVertexAttribArray(glGetAttribLocation(render.get_program(), "normal"));
glVertexAttribPointer(glGetAttribLocation(render.get_program(), "normal"),
    components_per_normal,
    GL_FLOAT,
    GL_TRUE,
    components_per_vertex * sizeof(GLfloat),
    (const GLvoid*)(components_per_position * sizeof(GLfloat)));

glEnableVertexAttribArray(glGetAttribLocation(render.get_program(), "colour"));
glVertexAttribPointer(glGetAttribLocation(render.get_program(), "colour"),
    components_per_colour,
    GL_FLOAT,
    GL_TRUE,
    components_per_vertex * sizeof(GLfloat),
    (const GLvoid*)(components_per_normal * sizeof(GLfloat) + components_per_position * sizeof(GLfloat)));

glDrawArrays(GL_TRIANGLES, 0, num_vertices);

glDeleteBuffers(1, &triangle_buffer);
}

I'm not sure if you're getting notifications of my edits.

shawn_halayka
  • 96
  • 1
  • 1
  • 9
  • Hey this looks great, I'll try it out. I noticed the welding, does it still output un-welded vertex normals? – Nork Apr 01 '21 at 05:18
  • Ok so semi-great news! this almost works, except it seems every so many rows it makes a line of opposite normals... like this: https://imgur.com/JMRPkKo – Nork Apr 01 '21 at 05:49
  • @Nork is your terrain with consistent winding? If not then some normals will cancel out causing havoc in result .. similar to what your screenshot shows. Try enable `GL_CULL_FACE` to see if true or not – Spektre Apr 01 '21 at 06:24
  • 1
    @Spektre Face culling enabled and as far as I'm aware my winding is correct. I had an idea that it might be to do with my chunking system since the problem seems to line up with my chunk sizes, so I'm currently investigating that. – Nork Apr 01 '21 at 06:31
  • @Nork yes that might be the reason ... chunk should be one grid cell larger on each direction than rendered as the normals need one neighboring face ... – Spektre Apr 01 '21 at 07:04
  • 1
    @Nork The unwelded vertices are reconstructed in the draw_mesh function. Hope that helps. – shawn_halayka Apr 02 '21 at 01:19