■ OpenGL: Tutorial on using FreeType Fonts in OpenGL[1]
네헤 라는 곳!!! 여기에 좋은 글들이 많네 .. 자 이 내용을 중심으로 이 사람은 어떻게 FreeType library를 활용했는지 보자.
1. MSVC(Microsoft Visual C++) 새 프로젝트 생성
- FreeType Library - Include folder 포함
- 라이브러리 설정(OpenGL.dlls, FreeType.dlls)
2. FreeType 설정 및 빌드를 위한 파일 복사
참고 사이트 하단에 가면 "DOWNLOAD visual C++ code for This Lesson"
- 클릭해서 다운로드
- 폴더 내에 "freetype.cpp" and "freetype.h" 파일을 생성한 프로젝트에 복사
- *.dll 파일도 생성한 프로젝트에 복사
3. FreeType 헤더파일 분석
-#include <xxxx.h> 파일들은 핸들링 및 보다 쉬운 디버깅을 위해 선언
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | #ifndef FREE_NEHE_H #define FREE_NEHE_H // FreeType Headers #include <ft2build.h> #include <freetype/freetype.h> #include <freetype/ftglyph.h> #include <freetype/ftoutln.h> #include <freetype/fttrigon.h> // OpenGL Headers #include <windows.h> // (The GL Headers Need It) #include <GL/gl.h> #include <GL/glu.h> // Some STL Headers #include <vector> #include <string> // Using The STL Exception Library Increases The // Chances That Someone Else Using Our Code Will Correctly // Catch Any Exceptions That We Throw. #include <stdexcept> // MSVC Will Spit Out All Sorts Of Useless Warnings If // You Create Vectors Of Strings, This Pragma Gets Rid Of Them. #pragma warning(disable: 4786) |
- WGL(Window OpenGL)
- 폰트 저장을 위해 구조체 선언 및 네임스페이스로 욘속이 freetype 이라고 이름 지어줌 낭낭
- 머 13장에서 배웠던 내용이라는데 난 그냥 이글만 봐서... 느낌상 위 구조체를 사용함으로써 하나의 명령 glCAllLists 를 사용하여 문자열을 출력할 수 있는 좋은 능력을 보여준다고 하넹 +_+ 흣(대단한 녀석이군)
- 128ASCII 코드를 사용하기 때문에 128개의 텍스쳐를 만들어야 함. 마지막 비트는 폰트의 픽셀 높이 정보라는군.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | ///Wrap everything in a namespace, that we can use common ///function names like "print" without worrying about ///overlapping with anyone else's code. namespace freetype { //Inside of this namespace, give ourselves the ability //to write just "vector" instead of "std::vector" using std::vector; //Ditto for string. using std::string; //This holds all of the information related to any //freetype font that we want to create. struct font_data { float h; ///< Holds the height of the font. GLuint * textures; ///< Holds the texture id's GLuint list_base; ///< Holds the first display list id //The init function will create a font of //of the height h from the file fname. void init(const char * fname, unsigned int h); //Free all the resources assosiated with the font. void clean(); }; //The flagship function of the library - this thing will print //out text at window coordinates x,y, using the font ft_font. //The current modelview matrix will also be applied to the text. void print(const font_data &ft_font, float x, float y, const char *fmt, ...) ; } #endif |
마지막으로 우리는 문자열을 출력할 수 있는 프로토타입(prototype)가 필요하다고 하는군. 위 코드 마지막 줄에 프린트 욘속을 말함.
4. FreeType.cpp 파일 분석
- 각 문자를 찍기 위해 텍스쳐를 사용함.
- OpenGL 텍스쳐들은 2의 제곱을 가지는 차원수를 가져야 한다고 함.
1 2 3 4 5 6 7 8 9 | // This Function Gets The First Power Of 2 >= The // Int That We Pass It. inline int next_p2 (int a ) { int rval=1; // rval<<=1 Is A Prettier Way Of Writing rval*=2; while(rval<a) rval<<=1; return rval; } |
해서 위와 같은 소스로 2제곱을 해서 리턴함
다음 함수가 제일 중요함 각 폰트의 Glyph 정보를 읽고 bitmap 저장
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | // Create A Display List Corresponding To The Given Character. void make_dlist ( FT_Face face, char ch, GLuint list_base, GLuint * tex_base ) { // The First Thing We Do Is Get FreeType To Render Our Character // Into A Bitmap. This Actually Requires A Couple Of FreeType Commands: // Load The Glyph For Our Character. if(FT_Load_Glyph( face, FT_Get_Char_Index( face, ch ), FT_LOAD_DEFAULT )) throw std::runtime_error("FT_Load_Glyph failed"); // Move The Face's Glyph Into A Glyph Object. FT_Glyph glyph; if(FT_Get_Glyph( face->glyph, &glyph )) throw std::runtime_error("FT_Get_Glyph failed"); // Convert The Glyph To A Bitmap. FT_Glyph_To_Bitmap( &glyph, ft_render_mode_normal, 0, 1 ); FT_BitmapGlyph bitmap_glyph = (FT_BitmapGlyph)glyph; // This Reference Will Make Accessing The Bitmap Easier. FT_Bitmap& bitmap=bitmap_glyph->bitmap; |
이제 OpenGL Texture를 위한 legal source를 만들기 위해 빈공간에 저장된 bitmap 정보를 저장함
- OpenGL에서 bitmap는 픽셀당 8bit 정보 저장
- grays를 저장할 수 있음(anti-aliased text를 만드는데 필요한 정보임)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | // Use Our Helper Function To Get The Widths Of // The Bitmap Data That We Will Need In Order To Create // Our Texture. int width = next_p2( bitmap.width ); int height = next_p2( bitmap.rows ); // Allocate Memory For The Texture Data. GLubyte* expanded_data = new GLubyte[ 2 * width * height]; // Here We Fill In The Data For The Expanded Bitmap. // Notice That We Are Using A Two Channel Bitmap (One For // Channel Luminosity And One For Alpha), But We Assign // Both Luminosity And Alpha To The Value That We // Find In The FreeType Bitmap. // We Use The ?: Operator To Say That Value Which We Use // Will Be 0 If We Are In The Padding Zone, And Whatever // Is The FreeType Bitmap Otherwise. for(int j=0; j <height;j++) { for(int i=0; i < width; i++){ expanded_data[2*(i+j*width)]= expanded_data[2*(i+j*width)+1] = (i>=bitmap.width || j>=bitmap.rows) ? 0 : bitmap.buffer[i + bitmap.width*j]; } } |
위 작업 후 OpenGL Texture 를 만들어야 함.
- 알파 값을 통해 투명도 설정 가능하게 해놨음
1 2 3 4 5 6 7 8 9 10 11 12 13 | // Now We Just Setup Some Texture Parameters. glBindTexture( GL_TEXTURE_2D, tex_base[ch]); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); // Here We Actually Create The Texture Itself, Notice // That We Are Using GL_LUMINANCE_ALPHA To Indicate That // We Are Using 2 Channel Data. glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, expanded_data ); // With The Texture Created, We Don't Need The Expanded Data Anymore. delete [] expanded_data; |
이제 우리는 텍스트를 그리기 위해 텍스쳐가 맵핑된 quads를 사용할 거얌!
- quads를 이용함으로써 장점: 회전, 크기 변경, 문자열 사용 가능, 색상 적용 가능
흣 +_+
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | // Now We Create The Display List glNewList(list_base+ch,GL_COMPILE); glBindTexture(GL_TEXTURE_2D,tex_base[ch]); glPushMatrix(); // First We Need To Move Over A Little So That // The Character Has The Right Amount Of Space // Between It And The One Before It. glTranslatef(bitmap_glyph->left,0,0); // Now We Move Down A Little In The Case That The // Bitmap Extends Past The Bottom Of The Line // This Is Only True For Characters Like 'g' Or 'y'. glTranslatef(0,bitmap_glyph->top-bitmap.rows,0); // Now We Need To Account For The Fact That Many Of // Our Textures Are Filled With Empty Padding Space. // We Figure What Portion Of The Texture Is Used By // The Actual Character And Store That Information In // The x And y Variables, Then When We Draw The // Quad, We Will Only Reference The Parts Of The Texture // That Contains The Character Itself. float x=(float)bitmap.width / (float)width, y=(float)bitmap.rows / (float)height; // Here We Draw The Texturemapped Quads. // The Bitmap That We Got From FreeType Was Not // Oriented Quite Like We Would Like It To Be, // But We Link The Texture To The Quad // In Such A Way That The Result Will Be Properly Aligned. glBegin(GL_QUADS); glTexCoord2d(0,0); glVertex2f(0,bitmap.rows); glTexCoord2d(0,y); glVertex2f(0,0); glTexCoord2d(x,y); glVertex2f(bitmap.width,0); glTexCoord2d(x,0); glVertex2f(bitmap.width,bitmap.rows); glEnd(); glPopMatrix(); glTranslatef(face->glyph->advance.x >> 6 ,0,0); // Increment The Raster Position As If We Were A Bitmap Font. // (Only Needed If You Want To Calculate Text Length) // glBitmap(0,0,0,0,face->glyph->advance.x >> 6,0,NULL); // Finish The Display List glEndList(); } |
다음 init함수는 FreeType Font 라이브러리를 초기화 하고 주어진 폰트 파일을 읽은 다음 폰트 사이즈를 설정한 후 해당 갯수의 텍스쳐를 생성하여 정보를 저장하는 작업
준비 작업임
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | void font_data::init(const char * fname, unsigned int h) { // Allocate Some Memory To Store The Texture Ids. textures = new GLuint[128]; this->h=h; // Create And Initilize A FreeType Font Library. FT_Library library; if (FT_Init_FreeType( &library )) throw std::runtime_error("FT_Init_FreeType failed"); // The Object In Which FreeType Holds Information On A Given // Font Is Called A "face". FT_Face face; // This Is Where We Load In The Font Information From The File. // Of All The Places Where The Code Might Die, This Is The Most Likely, // As FT_New_Face Will Fail If The Font File Does Not Exist Or Is Somehow Broken. if (FT_New_Face( library, fname, 0, &face )) throw std::runtime_error("FT_New_Face failed (there is probably a problem with your font file)"); // For Some Twisted Reason, FreeType Measures Font Size // In Terms Of 1/64ths Of Pixels. Thus, To Make A Font // h Pixels High, We Need To Request A Size Of h*64. // (h << 6 Is Just A Prettier Way Of Writing h*64) FT_Set_Char_Size( face, h << 6, h << 6, 96, 96); // Here We Ask OpenGL To Allocate Resources For // All The Textures And Display Lists Which We // Are About To Create. list_base=glGenLists(128); glGenTextures( 128, textures ); // This Is Where We Actually Create Each Of The Fonts Display Lists. for(unsigned char i=0;i<128;i++) make_dlist(face,i,list_base,textures); // We Don't Need The Face Information Now That The Display // Lists Have Been Created, So We Free The Assosiated Resources. FT_Done_Face(face); // Ditto For The Font Library. FT_Done_FreeType(library); } |
폰트와 관련된 텍스쳐와 displaylist를 초기화 시키는 함수
1 2 3 4 5 | void font_data::clean() { glDeleteLists(list_base,128); glDeleteTextures(128,textures); delete [] textures; } |
자원을 썼으니 반납하시오!!!!
윈도우 화면 투영 함수
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | // A Fairly Straightforward Function That Pushes // A Projection Matrix That Will Make Object World // Coordinates Identical To Window Coordinates. inline void pushScreenCoordinateMatrix() { glPushAttrib(GL_TRANSFORM_BIT); GLint viewport[4]; glGetIntegerv(GL_VIEWPORT, viewport); glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); gluOrtho2D(viewport[0],viewport[2],viewport[1],viewport[3]); glPopAttrib(); } // Pops The Projection Matrix Without Changing The Current // MatrixMode. inline void pop_projection_matrix() { glPushAttrib(GL_TRANSFORM_BIT); glMatrixMode(GL_PROJECTION); glPopMatrix(); glPopAttrib(); } |
폰트 출력함수
출력함수는 lesson13과 매우 흡사하나 두가지 중요한 점이 다르다.
- 비트멥들 대신 2채널의 텍스쳐를 사용하기 위해 OpenGL 활성화 flags 설정
- 새로운 라인을 다루기 위해 추가적인 기능을 넣었음.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 | ///Much like Nehe's glPrint function, but modified to work ///with freetype fonts. void print(const font_data &ft_font, float x, float y, const char *fmt, ...) { // We want a coordinate system where things coresponding to window pixels. pushScreenCoordinateMatrix(); GLuint font=ft_font.list_base; float h=ft_font.h/.63f; //We make the height about 1.5* that of char text[256]; // Holds Our String va_list ap; // Pointer To List Of Arguments if (fmt == NULL) // If There's No Text *text=0; // Do Nothing else { va_start(ap, fmt); // Parses The String For Variables vsprintf(text, fmt, ap); // And Converts Symbols To Actual Numbers va_end(ap); // Results Are Stored In Text } //Here is some code to split the text that we have been //given into a set of lines. //This could be made much neater by using //a regular expression library such as the one avliable from //boost.org (I've only done it out by hand to avoid complicating //this tutorial with unnecessary library dependencies). const char *start_line=text; vector<string> lines; for(const char *c=text;*c;c++) { if(*c=='\n') { string line; for(const char *n=start_line;n<c;n++) line.append(1,*n); lines.push_back(line); start_line=c+1; } } /* if(start_line) { string line; for(const char *n=start_line;n<c;n++) line.append(1,*n); lines.push_back(line); } */ glPushAttrib(GL_LIST_BIT | GL_CURRENT_BIT | GL_ENABLE_BIT | GL_TRANSFORM_BIT); glMatrixMode(GL_MODELVIEW); glDisable(GL_LIGHTING); glEnable(GL_TEXTURE_2D); glDisable(GL_DEPTH_TEST); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glListBase(font); float modelview_matrix[16]; glGetFloatv(GL_MODELVIEW_MATRIX, modelview_matrix); //This is where the text display actually happens. //For each line of text we reset the modelview matrix //so that the line's text will start in the correct position. //Notice that we need to reset the matrix, rather than just translating //down by h. This is because when each character is //draw it modifies the current matrix so that the next character //will be drawn immediatly after it. for(int i=0;i<lines.size();i++) { glPushMatrix(); glLoadIdentity(); glTranslatef(x,y-h*i,0); glMultMatrixf(modelview_matrix); // The commented out raster position stuff can be useful if you need to // know the length of the text that you are creating. // If you decide to use it make sure to also uncomment the glBitmap command // in make_dlist(). // glRasterPos2f(0,0); glCallLists(lines[i].length(), GL_UNSIGNED_BYTE, lines[i].c_str()); // float rpos[4]; // glGetFloatv(GL_CURRENT_RASTER_POSITION ,rpos); // float len=x-rpos[0]; glPopMatrix(); } glPopAttrib(); pop_projection_matrix(); } |
5. 사용방법
step 1: 헤더파일 선언
1 | #include "freetype.h" // Header for our little font library. |
step 2: 전역 font_data object 생성
1 2 | // This holds all the information for the font that we are going to create. freetype::font_data our_font; |
step 3: 폰트 자원의 메모리 할당 및 해제
1 | our_font.init("test.TTF", 16); //Build the freetype font |
종료 함수에 삽입
1 | our_font.clean(); |
step 4: 폰트 드로웅~~~
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | int DrawGLScene(GLvoid) // Here's Where We Do All The Drawing { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear Screen And Depth Buffer glLoadIdentity(); // Reset The Current Modelview Matrix glTranslatef(0.0f,0.0f,-1.0f); // Move One Unit Into The Screen // Blue text glColor3ub(0,0,0xff); // Position the WGL Text On The Screen glRasterPos2f(-0.40f, 0.35f); glPrint("Active WGL Bitmap Text With NeHe - %7.2f", cnt1); // Print GL Text To The Screen // Here We Print Some Text Using Our Freetype Font // The Only Really Important Command Is The Actual Print() Call, // But For The Sake Of Making The Results A Bit More Interesting // I Have Put In Some Code To Rotate And Scale The Text. // Red Text glColor3ub(0xff,0,0); glPushMatrix(); glLoadIdentity(); glRotatef(cnt1,0,0,1); glScalef(1,.8+.3*cos(cnt1/5),1); glTranslatef(-180,0,0); freetype::print(our_font, 320, 200, "Active FreeType Text - %7.2f", cnt1); glPopMatrix(); // Uncomment This To Test Out Print's Ability To Handle Newlines. // freetype::print(our_font, 320, 200, "Here\nthere\nbe\n\nnewlines\n.", cnt1); cnt1+=0.051f; // Increase The First Counter cnt2+=0.005f; // Increase The First Counter return TRUE; // Everything Went OK } |
step 5: 예외처리 함수 삽입(+____+;;;)
1 2 3 4 | MSG msg; // Windows Message Structure BOOL done=FALSE; // Bool Variable To Exit Loop try { |
1 2 3 4 5 6 7 8 9 10 | // Shutdown KillGLWindow(); // Kill The Window // Catch Any Exceptions That Were Thrown } catch (std::exception &e) { MessageBox(NULL,e.what(),"CAUGHT AN EXCEPTION",MB_OK | MB_ICONINFORMATION); } return (msg.wParam); // Exit The Program } |
Now if we ever hit an exception, we will get a little message box telling us what happened. Note that exception handling can slow down your code, so when you are compiling a release version of your program, you may want to go to Project->Settings->C/C++, switch to the "C++ Language" category, and turn off exception handling.
So that's it! Compile the program and you should see some nice FreeType rendered text moving around underneath the original bitmapped text from lesson 13.
마지막 글
약간의 버그가 있다고 하넹
http://nehe.gamedev.net/data/lessons/http://www.cs.northwestern.edu/~sco590/fonts_tutorial.html
위 사이트 가서 버그를 샤샤삭~~~
뚜둥 실행화면
자 이렇게 나온단다.
나는 MFC 기반의 프로젝트를 만들고 있기 때문에 아마 좀 다르게 나오겠지 적용방법도 다르고 한번 해보자구웃!
역시나 제대로 출력이 안되넹
glPrint("FreeType using multi texture!!"); 요렇게 해서 찍어 봤지..
뒤에 g라는 욘속이 들어 가네 왜 그러징.. ㅋㅋ
흣... 이거 제대로 찍을라면 소스를 좀 수정해야 함.
아놔운서!!!(-_ㅡ;;)
수정해야 하는 소스는 다음과 같다.
위치: FreeType.cpp : void print(const font_data &ft_font, float x, float y, const char *fmt, ...)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | const char *start_line=text; vector<string> lines; for(const char *c=text;*c;c++) { if(*c=='\n') { string line; for(const char *n=start_line;n<c;n++) line.append(1,*n); lines.push_back(line); start_line=c+1; } } if(start_line) { string line; for(const char *n=start_line;n<c;n++) line.append(1,*n); lines.push_back(line); } |
위와 같이 되어 있는데.. 우선 데이터가 잘 넘어오는지 다 확인하고 나중에 lines 의 값을 찍어 보니 맹탕 알고 보니 위 소스를 보면 아하하고 아놔운서를 불렀더랬지
- 위 소스의 의도는 여러 줄을 찍는 것을 가능하게 해 주는 소스이다. 즉 넘거 받은 문자열 중에 개행문자를 만나면 줄을 바꾸고 저장.. 요런식으로 여러 줄을 찍을라고 한 작업이다.
1 2 3 4 5 6 7 8 | const char *start_line=text; vector<string> lines; string line; for (const char *c = text; *c; c++) { if (*c != '\n') line.append(1, *c); }lines.push_back(line); |
수정된 소스라오. 난 한줄만 찍어도 충분하다.
본인은 폰트를 찍어서 회전시켜야 하는 일이 생겼다. 대부분의 그래픽 응용에서 폰트에 회전을 줄 필요는 없는 듯 보였다. 그래서 몇일 작업했더랬지.. 그래서 결국에는 회전 성공.. 하 지금은 텍스쳐를 여러개 만들어서 하는 방법이다. 이 방법보다 좋은것은 텍스쳐 아틀라스로 하나의 텍스쳐에서 좌표를 가져와 버텍스 매핑하는 방법이 되겠지 우선 여기까지만 해도 괜찮을듯 하다. 여러번해보고 버그 생기면 다시 고쳐야겠지 아뵤 힘내세요.
[1]http://nehe.gamedev.net/tutorial/freetype_fonts_in_opengl/24001/
'OpenGL' 카테고리의 다른 글
OpenGL: 2D Scatter Plot(Demo Video) (1) | 2014.05.25 |
---|---|
OpenGL: Modern OpenGL Tutorial Text Rendering 02 (0) | 2014.05.20 |
OpenGL: Very Simple Textured Text (1) | 2014.05.18 |
Visual Studio: Setting FreeType Library (0) | 2014.05.16 |
OpenGL: Modern OpenGL Tutorial Text Rendering 01 (0) | 2014.05.15 |