【游戏课】技术片段之——使用BillBoard技术进行快速绘制

BillBoard技术是计算机图形学领域中进行快速绘制的一种方法。在类似游戏这种对实时性要求较高的场景下采取BillBoard技术可以大大加快绘制的速度从而提高画面的流畅性。

那么什么是BillBoard技术,BillBoard技术的原理是什么呢?

“BillBoard技术采用一个带有纹理的四边形,其纹理图像为该BillBoard所代表的物体的图像,即用带有该物体图像的长方形,代替生成该物体的图形画面。BillBoard放置于所代表物体的位置中心,并随相机的运动而变化,始终面对用户。将BillBoard技术与Alpha纹理和动画技术相结合,可以模拟很多自然现象,如树、烟、火、爆炸、云等。”——《计算机游戏程序设计》

简单地说,就是把3D的物体用2D来表示,然后让该物体始终朝向镜头。比如场景中的一棵树,对于整个场景来说不是主要物体,因此无需花费大量的时间去计算树的每一部分的细节。通常的做法是首先准备好一张树的照片,然后镜头运动的时候使得树始终正对着镜头,我们看到的始终是树的正面。

BillBoard四边形法向和向上向量的设置方式对应着不同的BillBoard技术,分为平行屏幕的BillBoard技术、平行物体的BillBoard技术、视点朝向的BillBoard技术和轴向BillBoard技术。下面实现的是平行屏幕的BillBoard技术。

采用OpenGL实现的思路是,在绘制树的时候将ModelView矩阵的左上角的3*3矩阵置为单位阵。

核心代码:

    float mat[16];
    glGetFloatv(GL_MODELVIEW_MATRIX, mat);

    // Identify the 3*3 sub matrix in Top-left corner 
    mat[1] = mat[2] = mat[6] = 0;
    mat[4] = mat[8] = mat[9] = 0;
    mat[0] = mat[5] = mat[10] = 1;

    glLoadMatrixf(mat);

为了演示BillBoard技术,我们假设场景有一个茶壶和一棵树(好怪异的搭配)。达到的效果应该是视点转动时茶壶不动,树始终跟人的视线垂直。

在OpenGL中当视点发生变化时,实际上我们的视线是不动的,始终朝向电脑屏幕,而场景在做相对运动。所以实际上绘制出的效果应该是树始终处于画面中朝向保持不变,茶壶会发生转动。

场景一:

【游戏课】技术片段之——使用BillBoard技术进行快速绘制_第1张图片

场景二(视点变化):

【游戏课】技术片段之——使用BillBoard技术进行快速绘制_第2张图片

项目的代码如下:

控制:WSAD控制视点上下左右移动,ZC控制视点前后移动。

#define GLUT_DISABLE_ATEXIT_HACK
#define _CRT_SECURE_NO_WARNINGS
#include 
#include 
#include 
#include 
#include 

#define BITMAP_ID 0x4D42

//about multitexture
PFNGLMULTITEXCOORD1FARBPROC     glMultiTexCoord1fARB = NULL;
PFNGLMULTITEXCOORD2FARBPROC     glMultiTexCoord2fARB = NULL;
PFNGLACTIVETEXTUREARBPROC       glActiveTextureARB = NULL;
PFNGLCLIENTACTIVETEXTUREARBPROC glClientActiveTextureARB = NULL;

//texture identifiers
static GLuint texture[1];

//for scene movement
GLfloat fRotate;
//whether perspective or orthographic
bool bPersp = true;
//whether to animate
bool bAnim = false;
//whether wire or solid
bool bWire = false;
//whether to combine texure and lightening
bool bTexLit = false;

//view port size
int wHeight = 512;
int wWidth = 512;

