Lesson 13: Face generation
We can generate 3D faces from 2D flat images. Using OpenGL this can be challenging, after all it is not a lot more than a primitive triangle rendering API. The technique we will use in this tutorial is very similar, if not nearly identical, to terrain generation in our previous tutorial. We got the original face image from the web: http://everythingforpeople.files.wordpress.com/2011/11/7098-natalie-portman-black-and-white-earrings-closeup-face.jpg A face image is not all we need. We will also need a height map image. You can create one in paint based on the original image, or use more sophisticated tools Finally we have two images: the frontal face texture and the height map texture. Such that we have this setup (left: input data, right: output).
The height map (gray scale image above) is a representations of the depth of each vertex. The darker the color, the more depth it has. In other words, the height map is a matrix M where k ∈ M is the Y-coordinates of a vertex. The X and Z coordinates of the vertexes are evenly spaced across the space. The quality of the face image depends on the height map image, e.g. garbage in, garbage out. In our case, the height map is detailed enough for an example. Thus we simply render a set of vertexes and connect them as quads. See image below:
We then calculate the texture coordinates to use on those quads. In our case the height map image is 24bpp. If you decide to use another depth, you’ll have to modify the code. In addition we use a hard coded image and image width/height. These are all things you may want to modify. Basically all the code below does is allocate memory for our matrix M (in this case height[][]), load an image, and fill the area with the pixel data.
int** height; Uint32 get_pixel(SDL_Surface *surface, int x, int y) { int bpp = surface->format->BytesPerPixel; /* Here p is the address to the pixel we want to retrieve */ Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x * bpp; switch(bpp) { case 1: return *p; case 2: return *(Uint16 *)p; case 3: if(SDL_BYTEORDER == SDL_BIG_ENDIAN) return p[0]We can then draw the face on the screen using:
void drawMap() { float tw = 1.3; float texturestep = 1.0f / (MAP_W); glPushMatrix(); for (int x = 1; xBasically what this does is draw a set of connected quads, where each vertex has a coordinate according to the height map. Once everything is setup, you should get results as:
The total code will look something like this.
/* * OpenGL Face Generation * https://talkera.org/opengl/ * http://openglsamples.sf.net * * Compilation trough: * gcc cube.c -lglut -lGL -lGLU -lSDL * */ #include#include #include #include #include "SDL/SDL.h" /* screen width, height, and bit depth */ #define SCREEN_WIDTH 640 #define SCREEN_HEIGHT 480 #define SCREEN_BPP 16 /* Setup useful booleans */ #define TRUE 1 #define FALSE 0 /* This is our SDL surface */ SDL_Surface *surface; /* Used for rotating the cube */ GLfloat rotationXaxis; GLfloat rotationYaxis; GLfloat rotationZaxis; /* Storage For One Texture ( NEW ) */ GLuint texture[1]; /* Release resources and quit */ void Quit( int returnCode ) { SDL_Quit( ); exit( returnCode ); } /* Loads in a bitmap as a GL texture */ int LoadGLTextures( ) { int Status = FALSE; /* Create storage space for the texture */ SDL_Surface *TextureImage[1]; /* Load The Bitmap into Memory */ if ((TextureImage[0] = SDL_LoadBMP("face.bmp"))) { Status = TRUE; glGenTextures( 1, &texture[0] ); glBindTexture( GL_TEXTURE_2D, texture[0] ); glTexImage2D( GL_TEXTURE_2D, 0, 3, TextureImage[0]->w, TextureImage[0]->h, 0, GL_BGR, GL_UNSIGNED_BYTE, TextureImage[0]->pixels ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); } /* Free up some memory */ if ( TextureImage[0] ) SDL_FreeSurface( TextureImage[0] ); return Status; } /* function to reset our viewport after a window resize */ int resizeWindow( int width, int height ) { /* Height / width ration */ GLfloat ratio; /* Protect against a divide by zero */ if ( height == 0 ) height = 1; ratio = ( GLfloat )width / ( GLfloat )height; /* Setup our viewport. */ glViewport( 0, 0, ( GLint )width, ( GLint )height ); /* * change to the projection matrix and set * our viewing volume. */ glMatrixMode( GL_PROJECTION ); glLoadIdentity( ); /* Set our perspective */ gluPerspective( 45.0f, ratio, 0.1f, 1500.0f ); /* Make sure we're chaning the model view and not the projection */ glMatrixMode( GL_MODELVIEW ); /* Reset The View */ glLoadIdentity( ); return( TRUE ); } /* function to handle key press events */ void handleKeyPress( SDL_keysym *keysym ) { switch ( keysym->sym ) { case SDLK_ESCAPE: /* ESC key was pressed */ Quit( 0 ); break; case SDLK_F1: /* F1 key was pressed * this toggles fullscreen mode */ SDL_WM_ToggleFullScreen( surface ); break; default: break; } return; } /* OpenGL initialization function */ int initGL( GLvoid ) { /* Load in the texture */ if ( !LoadGLTextures( ) ) return FALSE; /* Enable Texture Mapping ( NEW ) */ glEnable( GL_TEXTURE_2D ); /* Enable smooth shading */ glShadeModel( GL_SMOOTH ); /* Set the background black */ glClearColor( 0.0f, 0.0f, 0.0f, 0.5f ); /* Depth buffer setup */ glClearDepth( 1.0f ); /* Enables Depth Testing */ glEnable( GL_DEPTH_TEST ); /* The Type Of Depth Test To Do */ glDepthFunc( GL_LEQUAL ); /* Really Nice Perspective Calculations */ glHint( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST ); glEnable(GL_NORMALIZE) ; return( TRUE ); } int** height; Uint32 get_pixel(SDL_Surface *surface, int x, int y) { int bpp = surface->format->BytesPerPixel; /* Here p is the address to the pixel we want to retrieve */ Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x * bpp; switch(bpp) { case 1: return *p; case 2: return *(Uint16 *)p; case 3: if(SDL_BYTEORDER == SDL_BIG_ENDIAN) return p[0] hw_available ) videoFlags |= SDL_HWSURFACE; else videoFlags |= SDL_SWSURFACE; /* This checks if hardware blits can be done */ if ( videoInfo->blit_hw ) videoFlags |= SDL_HWACCEL; /* Sets up OpenGL double buffering */ SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 ); /* get a SDL surface */ surface = SDL_SetVideoMode( SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_BPP, videoFlags ); /* Verify there is a surface */ if ( !surface ) { fprintf( stderr, "Video mode set failed: %s\n", SDL_GetError( ) ); Quit( 1 ); } /* initialize OpenGL */ initGL( ); /* resize the initial window */ resizeWindow( SCREEN_WIDTH, SCREEN_HEIGHT ); /* load map */ loadMap(); /* wait for events */ while ( !done ) { /* handle the events in the queue */ while ( SDL_PollEvent( &event ) ) { switch( event.type ) { case SDL_ACTIVEEVENT: if ( event.active.gain == 0 ) isActive = FALSE; else isActive = TRUE; break; case SDL_VIDEORESIZE: /* handle resize event */ surface = SDL_SetVideoMode( event.resize.w, event.resize.h, 16, videoFlags ); if ( !surface ) { fprintf( stderr, "Could not get a surface after resize: %s\n", SDL_GetError( ) ); Quit( 1 ); } resizeWindow( event.resize.w, event.resize.h ); break; case SDL_KEYDOWN: /* handle key presses */ handleKeyPress( &event.key.keysym ); break; case SDL_QUIT: /* handle quit requests */ done = TRUE; break; default: break; } } /* draw the scene */ if ( isActive ) drawGLScene( ); } /* clean ourselves up and exit */ Quit( 0 ); /* Should never get here */ return( 0 ); } Keep in mind that this is for 24bpp heightmaps only, thus it would need some modifications for other color depths. In addition I hardcoded 128×128 resolution. This serves as example, but if you want to include it in a game or experiment you may want to modify a bit
You may want to use a separate class SDL_LoadBMP loads bitmap images only.