Nibiru经测试,用了OpenGL的wglUseFontBitmaps,glCallLists函数来显示文字。 $ ]( w3 O, `" {' G* p! u
也许是一个汉化的突破口?
" ?/ f2 e; i1 D' g1 V1 g
+ D3 n8 g1 }# G8 j' Q---- 本文详细讨论了在OpenGL中显示文本的几种方法。
# h, O3 ?- r$ X3 M& |& i( \/ M2 A s7 P5 z. u
----也许大多数程序员使用OpenGL更多的是将精力集中于动态三维图形应用,因此,OpenGL中的文本显示往往被忽视,使人有不见积薪之感。本文介绍了几种文本显示的方法,希望能对使用OpenGL的编程者有所帮助。
4 Y8 O/ f, c6 O# P2 t. O, F" y7 ~) K |; ?( Y. e- n, z5 n5 R
建立并修改程序 4 w/ v2 z) M4 D6 A3 D. d
----建立一个MFC SDI Windows应用工程Text,除单文档属性外,使用其他的所有默认选择。在菜单Project打开Settings对话框,在Link属性页的 object/library modules编辑框中加入opengl32.lib glu32.lib glaux.lib三个GL库。我们利用这些库函数完成图形编辑工作。
" f% x+ ?0 b0 q ]0 i9 L----为使VC++的AppWizard产生的SDI应用程序能使用 OpenGL绘图,还需要作一些修改,说明如下。
) g5 U) [) X/ \9 a+ S6 p$ O3 Y" I) w2 a& k. t. e
----1.介绍PreCreateWindow函数 : R* ~, E3 t( P3 ^5 K3 `1 `
+ W/ }2 A0 H6 \( P0 ^
---- OpenGL窗口必须具有WS_CLIPCHILDREN(创建父窗口使用的Windows风格,用于重绘时剪裁子窗口所覆盖的区域)和WS_CLIPIBLINGS(创建子窗口使用的Windows风格,用于重绘时剪裁其他子窗口所覆盖的区域)两种风格。此外,窗口类属性不能包括CS_PARENTDC风格。具体程序实现如下: 6 w5 _) v* B/ ~% p1 d+ \2 I
$ ]9 Z/ `; X( {! N9 q7 c; s1 f+ iBOOL CTextView::PreCreateWindow ) m& _, \3 p) |1 g( p7 Y! D- ?8 i& e
(CREATESTRUCT& cs) $ E2 ?1 c5 u$ e9 m% S& Q1 R
{
9 i- x4 a. ^: U// TODO: Modify the Window class or styles here by modifying 0 l/ Z) y7 S- u- A A
// the CREATESTRUCT cs
3 O' S' R, K0 y* T6 P m) U N7 {; {% ?8 q# e
//An OpenGL window must be created with the following flag ( T4 h- n! y& J/ f
// and must not include CS_PARENTIDC for the class style.
9 w! Y3 u! f* S1 ics.style|=WS_CLIPSIBLINGS|WS_CLIPCHILDREN;
b4 q2 }, c% X6 L/ ^9 ~' E% q2 M3 h( |. ]* {
return CView::PreCreateWindow(cs);
5 J; t4 U0 G# N; g0 s; G+ P3 d( G} 3 y9 {$ d% H' s. i6 F! [) N
6 q1 _/ ?% S7 t% y
----2.OnCreate函数中定义像素格式PIXELFORMAT和创建 RC 4 P# P0 s* M4 A V' N8 a2 w$ I
# O5 ?* \7 X/ a+ m) D
----要使窗口支持OpenGL绘图,必须对窗口进行初始化。其中包括定义像素格式PIXELFORMAT和创建RC,为OpenGL指定一个合适的像素格式,创建着色上下文并将它和窗口的设备上下文关联起来。着色上下文保存着当前着色环境的信息。可在OnCreate中调用一个自建视口成员函数SetupPixelFormat(),具体函数如下:
. _$ p9 V" |$ O" c& m ~8 B! _
BOOL CTextView::SetupPixelFormat()
) ^1 n. N" A2 V' V# v& W$ o{
1 [3 M/ T' |- i( i! u//Create a rendering context
' h0 p( P) D0 j$ {% h5 h5 T- XCDC* m_pDC=GetDC(); 6 P! g, A# O+ D; A. u0 u% \% G
if(m_pDC==NULL) //failure to get DC
0 S! e9 F7 d$ g L{ } V' o* z( l% k
MessageBox(“Could't get a valid DC."); ( }* w7 c' D1 W5 ]
return FALSE;
: W! m8 b |8 R. x} ( s& ^6 i7 H( [( S# c4 j
+ _5 N/ e. _7 q# ~0 J# m//Default pixel format is a single-buffered, 8 E0 [! \* i: {" L
//OpenGL support hardware-accelerated,RGBA mode format 9 {* E* Y0 ?: V3 [
PIXELFORMATDESCRIPTOR pfd =
, f" d& h7 ]4 N. n; n{ 2 ?3 [0 d( s$ n0 D# m5 J( Z
sizeof(PIXELFORMATDESCRIPTOR),//Structure size. . k0 P* o* J& F9 m
1,
# g3 Z0 n( l) n% G4 q, q// Structure version number.Property flags(特性标志):
7 N- w0 O m0 u) @# GPFD_DRAW_TO_WINDOW | // support window
L! g8 L" W/ D& r2 Y6 bPFD_SUPPORT_OPENGL | // support OpenGL 7 n( ?1 t: Z" E$ I4 W
PFD_DOUBLEBUFFER, ! j* Y7 Z7 c/ X; G! J
PFD_TYPE_RGBA, // RGBA type $ W( _9 |& i$ R M* o& Y
24, // 32-bit color.
2 k* R- |8 A! ^5 q0, 0, 0, 0, 0, 0, // Not concerned with these:不涉及的属性 1 r. p# {# q' {9 Y1 p; g
0, // No alpha :无alpha缓存
0 J' n' \% v8 H2 Y( [2 c0, // Shift bit ignored:忽略转换位
y O& \% M! Y- [, [4 T0, 0, 0, 0, 0,// No accum buffer:没有累积缓存
0 @" D! h8 ?) [32, // 32-bit depth buffer.
. D& h1 F! v# S9 J" X. D% D9 X0, // No stencil:无模板缓存
1 f5 M8 m- f1 r" J4 a$ t$ c. y0, // No auxliliary buffers:无辅助缓存
. a/ ?4 ]8 `" H. BPFD_MAIN_PLANE, // Main layer type.:主层类型
: i8 X. D1 d" n) l2 V/ `2 U0, // Reserved.:保留结构数 ( K; |+ S6 Z, H6 r. ~5 C1 H5 Y
0, 0, 0 // Unsupported.:不支持结构数
' Q0 @4 i* a$ S2 w5 M) x}; + w1 n2 Y9 q, n4 w4 t3 o1 K* n
int nPixelFormat=
X8 o, \3 c" O0 \$ F" [ChoosePixelFormat(m_pDC->GetSafeHdc(),&pfd);
- B7 u* ?( l$ O; K' ~if( nPixelFormat ==0)
& l0 r+ c! r% P( {: r. Y% H3 u0 o7 R1 ^{ * Y% V# M! | l# a) f
MessageBox(“ChoosePixelFormat failed.");
4 L+ D' L9 R! B+ a1 b8 ?# ~/ breturn FALSE; + ^0 o7 N6 x4 `# W% v. ~. l T1 S
} / P: ?# h3 j1 ~. I& s/ J
( r5 J4 r# V! y! @/ c* }if(SetPixelFormat(m_pDC->GetSafeHdc(),
J! o" U5 R5 C# f% |nPixelFormat,&pfd)==0)
: t, Z& v. T, K8 f{
) r: @7 g0 u) \8 c* [& JMessageBox(“SetPixelFormat failed."); J2 l$ u- L$ w- x* ~
return FALSE;
& S2 Q+ G; O% f}
' s/ Z% |5 F) h. Z) D9 [! `& l* ^& S5 M2 h, n
if( (m_hRC=wglCreateContext(m_pDC->
; m2 L& r3 z% D3 T# {5 f2 [GetSafeHdc())) ==0)
: ~/ M5 D9 u4 l& m5 o) m4 M4 N{
, E$ j3 a7 i% O8 O, } oMessageBox(“wglCreateContext failed.");
0 M" l, y$ G/ Creturn FALSE;
# Y7 p0 b/ b- A4 X} $ Z& d, ^# l! P4 ^8 ]# q6 c
if( (wglMakeCurrent(m_pDC->GetSafeHdc(),m_hRC)) ==0) " ^3 _; h% D; |3 b* `
{ C* ~) Y" i7 B' i2 {
MessageBox(“wglMakeCurrent failed.");
0 A; |! R' r% ?8 W8 o8 A$ H# Treturn FALSE;
: [) }( a' A$ \& P5 b$ N7 Q} 6 E7 |* I7 g+ o% d8 m! h
4 I1 }* X* A3 d. ]; oif(m_pDC) ReleaseDC(m_pDC); $ I$ @0 a# v8 w" L& I7 d3 a/ p
return TRUE;
& F# r* {/ d5 W} 6 `/ t0 Q3 J+ Q9 x- G0 ]
+ |5 g4 l ` }; P1 e6 ?/ {5 n( E& t
----3.在OnCreate()函数中调用初始化背景函数 InitializeOpenGL()
g: `5 Q! U& x+ A; \
2 G+ K9 ^3 @0 ~void CTextView::InitializeOpenGL()
* A5 U/ Q1 ~" S2 |2 f{ & i; V8 j3 _3 U! S G
glClearColor(0.2f,0.2f,0.2f,0.0f); 7 j9 r9 ?/ ?! b
glClearDepth(1.0); ! W, h9 P' N ^- N! M" D
glDepthFunc(GL_LESS);
3 q: k, ]% X5 zglEnable(GL_DEPTH_TEST);
# s1 \+ c" i' L2 jglShadeModel(GL_SMOOTH);
* K; M4 i7 P) ?& f1 G4 \% ^' ]}
6 l3 w3 m" v# t8 N: `6 j D5 X+ f5 j; t8 ^
----4.在OnCreate()中启动动画定时器 0 L* u, ?. z1 O
* @& V; h g! lSetTimer(0,40,NULL); - f7 W0 T p# X5 I2 }
$ R6 Y+ [# j6 Q3 Z N* O
----5.在收到WM_SIZE消息时要重新计算场景尺寸,用OnSize 设置图形显示模式
2 ^" ?- `( \$ _4 x+ G& ^: p0 K5 o: F" n
----为了使物体能合适的显示,必须要经过投影和确定视口的工作。
4 u# H, Z0 u3 V7 V, ~1 |
: q7 `1 C! q$ ^* |$ B% H5 W7 v- Yvoid CTextView::OnSize(UINT nType, int cx, int cy) / j! H2 P, }8 k
{
4 m S1 P4 Z( Y9 B! lCView::OnSize(nType, cx, cy);
4 D& k5 a9 l6 } y, J
1 t" F d3 G. r* n5 D; v8 F// TODO: Add your message handler code here 7 E& V. F/ m4 b% R4 m
//Save the wide and height of the current window Client % {( o% g/ r5 X3 Q" Y: N
GLsizei nWidth=(GLsizei)cx;
$ J# A2 Y2 B/ QGLsizei nHeight=(GLsizei)cy; 5 k& K/ k8 M+ A& p
ratio=(double)cx/(double)cy; - }8 B- N3 Z) h$ l
' l- R2 N5 C3 W* x" V
//Coupute the aspect ratio ' o2 d* ]4 L* m1 ?$ Z, ~
GLdouble dAspect=(GLdouble)nWidth/(GLdouble)nHeight; * k1 ]" V9 n$ F( Y3 d+ {
7 t6 h/ h+ \# g. O/ ]! YglViewport(0,0,nWidth,nHeight); $ @ e9 X+ f" u7 [4 M
glMatrixMode(GL_PROJECTION); ; D+ X& c+ c2 o. q7 I
glLoadIdentity();
2 ^2 g% t9 Z, b# M% {5 x$ M) ggluPerspective 5 T8 @ N$ g7 j6 F: [' u
(FOV,dAspect,NEARPLANE,FARPLANE);
. {1 L. @4 t4 Y& s; |2 H5 @$ ]2 Y9 S& D1 C s
glMatrixMode(GL_MODELVIEW); + P& N8 P7 v+ q2 e
glLoadIdentity(); {7 w# \0 }/ F# L2 d, e
} / M6 w* A& z3 a# ?: j- W C6 D
: r' d! I- W' N* S% G7 r
----6.在OnDraw()中简单调用DrawScene()以执行OpenGL函数 ; K9 M, q/ ?9 c* F
' ^; J. _' D7 A L! O; ^void CTextView::OnDraw(CDC* pDC) ; E1 h2 ?/ B2 @( w7 n
{ 0 l: Y. o& U7 w- c) q% Y
CTextDoc* pDoc = GetDocument(); , K$ L$ W6 _+ |$ z9 X) C, [
ASSERT_VALID(pDoc); 1 M" S# r/ W9 d1 |" c
. H: `$ [- k) I
// TODO: add draw code for native data here ! s! Q7 k$ ?3 o; n+ _. t
DrawScene(); 6 r1 v+ L8 G( ^8 B
//Invalidate();
- G5 y4 \. i: W( P' z( b( O}
1 L7 t- a1 U4 j
8 I: ] s# ~( h7 t: B----7.撤销视窗时删除上下文并撤销定时器 w+ p' A2 x3 V9 Y! Q
9 }6 b4 V1 a* Y# Z! g) @- s C
void CTextView::OnDestroy() 8 i: _/ g2 d7 }5 o# F
{ 4 C! M ?7 q- G9 M0 K# b
CView::OnDestroy(); / |- c! f$ ^2 i' n9 N
X. \- Z# d9 x1 k// TODO: Add your message handler code here 5 R8 s: w+ ^' S4 i
//This call makes the current RC not current
2 }. D4 J# L5 o. e1 T7 yif(wglMakeCurrent(0,0)==FALSE)
, J. A/ F/ l8 O; y6 Z5 N6 IMessageBox(“wglMakeCurrent failed."); : l% Q5 Y( s" L% w. I; _
3 L" _5 v* H7 Z* x8 c//delete the RC
! D2 m6 G# P/ s3 _* eif(m_hRC && (wglDeleteContext(m_hRC)==FALSE))
$ t$ x8 X) L2 ^3 k! Q( I3 YMessageBox(“wglDeleteContext fail."); & c; X! N5 f- ?+ S7 ~- V" M
KillTimer(1);
3 F( k2 I4 r. Z9 C E+ |}
* q2 Q( o4 q7 Y: ?7 [. W0 l& |2 Y4 z9 v9 g
----8.当40ms定时器时间到时,简单地将整个场景的客户区置无效,使之重画 - `4 d0 v, ]; t2 A: c: |
; f4 w$ M5 C/ B" S* |( K% A4 ]
void CTextView::OnTimer(UINT nIDEvent)
! Y! o5 J5 ^( X8 q{ $ t0 N8 N" {8 P, W
// TODO: Add your message handler code here and/or call default
9 h7 \2 K( L. |/ I+ J- a6 SInvalidate(); 0 l1 I+ G9 r& q% v! Q5 ?
CView::OnTimer(nIDEvent);
9 H W% f H8 t* w# j% x( D}
9 b- X% F" i m8 h2 p$ [5 p: K
: f" t5 @9 t: C* _* K! j2 P" y修改界面 2 \9 X& G; x7 w$ V9 k0 _
---- 删除菜单IDR_MAINFRAME中File下除去Exit菜单项外的所有选项,添加“显示GDI文字” “列表制作文字”“列表三维文字”三个菜单项,并给他们分配适当的ID。使用ClassWizard为这三个菜单项在视类中添加命令处理函数和界面更新函数,其中显示GDI文字的对应的函数如下: 0 p W' q. b& O0 l$ k3 T! ]4 q6 Y
void CTextView::OnGdiText() , F) u l; r8 A$ G% j8 B8 C
{
( a: l; X& a) w( M// TODO: Add your command handler code here
. Z9 r' l _7 P3 u& c, l8 _m_iWhichText=0;
! Y$ e* `' {* o4 u! v$ eInvalidate();
$ V& a" ~' S. [} " C- z/ ^9 C! a6 @
$ j& p" C! O% c, q a
void CTextView::OnUpdateGdiText(CCmdUI* pCmdUI) ! F; i5 x% L+ i6 g
{ ) p8 x; D' A( l2 u' ~/ O5 X
// TODO: Add your command update UI handler code here 1 u) ]3 p$ v: F" m6 \
if(m_iWhichText==0) pCmdUI->SetCheck();
6 z9 l0 A3 D- q, Delse pCmdUI->SetCheck(0); % M& T# V7 U3 U5 k! [9 \
} % d/ V/ [% s' A) C1 @
& o# ?1 n Q8 L8 Z----增加Draw3DText()、DrawListText()和DrawGdiText()三个函数用于三种不同的文本绘制方法。增加DrawScene()函数,它被OnDraw函数调用,用于绘制场景。在DrawScene()函数中,当m_iWhichText为0、1、2时,分别调用 DrawGdiText()、DrawListText()和Draw3DText()显示文本。具体函数实现如下: , e# y: f& z2 H ^# C" b1 Z
' y$ d8 g" E8 E8 g0 G9 `) V* N* k
void CTextView::OnDraw(CDC* pDC) 5 f$ e3 N: _- j, E: h8 Y
{
- S' E7 R, M @% i/ D6 ECTextDoc* pDoc = GetDocument();
. c/ X/ O: t- z8 X' LASSERT_VALID(pDoc); ( \) F% _. ~# W' L1 \( c7 g% K1 a
; Q* _1 _0 a$ [# k7 Z/ v% u2 n
// TODO: add draw code for native data here
4 D9 `; d5 s7 @- `5 r- P& RDrawScene();
% U( c% P7 z' c, W8 _//Invalidate(); " N" S" Y& q; X. K% w7 |! q8 K5 E( i
} 2 ~$ _$ w' h9 w, ~3 U' {+ V7 A6 z
' H6 v6 O+ T# X& k1 \
void CTextView::DrawScene() ' H3 b |+ u; y$ |- M" A- ~
{ 1 u8 M0 W! \( ?5 c: M% ^
glClear
! l- `' g' t, y0 @8 V/ P(GL_COLOR_BUFFERBIT|GL_DEPTH_BUFFER_BIT);
: J _- D2 y: h) l! k% i; U) C2 F
7 g g. g6 ^. _+ XglPushMatrix(); ; N, J6 R6 M: n) t9 l& }/ I
glTranslatef(0.0f,0.0f,-FARPLANE);
) D4 \" `% \! s& B' T//TextureMap();
2 W0 L" a! t R2 |1 I/ a* U, ]glPopMatrix();
5 K1 H+ I e0 ?glPushMatrix();
# k9 g- @- Q" U* f, l* l3 d# UglTranslatef ; V) P. D: _; E) v7 V$ {
(0.0f,0.0f,-(FARPLANE+NEARPLANE)/2);
+ |4 t1 z8 Q, d$ g8 E$ H9 K( U; `8 m8 e; x* s6 C" c
if(m_iWhichText==1) DrawListText();
& D- L( g( j/ @* ~% p$ Aif(m_iWhichText==2) Draw3DText(); ! O- C8 {! h7 v: q M* F, M
glPopMatrix();
0 Z( j6 _+ B2 xglFinish(); 9 F# F5 ?0 N+ p5 l+ `9 W5 Z- e
SwapBuffers(wglGetCurrentDC());
+ [' f$ {6 n3 |2 {3 {- c
; s7 j+ O0 e7 u# D; kif(m_iWhichText==0) DrawGdiText(); 1 c( R8 ~6 O7 A3 @% x8 g
}
/ G$ H# ~* k0 ^' P; v. f6 E. t9 J% K3 n, \& [
GDI 显示文本 " r6 a& V% T: L4 }
---- 调用wglGetCurrentDC()函数取得当前的设备上下文,使用TextOut函数显示文本,不过要注意在DoubleBuffer模式下,绘制函数要在glFinish()和 SwapBuffers(wglGetCurrentDC())函数之后调用,否则会产生闪烁,在绘制OpenGL结束之前使用GDI函数,要除去闪烁则只能使用SingleBuffer模式,具体函数如下: 4 x- N+ M: z, j/ B' S
void CTextView::DrawGdiText()
- `6 N% O/ r" a; r- s' `8 b0 a$ t8 o{
) O2 K& z* Q/ I8 O2 VHDC hdc=wglGetCurrentDC(); 9 ^* s2 A @. b+ u
::SetBkMode( hdc, TRANSPARENT );
$ O" \0 e- f$ ^1 m+ a::SetTextColor( hdc, RGB(250,0,0) );
8 \ P8 l1 _, `8 n3 u* B
/ J+ i. u2 C5 d& L& t9 rCString sState(“显示GDI文本。"); / Y. T$ k' d! Y/ s
::TextOut(hdc,5,5,sState,sState.GetLength());
. W+ i" X- G4 X( `% |}
- w, b1 \+ S% j' C0 @7 e# S1 D2 r. G* \- E
wglUseFontBitmaps 3 F; H( Q6 x7 f7 c. C
函数显示文字
4 @! ^) n8 k5 _. E0 M% c) d# W* v7 c----使用wglUseFontBitmaps()将ASCII字符装入显示列表,然后使用glCallLists()函数利用显示列表序列显示文本。wglUseFontBitmaps有四个参数,分别是当前使用的DC、从第几个ASCII字符起始装入列表、装入列表的ASCII字符数和起始的列表序号。glListBase()指定glCallLists执行的起始列表序列号。glCallLists()含有三个参数:执行列表序列的个数、列表值的类型和所要显示的文本。注意如果所要显示的文本是字符串,它所提供的信息是相对于起始装入ASCII字符的偏移量,因此最终所显示的ASCII字符是从glListBase()所指定的列表起始号在经过glCallLists()中偏移后的列表,因此wglUseFontBitmaps的从第几个ASCII字符起始装入列表参数、glListBase()指定的 glCallLists执行的起始列表序列号和glCallLists()中的所要显示的文本参数都可以影响最终显示结果。由于显示的是ASCII 字符,因此不能显示汉字。glRasterPos3f函数决定在 OpenGL视景体坐标系下的偏移。具体函数实现如下:
, a" W* w: q- Wvoid CTextView::DrawListText()
5 L$ @& Z2 d# V9 k) \/ N$ ]& y{ 5 f. i4 \0 L# w$ T \& P
wglUseFontBitmaps(wglGetCurrentDC(),0,256,1000);
8 R' a6 B# V# g' D6 M& _( `( {glListBase(1000);
8 j! a- t: z- |# k; ?glRasterPos3f(-5.0f,0.0f,0.0f); " v6 L1 a! W! p" k
glCallLists(20,GL_UNSIGNED
* ?8 u" V' s6 y: \4 D% l7 b7 L- X/ n_BYTE,“Draw with List Text."); + M. D8 A5 B( Z& {
}
- p% v# f6 }3 g6 u6 c$ I5 g8 c1 l G- Z, M. p1 n
wglUseFontOutlines + i! C$ _4 r- O O, C
函数显示三维文字
2 Z8 a% K; o8 K1 ?$ U6 q----wglUseFontOutlines使得OpenGL可以显示三维文字。它的用法与wglUseFontBitmaps函数大致相同,但是多了内插计算参数、字体深度、显示方式和装载字模的缓存四个参数,且只能显示TrueType字体,显示前应该先选择字体类型。具体函数实现如下: 5 ?1 n' |: ]! @+ @: B6 s
void CTextView::Draw3DText() 0 \' C1 K6 A* ]- k4 b# W
{
% N- Z" Z. ]2 _$ UGLYPHMETRICSFLOAT agmf[256];
. e; l; V- T5 n: G2 W4 K1 S// create display lists for glyphs 0 through 255
g! O( D* e( z- d) Y4 u// with 0.1 extrusion and default deviation. " r, X% U# u8 T4 J' b
//The display list numbering starts at 1000 2 `- x0 P5 C7 N( E/ m1 w4 V1 s
(it could be any number)
9 B. b6 W6 M+ kwglUseFontOutlines(wglGetCurrentDC(), / U7 k$ U+ ~( r0 t& X3 |
0,255,1000,0.3f,0.8f, WGL_FONT_LINES ,agmf);
' [: {6 |& C( s3 ?" c- p, r5 A2 p+ C; w& _. G7 V W) f) X
// Set up transformation to draw the string ; v3 d5 T7 f( Z) p( O# ^
glTranslatef(-15.0f,0.0f,0.0f);
/ ^2 Z3 `. {! D3 i; k/ @glScalef(4.0f, 4.0f, 4.0f);
" E: r& F9 y4 P/ q" H// Display a string : S9 G" k* J7 R4 ]0 b
glListBase(1000); 7 H) ?: T k3 E" A
// Indicates the start of display lists for the glyphs
: ^; c, U, c/ g, r// Draw the characters in a string
" `2 f5 T* l- x
& J& P* y4 R8 S: M. z- A4 L) {1 m9 Y/ K) GglCallLists(26, GL_UNSIGNED_BYTE, 3 C# f, j5 ~, W
“Draw outline list 3D text.");
. I4 m6 M3 B8 N8 ^2 P} |