unsigned char *LoadBitmapFile(char *filename, BITMAPINFOHEADER *bitmapInfoHeader)
{
    FILE *filePtr;
    BITMAPFILEHEADER bitmapFileHeader;
    unsigned char *bitmapImage;
    int	imageIdx = 0;
    unsigned char tempRGB;

    filePtr = fopen(filename, "rb");
    if (filePtr == NULL) return NULL;
    fread(&bitmapFileHeader, sizeof(BITMAPFILEHEADER), 1, filePtr);
    if (bitmapFileHeader.bfType != BITMAP_ID) {
        fprintf(stderr, "Error in LoadBitmapFile: the file is not a bitmap file\n");
        return NULL;
    }
    fread(bitmapInfoHeader, sizeof(BITMAPINFOHEADER), 1, filePtr);
    fseek(filePtr, bitmapFileHeader.bfOffBits, SEEK_SET);
    bitmapImage = new unsigned char[bitmapInfoHeader->biSizeImage];
    if (!bitmapImage) {
        fprintf(stderr, "Error in LoadBitmapFile: memory error\n");
        return NULL;
    }

    fread(bitmapImage, 1, bitmapInfoHeader->biSizeImage, filePtr);
    if (bitmapImage == NULL) {
        fprintf(stderr, "Error in LoadBitmapFile: memory error\n");
        return NULL;
    }
    for (imageIdx = 0;
        imageIdx < bitmapInfoHeader->biSizeImage; imageIdx += 3) {
        tempRGB = bitmapImage[imageIdx];
        bitmapImage[imageIdx] = bitmapImage[imageIdx + 2];
        bitmapImage[imageIdx + 2] = tempRGB;
    }
    fclose(filePtr);
    return bitmapImage;
}

void TextLoad(int i, char *filename)
{
    BITMAPINFOHEADER bitmapInfoHeader;
    unsigned char*   bitmapData;

    bitmapData = LoadBitmapFile(filename, &bitmapInfoHeader);

    glBindTexture(GL_TEXTURE_2D, texture[i]);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, bitmapInfoHeader.biWidth,
        bitmapInfoHeader.biHeight, 0, GL_RGB, GL_UNSIGNED_BYTE,
        bitmapData);
}


void Init()
{
    glClearColor(0.0, 0.0, 0.0, 0.0);
    glShadeModel(GL_SMOOTH);

    // initialize OpenGL lighting
    GLfloat lightPos[] = { 0.0, 0.0, 1.0, 1 };
    GLfloat lightAmb[4] = { 1.0, 1.0, 1.0, 1.0 };
    GLfloat lightDiff[4] = { 1.0, 1.0, 1.0, 1.0 };
    GLfloat lightSpec[4] = { 1.0, 1.0, 1.0, 1.0 };

    //glLightfv(GL_LIGHT0, GL_POSITION, lightPos);
    glLightfv(GL_LIGHT0, GL_AMBIENT, lightAmb);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, lightDiff);
    glLightfv(GL_LIGHT0, GL_SPECULAR, lightSpec);
    glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, lightPos);
    glLightModeli(GL_LIGHT_MODEL_COLOR_CONTROL_EXT, GL_SEPARATE_SPECULAR_COLOR_EXT);
    GLfloat black[] = { 0.0, 0.0, 0.0, 1.0 };
    //glLightModelfv(GL_LIGHT_MODEL_AMBIENT, black);

    glEnable(GL_LIGHT0);
    glEnable(GL_LIGHTING);
    glEnable(GL_DEPTH_TEST);

    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

    glGenTextures(1, texture);
    TextLoad(0, "tree.bmp");

    //define multitexture
    glMultiTexCoord1fARB = (PFNGLMULTITEXCOORD1FARBPROC)wglGetProcAddress("glMultiTexCoord1fARB");
    glMultiTexCoord2fARB = (PFNGLMULTITEXCOORD2FARBPROC)wglGetProcAddress("glMultiTexCoord2fARB");
    glActiveTextureARB = (PFNGLACTIVETEXTUREARBPROC)wglGetProcAddress("glActiveTextureARB");
    glClientActiveTextureARB = (PFNGLCLIENTACTIVETEXTUREARBPROC)wglGetProcAddress("glClientActiveTextureARB");
}

void Draw_Teapot()
{
    glPushMatrix();
    glTranslatef(0, -1.5, 0);
    glutSolidTeapot(0.5);
    glPopMatrix();
}

void Draw_Tree()
{
    glPushMatrix();
    float mat[16];
    glGetFloatv(GL_MODELVIEW_MATRIX, mat);

    // Identify the 3*3 sub matrix in Top-left corner 
    mat[1] = mat[2] = mat[6] = 0;
    mat[4] = mat[8] = mat[9] = 0;
    mat[0] = mat[5] = mat[10] = 1;

    glLoadMatrixf(mat);

    //choose texture
    glActiveTextureARB(GL_TEXTURE0_ARB);
    glEnable(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D, texture[0]);
    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT);
    glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_REPLACE);

    //top
    glBegin(GL_QUADS);
    glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 0, 0);
    glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 1, 0);
    glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 1, 1);
    glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 0, 1);

    //bottom 
    glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 1, 1); glVertex3f(2, 2, 0);
    glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 1, 0); glVertex3f(2, -2, 0);
    glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 0, 0); glVertex3f(-2, -2, 0);
    glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 0, 1); glVertex3f(-2, 2, 0);

    glEnd();

    glTranslatef(0, 0, 0);
    glActiveTextureARB(GL_TEXTURE0_ARB);
    glDisable(GL_TEXTURE_2D);

    glPopMatrix();
}

