原文 ) w( V& Y- | I8 }# A
http://blog.chinaunix.net/u/26313/showart_1663543.html
" y! N/ f! V/ r% \3 @; Z# L; d7 A
内容超多的一课!不过我想精彩的程度也一定不会让大家失望。大家不妨先浏览一下课程里的图片:)。
( y7 B, |9 z9 ?7 X* f3 ^; G- O# ^0 c, G d1 ~
本课我们来谈谈如何显示文字。 ! r$ g* n3 ~" i& J# S0 [
OpenGL并没有直接提供显示文字的功能,并且,OpenGL也没有自带专门的字库。因此,要显示文字,就必须依赖操作系统所提供的功能了。 9 E8 F4 _; H4 t& M8 @& R
各种流行的图形操作系统,例如Windows系统和Linux系统,都提供了一些功能,以便能够在OpenGL程序中方便的显示文字。 8 x1 p( @" M/ K) G+ c' m, ]
最常见的方法就是,我们给出一个字符,给出一个显示列表编号,然后操作系统由把绘制这个字符的OpenGL命令装到指定的显示列表中。当需要绘制字符的时候,我们只需要调用这个显示列表即可。 , x9 z4 c5 R7 \9 b9 Q; ]% P7 G- @; m* i8 i
不过,Windows系统和Linux系统,产生这个显示列表的方法是不同的(虽然大同小异)。作为我个人,只在Windows系统中编程,没有使用Linux系统的相关经验,所以本课我们仅针对Windows系统。 ) f& \+ @. N" M
; E+ A0 M* L5 e4 b j! h* AOpenGL版的“Hello, World!”
/ @: U3 ^" g6 f写完了本课,我的感受是:显示文字很简单,显示文字很复杂。看似简单的功能,背后却隐藏了深不可测的玄机。 - P: v$ C, `. b2 C2 T+ S2 K
呵呵,别一开始就被吓住了,让我们先从“Hello, World!”开始。 ( C: d m9 x; \0 x& _' D: B
前面已经说过了,要显示字符,就需要通过操作系统,把绘制字符的动作装到显示列表中,然后我们调用显示列表即可绘制字符。
; F7 d$ @" \3 l+ t- q) G) s8 ?假如我们要显示的文字全部是ASCII字符,则总共只有0到127这128种可能,因此可以预先把所有的字符分别装到对应的显示列表中,然后在需要时调用这些显示列表。 3 }* {- O2 \! B7 e- V
Windows系统中,可以使用wglUseFontBitmaps函数来批量的产生显示字符用的显示列表。函数有四个参数:
3 @$ L* n# B$ d; L8 Y0 ?( p6 }第一个参数是HDC,学过Windows GDI的朋友应该会熟悉这个。如果没有学过,那也没关系,只要知道调用wglGetCurrentDC函数,就可以得到一个HDC了。具体的情况可以看下面的代码。
5 L+ e/ h$ a" G( P% C" n# Z0 w* X第二个参数表示第一个要产生的字符,因为我们要产生0到127的字符的显示列表,所以这里填0。 7 j8 C+ J; y) ]" k
第三个参数表示要产生字符的总个数,因为我们要产生0到127的字符的显示列表,总共有128个字符,所以这里填128。
0 m3 h( j/ Q9 t# @! L第四个参数表示第一个字符所对应显示列表的编号。假如这里填1000,则第一个字符的绘制命令将被装到第1000号显示列表,第二个字符的绘制命令将被装到第1001号显示列表,依次类推。我们可以先用glGenLists申请128个连续的显示列表编号,然后把第一个显示列表编号填在这里。
1 P) G7 o3 N7 y% c, [还要说明一下,因为wglUseFontBitmaps是Windows系统特有的函数,所以在使用前需要加入头文件:#include <windows.h>。
. O+ B' U& s$ G$ f7 R9 P' z现在让我们来看具体的代码: + J& U2 X7 `" v1 G! p. w
6 F. g$ j! y: j# G% Z2 O5 `7 j
#include <windows.h> & r. F; o( r+ a% Z& t) @
& e; t1 k$ j2 \3 i/ m9 U2 r" ?
// ASCII字符总共只有0到127,一共128种字符
% K1 D) E/ h/ {3 |3 v8 p#define MAX_CHAR 128 . L# C+ ? m2 Q/ l' @) v+ `
' F# F' R+ |1 ~
void drawString(const char* str) { 9 f4 d' o" ^3 Z6 m/ Q1 o# l2 {
static int isFirstCall = 1; 8 u7 L4 v' l7 R% x, [
static GLuint lists; 5 [% r! T: y: O0 Z# E5 o7 B9 g' u
' _+ ^# w! |6 q% y, K; N8 L if( isFirstCall ) { // 如果是第一次调用,执行初始化 5 @( a( W- A |3 p. Y6 q m! v3 i1 G% j
// 为每一个ASCII字符产生一个显示列表 & @. Y& K5 @" Z* K/ A" }' @ a. V
isFirstCall = 0; , X* ]; @) W, m) o
- m; T2 ^7 W. \ // 申请MAX_CHAR个连续的显示列表编号
. I% C5 v" g; V: q lists = glGenLists(MAX_CHAR);
4 `% f5 p5 m) s0 M
! J- k' \, S+ p* f // 把每个字符的绘制命令都装到对应的显示列表中 2 p% z2 W$ s; d7 t3 l) U6 a
wglUseFontBitmaps(wglGetCurrentDC(), 0, MAX_CHAR, lists);
) z4 S4 R+ t' A; c# ~, b } 1 ^$ {! a9 v+ o$ K3 i
// 调用每个字符对应的显示列表,绘制每个字符
+ k m2 x" D0 Z: o8 p for(; *str!='\0'; ++str)
" a' r& z$ E4 x1 B5 N1 q3 O glCallList(lists + *str); 7 u& w9 j1 Q0 `- ?" s- a
}
9 z! w ^ H# b8 V% b
& ~$ N( ]8 ?, ?0 A5 v: ^% R5 I* ?2 t5 y5 r& `3 S1 m+ ^
+ R$ b9 t+ y5 }+ ]: k显示列表一旦产生就一直存在(除非调用glDeleteLists销毁),所以我们只需要在第一次调用的时候初始化,以后就可以很方便的调用这些显示列表来绘制字符了。 . O: H; z; D, i5 {; e8 g M' p& Z
绘制字符的时候,可以先用glColor*等指定颜色,然后用glRasterPos*指定位置,最后调用显示列表来绘制。 1 f$ T4 E6 r1 y& O9 Y
4 m, s0 f: z* t
void display(void) { 1 y7 W" n: C- q H( Z o e/ f
glClear(GL_COLOR_BUFFER_BIT);
* F8 C) p, i& }$ P/ V% Q W3 ^% [6 h( B0 _
glColor3f(1.0f, 0.0f, 0.0f); ! o& Z$ n; B6 G* b$ k
glRasterPos2f(0.0f, 0.0f); 4 ?1 G9 @7 P1 _& |
drawString("Hello, World!");
9 Q# E. U; L3 C- _- x/ O
* ^! p& P0 r% t% E( }; ?7 o glutSwapBuffers();
0 t& ]1 F# y# W% A2 @9 f6 o}
$ L* l" n& Z. G+ N' U9 r; |( k3 { [5 R' C
3 S3 K5 E; v5 T% E4 u* |
( K! S0 y6 w% ^8 i效果如图: 6 j. D. m: a k; l4 I( Z
6 K' [8 ~+ S, }
: s# g* V9 g' N( {+ I; B5 {指定字体
" @) G5 e4 S- j9 M& C# U: D: O在产生显示列表前,Windows允许选择字体。
+ J9 k1 H9 l; `我做了一个selectFont函数来实现它,大家可以看看代码。
' A, X1 J# Y% S" A7 b' b: W
% Y; Z/ m* w, J( Avoid selectFont(int size, int charset, const char* face) { 3 o" \ [; }% R. U
HFONT hFont = CreateFontA(size, 0, 0, 0, FW_MEDIUM, 0, 0, 0, % l( T, M( u: o1 [2 U
charset, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
! ^! O4 j3 f$ j- V1 S7 x6 O DEFAULT_QUALITY, DEFAULT_PITCH | FF_SWISS, face);
2 u& ] A X4 P HFONT hOldFont = (HFONT)SelectObject(wglGetCurrentDC(), hFont); & t. Z9 L+ o% K" P+ f
DeleteObject(hOldFont); + D9 R7 `: A) U: b- R+ S: b8 }+ I2 _
} 5 ^" \2 [+ U, J
3 y2 e' ?& R' d1 Hvoid display(void) {
. Z% V' v7 p3 l2 i$ ^ selectFont(48, ANSI_CHARSET, "Comic Sans MS"); $ g. Q! ~3 G3 J: E; z
2 _5 @: u' J9 }% ~( V: H/ V' \+ E
glClear(GL_COLOR_BUFFER_BIT); 7 v( V4 M( ?4 s& X! |: P! c
& {" V# d, v* Y% M
glColor3f(1.0f, 0.0f, 0.0f);
% p0 B' D+ n( o$ g0 y) x; j3 j glRasterPos2f(0.0f, 0.0f); 5 z% K9 _' h! j& ~+ I( W- C: ?, [, s
drawString("Hello, World!"); 0 w1 H8 [+ h" n7 Y; G
7 f3 p/ i+ k% P: F g/ u2 M
glutSwapBuffers();
+ ^+ Q% v! y( U5 p}
7 n* }( r0 O! D" w& ?" B
( ~! @, A% R+ ~0 D
6 X. b7 i7 O2 ?+ I+ [( \' k最主要的部分就在于那个参数超多的CreateFont函数,学过Windows GDI的朋友应该不会陌生。没有学过GDI的朋友,有兴趣的话可以自己翻翻MSDN文档。这里我并不准备仔细讲这些参数了,下面的内容还多着呢:( 4 u }0 U0 r' k* b+ K& _% \5 F$ x1 ~
如果需要在自己的程序中选择字体的话,把selectFont函数抄下来,在调用glutCreateWindow之后、在调用wglUseFontBitmaps之前使用selectFont函数即可指定字体。函数的三个参数分别表示了字体大小、字符集(英文字体可以用ANSI_CHARSET,简体中文字体可以用GB2312_CHARSET,繁体中文字体可以用CHINESEBIG5_CHARSET,对于中文的Windows系统,也可以直接用DEFAULT_CHARSET表示默认字符集)、字体名称。 0 z- t* a) Q0 y( c( x
效果如图: . ?( T! J" b8 B, J
" u0 v/ ^: [+ _( K9 w5 A
" G3 u1 a9 P" B& Z" G( h显示中文 - l) M( B6 }- A# \$ @) A
原则上,显示中文和显示英文并无不同,同样是把要显示的字符做成显示列表,然后进行调用。 * T* \% u2 z- k/ ]
但是有一个问题,英文字母很少,最多只有几百个,为每个字母创建一个显示列表,没有问题。但是汉字有非常多个,如果每个汉字都产生一个显示列表,这是不切实际的。 * x2 x6 x L5 g/ H5 h. `
我们不能在初始化时就为每个字符建立一个显示列表,那就只有在每次绘制字符时创建它了。当我们需要绘制一个字符时,创建对应的显示列表,等绘制完毕后,再将它销毁。 2 y' P! {$ u, b6 Z
这里还经常涉及到中文乱码的问题,我对这个问题也不甚了解,但是网上流传的版本中,使用了MultiByteToWideChar这个函数的,基本上都没有出现乱码,所以我也准备用这个函数:)
. [& \+ \/ r- |* q- X$ ^! {6 U3 J通常我们在C语言里面使用的字符串,如果中英文混合的话,例如“this is 中文字符.”,则英文字符只占用一个字节,而中文字符则占用两个字节。用MultiByteToWideChar函数,可以转化为所有的字符都占两个字节(同时解决了前面所说的乱码问题:))。
/ ^/ |* }' @. [7 u- m5 Z! b转化的代码如下: ( L5 t5 ]) X7 s* T( [* X
3 b& u: D" M- p
// 计算字符的个数
O' _- _! w7 P/ c; N! e// 如果是双字节字符的(比如中文字符),两个字节才算一个字符
5 ], Z ^7 S2 k% a4 p/ g; M// 否则一个字节算一个字符 # E) S9 i" @, I* Z
len = 0;
0 P9 t: ~8 ? Q* Y2 Tfor(i=0; str!='\0'; ++i) ' t' l( Q5 B4 [7 ~/ q/ A* O. o
{ 2 F D7 _$ \1 F F
if( IsDBCSLeadByte(str) )
1 O* i& ^/ ~+ C$ | ++i; 1 r8 P' [5 p- f$ _+ u- \
++len;
8 U2 [5 E8 ?$ ^} Q1 @9 G6 E Y& h6 P$ Y( e7 O8 \) J
* |) z" Z6 V2 f+ w( n* @
// 将混合字符转化为宽字符 , R2 w3 M" D9 c! P+ C( g
wstring = (wchar_t*)malloc((len+1) * sizeof(wchar_t));
* P$ g9 w' V& H) VMultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, str, -1, wstring, len);
8 L' ~- @2 n9 ?wstring[len] = L'\0'; + ]# T& P5 l& H' o
1 u. ~; J& G& n+ ~// 用完后记得释放内存
0 J* t4 v' ~+ }* s J3 }: Ofree(wstring);
4 U1 l1 w0 v% e( c1 V% m% b8 u3 _. c: \1 Y' P
- B* |+ W1 T2 U7 N* T0 F2 S |, ^6 Y& g8 W
加上前面所讲到的wglUseFontBitmaps函数,即可显示中文字符了。 + G. J8 `* H/ [( W' C
0 |5 J2 {/ H6 W0 d! W" fvoid drawCNString(const char* str) {
! `! D E. ] f! t" A3 E6 g6 r int len, i;
- k5 ^. |( d* N: a# C% V+ Z wchar_t* wstring;
# f- `, ^' Y9 X$ Q7 P C HDC hDC = wglGetCurrentDC(); : N5 ^" l, Q- N! \, _
GLuint list = glGenLists(1); $ o& w" g5 r7 v8 d W" E9 J# C8 _ M1 d
/ d+ c" [" \3 ^* M, \ // 计算字符的个数 ( Q9 H) W+ O( Y& W6 m, v
// 如果是双字节字符的(比如中文字符),两个字节才算一个字符
0 z6 x3 J; R$ _. L0 Z // 否则一个字节算一个字符 0 ]1 y- O0 R- ], L
len = 0; 1 \( A& v$ n1 o1 {
for(i=0; str!='\0'; ++i) 3 y: w7 |# B. `' @( `4 n' u) j) S
{ 3 @: b+ W0 ?% E6 `. D# ?
if( IsDBCSLeadByte(str) ) ) `3 P# ?3 S, [" j
++i;
# ?, ~/ o+ o0 C6 D ++len; 4 k$ E9 P* e' [! O, ~
} / {& L! ~3 ^1 k+ c
3 X @* ^" l8 k
// 将混合字符转化为宽字符
( v9 W& @0 s1 g. k5 l9 f2 [+ f wstring = (wchar_t*)malloc((len+1) * sizeof(wchar_t));
* D9 h8 }% U4 v+ p MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, str, -1, wstring, len);
: d* h2 Q! m5 g# I/ U: [ wstring[len] = L'\0';
- {& \% X R4 U1 K$ t9 s7 E) u. k' t/ m' Z+ f+ k2 d' p1 @
// 逐个输出字符
3 k. e G' {0 p8 ~5 B for(i=0; i<len; ++i)
2 w _6 a4 S6 x! u C& X { , I5 ]. |4 h: n! `
wglUseFontBitmapsW(hDC, wstring, 1, list);
' E& M* f* A, o8 O0 J glCallList(list);
2 l$ n' z$ E; [/ u7 `. e. i } + V2 l4 ]+ c) i4 c
& A u5 R8 ^( T/ B // 回收所有临时资源 " w3 h8 E- Y# [+ M
free(wstring); % y* y8 m4 C5 \2 t* O
glDeleteLists(list, 1);
2 _9 _9 p: o0 X} ) }% ~# q2 b1 ?+ V* m0 Q
2 t$ v0 H8 `' H) D+ A; e" ~9 B
& |! w9 M; J3 K& T
( x& X0 d4 {, W8 ]( P; m9 `! k; `注意我用了wglUseFontBitmapsW函数,而不是wglUseFontBitmaps。wglUseFontBitmapsW是wglUseFontBitmaps函数的宽字符版本,它认为字符都占两个字节。因为这里使用了MultiByteToWideChar,每个字符其实是占两个字节的,所以应该用wglUseFontBitmapsW。
: t( h) ]$ D: \! |! [* g
/ _7 |) N% I+ I3 k$ \) o# a2 ]; vvoid display(void) {
5 k6 R! P) Y5 S0 K1 ` glClear(GL_COLOR_BUFFER_BIT); # B- Y z' s w4 t- |
0 H" M \4 B# R4 [
selectFont(48, ANSI_CHARSET, "Comic Sans MS");
$ R7 h6 X6 k8 Z5 E: V8 I7 w( A# O glColor3f(1.0f, 0.0f, 0.0f);
3 e6 \6 x/ o* _; v, C- q glRasterPos2f(-0.7f, 0.4f); ; V( \. x6 i. K7 O% C
drawString("Hello, World!");
) {" \+ x" g4 g' Q7 L* f* E" @6 h
% v9 p( |7 o! Z6 W9 o selectFont(48, GB2312_CHARSET, "楷体_GB2312"); & k0 V7 z7 u2 n8 i2 J# h8 q6 V
glColor3f(1.0f, 1.0f, 0.0f); 1 k. ]5 V) @" e Z* I, Z
glRasterPos2f(-0.7f, -0.1f); + C% u/ q/ k6 C( Y$ u8 O0 T
drawCNString("当代的中国汉字");
; x6 n: T8 R" z6 y) o0 U' y
# ~7 ?; q# F3 B( D selectFont(48, DEFAULT_CHARSET, "华文仿宋");
; k) Q- j2 m: }% L6 }8 b5 U& d3 b: T glColor3f(0.0f, 1.0f, 0.0f);
: f( K% v( U. \& f/ D3 i) Z+ f4 r glRasterPos2f(-0.7f, -0.6f);
7 r# T1 D# l: y% b drawCNString("傳統的中國漢字");
# _1 G4 S5 |2 c$ b/ n( X- ]2 L# B' X9 c
glutSwapBuffers(); / W" v2 p. f' [4 M: F8 l
}
4 u S# N8 g0 S2 i/ F
: S" z2 e4 p! e
7 B" l3 u! f7 D9 x! ?; _- t' {" {& w' _效果如图:
7 e* b2 {. p5 v& I. I" w# G |