冒险解谜游戏中文网 ChinaAVG

标题: 【OpenGL汉化研究】OpenGL如何显示文字 [打印本页]

作者: shane007    时间: 2010-1-23 16:50
标题: 【OpenGL汉化研究】OpenGL如何显示文字
原文
- a3 e( n6 H4 G4 g2 s/ a0 Dhttp://blog.chinaunix.net/u/26313/showart_1663543.html
- |, Y! {' d: Y
. J* w% ?; o" j: T8 q+ `' I4 k8 ~内容超多的一课!不过我想精彩的程度也一定不会让大家失望。大家不妨先浏览一下课程里的图片:)。
3 r! n+ f. W" G9 U3 o/ }6 @' M* J0 O5 t6 l$ q9 U1 t! ]
本课我们来谈谈如何显示文字。 ; s# ^$ u' h4 z& \) i
OpenGL并没有直接提供显示文字的功能,并且,OpenGL也没有自带专门的字库。因此,要显示文字,就必须依赖操作系统所提供的功能了。 # Q  ]. a  D1 U# u, t" Z: ?
各种流行的图形操作系统,例如Windows系统和Linux系统,都提供了一些功能,以便能够在OpenGL程序中方便的显示文字。 4 l  c. q1 w6 u4 [/ D& G2 N0 Y- b% `
最常见的方法就是,我们给出一个字符,给出一个显示列表编号,然后操作系统由把绘制这个字符的OpenGL命令装到指定的显示列表中。当需要绘制字符的时候,我们只需要调用这个显示列表即可。
" ?( t+ S: y* l1 M不过,Windows系统和Linux系统,产生这个显示列表的方法是不同的(虽然大同小异)。作为我个人,只在Windows系统中编程,没有使用Linux系统的相关经验,所以本课我们仅针对Windows系统。 5 L2 F( _0 p  N; n; R7 `  U
/ w* f0 c. I. t$ ~* z' I
OpenGL版的“Hello, World!”
$ |2 J* k4 B$ `9 Y写完了本课,我的感受是:显示文字很简单,显示文字很复杂。看似简单的功能,背后却隐藏了深不可测的玄机。 0 c& p3 j6 K, u
呵呵,别一开始就被吓住了,让我们先从“Hello, World!”开始。 4 B5 O* @  V6 v+ Z/ V
前面已经说过了,要显示字符,就需要通过操作系统,把绘制字符的动作装到显示列表中,然后我们调用显示列表即可绘制字符。
& F4 N  P+ y$ Y- G. S" T假如我们要显示的文字全部是ASCII字符,则总共只有0到127这128种可能,因此可以预先把所有的字符分别装到对应的显示列表中,然后在需要时调用这些显示列表。
: Z1 Y# A$ B5 }5 I7 I9 VWindows系统中,可以使用wglUseFontBitmaps函数来批量的产生显示字符用的显示列表。函数有四个参数: / m/ C3 F& W8 G, q3 a4 L" n7 ?7 k
第一个参数是HDC,学过Windows GDI的朋友应该会熟悉这个。如果没有学过,那也没关系,只要知道调用wglGetCurrentDC函数,就可以得到一个HDC了。具体的情况可以看下面的代码。
2 ]4 H0 k8 V' \% m) @5 m& k8 k& F3 h; D/ S第二个参数表示第一个要产生的字符,因为我们要产生0到127的字符的显示列表,所以这里填0。
1 `) P8 A) Y# }5 W第三个参数表示要产生字符的总个数,因为我们要产生0到127的字符的显示列表,总共有128个字符,所以这里填128。
( r3 G& [7 `& t; `2 X0 m第四个参数表示第一个字符所对应显示列表的编号。假如这里填1000,则第一个字符的绘制命令将被装到第1000号显示列表,第二个字符的绘制命令将被装到第1001号显示列表,依次类推。我们可以先用glGenLists申请128个连续的显示列表编号,然后把第一个显示列表编号填在这里。
# M0 e) D9 F* a: @% p( E5 ?( O还要说明一下,因为wglUseFontBitmaps是Windows系统特有的函数,所以在使用前需要加入头文件:#include <windows.h>。 / {/ c+ Y" T& ~/ K; p2 _0 [( P
现在让我们来看具体的代码: : [! C( I1 Y6 I. i% k4 {) J' ]  Y

* \& ?6 \4 t" C/ i! j& S- B0 L#include <windows.h>
  B7 K* f* @; U8 T) t2 n( ?% k9 [% f, o$ m
// ASCII字符总共只有0到127,一共128种字符 5 D0 \; O( h- [! }+ `
#define MAX_CHAR      128 0 ]9 T$ F- ]  h( @1 f

+ x, n" e3 S0 A3 `. E& X5 p; Q+ kvoid drawString(const char* str) { 6 N. x7 k3 {( J7 n/ }: W& h
    static int isFirstCall = 1;
' I4 y0 ]; I) m! Y+ c, f2 o8 z    static GLuint lists; - S$ M9 _& c) N3 b

3 G. `  A3 J0 F# |; e    if( isFirstCall ) { // 如果是第一次调用,执行初始化 , {" l8 |) Z' a( E, S! x  F5 V
                        // 为每一个ASCII字符产生一个显示列表
3 }. Z( d' r8 o% d: N! j        isFirstCall = 0; 7 ?+ v$ z! Y  o4 P- k% u

( X" S$ U: D; @5 t: c        // 申请MAX_CHAR个连续的显示列表编号
* K( Y" Q' d8 k" n. M9 A        lists = glGenLists(MAX_CHAR);
7 ?+ r* W/ h# E& N3 q6 Y& V$ G, K* K/ N) r4 U. d' m
        // 把每个字符的绘制命令都装到对应的显示列表中
) A1 d& |( M1 U& y        wglUseFontBitmaps(wglGetCurrentDC(), 0, MAX_CHAR, lists);
3 {  w- }  {( b    } . ]: Z- c2 {8 }7 q& E6 \, o( i2 }
    // 调用每个字符对应的显示列表,绘制每个字符 : P7 L6 Q% N, m& B) ]6 h( w8 b
    for(; *str!='\0'; ++str) & |: T2 L) z% p9 I  |" k8 F" x4 z# L
        glCallList(lists + *str);
  ~- J( N" }- h( H# ^# B/ v6 M}
) L6 |6 r: q  Y# f8 ~# D( q* [
- \  p% O8 r1 [6 ?5 e- K% G' t9 Y9 d; S& }3 O' x- `7 w! u9 D
9 f$ X4 f  L9 b  r  C, |; k
显示列表一旦产生就一直存在(除非调用glDeleteLists销毁),所以我们只需要在第一次调用的时候初始化,以后就可以很方便的调用这些显示列表来绘制字符了。   D6 [3 L6 }# c9 v3 _" j% |
绘制字符的时候,可以先用glColor*等指定颜色,然后用glRasterPos*指定位置,最后调用显示列表来绘制。 % w2 h% L, C0 c1 I; O

% Z% O. e! t4 O: O. U; xvoid display(void) { % o: S3 m8 K/ Y4 Q8 F- u' z
    glClear(GL_COLOR_BUFFER_BIT); 8 ]9 q' Z5 [1 k) H

3 ~1 M0 f5 j: B8 m    glColor3f(1.0f, 0.0f, 0.0f);   K% U+ Q% B, u$ O0 U* V. ^
    glRasterPos2f(0.0f, 0.0f);
4 Y- r& V/ V$ e  k0 U' v5 G    drawString("Hello, World!");
+ w; l9 W2 j2 Y7 \3 e( I$ m! m. A. e
    glutSwapBuffers();
6 o+ S+ B( u* |- h} $ s; K! y) P! i/ Q- ^" `& S* T

! O6 C9 ~" l- F4 ^+ [1 K% }5 o* Z: i1 v9 {: s1 ~; F
# J/ c( E  v- B0 u2 P1 B9 r
效果如图:
% E  V6 M% B' g7 ?0 n+ ?# I[attach]15073[/attach] . @% h1 k0 \0 C! m
8 k: e+ z* T# E. V3 Y
指定字体
: {& K& u: d. Z  }8 X在产生显示列表前,Windows允许选择字体。
' P: a3 |& j; [/ `1 X1 B4 W. N* T我做了一个selectFont函数来实现它,大家可以看看代码。 % F; E, y# g- f; w- P, c% X- S. R+ C

# N& X$ h8 n) X) b! l& b3 Z, Lvoid selectFont(int size, int charset, const char* face) { # q7 q2 t& b7 K, e8 X" `
    HFONT hFont = CreateFontA(size, 0, 0, 0, FW_MEDIUM, 0, 0, 0,
/ S. {+ k/ F1 J        charset, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
$ }! {- [6 ~/ _$ z        DEFAULT_QUALITY, DEFAULT_PITCH | FF_SWISS, face); , V6 {5 S! q/ h
    HFONT hOldFont = (HFONT)SelectObject(wglGetCurrentDC(), hFont);
) I4 c3 `* h5 r0 P    DeleteObject(hOldFont);
/ Q# Z9 m& f% v' B5 u* i0 {0 g} 2 U3 L. F4 i& Z" d( v9 `
4 F  l/ }7 Q6 \
void display(void) { 7 [% s; o* r0 s! V
    selectFont(48, ANSI_CHARSET, "Comic Sans MS"); ! m. ?; V& }% e
3 t2 Z! L- F2 U" h9 j9 P
    glClear(GL_COLOR_BUFFER_BIT);   d4 E' }  y; }5 o0 G
: ~( \  _5 n8 d1 `
    glColor3f(1.0f, 0.0f, 0.0f); 1 k' S1 L  o4 h1 p/ K0 ^
    glRasterPos2f(0.0f, 0.0f); ; {/ @" [0 V/ [& w( `4 x
    drawString("Hello, World!");
# }! R0 x* J$ x' l! T* q
7 J- L1 X9 S/ a/ X; @. D  P    glutSwapBuffers();
- [: B0 i! E. E: l% D4 S% f}
  {8 T% ?0 r% R) t) W3 {1 N) b' k& a
  X" ^3 @! k6 h  l) r( a% Z% q, T
最主要的部分就在于那个参数超多的CreateFont函数,学过Windows GDI的朋友应该不会陌生。没有学过GDI的朋友,有兴趣的话可以自己翻翻MSDN文档。这里我并不准备仔细讲这些参数了,下面的内容还多着呢:(
2 n6 W9 x% n5 F) s* {如果需要在自己的程序中选择字体的话,把selectFont函数抄下来,在调用glutCreateWindow之后、在调用wglUseFontBitmaps之前使用selectFont函数即可指定字体。函数的三个参数分别表示了字体大小、字符集(英文字体可以用ANSI_CHARSET,简体中文字体可以用GB2312_CHARSET,繁体中文字体可以用CHINESEBIG5_CHARSET,对于中文的Windows系统,也可以直接用DEFAULT_CHARSET表示默认字符集)、字体名称。
# C, g/ i" m) x. Q效果如图: - ?2 G& t% I( m! q
[attach]15074[/attach] $ t, u% P- G% Z$ f" Z

6 {* K: Z2 {* M  [/ b9 b; C显示中文
( `" l" J6 H2 K8 a原则上,显示中文和显示英文并无不同,同样是把要显示的字符做成显示列表,然后进行调用。 , ~$ h, M) o# o$ G" h
但是有一个问题,英文字母很少,最多只有几百个,为每个字母创建一个显示列表,没有问题。但是汉字有非常多个,如果每个汉字都产生一个显示列表,这是不切实际的。
6 C1 ]: N- Q+ s% [7 ^& h我们不能在初始化时就为每个字符建立一个显示列表,那就只有在每次绘制字符时创建它了。当我们需要绘制一个字符时,创建对应的显示列表,等绘制完毕后,再将它销毁。 , W8 z  r& i3 k) e  L" X
这里还经常涉及到中文乱码的问题,我对这个问题也不甚了解,但是网上流传的版本中,使用了MultiByteToWideChar这个函数的,基本上都没有出现乱码,所以我也准备用这个函数:)
% G; \0 J6 F7 \& g6 T  |7 z$ A( ~通常我们在C语言里面使用的字符串,如果中英文混合的话,例如“this is 中文字符.”,则英文字符只占用一个字节,而中文字符则占用两个字节。用MultiByteToWideChar函数,可以转化为所有的字符都占两个字节(同时解决了前面所说的乱码问题:))。   l  E$ g- R- j% L1 h4 X( h
转化的代码如下: " B% F* n0 b( I3 U3 h3 G# Z+ P  Q

# ^& v! _( ]2 ^8 d4 A// 计算字符的个数 $ v  O& `$ V1 J+ o
// 如果是双字节字符的(比如中文字符),两个字节才算一个字符
* }! c8 L$ F( \0 ^; l- ~// 否则一个字节算一个字符 , Y0 \! x) h1 \5 p, C
len = 0; : t6 t+ i/ T$ w  A  p' L9 R+ H/ i
for(i=0; str!='\0'; ++i) / r- W. H" c4 Q8 B
{
$ Y. |8 U9 y- W+ D8 u# V    if( IsDBCSLeadByte(str) )
) S  h- k8 I4 ]2 \1 P        ++i; 0 b  n  @) e2 s% H" s2 C% M
    ++len; ( t' B4 k" C% m' U6 o
}
/ }9 |- J! q/ k; G- z+ G) U( L
) {* @/ j, v- S" n0 I: ]// 将混合字符转化为宽字符
, L  f) z4 b3 m6 G% Iwstring = (wchar_t*)malloc((len+1) * sizeof(wchar_t)); 7 k: u* e, t# M
MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, str, -1, wstring, len); 4 }2 v: s9 f9 R0 H* H
wstring[len] = L'\0';
% J. {$ h: f2 g( b9 r  l# A! I3 _5 M/ @4 h
// 用完后记得释放内存
, Y. e' k1 N% e- l4 Mfree(wstring);
; k6 J% v( m+ ~+ s! f0 f
+ E$ ?. F& e" M0 p2 Y
2 V* y/ n  A8 V9 C6 D5 z" W  Q$ L: D
加上前面所讲到的wglUseFontBitmaps函数,即可显示中文字符了。
" [; L) J0 Y7 _7 w/ e$ D! Z& D
2 d! }+ J. M, d+ N8 Pvoid drawCNString(const char* str) {
2 c9 f0 D0 I, U% q4 r/ I3 \! p) J    int len, i; 8 r! ]9 v  t7 E, x5 L% }% g
    wchar_t* wstring; 0 u. \. l0 L$ j, V; `  N
    HDC hDC = wglGetCurrentDC(); 1 \: }5 z1 a& V0 D4 D# b$ `6 t5 I
    GLuint list = glGenLists(1); 1 X9 u" Y; f! s' B( A
3 o; A1 {: M  |1 h) q
    // 计算字符的个数
; i4 J- k6 h/ q    // 如果是双字节字符的(比如中文字符),两个字节才算一个字符 3 \) q7 y% e5 c5 l  k, F
    // 否则一个字节算一个字符 5 @5 u. C7 V% A1 r  `, h
    len = 0; - W4 k; ]7 R/ p/ A+ ~% c
    for(i=0; str!='\0'; ++i) + J" F  G- V9 J' E
    { + P2 A$ L8 o2 k5 |
        if( IsDBCSLeadByte(str) ) 9 ~# U9 a& Y! `  F" {; {4 ?: G' S
            ++i;
  C& \2 I/ t6 Z        ++len; 4 ^* J$ N: j- c9 P6 E) l* H8 i4 r9 b
    }
' K0 w+ v7 l- _) _, o1 Y
: k3 n( e6 J' a/ O& P9 ^2 j    // 将混合字符转化为宽字符
! ~" U) ~' _1 |- i: c    wstring = (wchar_t*)malloc((len+1) * sizeof(wchar_t));
: Z: W7 S. p$ Q- \9 b# ?  I    MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, str, -1, wstring, len); 0 h) i& w6 q% B: |/ K
    wstring[len] = L'\0'; + M& `0 I7 U8 `% N- R

' M4 \, T  l. t8 l6 E    // 逐个输出字符
* _! f4 x+ V6 f; [4 ^    for(i=0; i<len; ++i)
  X  u( K4 a, D8 R- X' q    {
. f! z, z' x1 A  j# [) T% ?/ g        wglUseFontBitmapsW(hDC, wstring, 1, list);
% Q8 Y+ D  {/ N$ L        glCallList(list);
- |3 d( f: Z6 N. i$ d$ Q6 \! N    }
0 j5 W# x1 [( i3 h2 `7 y4 o1 N3 n" _* l" @
    // 回收所有临时资源
3 h5 p: f  G# G% B+ N; J' C    free(wstring);
) N, d! T- P& t- G! f    glDeleteLists(list, 1);
0 w$ U4 @9 R# e} : u: S) \" m( Z; j
! q$ I  V( L; J: |

0 B; ]0 t- f  D" S9 H) a
& G$ {3 ]9 u- G/ Y/ S+ v- l1 p0 a- V$ }注意我用了wglUseFontBitmapsW函数,而不是wglUseFontBitmaps。wglUseFontBitmapsW是wglUseFontBitmaps函数的宽字符版本,它认为字符都占两个字节。因为这里使用了MultiByteToWideChar,每个字符其实是占两个字节的,所以应该用wglUseFontBitmapsW。 3 t6 u) Z: n0 b/ i# \$ I
7 D$ g% r- L: ^% s
void display(void) {
. n# C% Y2 Y: }  |  W    glClear(GL_COLOR_BUFFER_BIT);
6 Z2 X* W$ D8 j4 U" r2 h9 Z' d6 v. H9 |* M7 e9 R- _8 g
    selectFont(48, ANSI_CHARSET, "Comic Sans MS"); 7 b/ R: y8 [$ r3 n3 o" X( C& g- W
    glColor3f(1.0f, 0.0f, 0.0f);
- a  [4 X& c: h/ C1 ], G: }9 c    glRasterPos2f(-0.7f, 0.4f); / x7 E/ K) `) u5 u8 [- j' o2 P
    drawString("Hello, World!");
3 J1 K" M) \/ u( [: Q# n: ^3 i" L; y0 G, C  I
    selectFont(48, GB2312_CHARSET, "楷体_GB2312");
6 }3 k( N5 y+ W' M0 d    glColor3f(1.0f, 1.0f, 0.0f); 0 K. u& H: ?1 C3 ?5 z6 d/ r9 H
    glRasterPos2f(-0.7f, -0.1f); 0 D6 F% c/ W5 I  X/ U8 z
    drawCNString("当代的中国汉字");
+ G0 o9 u! U, S5 ]/ Q7 I: ^: f3 R9 u) P
    selectFont(48, DEFAULT_CHARSET, "华文仿宋"); ; n9 b' E) V9 F& }
    glColor3f(0.0f, 1.0f, 0.0f);
+ X% t& x8 v) X/ E2 y3 {    glRasterPos2f(-0.7f, -0.6f);
/ s3 F- M( Q) w0 {& F    drawCNString("傳統的中國漢字"); - ?' r& L# X* \/ ~* D8 ^
9 Q0 {# ~' {) g, F
    glutSwapBuffers();
* u6 s7 |+ w+ f6 W1 H. T- i}
+ b0 y$ y, e7 R; _2 `; L
- d1 D) H+ {3 n6 x0 H
+ I- m/ z- x9 F7 N! W效果如图:

$ N6 H7 i* w+ ^  f& [" j1 w% X[attach]15075[/attach]




欢迎光临 冒险解谜游戏中文网 ChinaAVG (https://chinaavg.com/) Powered by Discuz! X3.2