void DrawScene()
{
    Draw_Teapot();
    Draw_Tree();
}

void updateView(int width, int height)
{
    // Reset The Current View port
    glViewport(0, 0, (GLsizei)width, (GLsizei)height);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    float whRatio = (GLfloat)width / (GLfloat)height;
    if (bPersp) {
        gluPerspective(45.0f, whRatio, 0.1f, 100.0f);
    }
    else {
        glOrtho(-3, 3, -3, 3, -100, 100);
    }

    glMatrixMode(GL_MODELVIEW);
}

void reshape(int width, int height)
{
    if (height == 0)
    {
        height = 1;
    }

    wHeight = height;
    wWidth = width;

    updateView(wHeight, wWidth);
}

void idle()
{
    glutPostRedisplay();
}

//eye position
GLdouble eye[] = { 0, 0, 8 };
//where it is aimed
GLdouble center[] = { 0, 0, 0 };

void key(unsigned char k, int x, int y)
{
    switch (k)
    {
    case 27:
    case 'q': {exit(0); break; }
    case 'p': {bPersp = !bPersp; break; }

    case ' ': {bAnim = !bAnim; break; }
    case 'o': {bWire = !bWire; break; }

    case 'a': {
                  eye[0] += 0.2f;
                  //center[0] += 0.2f;
                  break;
    }
    case 'd': {
                  eye[0] -= 0.2f;
                  //center[0] -= 0.2f;
                  break;
    }
    case 'w': {
                  eye[1] -= 0.2f;
                  //center[1] -= 0.2f;
                  break;
    }
    case 's': {
                  eye[1] += 0.2f;
                  //center[1] += 0.2f;
                  break;
    }
    case 'z': {
                  eye[2] -= 0.2f;
                  center[2] -= 0.2f;
                  break;
    }
    case 'c': {
                  eye[2] += 0.2f;
                  center[2] += 0.2f;
                  break;
    }

    case 'l':{
                 bTexLit = !bTexLit;
                 break;
    }
    }

    updateView(wHeight, wWidth);
}

void begin_window_coords()
{
    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glLoadIdentity();
    glOrtho(0.0, wWidth, 0.0, wHeight, -1.0, 1.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}

void end_window_coords()
{
    glMatrixMode(GL_PROJECTION);
    glPopMatrix();
    glMatrixMode(GL_MODELVIEW);
}

void display()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // gradient background
    glDisable(GL_DEPTH_TEST);
    glDisable(GL_LIGHTING);
    begin_window_coords();
    glBegin(GL_QUADS);
    glColor3f(0.2, 0.4, 0.8);
    glVertex2f(0.0, 0.0);
    glVertex2f(wWidth, 0.0);
    glColor3f(0.05, 0.1, 0.2);
    glVertex2f(wWidth, wHeight);
    glVertex2f(0, wHeight);
    glEnd();
    end_window_coords();

    glLoadIdentity();
    gluLookAt(eye[0], eye[1], eye[2],
        center[0], center[1], center[2],
        0, 1, 0);

    if (bWire) {
        glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
    }
    else {
        glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
    }

    glEnable(GL_DEPTH_TEST);
    glEnable(GL_LIGHTING);

    glPushMatrix();
    glRotatef(fRotate, 0, 1, 0);
    DrawScene();
    glPopMatrix();

    if (bAnim){
        fRotate += 0.5f;
    }

    glutSwapBuffers();
}

int main(int argc, char *argv[])
{
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_RGBA | GLUT_DEPTH | GLUT_DOUBLE);
    glutInitWindowPosition(400, 200);
    glutInitWindowSize(512, 512);
    glutCreateWindow("BillBoard Illustration");
    Init();

    glutDisplayFunc(display);
    glutReshapeFunc(reshape);
    glutKeyboardFunc(key);
    glutIdleFunc(idle);

    glutMainLoop();
    return 0;
}



你可能感兴趣的:(技术片段)