冒险解谜游戏中文网 ChinaAVG

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

作者: shane007    时间: 2010-1-23 16:50
标题: 【OpenGL汉化研究】OpenGL如何显示文字
原文
8 g9 w, y* A# Ehttp://blog.chinaunix.net/u/26313/showart_1663543.html & m5 _& D3 a  a& V, l
( y3 W/ }* H5 I5 `* b5 v( P
内容超多的一课!不过我想精彩的程度也一定不会让大家失望。大家不妨先浏览一下课程里的图片:)。
& u) r1 e; y0 R. O; M% Q7 R5 U4 V* a2 V# \. ^
本课我们来谈谈如何显示文字。
2 L# N3 D" R8 h0 W6 qOpenGL并没有直接提供显示文字的功能,并且,OpenGL也没有自带专门的字库。因此,要显示文字,就必须依赖操作系统所提供的功能了。 # z  A+ {. ^: H- }& \; l
各种流行的图形操作系统,例如Windows系统和Linux系统,都提供了一些功能,以便能够在OpenGL程序中方便的显示文字。 6 ~5 P2 c8 {0 H+ x: \
最常见的方法就是,我们给出一个字符,给出一个显示列表编号,然后操作系统由把绘制这个字符的OpenGL命令装到指定的显示列表中。当需要绘制字符的时候,我们只需要调用这个显示列表即可。 5 v# H3 o1 ^# P
不过,Windows系统和Linux系统,产生这个显示列表的方法是不同的(虽然大同小异)。作为我个人,只在Windows系统中编程,没有使用Linux系统的相关经验,所以本课我们仅针对Windows系统。
" j8 M  C/ f) |4 f0 I$ z6 {3 Q" ]* A# ^2 x% U& Z. c
OpenGL版的“Hello, World!”
3 Z2 x4 v- X. H9 L, ]3 l- B" y/ r写完了本课,我的感受是:显示文字很简单,显示文字很复杂。看似简单的功能,背后却隐藏了深不可测的玄机。 : K2 F) y, `/ r- r; ?
呵呵,别一开始就被吓住了,让我们先从“Hello, World!”开始。 - i8 V+ Z2 H$ P$ N& k9 q; y4 U
前面已经说过了,要显示字符,就需要通过操作系统,把绘制字符的动作装到显示列表中,然后我们调用显示列表即可绘制字符。
0 X+ R; F4 u6 T$ C6 ~假如我们要显示的文字全部是ASCII字符,则总共只有0到127这128种可能,因此可以预先把所有的字符分别装到对应的显示列表中,然后在需要时调用这些显示列表。
7 \3 N+ U" I# P/ L7 _7 XWindows系统中,可以使用wglUseFontBitmaps函数来批量的产生显示字符用的显示列表。函数有四个参数:
5 |2 x% r6 y7 N( v2 t, h- c% W第一个参数是HDC,学过Windows GDI的朋友应该会熟悉这个。如果没有学过,那也没关系,只要知道调用wglGetCurrentDC函数,就可以得到一个HDC了。具体的情况可以看下面的代码。 ! k# c3 U6 o0 |- D
第二个参数表示第一个要产生的字符,因为我们要产生0到127的字符的显示列表,所以这里填0。 3 g* I3 l( P9 X- e* H- A
第三个参数表示要产生字符的总个数,因为我们要产生0到127的字符的显示列表,总共有128个字符,所以这里填128。
+ {& R/ c6 t. O2 L1 \/ c, t第四个参数表示第一个字符所对应显示列表的编号。假如这里填1000,则第一个字符的绘制命令将被装到第1000号显示列表,第二个字符的绘制命令将被装到第1001号显示列表,依次类推。我们可以先用glGenLists申请128个连续的显示列表编号,然后把第一个显示列表编号填在这里。
' f6 _) L) T, k还要说明一下,因为wglUseFontBitmaps是Windows系统特有的函数,所以在使用前需要加入头文件:#include <windows.h>。
$ r$ x* d# t2 s1 K现在让我们来看具体的代码:
0 ?# a8 e# y# t" g' D: y" U8 j8 _6 K8 a8 u0 u. u7 v
#include <windows.h>
/ e& Y: e' c) ]5 t" Y
6 i2 _8 l- Q4 g2 T! C7 v8 A) x// ASCII字符总共只有0到127,一共128种字符
% ]0 Q$ R* Q9 Z6 Z$ f- e4 i#define MAX_CHAR      128 - M: h5 j, O8 j2 L* Y
1 z5 {! o6 P* _' ^3 R
void drawString(const char* str) { 7 q; S3 Q! l5 G9 h, O; @
    static int isFirstCall = 1;
7 \, U. N; G# [    static GLuint lists;
# X: V9 l. ]5 B
6 F. Z1 ]% I6 i8 j" p1 m: p! Z4 f9 d    if( isFirstCall ) { // 如果是第一次调用,执行初始化
* U$ Y& d: s- |; a, q: y- w8 S# C                        // 为每一个ASCII字符产生一个显示列表
$ R4 |: @$ u6 }! a/ K. X        isFirstCall = 0;
- o$ M3 Y5 g' `& q  ?0 I0 |* E0 R' c6 U+ e, l3 s0 i- r
        // 申请MAX_CHAR个连续的显示列表编号 # O. v- \& |: {5 l$ G; y
        lists = glGenLists(MAX_CHAR); " P1 u- }1 |5 ?/ [& q/ `2 D

4 r1 o8 f9 d1 S: D" ~        // 把每个字符的绘制命令都装到对应的显示列表中 1 z: A. w. n6 M: g0 j5 k% v
        wglUseFontBitmaps(wglGetCurrentDC(), 0, MAX_CHAR, lists); $ v( H. D5 t  g9 ?& P
    }
' V: D0 G9 Z) E    // 调用每个字符对应的显示列表,绘制每个字符 " x6 g% J/ d- I9 S4 [1 ~# K& _
    for(; *str!='\0'; ++str)
# U0 c6 V; _( ?% |+ s        glCallList(lists + *str);
0 |+ }  k3 F0 j$ ?* x. R}
% k4 ?" k4 X$ y! v
8 a' g- v3 H1 X* t% J4 P
5 w2 U- T3 |, V/ k: d# Z/ x/ ]
' q% Q( G6 P& ^( s7 i. F. A显示列表一旦产生就一直存在(除非调用glDeleteLists销毁),所以我们只需要在第一次调用的时候初始化,以后就可以很方便的调用这些显示列表来绘制字符了。 , a# T! a2 o) n; p8 }
绘制字符的时候,可以先用glColor*等指定颜色,然后用glRasterPos*指定位置,最后调用显示列表来绘制。 % X4 m% G& c1 ?- J
! ?8 p) l/ K1 P- s% G) D
void display(void) { " s( k1 \5 w" x) o( Z
    glClear(GL_COLOR_BUFFER_BIT);
4 W* s5 @( P# I" b9 _. u
+ s- h0 s; s$ ^. M    glColor3f(1.0f, 0.0f, 0.0f);
, U" L- |' J, v# h4 C$ e    glRasterPos2f(0.0f, 0.0f); 6 I2 W/ W( X  A8 n7 ^
    drawString("Hello, World!"); + t6 e/ N2 w: y

" j' I: D. o* y0 p* e    glutSwapBuffers(); 4 m) p3 f& A7 \8 o+ ^- E4 n
} , j$ W3 e0 u5 o, W8 z" c+ G

" s7 E% A, p- l
$ ?& h3 j2 l. v- L" Y( X
0 _; i9 O8 V# _) ]' ^9 v: x9 p" @效果如图: ) y+ P( W( p- F' K1 K4 j( o% v
[attach]15073[/attach] 2 _8 W$ j  P: T! _8 I
# b( `2 S" Y% d; N# ~' ]6 D
指定字体
5 q) G) M$ i- ?& J$ w, e. `9 o& ~4 J在产生显示列表前,Windows允许选择字体。
# N8 g( T# y6 N# f; f我做了一个selectFont函数来实现它,大家可以看看代码。 " W# k  V. g- |, y, z" q
" L4 B8 Y3 ?- ~) I) m
void selectFont(int size, int charset, const char* face) {
. E" h6 _* `/ r* l    HFONT hFont = CreateFontA(size, 0, 0, 0, FW_MEDIUM, 0, 0, 0,
6 h/ i4 g5 F  Z+ p3 K8 h7 l$ g5 M$ A        charset, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
3 _$ `8 Y% x; T4 m) b4 V        DEFAULT_QUALITY, DEFAULT_PITCH | FF_SWISS, face); : j7 H% F9 Y4 o, j; g3 y8 H
    HFONT hOldFont = (HFONT)SelectObject(wglGetCurrentDC(), hFont); ) T& F, Z( U6 c9 o9 q; [
    DeleteObject(hOldFont);
" H$ C4 R+ M0 O: c+ R! W} ( W- h- a5 V3 `1 Q
% q" E+ s  y: C' B$ f7 K
void display(void) { " q8 J1 z9 N; Q  B
    selectFont(48, ANSI_CHARSET, "Comic Sans MS"); ( r, n9 N2 S$ R. V. d! L
8 {- Y6 d9 D0 ^  i$ Y
    glClear(GL_COLOR_BUFFER_BIT);
' M4 x! |( z* H4 ~/ D3 d4 j8 J) L% Y- |* u
    glColor3f(1.0f, 0.0f, 0.0f); 4 I7 I4 n8 u+ v& u. c4 t
    glRasterPos2f(0.0f, 0.0f); % x+ P. A. p! m$ @0 ]/ C+ I) c  s
    drawString("Hello, World!"); % K& n$ O: t+ J. u6 u; p
2 Z/ b8 I% F) x( T% L. \
    glutSwapBuffers(); * `" B" I$ f- o- Z5 {4 {
} ' _+ a( o9 H( }( R, e. M# j
5 M  ~. }8 o( I) l1 O9 x- ^

3 \, N/ h$ Y  M, Y- ]7 k) h8 a* n最主要的部分就在于那个参数超多的CreateFont函数,学过Windows GDI的朋友应该不会陌生。没有学过GDI的朋友,有兴趣的话可以自己翻翻MSDN文档。这里我并不准备仔细讲这些参数了,下面的内容还多着呢:(
& ?7 e' l! `1 U# D如果需要在自己的程序中选择字体的话,把selectFont函数抄下来,在调用glutCreateWindow之后、在调用wglUseFontBitmaps之前使用selectFont函数即可指定字体。函数的三个参数分别表示了字体大小、字符集(英文字体可以用ANSI_CHARSET,简体中文字体可以用GB2312_CHARSET,繁体中文字体可以用CHINESEBIG5_CHARSET,对于中文的Windows系统,也可以直接用DEFAULT_CHARSET表示默认字符集)、字体名称。 4 H. B1 W9 B2 ^& X
效果如图: 0 g, K  ^  i4 Y/ r$ s) f% d
[attach]15074[/attach] & S6 y8 K" y# `. h1 j$ Y$ B
: s. L. A1 }, l9 o& l
显示中文
- s5 _) v4 M2 s原则上,显示中文和显示英文并无不同,同样是把要显示的字符做成显示列表,然后进行调用。 " W) W0 c& ^7 j1 W1 h- z
但是有一个问题,英文字母很少,最多只有几百个,为每个字母创建一个显示列表,没有问题。但是汉字有非常多个,如果每个汉字都产生一个显示列表,这是不切实际的。
8 S: @! u/ p  |( U% w# J. U& z我们不能在初始化时就为每个字符建立一个显示列表,那就只有在每次绘制字符时创建它了。当我们需要绘制一个字符时,创建对应的显示列表,等绘制完毕后,再将它销毁。 ) ^/ T, V+ G1 ]' M# x* [. R
这里还经常涉及到中文乱码的问题,我对这个问题也不甚了解,但是网上流传的版本中,使用了MultiByteToWideChar这个函数的,基本上都没有出现乱码,所以我也准备用这个函数:)
" u1 w5 p6 H: r) ~  ?通常我们在C语言里面使用的字符串,如果中英文混合的话,例如“this is 中文字符.”,则英文字符只占用一个字节,而中文字符则占用两个字节。用MultiByteToWideChar函数,可以转化为所有的字符都占两个字节(同时解决了前面所说的乱码问题:))。 6 m2 ?) a! v8 O5 J+ E
转化的代码如下: 9 k5 w, n( i6 Q- @2 h* i4 H
0 ^" S) c$ v' V' P' ?7 V
// 计算字符的个数
3 u( g" p7 c, c  E, B// 如果是双字节字符的(比如中文字符),两个字节才算一个字符
6 r. Z% G& J8 ~6 C// 否则一个字节算一个字符 & K1 o7 L' e1 q' f
len = 0; + N$ z. }1 ]. G- Y  H4 R
for(i=0; str!='\0'; ++i)
1 Q, H5 H0 f! t" D* l{ 0 j, E/ K; H8 p% M$ h( m$ d
    if( IsDBCSLeadByte(str) ) 1 R# a$ M1 f- s; b$ b  `/ B7 p
        ++i; 5 r- ?8 ^' h1 ^; _! h: f  q2 K: S* e
    ++len; 0 A, b/ N) T4 _  K( n5 `  W$ q
}
; I& D( K0 A1 n$ a" ?' k! _0 e  M; U' ]4 Q
// 将混合字符转化为宽字符
6 b' E# x0 ^7 Y' `wstring = (wchar_t*)malloc((len+1) * sizeof(wchar_t));
: Z8 o5 G: u  U8 h1 U2 iMultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, str, -1, wstring, len); * }1 i0 B. W3 r( V' v1 s
wstring[len] = L'\0'; ; ^+ ?6 Y; B1 B+ \

8 g+ @& u* @" S, z// 用完后记得释放内存 ; ]$ i! B1 A/ t  B1 S$ D" n
free(wstring);
' }4 R6 f' b8 T# w6 o. r- q4 K: O4 J: ?& ?: {. w" \- P

5 L* q3 m* c$ ?) `1 ^$ l* }8 b$ o0 I8 N' L" ]* f% Z, w
加上前面所讲到的wglUseFontBitmaps函数,即可显示中文字符了。
7 D/ r9 r! A- I9 i) T/ ~
% C# ~/ u2 S$ a# a$ `# Vvoid drawCNString(const char* str) { 8 o. M8 T) t2 I3 j' N( q
    int len, i;
