Normals for height map data

2020-07-23 08:42发布

问题:

I want to find normals for height map data. I am using gl_triangles in my code for indices. How would I find normals for this?

回答1:

Given a triangle (vert1, vert2, vert3) its normal is ((vert2 - vert1).cross(vert3 - vert1)).normalize().

For smooth, per-vertex normals: Foreach vertex, sum together the face normals for each triangle that vertex is a part of, then normalize the sum.

EDIT: Example:

#include <GL/glut.h>
#include <vector>
#include <cmath>
#include <Eigen/Core>
#include <Eigen/Geometry>

using namespace std;
using namespace Eigen;

typedef Matrix< Vector3f, Dynamic, Dynamic > VecMat;

// given a matrix of heights returns a matrix of vertices
VecMat GetVerts( const MatrixXf& hm )
{
    VecMat verts( hm.rows(), hm.cols() );
    for( int col = 0; col < hm.cols(); ++col )
        for( int row = 0; row < hm.rows(); ++row )
            verts( row, col ) = Vector3f( col, row, hm( row, col ) );
    return verts;
}

VecMat GetNormals( const VecMat& hm )
{
    VecMat normals( hm );
    for( int col = 0; col < hm.cols(); ++col )
        for( int row = 0; row < hm.rows(); ++row )
        {
            Vector3f sum( Vector3f::Zero() );
            const Vector3f& cur = hm( row, col );
            if( row+1 < hm.rows() && col+1 < hm.cols() )
                sum += ( hm( row+0, col+1 ) - cur ).cross( hm( row+1, col+0 ) - cur ).normalized();
            if( row+1 < hm.rows() && col > 0 )
                sum += ( hm( row+1, col+0 ) - cur ).cross( hm( row+0, col-1 ) - cur ).normalized();
            if( row > 0 && col > 0 )
                sum += ( hm( row+0, col-1 ) - cur ).cross( hm( row-1, col+0 ) - cur ).normalized();
            if( row > 0 && col+1 < hm.cols() )
                sum += ( hm( row-1, col+0 ) - cur ).cross( hm( row+0, col+1 ) - cur ).normalized();
            normals( row, col ) = sum.normalized();
        }
    return normals;
}

// returns an index array for a GL_TRIANGLES heightmap
vector< unsigned int > GetIndices( int rows, int cols )
{
    vector< unsigned int > indices;
    for( int col = 1; col < cols; ++col )
        for( int row = 1; row < rows; ++row )
        {
            // Eigen default storage order is column-major
            // lower triangle
            indices.push_back( (col-1) * rows + (row-1) );
            indices.push_back( (col-0) * rows + (row-1) );
            indices.push_back( (col-1) * rows + (row-0) );
            // upper triangle
            indices.push_back( (col-1) * rows + (row-0) );
            indices.push_back( (col-0) * rows + (row-1) );
            indices.push_back( (col-0) * rows + (row-0) );
        }
    return indices;
}

VecMat heightmap;
VecMat normals;
vector< unsigned int > indices;
void init()
{
    // wavy heightmap
    MatrixXf hm( 64, 64 );
    for( int col = 1; col < hm.cols(); ++col )
        for( int row = 1; row < hm.rows(); ++row )
        {
            float x = ( col - ( hm.cols() / 2.0f ) ) / 2.0f;
            float y = ( row - ( hm.rows() / 2.0f ) ) / 2.0f;
            hm( row, col ) = cos( sqrt( x * x + y * y ) );
        }

    heightmap = GetVerts( hm );
    heightmap.array() -= Vector3f( hm.cols() / 2.0f, hm.rows() / 2.0f, 0 );
    for( int col = 0; col < hm.cols(); ++col )
        for( int row = 0; row < hm.rows(); ++row )
            heightmap( row, col ).array() *= Vector3f( 1 / 4.0f, 1 / 4.0f, 1.0f ).array();

    normals = GetNormals( heightmap );
    indices = GetIndices( heightmap.rows(), heightmap.cols() );
}

void display()
{
    glEnable( GL_DEPTH_TEST );
    glEnable( GL_CULL_FACE );
    glShadeModel( GL_SMOOTH );

    glEnable( GL_LIGHTING );
    GLfloat global_ambient[] = { 0.0, 0.0, 0.0, 1.0 };
    glLightModelfv( GL_LIGHT_MODEL_AMBIENT, global_ambient );
    glEnable( GL_COLOR_MATERIAL );
    glColorMaterial( GL_FRONT, GL_AMBIENT_AND_DIFFUSE );

    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

    glMatrixMode( GL_PROJECTION );
    glLoadIdentity();
    double w = glutGet( GLUT_WINDOW_WIDTH );
    double h = glutGet( GLUT_WINDOW_HEIGHT );
    gluPerspective( 60, w / h, 1, 100 );

    glMatrixMode( GL_MODELVIEW );
    glLoadIdentity();
    gluLookAt( 8, 8, 8, 0, 0, 0, 0, 0, 1 );

    // spinning light
    glEnable( GL_LIGHT0 );
    float angle = 20 * ( glutGet( GLUT_ELAPSED_TIME ) / 1000.0f ) * (3.14159f / 180.0f);
    float x = cos( -angle ) * 6;
    float y = sin( -angle ) * 6;
    GLfloat light_position[] = { x, y, 2, 1.0 };
    glLightfv( GL_LIGHT0, GL_POSITION, light_position );    
    glDisable( GL_LIGHTING );
    glPointSize( 5 );
    glBegin(GL_POINTS);
    glColor3ub( 255, 255, 255 );
    glVertex3fv( light_position );
    glEnd();
    glEnable( GL_LIGHTING );

    glColor3ub(255,0,0);
    glEnableClientState( GL_VERTEX_ARRAY );
    glEnableClientState( GL_NORMAL_ARRAY );
    glVertexPointer( 3, GL_FLOAT, sizeof( Vector3f ), heightmap(0,0).data() );
    glNormalPointer( GL_FLOAT, sizeof( Vector3f ), normals(0,0).data() );
    glDrawElements( GL_TRIANGLES, indices.size(), GL_UNSIGNED_INT, &indices[0] );
    glDisableClientState( GL_VERTEX_ARRAY );
    glDisableClientState( GL_NORMAL_ARRAY );

    glutSwapBuffers();
}

void timer( int extra )
{
    glutPostRedisplay();
    glutTimerFunc( 16, timer, 0 );
}

int main( int argc, char **argv )
{
    glutInit( &argc, argv );
    glutInitDisplayMode( GLUT_RGBA | GLUT_DEPTH | GLUT_DOUBLE );
    glutInitWindowSize( 640, 480 );
    glutCreateWindow( "Heightmap" );
    init();
    glutDisplayFunc( display );
    glutTimerFunc( 0, timer, 0 );
    glutMainLoop();
    return 0;
}