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);
在OpenGL中当视点发生变化时,实际上我们的视线是不动的,始终朝向电脑屏幕,而场景在做相对运动。所以实际上绘制出的效果应该是树始终处于画面中朝向保持不变,茶壶会发生转动。
场景一:
场景二(视点变化):
项目的代码如下:
控制: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;
}