+ N: h4 ^0 U% |- X  N( \    wchar_t* wstring; & X% h# g& j$ i" m- d. ], e$ u- \
    HDC hDC = wglGetCurrentDC(); ( l# d6 V/ U5 V
    GLuint list = glGenLists(1); 3 D8 v4 |3 c% `3 B: _: p

4 q9 G9 {5 M' F' |8 ?9 @0 S' V    // 计算字符的个数
) Z  ^- Y* E& Y8 b) u3 o    // 如果是双字节字符的(比如中文字符),两个字节才算一个字符
  `) p3 u- _5 ]3 O    // 否则一个字节算一个字符 7 D* z7 G! N/ S
    len = 0; , J: J" r8 h0 g* {5 }( f
    for(i=0; str!='\0'; ++i) $ F/ Y+ _8 E; b8 v6 Y# a- ~9 R' Y
    {
2 g+ g/ ]5 c# u: G& G3 H        if( IsDBCSLeadByte(str) )
3 m8 b) @, D8 |+ d4 o. b; T- a            ++i;
. x+ N. S3 t7 \2 m        ++len; 0 Z5 ]0 n& i  X
    }
; w& k: _7 L# B, d
' b. F0 N/ G1 k* m4 ~& D1 c    // 将混合字符转化为宽字符
3 J* [; [# h' E& M    wstring = (wchar_t*)malloc((len+1) * sizeof(wchar_t)); # I5 v5 a0 d- f; Q  t: l: O+ C
    MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, str, -1, wstring, len);
. c1 @  e1 f" L; s! R# B+ W' \    wstring[len] = L'\0'; # A. q5 ?6 J# {4 ?$ t/ ~& a

* ?) `6 H3 d; q0 ~9 @    // 逐个输出字符
$ W2 K) u& O; x$ w    for(i=0; i<len; ++i) ( t  m! `4 H7 O, y5 o- J
    { 5 J, h* \9 M' J
        wglUseFontBitmapsW(hDC, wstring, 1, list); . g$ D, O) \2 W
        glCallList(list);
