Nibiru经测试,用了OpenGL的wglUseFontBitmaps,glCallLists函数来显示文字。 ! \: _; b7 ]- _! P$ C4 s- m
也许是一个汉化的突破口? / e- K' y* S1 P- a& Y
. m: L9 F2 b; x) n# P+ B0 J, x5 U w
---- 本文详细讨论了在OpenGL中显示文本的几种方法。
. A+ ^8 t/ [" z( U8 x# _$ D3 b3 V: Q+ e: t+ t
----也许大多数程序员使用OpenGL更多的是将精力集中于动态三维图形应用,因此,OpenGL中的文本显示往往被忽视,使人有不见积薪之感。本文介绍了几种文本显示的方法,希望能对使用OpenGL的编程者有所帮助。 5 V- L i( D' h% d% I
" h. z3 y- Z/ O7 I4 O0 X8 }
建立并修改程序
& q: \7 g0 b) ^3 b( K, s----建立一个MFC SDI Windows应用工程Text,除单文档属性外,使用其他的所有默认选择。在菜单Project打开Settings对话框,在Link属性页的 object/library modules编辑框中加入opengl32.lib glu32.lib glaux.lib三个GL库。我们利用这些库函数完成图形编辑工作。 " Y: U2 ?4 }8 o4 L& e# s
----为使VC++的AppWizard产生的SDI应用程序能使用 OpenGL绘图,还需要作一些修改,说明如下。
- z( A& o9 G: k, s& d0 v# y& _" t8 F6 \
----1.介绍PreCreateWindow函数
7 @# k' d# D/ ]3 `& H0 I. z( g' h7 A$ A' Z1 B- t; M
---- OpenGL窗口必须具有WS_CLIPCHILDREN(创建父窗口使用的Windows风格,用于重绘时剪裁子窗口所覆盖的区域)和WS_CLIPIBLINGS(创建子窗口使用的Windows风格,用于重绘时剪裁其他子窗口所覆盖的区域)两种风格。此外,窗口类属性不能包括CS_PARENTDC风格。具体程序实现如下:
/ E8 X, H$ H# e8 F$ o: C
# t& ^+ m* C& k+ [ t& E: z Q1 iBOOL CTextView::PreCreateWindow + v% y* X" \8 d6 N' n
(CREATESTRUCT& cs) * {2 e0 K# |# L' V& M5 x" s
{ 8 }8 D0 I9 Y6 F6 D! H
// TODO: Modify the Window class or styles here by modifying
+ t( P& c: j' r1 \& F) e& B% A3 G1 T// the CREATESTRUCT cs 3 C2 K- a" j+ X8 P6 j$ M% ]( @
- E4 U2 ?6 [2 E" D//An OpenGL window must be created with the following flag
) ?1 J k* x S" S2 |// and must not include CS_PARENTIDC for the class style.
4 t" y$ \% s8 p3 |cs.style|=WS_CLIPSIBLINGS|WS_CLIPCHILDREN; ' h2 W c6 Y3 y4 D: k6 W0 V
: O: W C% h/ j2 Z6 P2 Areturn CView::PreCreateWindow(cs); $ s7 A( c! T7 L! u, E+ ^0 D' M
}
[7 R0 A& l) \3 j v
+ s6 G: p, Y: K----2.OnCreate函数中定义像素格式PIXELFORMAT和创建 RC 0 ^* R, a2 B5 s& N
. `8 m/ q+ e/ M* v----要使窗口支持OpenGL绘图,必须对窗口进行初始化。其中包括定义像素格式PIXELFORMAT和创建RC,为OpenGL指定一个合适的像素格式,创建着色上下文并将它和窗口的设备上下文关联起来。着色上下文保存着当前着色环境的信息。可在OnCreate中调用一个自建视口成员函数SetupPixelFormat(),具体函数如下:
" k* X" Y9 w9 i _- R1 P% \, t8 r% x! w! T
BOOL CTextView::SetupPixelFormat() 0 _" Z$ e5 M. T6 \
{
# ^7 M1 O! C0 N1 k7 e' P# y# I3 Z//Create a rendering context
2 w( g+ ]3 p9 ^( F* B% L; z2 z& tCDC* m_pDC=GetDC(); - h% Y$ w. W7 L* l J7 n
if(m_pDC==NULL) //failure to get DC ) x8 M) l6 _% u+ H, m& F5 H
{ 8 P; w: ]7 @3 [
MessageBox(“Could't get a valid DC."); ( }5 ^) q" G9 u' s& a) S
return FALSE; 2 m8 v: A$ D. W" @% q' p t3 n
} % j( H9 e) B% v& ^$ B1 _% u1 f0 J
- r+ x/ E9 L1 A( C0 W$ `//Default pixel format is a single-buffered, $ |3 v9 y' V: a" c3 J o! Q. O
//OpenGL support hardware-accelerated,RGBA mode format
8 ~- B! ]7 {+ i7 o+ t* R3 zPIXELFORMATDESCRIPTOR pfd = # n" e, _! h+ g- R! g
{
# p5 z! P) Z! e5 w' x' J& C$ Csizeof(PIXELFORMATDESCRIPTOR),//Structure size.
1 g) p2 K- B9 @5 \! l1,
0 a& d, \/ }2 q. g& q// Structure version number.Property flags(特性标志):
, @* U2 M+ T& F, N3 H: i8 _* \PFD_DRAW_TO_WINDOW | // support window
) G9 |0 v1 P3 xPFD_SUPPORT_OPENGL | // support OpenGL
' T2 r0 y: [0 g! iPFD_DOUBLEBUFFER,
$ S/ r8 {3 G4 ?! _* lPFD_TYPE_RGBA, // RGBA type 2 k/ [* T. E; E9 i8 h [ P, a
24, // 32-bit color. 4 O( I/ g9 y$ o& b% @$ u
0, 0, 0, 0, 0, 0, // Not concerned with these:不涉及的属性 # d0 g; x' v, R: s; K5 O3 z
0, // No alpha :无alpha缓存 & {8 ?- K( b2 v+ a6 J x( \. R. |! {
0, // Shift bit ignored:忽略转换位 ! c3 ~2 Y# d0 `- i8 i
0, 0, 0, 0, 0,// No accum buffer:没有累积缓存
" Y t$ a( `. C" x/ u32, // 32-bit depth buffer. + G2 X5 ?- B& ] } b1 L: Z% L3 Y' x
0, // No stencil:无模板缓存
/ b5 @' c( N$ u0, // No auxliliary buffers:无辅助缓存
5 ] s+ D0 K& c3 s. _% MPFD_MAIN_PLANE, // Main layer type.:主层类型
( d/ }8 C& \3 z0, // Reserved.:保留结构数
" g$ n' g& }4 Q k! }0, 0, 0 // Unsupported.:不支持结构数
. a1 b; i! w1 H/ m2 \4 {8 _};
& P- W0 v8 E- F( c8 Fint nPixelFormat= 7 L+ c1 M g! o
ChoosePixelFormat(m_pDC->GetSafeHdc(),&pfd); % n8 Z7 C/ J/ o( z( v
if( nPixelFormat ==0) 0 S3 W" i- A- _) a: y
{ * Y+ W$ f! I- R+ c: x5 K( |3 [
MessageBox(“ChoosePixelFormat failed."); 9 ?+ @8 G; [0 b
return FALSE;
9 ]1 y( B8 J5 X7 C1 O} " R4 N1 ]. o _4 w5 J0 D0 Z' e
; s# A i' I( _% c3 {: ^& {7 M6 [if(SetPixelFormat(m_pDC->GetSafeHdc(),
/ h" f0 S/ o( I7 GnPixelFormat,&pfd)==0)
' C2 o6 Q7 M0 l4 o- o+ G9 }{
, c+ ]% y+ h! [7 K: o$ E4 EMessageBox(“SetPixelFormat failed.");
$ H7 ~/ t, R4 y; F( m4 I oreturn FALSE;
& D3 g: z% W) _6 j}
& |' c9 i4 D( E- E) }( Y- u2 Q$ D+ k" A3 V
if( (m_hRC=wglCreateContext(m_pDC->
( h* ^2 N1 ?& U- @/ ^GetSafeHdc())) ==0) 5 J/ `. R8 [& D( [
{ 3 l4 E+ u6 v8 y# J) l6 r6 n
MessageBox(“wglCreateContext failed."); & W* U2 p7 k! b" i
return FALSE; 5 a( s" M! O6 J8 ^' W
} ' \- g( c4 y( x) w
if( (wglMakeCurrent(m_pDC->GetSafeHdc(),m_hRC)) ==0) : c. m0 M* P, U- y9 W
{
# U& ]% s, W" @* F# sMessageBox(“wglMakeCurrent failed.");
* H* I: y. n% y* L! greturn FALSE; 1 v$ o) h1 g0 f
} 5 K. T" H# o% w4 r
1 F$ D+ \$ X' G4 H$ p. f3 Dif(m_pDC) ReleaseDC(m_pDC); l. S+ d" k4 _5 [8 b- d
return TRUE; + ]* d8 k# m- M1 _8 Z
} : V! S C6 I. N: Z' t& k6 {
% i* k N1 K/ L: L i6 F, P: M/ A----3.在OnCreate()函数中调用初始化背景函数 InitializeOpenGL() 3 N& q4 g- M ?
, t# @ Y: _! \" E( s
void CTextView::InitializeOpenGL()
1 G2 {: G3 D2 k' z1 x" |6 E{
) }$ X- Q8 c& pglClearColor(0.2f,0.2f,0.2f,0.0f); % f+ w, W2 H7 I, d) _
glClearDepth(1.0);
) m# g* z" e' l" M+ i. F& X' b XglDepthFunc(GL_LESS); ' X0 G. T U: I! H8 D2 }3 U' ^
glEnable(GL_DEPTH_TEST); ! J1 {1 @0 z$ ]
glShadeModel(GL_SMOOTH);
5 l$ a6 L$ o2 M) f}
7 ^, {6 b( l$ E' q; x7 f( Y0 Y
$ C0 a9 \ h8 F* L7 L----4.在OnCreate()中启动动画定时器 * _9 e5 ^2 Y9 S% F' K2 R/ G: S
2 C7 D( S: b4 u0 h+ Y0 h
SetTimer(0,40,NULL); 4 Q' t: p! T+ k5 b
* E9 O R+ k# o; V; \
----5.在收到WM_SIZE消息时要重新计算场景尺寸,用OnSize 设置图形显示模式 / j2 k" ]( R9 Y1 G* H
3 r, O7 V, ?7 t: ]0 i5 i2 b# A/ j* b----为了使物体能合适的显示,必须要经过投影和确定视口的工作。 9 F8 u# o7 H1 T0 c% o3 o
3 P$ E; k: X6 rvoid CTextView::OnSize(UINT nType, int cx, int cy) 4 r+ a) B0 _1 I/ N+ `
{
5 A- i# a7 T: sCView::OnSize(nType, cx, cy);
: k& |( v+ L, }4 e/ I8 x3 ?; Y0 X. d. N
// TODO: Add your message handler code here 3 v9 b; X+ m) m: b+ D V0 r0 [
//Save the wide and height of the current window Client % h: p4 t" W) I0 k: s* ^
GLsizei nWidth=(GLsizei)cx;
2 E, q- \# x/ N! C0 z6 `: ^5 a1 |3 YGLsizei nHeight=(GLsizei)cy; 3 y5 X9 j2 x, c# M4 Q- r
ratio=(double)cx/(double)cy;
. ?8 x, J7 _; Y' w9 O* p* z- }8 W# z: R8 G# e
//Coupute the aspect ratio
/ M$ b& b( G: K4 @- h2 eGLdouble dAspect=(GLdouble)nWidth/(GLdouble)nHeight; : ^5 D& D/ d2 R5 F& x- @
+ q6 G7 x3 w/ s9 l
glViewport(0,0,nWidth,nHeight); & j2 l7 O6 F0 ^! k; Z3 f5 g
glMatrixMode(GL_PROJECTION); 0 s( t5 ?, g J( K# ~* x" q
glLoadIdentity();
1 X8 b6 _0 z. y8 `) g. QgluPerspective
- u5 k+ n1 u' ], c i4 e(FOV,dAspect,NEARPLANE,FARPLANE);
( E1 E$ p1 H" T0 y4 E& u$ i1 Z8 A. F( w& N
glMatrixMode(GL_MODELVIEW); " [, b E& N: J& }" T
glLoadIdentity(); 7 X1 \6 j2 B4 M, G
}
& C9 }) o9 K0 c w+ f# ^
0 F7 k$ | u7 b: @----6.在OnDraw()中简单调用DrawScene()以执行OpenGL函数
5 W1 p, p7 h3 h) l
5 r( f! M' m1 d6 v% d5 hvoid CTextView::OnDraw(CDC* pDC)
4 u" M2 F' L* T7 ^$ ^{
6 ^+ b3 y7 P# z9 HCTextDoc* pDoc = GetDocument(); / W$ F8 q. H* Z, d; b$ q
ASSERT_VALID(pDoc); , G8 V5 x6 m, o- ]1 Y& R
, w$ m9 Z ?) A
// TODO: add draw code for native data here
* ~, S; F2 E6 E' {DrawScene(); ! I* m; I. u! `, I8 N: x7 \
//Invalidate();
' J2 ^5 a% H" \6 c/ u! D# q: l} * H% O2 {( G! i* D) o8 [/ e8 ~
9 B( m& Z5 ?, d2 J" @6 |8 C
----7.撤销视窗时删除上下文并撤销定时器
- Y7 ^& ^6 h$ d6 E. R! T$ Q) s. y- _$ e
void CTextView::OnDestroy() ) [! U/ R1 R& j& b+ }% p5 j
{ 7 l9 f) h! K, N0 l2 z+ i, f
CView::OnDestroy(); 6 }, q/ a' q% P! D% l9 H! b
/ \7 i/ n" K7 E1 P8 [+ d) k
// TODO: Add your message handler code here
1 H0 S7 F2 U4 K2 p1 ~//This call makes the current RC not current ; f; W5 p3 L2 n& n; m
if(wglMakeCurrent(0,0)==FALSE) 6 j8 X7 E; ]+ h6 D9 M
MessageBox(“wglMakeCurrent failed."); . D) D" |# ^* ~ _ Z9 P
% y+ M7 G, P' j4 q! {6 {8 ?
//delete the RC
1 I: H9 u; w- [& p# _) p4 }if(m_hRC && (wglDeleteContext(m_hRC)==FALSE)) % X; j# U$ h f4 S
MessageBox(“wglDeleteContext fail.");
! Y+ Y0 K3 }* w! t) N$ N- vKillTimer(1);
( v) H/ Y$ H0 j" f0 k3 {: N7 i}
1 I6 o+ Y, v( Z+ g! D
5 E9 A( r& r1 D0 V% t$ S/ g----8.当40ms定时器时间到时,简单地将整个场景的客户区置无效,使之重画 3 L" p0 |# z$ g" @7 S& J- ~( z" L
) U R/ h; V% \& p3 C' ^6 j
void CTextView::OnTimer(UINT nIDEvent) + @' K! f& q1 B9 ?1 p$ b% A; t0 @
{ . S, ^# W, b7 Q1 O- K
// TODO: Add your message handler code here and/or call default 0 Y! f; L& N' `4 M1 f
Invalidate(); % b) K8 f8 H* V) H C. e
CView::OnTimer(nIDEvent);
1 h9 U% \+ G4 u& f4 W3 ^3 T, [} . k6 N, ~. b" G0 g4 n9 ?+ [& V; V
6 t4 ~/ Y1 f, \8 n% i2 U2 K修改界面
7 m c6 B/ g' N% f, i. @---- 删除菜单IDR_MAINFRAME中File下除去Exit菜单项外的所有选项,添加“显示GDI文字” “列表制作文字”“列表三维文字”三个菜单项,并给他们分配适当的ID。使用ClassWizard为这三个菜单项在视类中添加命令处理函数和界面更新函数,其中显示GDI文字的对应的函数如下:
6 z6 U( o; N8 t$ bvoid CTextView::OnGdiText()
: Z3 L& P! [2 l( }1 e6 C4 ?6 \{ # F) E0 o2 r. W: \2 L( t
// TODO: Add your command handler code here f/ x& @( D8 I8 i8 n6 w
m_iWhichText=0; 1 u6 @5 {( ]3 o) f* `3 ?
Invalidate(); % j8 z( C" [/ E* {/ v4 L
}
5 X( g7 Q0 [( H- T2 R1 Z4 O9 r9 O+ Y9 m) r) N3 `0 b9 Z
void CTextView::OnUpdateGdiText(CCmdUI* pCmdUI)
* J7 X3 s" u6 m% e{ 8 f1 C) x2 c/ b& W: f$ {. E
// TODO: Add your command update UI handler code here 5 C# x" y1 i s: e/ K! f
if(m_iWhichText==0) pCmdUI->SetCheck(); 7 R X+ t5 d% ?, m2 f* Z
else pCmdUI->SetCheck(0); 9 L6 |& M! J2 x* O# T
} . j7 o. z' m' y4 V* a H4 X
) o" s% p' X3 e- C- {' @
----增加Draw3DText()、DrawListText()和DrawGdiText()三个函数用于三种不同的文本绘制方法。增加DrawScene()函数,它被OnDraw函数调用,用于绘制场景。在DrawScene()函数中,当m_iWhichText为0、1、2时,分别调用 DrawGdiText()、DrawListText()和Draw3DText()显示文本。具体函数实现如下: 3 t% T7 E$ q& J1 a" h, W
4 f6 Y5 L. u1 S( x6 \5 rvoid CTextView::OnDraw(CDC* pDC) 5 R% j! P1 X/ a& a
{
* }" W9 k4 b8 I8 ~9 g! s" S" r+ P* l0 _CTextDoc* pDoc = GetDocument(); ( |. B0 I" _3 c
ASSERT_VALID(pDoc);
) n; d: W* p h
8 s1 z/ Y" C5 {: @" q3 D! r// TODO: add draw code for native data here ' B# l. m& M) y$ E8 u* o
DrawScene();
7 X7 _* E/ m8 l/ G2 @//Invalidate(); 1 Z- s* q* c) H# j% k( I
}
% }: w; E. h9 s* W. e! n9 [5 e5 U) V: m, m! v8 O
void CTextView::DrawScene()
; x4 q" p' C4 Q" |$ e6 G, y{
; p" Q7 k! K ]; k! ~glClear 8 p! }, ]3 Q. N. Z" Z+ n
(GL_COLOR_BUFFERBIT|GL_DEPTH_BUFFER_BIT); + I1 K# t- e% J
2 ~% r0 Y# G( k% p% [; b
glPushMatrix();
" `7 a; d: T% \3 q6 X: ]glTranslatef(0.0f,0.0f,-FARPLANE); 9 b* w6 t& X5 p5 S5 Z7 t
//TextureMap(); - K7 k- o( h* R) l, o
glPopMatrix();
3 R/ n' N; h/ ~$ qglPushMatrix(); ) f, n3 l' ~' e: f6 i4 G
glTranslatef $ W4 V& j U% K# x R& o
(0.0f,0.0f,-(FARPLANE+NEARPLANE)/2); ! ~/ N: a2 c& `( t0 _6 ?
5 m, R: ]3 k) N4 M! r& h# Fif(m_iWhichText==1) DrawListText(); ; I2 L# m; V! L6 g
if(m_iWhichText==2) Draw3DText();
, n7 x& {/ h5 S' {2 P/ B( S4 z: B+ C5 `glPopMatrix(); : h' r+ r& f) a+ G9 X
glFinish();
: r, F1 N8 k+ o) j' B1 ESwapBuffers(wglGetCurrentDC()); T2 M( j: L8 U. H" }
" `1 l' Z& j1 o
if(m_iWhichText==0) DrawGdiText(); 5 s/ U) K2 |3 p3 @) [& G5 J0 m3 q
}
* M; x, _7 I6 V) y" u: y! h N8 F4 o5 J5 [% @
GDI 显示文本
. g( @2 b; B t S---- 调用wglGetCurrentDC()函数取得当前的设备上下文,使用TextOut函数显示文本,不过要注意在DoubleBuffer模式下,绘制函数要在glFinish()和 SwapBuffers(wglGetCurrentDC())函数之后调用,否则会产生闪烁,在绘制OpenGL结束之前使用GDI函数,要除去闪烁则只能使用SingleBuffer模式,具体函数如下:
1 X8 v# y: s" Q, \, L3 h6 Evoid CTextView::DrawGdiText()
- E* T6 R; f- g: V' V! [% J! x! O# ~{
; @6 f$ G- ~7 o3 j9 @HDC hdc=wglGetCurrentDC(); % q& {' V7 `" v- u% n
::SetBkMode( hdc, TRANSPARENT ); % p j% m' U+ m S; h' D' U
::SetTextColor( hdc, RGB(250,0,0) );
7 y# g( j" w5 U5 c4 J- x: T4 R3 Z. q$ c# @/ P) p& X. B
CString sState(“显示GDI文本。"); 5 }4 t6 g# I/ r/ _. g. }8 `
::TextOut(hdc,5,5,sState,sState.GetLength()); 1 o) V. t5 y" Q- A2 ^# F; h
} 8 ~# q# ^$ A* O2 R. k# S
4 f4 e$ X- K( X* r; CwglUseFontBitmaps ; b+ R5 i" e) r( S
函数显示文字
( H% `; |# o, S1 G4 Q. Z9 }0 R----使用wglUseFontBitmaps()将ASCII字符装入显示列表,然后使用glCallLists()函数利用显示列表序列显示文本。wglUseFontBitmaps有四个参数,分别是当前使用的DC、从第几个ASCII字符起始装入列表、装入列表的ASCII字符数和起始的列表序号。glListBase()指定glCallLists执行的起始列表序列号。glCallLists()含有三个参数:执行列表序列的个数、列表值的类型和所要显示的文本。注意如果所要显示的文本是字符串,它所提供的信息是相对于起始装入ASCII字符的偏移量,因此最终所显示的ASCII字符是从glListBase()所指定的列表起始号在经过glCallLists()中偏移后的列表,因此wglUseFontBitmaps的从第几个ASCII字符起始装入列表参数、glListBase()指定的 glCallLists执行的起始列表序列号和glCallLists()中的所要显示的文本参数都可以影响最终显示结果。由于显示的是ASCII 字符,因此不能显示汉字。glRasterPos3f函数决定在 OpenGL视景体坐标系下的偏移。具体函数实现如下: ( ~3 b4 I5 z! ?4 Y3 Y; d, z+ f' h* G
void CTextView::DrawListText() 8 y; a* [& L0 q- Y) t* g
{ 1 F8 s: \2 _. Q: ]- w. l. `
wglUseFontBitmaps(wglGetCurrentDC(),0,256,1000);
5 [! F8 q" z" ~" h! m: U, E( B) wglListBase(1000);
$ ?1 X7 Z2 y* p @4 N# L- gglRasterPos3f(-5.0f,0.0f,0.0f);
9 k' n3 \* {8 D+ _+ cglCallLists(20,GL_UNSIGNED
6 D( r" b. _" R_BYTE,“Draw with List Text."); ! U9 }$ j6 X/ [4 P4 ?! ?
} 9 h% V) k+ h0 N
8 G3 ~" X! L: q$ ^% {: c
wglUseFontOutlines
4 c& I N( B/ |) p+ W函数显示三维文字
1 M7 |6 D- i1 y8 v# P3 a( m! l" ?/ U----wglUseFontOutlines使得OpenGL可以显示三维文字。它的用法与wglUseFontBitmaps函数大致相同,但是多了内插计算参数、字体深度、显示方式和装载字模的缓存四个参数,且只能显示TrueType字体,显示前应该先选择字体类型。具体函数实现如下: . V5 Z3 p- U6 c2 G+ a3 U; y. w
void CTextView::Draw3DText()
: ^: {/ h/ X2 j; S5 S9 P{ 9 S6 K3 g: v0 T" j, B
GLYPHMETRICSFLOAT agmf[256];
" `* `0 _$ v; r+ I) r* B3 C5 H- S// create display lists for glyphs 0 through 255
) p' ?2 }7 J1 A7 a4 {// with 0.1 extrusion and default deviation.
5 }+ D/ v( t' N" P0 }//The display list numbering starts at 1000
( t) o: k+ O' D(it could be any number)
# o/ m, [0 ~" j4 J5 JwglUseFontOutlines(wglGetCurrentDC(),
. ~0 W+ J2 ?0 d' B& X0,255,1000,0.3f,0.8f, WGL_FONT_LINES ,agmf); % Y* r+ p% W% ^- v H# o+ `
4 l: w& W r# g
// Set up transformation to draw the string 5 T' P: Z' j( t+ O. \; O
glTranslatef(-15.0f,0.0f,0.0f);
2 l0 y1 b( X: z& t+ B9 xglScalef(4.0f, 4.0f, 4.0f);
! j; ?- ~3 M6 u3 T2 l// Display a string " o# Z+ r: s, a; Y% V% a4 }
glListBase(1000); ! d- R& L, S0 J! M
// Indicates the start of display lists for the glyphs
4 Q& v7 |- _% u- o1 j// Draw the characters in a string 1 ^9 e# |' {8 q5 ^( j9 p
2 c; ]% b2 T; K+ P3 d8 TglCallLists(26, GL_UNSIGNED_BYTE,
7 X4 j, F, ~! [9 e) |8 d7 V! L& Q“Draw outline list 3D text."); ; h! }$ _2 b) h' I
} |