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;
}

显示效果如下

posted @ 2020-05-23 11:17  采男孩的小蘑菇  阅读(281)  评论(0编辑  收藏  举报