9 V2 W9 D1 K. a5 i* U0 d, O  e  E    }
, W7 E9 h' h7 X) I- Z  ^5 A. c
' ?8 F0 S: e- x; U7 n! s    // 回收所有临时资源 " D- a4 k: ?9 H
    free(wstring);
- r' r- [; C2 `0 R' T4 s1 l3 W    glDeleteLists(list, 1);
  \% h. J, Y  c( c! I5 ^" U) o# E}
5 p! }6 O. b% ]9 @4 \
- r! |+ D3 k0 k# u4 R4 |1 B+ Y8 _' @* m
3 }4 `  M2 z! o- G) F3 c/ T" g' z0 A
注意我用了wglUseFontBitmapsW函数,而不是wglUseFontBitmaps。wglUseFontBitmapsW是wglUseFontBitmaps函数的宽字符版本,它认为字符都占两个字节。因为这里使用了MultiByteToWideChar,每个字符其实是占两个字节的,所以应该用wglUseFontBitmapsW。 # w7 a1 O; Q/ {) O+ l" ]
# {  `* t  A4 E8 Y& G9 a1 b" h
void display(void) { % ^1 \+ T, [. X( X+ f+ w4 j
    glClear(GL_COLOR_BUFFER_BIT);
& G. y. G8 g! s; \: u/ _7 x' S0 d1 s/ U3 t- d0 {' o
    selectFont(48, ANSI_CHARSET, "Comic Sans MS"); ; A: [1 h+ V( _- h2 T  B
    glColor3f(1.0f, 0.0f, 0.0f); 4 U. S# }- X; y* j
    glRasterPos2f(-0.7f, 0.4f);
" [6 ~4 @& d2 i; q. Q' O% ^    drawString("Hello, World!");
2 V' [: O- N. ]1 \+ t' v
2 d* u( J3 s3 k  }    selectFont(48, GB2312_CHARSET, "楷体_GB2312");
% ~, ]. G6 i  I; J( S8 @3 V    glColor3f(1.0f, 1.0f, 0.0f);
, n" N2 ]2 h$ f/ X8 b    glRasterPos2f(-0.7f, -0.1f); + `; L3 ?* i3 n9 c) ~, k1 O2 _: M% F
    drawCNString("当代的中国汉字");
, ^( T% h& a9 g3 f6 B7 ^7 k0 L$ Y* I/ F6 ?& x
    selectFont(48, DEFAULT_CHARSET, "华文仿宋");
+ N% ]: s  ]; e1 q# m    glColor3f(0.0f, 1.0f, 0.0f);
4 f0 |# i1 U7 I    glRasterPos2f(-0.7f, -0.6f); 8 X7 S2 e# a, X
    drawCNString("傳統的中國漢字"); 9 Y4 y% ?8 c2 @& A8 b

9 q3 Z% b; f; Y9 K! x8 i    glutSwapBuffers(); 5 P5 \9 b; U( R+ X  _$ q* z
} ) u3 i7 K) t) w' X6 a  w, r
; I  d* E  {, r  T: j3 E; s- j. \# ^

% W5 f6 s$ T9 S$ g6 w# s效果如图:
5 c* H" _5 H  R, ?6 l1 {
[attach]15075[/attach]




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