GLUT Tutorials 13:位图字体和正交投影
博客转自:http://www.lighthouse3d.com/tutorials/glut-tutorial/bitmap-fonts-and-orthogonal-projections/
位图字体的通常使用是在2维空间给用户提供信息。例如,一个简单的例子就是当我们想去显示应用程序的帧率的时候。显示信息应当出现在屏幕上的固定位置,即使在用户移动相机视野时候。利用正交投影很容易计算位图字体的位置,而不是依靠透视投影,因为我们可以阐明像素的位置。
基本的概略流程就是在透视投影模式下去绘制世界,之后,切换到正交投影去绘制字体,之后在切换回透视投影模式即可。接下俩的模板就是一个渲染函数去实现这个效果。
void renderScene() { // do everything we need to render the world as usual ... setOrthographicProjection(); glPushMatrix(); glLoadIdentity(); renderBitmapString(5,30,GLUT_BITMAP_HELVETICA_18,"Lighthouse3D"); glPopMatrix(); restorePerspectiveProjection(); glutSwapBuffers(); }
这两个函数 setOrthograpicProjection 和 restorePerspectiveProjection 在下面呈现。第一个函数 改变矩阵模式到 GL_PROJECTION,意味着我们正在相机上工作。之后,我们并保存先前的透视投影设置,方便绘制字体后可以恢复。之后设置 GL_PROJECTION 矩阵为单位矩阵,利用gluOrtho声明正交投影。
这个函数的参数表明x和y的范围,围绕Y周反转之后平移操作。平移中的负值是向下,其实就是平移原点到左上角。这种方式使在屏幕坐标上写字符很容易。
The variables w and h we’re computed elsewhere (see the changeSize function in the source code).
void setOrthographicProjection() { // switch to projection mode glMatrixMode(GL_PROJECTION); // save previous matrix which contains the //settings for the perspective projection glPushMatrix(); // reset matrix glLoadIdentity(); // set a 2D orthographic projection gluOrtho2D(0, w, 0, h); // invert the y axis, down is positive glScalef(1, -1, 1); // mover the origin from the bottom left corner // to the upper left corner glTranslatef(0, -h, 0); // switch back to modelview mode glMatrixMode(GL_MODELVIEW); }
A faster way to perform the ortho projection is as follows. The idea is to set the projection in such a way that no scale and translation are required.
void setOrthographicProjection() { // switch to projection mode glMatrixMode(GL_PROJECTION); // save previous matrix which contains the //settings for the perspective projection glPushMatrix(); // reset matrix glLoadIdentity(); // set a 2D orthographic projection gluOrtho2D(0, w, h, 0); // switch back to modelview mode glMatrixMode(GL_MODELVIEW); }
This function is very simple. Since we saved the settings of the perspective projection before we set the orthographic projection, all we have to do is to change the matrix mode to GL_PROJECTION, pop the matrix, i.e. restore the settings, and finally change the matrix mode again to GL_MODELVIEW.
void restorePerspectiveProjection() { glMatrixMode(GL_PROJECTION); // restore previous projection matrix glPopMatrix(); // get back to modelview mode glMatrixMode(GL_MODELVIEW); }
The function above, renderBitmapString, as presented in the previous section will write the characters continuously, without extra spacing, except where a space character appears in the text. In order to add extra spacing we must keep track of where the current raster position is so that we can add the extra spacing to the x coordinate, for example. There are at least two different approaches to keep track of the raster position; one is to compute the current raster position after drawing a bitmap. The second option involves asking the OpenGL state machine what is the current raster position.
The first approach requires that we know the dimensions of the character. While the maximum height is always constant for a particular font, the width may vary in some fonts. Fortunately GLUT provides a function that returns the width of a character. The function is glutBitmapWidth and the syntax is as follows:
int glutBitmapWidth(void *font, int character); Parameters: font – one of the pre defined fonts in GLUT, see the previous section for the possible values. character – the character which we want to know the width
So for instance if we want a function that writes a string with a certain amount of pixels between each character we can write:
void renderSpacedBitmapString( float x, float y, int spacing, void *font, char *string) { char *c; int x1=x; for (c=string; *c != '\0'; c++) { glRasterPos2f(x1,y); glutBitmapCharacter(font, *c); x1 = x1 + glutBitmapWidth(font,*c) + spacing; } }
If we want to draw vertical text we can do as follows:
void renderVerticalBitmapString( float x, float y, int bitmapHeight, void *font, char *string) { char *c; int i; for (c=string,i=0; *c != '\0'; i++,c++) { glRasterPos2f(x, y+bitmapHeight*i); glutBitmapCharacter(font, *c); } }
The variable bitmapHeight can be easily computed because we know the maximum height of each font, it is the last number in the font name. For instance, GLUT_BITMAP_TIMES_ROMAN_10 is 10 pixels tall.
One last thing, GLUT has yet another function for bitmap fonts, its glutBitMapLength and it computes the length in pixels of a string. The return value of this function is the sum of the widths for every character in the string. Here goes the syntax:
int glutBitmapLength(void *font, char *string); Parameters: font – one of the pre defined fonts in GLUT, see the previous section for the possible values. string – the string which we want to know the length in pixels
全部代码如下
#include <stdlib.h> #include <stdio.h> #include <math.h> #ifdef __APPLE__ #include <GLUT/glut.h> #else #include <GL/glut.h> #endif // angle of rotation for the camera direction float angle = 0.0f; // actual vector representing the camera's direction float lx = 0.0f, lz = -1.0f; // XZ position of the camera float x = 0.0f, z = 5.0f; // the key states. These variables will be zero //when no key is being presses float deltaAngle = 0.0f; float deltaMove = 0; int xOrigin = -1; // Constant definitions for Menus #define RED 1 #define GREEN 2 #define BLUE 3 #define ORANGE 4 #define FILL 1 #define LINE 2 // Pop up menu identifiers int fillMenu, fontMenu, mainMenu, colorMenu; // color for the nose float red = 1.0f, blue = 0.5f, green = 0.5f; // scale of snowman float scale = 1.0f; // menu status int menuFlag = 0; // default font void *font = GLUT_BITMAP_TIMES_ROMAN_24; #define INT_GLUT_BITMAP_8_BY_13 1 #define INT_GLUT_BITMAP_9_BY_15 2 #define INT_GLUT_BITMAP_TIMES_ROMAN_10 3 #define INT_GLUT_BITMAP_TIMES_ROMAN_24 4 #define INT_GLUT_BITMAP_HELVETICA_10 5 #define INT_GLUT_BITMAP_HELVETICA_12 6 #define INT_GLUT_BITMAP_HELVETICA_18 7 int width, height; void changeSize(int w, int h) { // Prevent a divide by zero, when window is too short // (you cant make a window of zero width). if (h == 0) h = 1; width = w; height = h; float ratio = w * 1.0 / h; // Use the Projection Matrix glMatrixMode(GL_PROJECTION); // Reset Matrix glLoadIdentity(); // Set the viewport to be the entire window glViewport(0, 0, w, h); // Set the correct perspective. gluPerspective(45.0f, ratio, 0.1f, 100.0f); // Get Back to the Modelview glMatrixMode(GL_MODELVIEW); } void drawSnowMan() { glScalef(scale, scale, scale); glColor3f(1.0f, 1.0f, 1.0f); // Draw Body glTranslatef(0.0f, 0.75f, 0.0f); glutSolidSphere(0.75f, 20, 20); // Draw Head glTranslatef(0.0f, 1.0f, 0.0f); glutSolidSphere(0.25f, 20, 20); // Draw Eyes glPushMatrix(); glColor3f(0.0f, 0.0f, 0.0f); glTranslatef(0.05f, 0.10f, 0.18f); glutSolidSphere(0.05f, 10, 10); glTranslatef(-0.1f, 0.0f, 0.0f); glutSolidSphere(0.05f, 10, 10); glPopMatrix(); // Draw Nose glColor3f(red, green, blue); glRotatef(0.0f, 1.0f, 0.0f, 0.0f); glutSolidCone(0.08f, 0.5f, 10, 2); glColor3f(1.0f, 1.0f, 1.0f); } void renderBitmapString( float x, float y, float z, void *font, char *string) { char *c; glRasterPos3f(x, y, z); for (c = string; *c != '\0'; c++) { glutBitmapCharacter(font, *c); } } void renderSpacedBitmapString( float x, float y, int spacing, void *font, char *string) { char *c; int x1 = x; for (c = string; *c != '\0'; c++) { glRasterPos2f(x1, y); glutBitmapCharacter(font, *c); x1 = x1 + glutBitmapWidth(font, *c) + spacing; } } void renderVerticalBitmapString( float x, float y, int bitmapHeight, void *font, char *string) { char *c; int i; for (c = string, i = 0; *c != '\0'; i++, c++) { glRasterPos2f(x, y + bitmapHeight*i); glutBitmapCharacter(font, *c); } } void computePos(float deltaMove) { x += deltaMove * lx * 0.1f; z += deltaMove * lz * 0.1f; } void setOrthographicProjection() { // switch to projection mode glMatrixMode(GL_PROJECTION); // save previous matrix which contains the //settings for the perspective projection glPushMatrix(); // reset matrix glLoadIdentity(); // set a 2D orthographic projection gluOrtho2D(0, width, 0, height); // invert the y axis, down is positive glScalef(1, -1, 1); // mover the origin from the bottom left corner // to the upper left corner glTranslatef(0, -height, 0); // switch back to modelview mode glMatrixMode(GL_MODELVIEW); } void restorePerspectiveProjection() { glMatrixMode(GL_PROJECTION); // restore previous projection matrix glPopMatrix(); // get back to modelview mode glMatrixMode(GL_MODELVIEW); } void renderScene(void) { if (deltaMove) computePos(deltaMove); // Clear Color and Depth Buffers glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Reset transformations glLoadIdentity(); // Set the camera gluLookAt(x, 1.0f, z, x + lx, 1.0f, z + lz, 0.0f, 1.0f, 0.0f); // Draw ground glColor3f(0.9f, 0.9f, 0.9f); glBegin(GL_QUADS); glVertex3f(-100.0f, 0.0f, -100.0f); glVertex3f(-100.0f, 0.0f, 100.0f); glVertex3f(100.0f, 0.0f, 100.0f); glVertex3f(100.0f, 0.0f, -100.0f); glEnd(); // Draw 36 SnowMen char number[3]; for (int i = -3; i < 3; i++) { for (int j = -3; j < 3; j++) { glPushMatrix(); glTranslatef(i*10.0f, 0.0f, j * 10.0f); drawSnowMan(); sprintf(number, "%d", (i + 3) * 6 + (j + 3)); renderBitmapString(0.0f, 0.5f, 0.0f, (void *)font, number); glPopMatrix(); } } setOrthographicProjection(); glPushMatrix(); glLoadIdentity(); renderBitmapString(5.0, 30.0, 0.0, GLUT_BITMAP_HELVETICA_18, "Fixed Font"); renderSpacedBitmapString(5.0, 130, 5.0, GLUT_BITMAP_HELVETICA_18, "Fixed Font"); renderVerticalBitmapString(230.0, 50.0, 30, GLUT_BITMAP_HELVETICA_18, "Fixed Font"); glPopMatrix(); restorePerspectiveProjection(); glutSwapBuffers(); } // ----------------------------------- // KEYBOARD // ----------------------------------- void processNormalKeys(unsigned char key, int xx, int yy) { switch (key) { case 27: glutDestroyMenu(mainMenu); glutDestroyMenu(fillMenu); glutDestroyMenu(colorMenu); glutDestroyMenu(fontMenu); exit(0); break; } } void pressKey(int key, int xx, int yy) { switch (key) { case GLUT_KEY_UP: deltaMove = 0.5f; break; case GLUT_KEY_DOWN: deltaMove = -0.5f; break; } } void releaseKey(int key, int x, int y) { switch (key) { case GLUT_KEY_UP: case GLUT_KEY_DOWN: deltaMove = 0; break; } } // ----------------------------------- // MOUSE // ----------------------------------- void mouseMove(int x, int y) { // this will only be true when the left button is down if (xOrigin >= 0) { // update deltaAngle deltaAngle = (x - xOrigin) * 0.001f; // update camera's direction lx = sin(angle + deltaAngle); lz = -cos(angle + deltaAngle); } } void mouseButton(int button, int state, int x, int y) { // only start motion if the left button is pressed if (button == GLUT_LEFT_BUTTON) { // when the button is released if (state == GLUT_UP) { angle += deltaAngle; xOrigin = -1; } else {// state = GLUT_DOWN xOrigin = x; } } } // ----------------------------------- // MENUS // ----------------------------------- void processMenuStatus(int status, int x, int y) { if (status == GLUT_MENU_IN_USE) menuFlag = 1; else menuFlag = 0; } void processMainMenu(int option) { // nothing to do in here // all actions are for submenus } void processFillMenu(int option) { switch (option) { case FILL: glPolygonMode(GL_FRONT, GL_FILL); break; case LINE: glPolygonMode(GL_FRONT, GL_LINE); break; } } void processFontMenu(int option) { switch (option) { case INT_GLUT_BITMAP_8_BY_13: font = GLUT_BITMAP_8_BY_13; break; case INT_GLUT_BITMAP_9_BY_15: font = GLUT_BITMAP_9_BY_15; break; case INT_GLUT_BITMAP_TIMES_ROMAN_10: font = GLUT_BITMAP_TIMES_ROMAN_10; break; case INT_GLUT_BITMAP_TIMES_ROMAN_24: font = GLUT_BITMAP_TIMES_ROMAN_24; break; case INT_GLUT_BITMAP_HELVETICA_10: font = GLUT_BITMAP_HELVETICA_10; break; case INT_GLUT_BITMAP_HELVETICA_12: font = GLUT_BITMAP_HELVETICA_12; break; case INT_GLUT_BITMAP_HELVETICA_18: font = GLUT_BITMAP_HELVETICA_18; break; } } void processColorMenu(int option) { switch (option) { case RED: red = 1.0f; green = 0.0f; blue = 0.0f; break; case GREEN: red = 0.0f; green = 1.0f; blue = 0.0f; break; case BLUE: red = 0.0f; green = 0.0f; blue = 1.0f; break; case ORANGE: red = 1.0f; green = 0.5f; blue = 0.5f; break; } } void createPopupMenus() { fontMenu = glutCreateMenu(processFontMenu); glutAddMenuEntry("BITMAP_8_BY_13 ", INT_GLUT_BITMAP_8_BY_13); glutAddMenuEntry("BITMAP_9_BY_15", INT_GLUT_BITMAP_9_BY_15); glutAddMenuEntry("BITMAP_TIMES_ROMAN_10 ", INT_GLUT_BITMAP_TIMES_ROMAN_10); glutAddMenuEntry("BITMAP_TIMES_ROMAN_24", INT_GLUT_BITMAP_TIMES_ROMAN_24); glutAddMenuEntry("BITMAP_HELVETICA_10 ", INT_GLUT_BITMAP_HELVETICA_10); glutAddMenuEntry("BITMAP_HELVETICA_12", INT_GLUT_BITMAP_HELVETICA_12); glutAddMenuEntry("BITMAP_HELVETICA_18", INT_GLUT_BITMAP_HELVETICA_18); fillMenu = glutCreateMenu(processFillMenu); glutAddMenuEntry("Fill", FILL); glutAddMenuEntry("Line", LINE); colorMenu = glutCreateMenu(processColorMenu); glutAddMenuEntry("Red", RED); glutAddMenuEntry("Blue", BLUE); glutAddMenuEntry("Green", GREEN); glutAddMenuEntry("Orange", ORANGE); mainMenu = glutCreateMenu(processMainMenu); glutAddSubMenu("Polygon Mode", fillMenu); glutAddSubMenu("Color", colorMenu); glutAddSubMenu("Font", fontMenu); // attach the menu to the right button glutAttachMenu(GLUT_RIGHT_BUTTON); // this will allow us to know if the menu is active glutMenuStatusFunc(processMenuStatus); } // ----------------------------------- // MAIN // ----------------------------------- int main(int argc, char **argv) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA); glutInitWindowPosition(100, 100); glutInitWindowSize(320, 320); glutCreateWindow("Lighthouse3D - GLUT Tutorial"); // register callbacks glutDisplayFunc(renderScene); glutReshapeFunc(changeSize); glutIdleFunc(renderScene); glutIgnoreKeyRepeat(1); glutKeyboardFunc(processNormalKeys); glutSpecialFunc(pressKey); glutSpecialUpFunc(releaseKey); // here are the two new functions glutMouseFunc(mouseButton); glutMotionFunc(mouseMove); // OpenGL init glEnable(GL_DEPTH_TEST); glEnable(GL_CULL_FACE); // init Menus createPopupMenus(); // enter GLUT event processing cycle glutMainLoop(); return 1; }
显示效果如下