具有紋理貼圖的輪廓字體(Texture Mapped Outline Fonts)
在發布了前兩篇關于位圖字體和輪廓字體的教程以后,我收到很多郵件,很多讀者都想知道如何才能給字體賦予紋理貼圖。你可以使用自動紋理坐標生成器。它會為字體上的每一個多邊形生成紋理坐標。
一個小注釋,這段代碼是專門針對Windows寫的,它使用了Windows的wgl函數來創建字體,顯然,Apple機系統有agl,X系統有glx來支持做同樣事情的,不幸的是,我不能保證這些代碼也是容易使用的。如果哪位有能在屏幕上顯示文字且獨立于平臺的代碼,請告訴我,我將重寫一個有關字體的教程。
我們將使用第14課的代碼來創作紋理字體的演示。如果程序中哪部分的代碼有變化,我會重寫那部分的所有代碼以便看出我做的改動。
下面這部分代碼類似于第14課的代碼,但是這次我們還要加上stdarg.h頭文件。
#include <windows.h> // Header File For Windows
#include <math.h> // Header File For Math Library
#include <stdio.h> // Header File For Standard Input/Output
#include <gl\gl.h> // Header File For The OpenGL32 Library
#include <gl\glu.h> // Header File For The GLu32 Library
#include <gl\glaux.h> // Header File For The Glaux Library
HDC hDC=NULL; // Private GDI Device Context
HGLRC hRC=NULL; // Permanent Rendering Context
HWND hWnd=NULL; // Holds Our Window Handle
HINSTANCE hInstance; // Holds The Instance Of The Application
bool keys[256]; // Array Used For The Keyboard Routine
bool active=TRUE; // Window Active Flag Set To TRUE By Default
bool fullscreen=TRUE; // Fullscreen Flag Set To Fullscreen Mode By Default我們還要添加一個叫做texture[]的整型變量。它用于保存紋理。后面3行是第14課中的代碼,本課不做改動。
GLuint texture[1]; // One Texture Map
GLuint base; // Base Display List For The Font Set
GLfloat rot; // Used To Rotate The Text
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);// Declaration For WndProc 下面的部分做了一些小改動。我打算在這課使用wingdings字體來顯示一個海盜旗(骷髏頭和十字骨頭)的標志。如果你想顯示文字的話,就不用改動第14課中的代碼了,也可以選擇另一種字體。
有些人想知道如何使用wingdings字體,這也是我不用標準字體的一個原因。wingdings是一種符號字體,使用它時需要做一些改動。告訴Windows使用wingdings字體并不太簡單。如果你把字體的名字改為wingdings,你會注意到字體其實并沒有選到。你必須告訴Windows這種字體是一種符號字體而不是一種標準字符字體。后面會繼續解釋。
GLvoid BuildFont(GLvoid) // Build Our Bitmap Font
{
GLYPHMETRICSFLOAT gmf[256]; // Address Buffer For Font Storage
HFONT font; // Windows Font ID
base = glGenLists(256); // Storage For 256 Characters
font = CreateFont( -12, // Height Of Font
0, // Width Of Font
0, // Angle Of Escapement
0, // Orientation Angle
FW_BOLD, // Font Weight
FALSE, // Italic
FALSE, // Underline
FALSE, // Strikeout這就是有魔力的那一行!不使用第14課中的ANSI_CHARSET,我們將使用SYMBOL_CHARSET。這會告訴Windows我們創建的字體并不是由標準字符組成的典型字體。所謂符號字體通常是由一些小圖片(符號)組成的。如果你忘了改變這行,wingdings,webdings以及你想用的其它符號字體就不會工作。
SYMBOL_CHARSET, // Character Set Identifier下面幾行沒有變化。
OUT_TT_PRECIS, // Output Precision
CLIP_DEFAULT_PRECIS, // Clipping Precision
ANTIALIASED_QUALITY, // Output Quality
FF_DONTCARE|DEFAULT_PITCH, // Family And Pitch既然我們已經選擇了符號字符集標識符,我們就可以選擇wingdings字體了!
"Wingdings"); // Font Name剩下幾行代碼沒有變化
SelectObject(hDC, font); //Selects The Font We Created
wglUseFontOutlines( hDC, // Select The Current DC
0, // Starting Character
255, // Number Of Display Lists To Build
base, // Starting Display Lists我們允許有更多的誤差,這意味著GL不會嚴格的遵守字體的輪廓線。如果你把誤差設置為0.0f,你就會發現嚴格地在曲面上貼圖存在一些問題。但是如果你允許一定的誤差,很多問題都可以避免。
0.1f, // Deviation From The True Outlines下面三行代碼還是相同的。
0.2f, // Font Thickness In The Z Direction
WGL_FONT_POLYGONS, // Use Polygons, Not Lines
gmf); // Address Of Buffer To Recieve Data
}
在ReSizeGLScene()函數之前,我們要加上下面一段代碼來讀取紋理。你可能會認得這些前幾課中的代碼。我們創建一個保存位圖的地方,讀取位圖,告訴Windows生成一個紋理,并把它保存在texture[0]中。
我們創建一種細化紋理(mipmapped texture),這樣會看起來好些。紋理的名字叫做lights.bmp。
AUX_RGBImageRec *LoadBMP(char *Filename) // Loads A Bitmap Image
{
FILE *File=NULL; // File Handle
if (!Filename) // Make Sure A Filename Was Given
{
return NULL; // If Not Return NULL
}
File=fopen(Filename,"r"); // Check To See If The File Exists
if (File) // Does The File Exist?
{
fclose(File); // Close The Handle
return auxDIBImageLoad(Filename);// Load The Bitmap And Return A Pointer
}
return NULL; // If Load Failed Return NULL
}
int LoadGLTextures() // Load Bitmaps And Convert To Textures
{
int Status=FALSE; // Status Indicator
AUX_RGBImageRec *TextureImage[1]; // Create Storage Space For The Texture
memset(TextureImage,0,sizeof(void *)*1); // Set The Pointer To NULL
if (TextureImage[0]=LoadBMP("Data/Lights.bmp")) // Load the Bitmap
{
Status=TRUE; // Set The Status To TRUE
glGenTextures(1, &texture[0]); // Create The Texture
// Build Linear Mipmapped Texture
glBindTexture(GL_TEXTURE_2D, texture[0]);
gluBuild2DMipmaps(GL_TEXTURE_2D, 3, TextureImage[0]->sizeX, TextureImage[0]->sizeY, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_NEAREST);下面四行代碼將為我們繪制在屏幕上的任何物體自動生成紋理坐標。函數glTexGen非常強大,而且復雜,如果要完全講清楚它的數學原理需要再寫一篇教程。不過,你只要知道GL_S和GL_T是紋理坐標就可以了。默認狀態下,它被設置為提取物體此刻在屏幕上的x坐標和y坐標,并把它們轉換為頂點坐標。你會發現到物體在z平面沒有紋理,只顯示一些斑紋。正面和反面都被賦予了紋理,這些都是由glTexGen函數產生的。(X(GL_S)用于從左到右映射紋理,Y(GL_T)用于從上到下映射紋理。
GL_TEXTURE_GEN_MODE允許我們選擇我們想在S和T紋理坐標上使用的紋理映射模式。你有3種選擇:
GL_EYE_LINEAR - 紋理會固定在屏幕上。它永遠不會移動。物體將被賦予處于它通過的地區的那一塊紋理。
GL_OBJECT_LINEAR - 這種就是我們使用的模式。紋理被固定于在屏幕上運動的物體上。
GL_SPHERE_MAP - 每個人都喜歡。創建一種有金屬質感的物體。
需要注意的是我省略了很多代碼。我們還需要設置GL_OBJECT_PLANE,但是,默認參數就是我們想要的參數。如果你想了解更多,那就買一本好書,或者查閱MSDN。
// Texturing Contour Anchored To The Object
glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
// Texturing Contour Anchored To The Object
glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
glEnable(GL_TEXTURE_GEN_S);
glEnable(GL_TEXTURE_GEN_T);
}
if (TextureImage[0]) // If Texture Exists
{
if (TextureImage[0]->data) // If Texture Image Exists
{
free(TextureImage[0]->data); // Free The Texture Image Memory
}
free(TextureImage[0]); // Free The Image Structure
}
return Status; // Return The Status
}在InitGL()的最后有幾行新代碼。BuildFont()被放到了讀取紋理的代碼之后。glEnable(GL_COLOR_MATERIAL) 這行被刪掉了,如果你想使用glColor3f(r,g,b)來改變紋理的顏色,那么就把glEnable(GL_COLOR_MATERIAL)這行重新加到這部分代碼中。
int InitGL(GLvoid) // All Setup For OpenGL Goes Here
{
if (!LoadGLTextures()) // Jump To Texture Loading Routine
{
return FALSE; // If Texture Didn't Load Return FALSE
}
BuildFont(); // Build The Font
glShadeModel(GL_SMOOTH); // Enable Smooth Shading
glClearColor(0.0f, 0.0f, 0.0f, 0.5f); // Black Background
glClearDepth(1.0f); // Depth Buffer Setup
glEnable(GL_DEPTH_TEST); // Enables Depth Testing
glDepthFunc(GL_LEQUAL); // The Type Of Depth Testing To Do
glEnable(GL_LIGHT0); // Quick And Dirty Lighting (Assumes Light0 Is Set Up)
glEnable(GL_LIGHTING); // Enable Lighting
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // Really Nice Perspective Calculations啟動2D紋理映射,并選擇第一個紋理。這樣就把第一個紋理映射到我們繪制在屏幕上的3D物體上了。如果你想加入更多的操作,可以按自己的意愿啟動或禁用紋理映射。
glEnable(GL_TEXTURE_2D); // Enable Texture Mapping
glBindTexture(GL_TEXTURE_2D, texture[0]); // Select The Texture
return TRUE; // Initialization Went OK重置大小的代碼沒有變化,但DrawGLScene這部分代碼有變化。
int DrawGLScene(GLvoid) // Here's Where We Do All The Drawing
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear The Screen And The Depth Buffer
glLoadIdentity(); // Reset The View這里是第一處變動。我們打算使用COS和SIN讓物體繞著屏幕旋轉而不是把它固定在屏幕中間。我們將把物體向屏幕里移動3個單位。在x軸,我們將移動范圍限制在-1.1到+1.1之間。我們使用rot變量來控制左右移動。我們把上下移動的范圍限制在+0.8到-0.8之間。同樣使用rot變量來控制上下移動(最好充分利用你的變量)。
Position The Texture
glTranslatef(1.1f*float(cos(rot/16.0f)),0.8f*float(sin(rot/20.0f)),-3.0f);下面做常規的旋轉。這會使符號在X,Y和Z軸旋轉。
glRotatef(rot,1.0f,0.0f,0.0f); // Rotate On The X Axis
glRotatef(rot*1.2f,0.0f,1.0f,0.0f); // Rotate On The Y Axis
glRotatef(rot*1.4f,0.0f,0.0f,1.0f); // Rotate On The Z Axis我們將物體相對觀察點向左向下移動一點,以便于把符號定位于每個軸的中心。否則,當我們旋轉它的時候,看起來就不像是在圍繞它自己的中心在旋轉。-0.35只是一個能讓符號正確顯示的數。我也試過一些其它數,因為我不知道這種字體的寬度是多少,可以適情況作出調整。我不知道為什么這種字體沒有一個中心。
glTranslatef(-0.35f,-0.35f,0.1f); // Center On X, Y, Z Axis最后,我們繪制海盜旗的符號,然后增加rot變量,從而使這個符號在屏幕中旋轉和移動。如果你不知道我是如何從字母‘N’中得到海盜旗符號的,那就打開Microsoft Word或是寫字板。在字體下拉菜單中選擇Wingdings字體。輸入大寫字母‘N’,就會顯示出海盜旗符號了。
glPrint("N"); // Draw A Skull And Crossbones Symbol
rot+=0.1f; // Increase The Rotation Variable
return TRUE; // Keep Going
}最后要做的事就是在KillGLWindow()的最后添加KillFont()函數,如下所示。添加這行代碼很重要。它將在我們退出程序之前做清理工作。
if (!UnregisterClass("OpenGL",hInstance)) // Are We Able To Unregister Class
{
MessageBox(NULL,"Could Not Unregister Class.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
hInstance=NULL; // Set hInstance To NULL
}
KillFont(); 盡管我沒有講的細致入微,但我想你應該很好的理解了如何讓OpenGL為你生成紋理坐標。在給你的字體或者是同類物體賦予紋理映射時,應該沒有問題了,而且只需要改變兩行代碼,你就可以啟用球體映射了,它的效果簡直酷斃了!