冒险解谜游戏中文网 ChinaAVG
标题:
【OpenGL汉化研究】OpenGL如何显示文字
[打印本页]
作者:
shane007
时间:
2010-1-23 16:50
标题:
【OpenGL汉化研究】OpenGL如何显示文字
原文
8 g9 w, y* A# E
http://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 q
OpenGL并没有直接提供显示文字的功能,并且,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 X
Windows系统中,可以使用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" U
8 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 |* E
0 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 i
MultiByteToWideChar(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$ `# V
void 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# u
4 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