Normal averaging of heightmap

2020-08-01 08:45发布

问题:

i have the following code for calculating Heightmap normals

void CalcMapNormals(HeightMap * map, Vec3f normals[])
{
int     dst, i, j, right, bottom;
Vec3f   p0, p1, p2;
Vec3f   n0;

/* Avoid writing map->rows|cols - 1 all the time */
right = map->cols - 1;
bottom = map->rows - 1;

dst = 0;
for (i = 0; i < map->rows; i++) {
    for (j = 0; j < map->cols; j++) {
        Vec3Set(normals[dst], 0, 0, 0);
        /* Vertex can have 2, 3, or 4 neighbours horizontally and vertically */
        if (i < bottom && j < right) {
            /* Right and below */
            GetHeightPoint(map, i, j, p0);
            GetHeightPoint(map, i + 1, j, p1);
            GetHeightPoint(map, i + 1, j + 1, p2);
            CalcTriNormal(n0, p0, p1, p2);
            VecAdd(normals[dst], normals[dst], n0);
        }
        /*  TODO: the other three possibilities */
        VecNormalize(normals[dst]);
        dst += 1;
    }
}
/* Sanity check */
if (dst != map->rows * map->cols)
    Fail("Internal error in CalcMapNormals: normals count mismatch");
}

I understand that the code get the three vertexes of the triangle, calculate its normal and then add them and normalize them to get averaged normal. But i don't know how you can get the other three possibilities, ive been doing something like the following:

void CalcMapNormals(HeightMap * map, Vec3f normals[])
{
int     dst, i, j, right, bottom;
Vec3f   p0, p1, p2;
Vec3f   n0;
Vec3f   p3, p4, p5;
Vec3f   n1;
Vec3f   p6, p7, p8;
Vec3f   n2;
Vec3f   p9, p10, p11;
Vec3f   n3;
/* Avoid writing map->rows|cols - 1 all the time */
right = map->cols - 1;
bottom = map->rows - 1;

dst = 0;
for (i = 0; i < map->rows; i++) {
    for (j = 0; j < map->cols; j++) {
        Vec3Set(normals[dst], 0, 0, 0);
        /* Vertex can have 2, 3, or 4 neighbours horizontally and vertically */
        if (i < bottom && j < right) {
            /* Right and below */
            GetHeightPoint(map, i, j, p0);
            GetHeightPoint(map, i + 1, j, p1);
            GetHeightPoint(map, i + 1, j + 1, p2);
            CalcTriNormal(n0, p0, p1, p2);
            VecAdd(normals[dst], normals[dst], n0);
        }
        if ( i > bottom && j > 0)
        {
            GetHeightPoint(map, i, j, p3);
            GetHeightPoint(map, i + 1, j, p4);
            GetHeightPoint(map, i, j -1, p5);
            CalcTriNormal(n1, p3, p4, p5);
            VecAdd(normals[dst], normals[dst], n1);
        }
        if ( i > 0 && j > 0)
        {
            GetHeightPoint(map, i, j, p6);
            GetHeightPoint(map, i, j - 1, p7);
            GetHeightPoint(map, i - 1, j, p8);
            CalcTriNormal(n2, p6, p7, p8);
            VecAdd(normals[dst], normals[dst], n2);

        }
        if ( i > bottom && j < right)
        {

            GetHeightPoint(map, i, j, p9);
            GetHeightPoint(map, i-1, j, p10);
            GetHeightPoint(map, i, j+1, p11);
            CalcTriNormal(n3, p9, p10, p11);
            VecAdd(normals[dst], normals[dst], n3);
        }
        /*  TODO: the other three possibilities */
        VecNormalize(normals[dst]);
        dst += 1;
    }
}
/* Sanity check */
if (dst != map->rows * map->cols)
    Fail("Internal error in CalcMapNormals: normals count mismatch");
}

But i don't think its giving me the result i wanted, i get the concept of normal averaging, but can't figure out the code.

回答1:

Hi Yzwboy here is one way I would try to make "smoothed" normals (averaged based on adjacent triangles):

In order to compute "smoothed" normals you will need to assign to each vertex a normal which is averaged across the normals of the triangles adjacent to the vertex.

I would calculate a weighted average based on the angle between the two edges adjacent to the vertex in question (cross product which is an easy calculation):

Pseudocode:

Vec3F faceNormal(int face_id, int vertex_id) // assumes C-->B-->A is clockwise
{
    Vec3f A = triangleMesh.face[face_id].vertex[vertex_id];       // A
    Vec3f B = triangleMesh.face[face_id].vertex[(vertex_id+1)%3]; // B
    Vec3f C = triangleMesh.face[face_id].vertex[(vertex_id+2)%3]; // C
    Vec3f BA = B-A;
    Vec3f CA = C-A;
    Vec3f Normal = BA.cross(CA);

    float sin_alpha = length(Normal) / (BA.len() * CA.len() );  // depending on your implementation of Vec3f it could be .magnitude() or .length() instead of .len()
    return (Normal.normalize() * asin(sin_alpha);)
}

And then to vary the normal by vertex:

void computeNormals() {
    for (vertex v in triangleMesh)
    {
        Vec3f Normal (0,0,0);
        for (int i = 0;i < TriangleCount;i++)
            if (triangleMesh.face[i].contains(v) )
            {
                int vertID = vertexPositionInTriangle(i,v); //Can be 0,1 or 2.  Use an enum to make A = 0, B=1, C=2 if that is easier to read:)
                Normal = Normal + faceNormal(i,vertID);
            }
        addNormalToVertexV(Normal.normalize(),v); // this is a function to set the normal for a vertex, the vertex class must have a member for normal though and the arguments for the function are Vec3f, Vec3f
    }
}

You could also compute the area of a each triangle to use as the weighting, though I find using the angles works best for looks most times.

I have tried to use names which match the Vec3f spec, as well as inbuilt functions to save work, but you will need to do some coding to get the pseudocode working (I dont have access to a GL Test environment here).

Hope this helps :)