Nibiru经测试,用了OpenGL的wglUseFontBitmaps,glCallLists函数来显示文字。
- B) r, a8 v3 S) t. S也许是一个汉化的突破口? & Y; V B3 S( J8 E' h' @
e/ T! B9 D2 K8 q8 z
---- 本文详细讨论了在OpenGL中显示文本的几种方法。 a7 v9 e5 E4 o) T4 T
+ ~. o0 I& |/ B7 O6 f% [. r----也许大多数程序员使用OpenGL更多的是将精力集中于动态三维图形应用,因此,OpenGL中的文本显示往往被忽视,使人有不见积薪之感。本文介绍了几种文本显示的方法,希望能对使用OpenGL的编程者有所帮助。 / O5 l0 Q3 n6 Q2 f
4 L1 L8 N/ I0 b' S" z* l; l
建立并修改程序
, H: t6 t. A7 N( Y) p----建立一个MFC SDI Windows应用工程Text,除单文档属性外,使用其他的所有默认选择。在菜单Project打开Settings对话框,在Link属性页的 object/library modules编辑框中加入opengl32.lib glu32.lib glaux.lib三个GL库。我们利用这些库函数完成图形编辑工作。
, X6 V- y9 S, b% F" s7 g----为使VC++的AppWizard产生的SDI应用程序能使用 OpenGL绘图,还需要作一些修改,说明如下。
$ i9 r0 a! K" x+ ?% R/ l0 _+ ~
) ^+ `' ?- _; S+ Y( i6 K- ^8 `----1.介绍PreCreateWindow函数 ( ]) Z, o- z7 {& C
" d) `2 D8 A$ \# b
---- OpenGL窗口必须具有WS_CLIPCHILDREN(创建父窗口使用的Windows风格,用于重绘时剪裁子窗口所覆盖的区域)和WS_CLIPIBLINGS(创建子窗口使用的Windows风格,用于重绘时剪裁其他子窗口所覆盖的区域)两种风格。此外,窗口类属性不能包括CS_PARENTDC风格。具体程序实现如下:
r$ F3 F+ y8 O; }) f4 q6 P" {0 J# ^, \. }9 m
BOOL CTextView::PreCreateWindow , `, K( T; y4 p" m
(CREATESTRUCT& cs) 4 g3 o( S H* Q1 r
{ ! Y1 e) ?) f- G) @' l4 `
// TODO: Modify the Window class or styles here by modifying ) K; M0 }/ n9 S* `
// the CREATESTRUCT cs ; q& N* I) |$ P2 U1 E5 ~- r
. A' C/ J6 ^5 N. u
//An OpenGL window must be created with the following flag
; m* i, r% }! i, }$ ? e4 ]: A// and must not include CS_PARENTIDC for the class style. 7 y6 B8 L1 v$ M) ?
cs.style|=WS_CLIPSIBLINGS|WS_CLIPCHILDREN; 7 ]0 [ S( C) u: e2 M/ H/ m- J1 Y
" H# Q* |0 O0 _7 Y
return CView::PreCreateWindow(cs);
0 ~3 R- |8 z7 F0 K: R% j6 x" H}
" Y' K& H! S0 e$ y E6 L; W, p! T( h" M- Q/ I3 Z, M
----2.OnCreate函数中定义像素格式PIXELFORMAT和创建 RC ! J0 J$ W& q* l" ~
6 U2 Y0 `# U! q5 e) [9 M3 J! L3 H! B----要使窗口支持OpenGL绘图,必须对窗口进行初始化。其中包括定义像素格式PIXELFORMAT和创建RC,为OpenGL指定一个合适的像素格式,创建着色上下文并将它和窗口的设备上下文关联起来。着色上下文保存着当前着色环境的信息。可在OnCreate中调用一个自建视口成员函数SetupPixelFormat(),具体函数如下: 0 x9 ~9 K1 ]# f9 O, \$ I/ P8 X
. k8 ^ i* v0 g0 W$ C/ Q. a# f
BOOL CTextView::SetupPixelFormat()
) p# u. z' ]& F* d{ + L2 F( ]" d; t9 n5 } N6 k$ o' u! \
//Create a rendering context
2 i+ _; V1 y8 c/ `$ O" F) LCDC* m_pDC=GetDC();
* O% d$ N- N3 mif(m_pDC==NULL) //failure to get DC ) z' R) ]9 h, Z# J5 Y7 Z
{ 3 g. G1 o3 x. L* ?9 }$ e& h# n
MessageBox(“Could't get a valid DC."); 0 ]- ]( D# K- R8 m0 t
return FALSE;
( E' u4 W7 `4 _7 |}
9 v; c9 u5 R. H; Q6 u, v8 o8 Q* t1 B; \
//Default pixel format is a single-buffered, 6 ~0 C \+ f6 T4 Z }9 k. F' u
//OpenGL support hardware-accelerated,RGBA mode format , [9 O5 X" r# ~+ ~0 b' {) X
PIXELFORMATDESCRIPTOR pfd = 4 P# ?" H/ B. k" J$ Z: m
{
8 v) N" V* X; K9 c8 p& Vsizeof(PIXELFORMATDESCRIPTOR),//Structure size. 7 B/ k# i5 Z4 J# f
1, ' b/ ?, a5 \1 [% k$ y( I
// Structure version number.Property flags(特性标志):
3 O4 D* r) k; }; C8 j7 ^PFD_DRAW_TO_WINDOW | // support window & {- L* [$ s' L. m
PFD_SUPPORT_OPENGL | // support OpenGL
1 t! _9 [. B& m# K3 ^+ l# cPFD_DOUBLEBUFFER,
3 H# E1 R' n& ^/ b( o! R" jPFD_TYPE_RGBA, // RGBA type
8 Q. e) u- _$ Y5 e+ y& O, G& l" Q24, // 32-bit color.
( `$ s1 q: f. X4 n; A. j0, 0, 0, 0, 0, 0, // Not concerned with these:不涉及的属性
" Q# l, X1 z7 O, ?. B0, // No alpha :无alpha缓存
/ a9 \. Z" f0 M# F) @/ o0, // Shift bit ignored:忽略转换位 & M4 P! k' L7 j* m
0, 0, 0, 0, 0,// No accum buffer:没有累积缓存 : H6 h; u* ^. n) U7 Z
32, // 32-bit depth buffer. 7 J! ^! Z/ r& P2 B
0, // No stencil:无模板缓存 - u, o( D% J$ P8 j* g( H1 ~; y
0, // No auxliliary buffers:无辅助缓存
: H& S3 {1 y" f% k% s9 o& ~PFD_MAIN_PLANE, // Main layer type.:主层类型
% C# [' u* Z, R3 P6 I: t: z0, // Reserved.:保留结构数 7 E/ P" u4 _: K- N' X
0, 0, 0 // Unsupported.:不支持结构数 ' s, s: ] \ f. B5 c2 J2 l i6 v3 x
}; 9 s$ h& [0 t0 n) G1 P0 q- q
int nPixelFormat= 6 A! @. g$ y$ c! q# N
ChoosePixelFormat(m_pDC->GetSafeHdc(),&pfd);
# M+ U8 `3 K1 z' F$ Vif( nPixelFormat ==0) 7 H, w+ H$ x$ q4 J0 ^! X5 F; B
{
1 r0 Y/ B' q+ cMessageBox(“ChoosePixelFormat failed.");
% p: a& F3 L: u5 i3 @: Y9 C1 Dreturn FALSE;
2 H! y, r5 w) N}
5 c) R5 j% x) \# A- Z# R: R
7 j7 o1 o- z) G* q8 n: yif(SetPixelFormat(m_pDC->GetSafeHdc(),
$ i: s3 X7 U- A4 i) s$ GnPixelFormat,&pfd)==0) / Z$ P0 ]* ^. |6 z, y: n! t
{
1 m- _! E4 ^: j- a; V4 _* e4 WMessageBox(“SetPixelFormat failed.");
. F- p. y, M0 k0 S' |! Areturn FALSE; & f* L! U O1 S3 {4 v9 i# D7 [. I
} 0 A6 z8 D) ]4 v4 L. z
H1 M. z3 S0 W* r
if( (m_hRC=wglCreateContext(m_pDC->
* i0 p# Q0 ] u; J; uGetSafeHdc())) ==0) * \+ O6 `" m* ^, C0 r( K
{
. W! ~$ b9 F7 B7 KMessageBox(“wglCreateContext failed.");
* j, Z6 k1 B3 T/ Y8 ]- T3 P$ @return FALSE;
2 _$ h9 D$ w6 F& P} 2 }+ t2 G0 h( X0 G
if( (wglMakeCurrent(m_pDC->GetSafeHdc(),m_hRC)) ==0) 2 o/ j9 X* c. R
{ : e: _* S, M" j; U- m. O# }. P
MessageBox(“wglMakeCurrent failed."); ' H! {$ P. r m
return FALSE; 9 u* T, W: U, `
}
, Z% |5 g4 t/ F( x6 \& U7 t4 s; L+ v% ~6 Y
if(m_pDC) ReleaseDC(m_pDC);
& g$ Q. y1 s0 k# ^- b% s: }0 A; m2 creturn TRUE; , t2 N7 q6 M( I' M2 j
} ' i, I$ q; R, q3 g) B4 M
+ k s/ ^, q' R5 W----3.在OnCreate()函数中调用初始化背景函数 InitializeOpenGL()
! _( X9 S5 J3 G
* v6 i$ w) N6 I6 r# \void CTextView::InitializeOpenGL() 3 \* C8 K# i' j
{
/ g; w7 T4 n3 }; ], J sglClearColor(0.2f,0.2f,0.2f,0.0f);
7 ?0 |& O- t) K# P0 E( nglClearDepth(1.0); ; l0 L, O! g6 E& E! f5 j2 ~5 A" T
glDepthFunc(GL_LESS);
) M8 c- p. B ZglEnable(GL_DEPTH_TEST); # i# h% _4 Z# @- |
glShadeModel(GL_SMOOTH);
6 B3 ~; J8 s- R} 2 c Y( }) D5 ~: r6 p/ D& o
0 M4 y9 _6 o- V& E# N% u- y----4.在OnCreate()中启动动画定时器
+ R+ G2 g' H s% U
4 d8 u( l/ `8 y6 z8 `SetTimer(0,40,NULL); 0 S$ N! X$ {) X, _- L
' M$ s, ]4 J2 _8 F
----5.在收到WM_SIZE消息时要重新计算场景尺寸,用OnSize 设置图形显示模式
, D( {, N$ d' D
; Y! E a v' u----为了使物体能合适的显示,必须要经过投影和确定视口的工作。
& X5 q! V) y' g9 W2 U' t* [2 x( Q+ `# M+ g7 O+ ]( m9 H1 o
void CTextView::OnSize(UINT nType, int cx, int cy)
3 `4 C1 B9 Y. P9 t* h( c( w{
7 T3 E) m" x2 n9 z- s, ^CView::OnSize(nType, cx, cy);
# f0 f5 o& ~# l! c: F4 ?8 U. }/ s q2 ~! Q) G* S
// TODO: Add your message handler code here * H# Q( \% V" J- M8 S: K: `% P3 Y
//Save the wide and height of the current window Client
( g* j/ J `+ u, b4 B6 z5 yGLsizei nWidth=(GLsizei)cx;
6 o0 b A. ]: q. y( _* WGLsizei nHeight=(GLsizei)cy;
% J3 x* d7 Z4 h q9 A3 Uratio=(double)cx/(double)cy;
0 W6 m* z# j1 w( G4 ]
' N3 `. \& G. p e//Coupute the aspect ratio
1 e1 X6 O& B$ O& |- z. iGLdouble dAspect=(GLdouble)nWidth/(GLdouble)nHeight;
4 U/ P6 X3 M5 B1 _% w) y
! Q+ S1 T# ^/ d6 G" w$ ~1 tglViewport(0,0,nWidth,nHeight); / M3 U* k) n2 P# K6 c* I
glMatrixMode(GL_PROJECTION);
9 f& d" S6 e' _4 k) n- g2 qglLoadIdentity();
' m' G/ M( v4 P& f" Y2 Z# }gluPerspective
. P$ [. ? ]" s) {$ o(FOV,dAspect,NEARPLANE,FARPLANE);
) C+ l% `; d* `3 S, {
* f4 d/ \: A9 X5 I+ `. tglMatrixMode(GL_MODELVIEW);
7 H8 H2 Z7 w+ m3 }% b/ M- bglLoadIdentity();
) T+ M8 ]% e6 C1 H}
; d0 \! m: D4 y. g( t
1 ^* {8 h d3 d9 \- V----6.在OnDraw()中简单调用DrawScene()以执行OpenGL函数
6 S# f# O1 \, ^/ F9 i2 }) K' r0 z$ j8 @
void CTextView::OnDraw(CDC* pDC) ; G! T9 O3 \0 l8 ~7 H0 _+ l
{
/ {! D7 ~6 T+ e) d+ x+ S4 o4 ]0 VCTextDoc* pDoc = GetDocument(); 2 i9 b. C& v5 j) {8 @
ASSERT_VALID(pDoc); & a/ c$ O6 O7 q. A; m- h- }
4 O% r& z9 ^% m& `( T0 {% B// TODO: add draw code for native data here ' e. B' E+ g) P* q
DrawScene(); + b3 l' a3 i& ^! n" m! v
//Invalidate(); % q' F/ x; B7 X5 N# T
}
6 ~% c' A; e: ?# T+ G* I D1 \. Y) \$ u/ |% ]# _; B; i2 z
----7.撤销视窗时删除上下文并撤销定时器
0 m, f9 e' v# p. Y2 n! k5 S ^3 X; ]/ R5 Y6 N E$ y
void CTextView::OnDestroy() ! h' c5 a: h8 l& |
{ o% {4 p; ]* D+ F
CView::OnDestroy(); 9 D+ `' e p& p& z4 {1 T& L1 g
6 M3 S7 A+ M/ f3 [% x* A
// TODO: Add your message handler code here
' E- h4 L- O" w//This call makes the current RC not current
1 N( S6 [* [% V3 b. h& Yif(wglMakeCurrent(0,0)==FALSE)
( N- q& D3 K3 S4 KMessageBox(“wglMakeCurrent failed.");
. `+ v0 u W- e# k5 x* } _1 h' O. d3 d
//delete the RC
1 ~# I" F6 A/ ] X. Kif(m_hRC && (wglDeleteContext(m_hRC)==FALSE))
; c' S+ ~0 |$ ]/ ~7 mMessageBox(“wglDeleteContext fail.");
" k2 o2 v$ i( l: G0 N0 y) Z6 GKillTimer(1); ; l; V" P' ?9 m4 ?8 |% y
}
- V# z% K8 ^% g' H
! J. q4 }$ Q- a----8.当40ms定时器时间到时,简单地将整个场景的客户区置无效,使之重画 # F9 }. z1 T2 P+ g* g: f' W
3 `7 V0 k4 @: J4 ?7 m0 d9 H: m Kvoid CTextView::OnTimer(UINT nIDEvent) # p4 f, P7 h# R! Q' x
{ - O# v3 X$ }' G. `. D
// TODO: Add your message handler code here and/or call default 7 U$ U2 h5 p# Z- ^* Q
Invalidate();
{9 i* e8 h! Q- ^6 A# uCView::OnTimer(nIDEvent);
8 q# A. @- Z! K4 b( J}
) x4 U& l' @2 _& m$ F: r; J0 r# f* e9 p) M
修改界面
) U0 }, |, S* _) u) T6 k---- 删除菜单IDR_MAINFRAME中File下除去Exit菜单项外的所有选项,添加“显示GDI文字” “列表制作文字”“列表三维文字”三个菜单项,并给他们分配适当的ID。使用ClassWizard为这三个菜单项在视类中添加命令处理函数和界面更新函数,其中显示GDI文字的对应的函数如下:
+ B& ]. l3 l4 f z/ M! E" b2 H1 lvoid CTextView::OnGdiText()
& |+ P# ]! d( f{ & r/ v5 y, k) i( A
// TODO: Add your command handler code here 9 \4 C' o/ y$ C4 O
m_iWhichText=0;
+ z, r+ K3 Y3 b4 i$ UInvalidate(); . C Z$ |, q- O7 `0 R
} # ^3 O1 ^ L" ^( v
* i9 _7 [# _: [void CTextView::OnUpdateGdiText(CCmdUI* pCmdUI) ! s! R* M& U3 y( F# X* C
{
4 t5 J! d* T8 c' z, ^$ g, @8 @// TODO: Add your command update UI handler code here 1 u- J) `% a+ A& c- R r* d( ^
if(m_iWhichText==0) pCmdUI->SetCheck(); + B' R' |/ J/ w& i
else pCmdUI->SetCheck(0);
* B4 Z9 m, P" ?}
) ] ^+ a* `% X+ O4 B$ k- s* G2 N% x. w, K
----增加Draw3DText()、DrawListText()和DrawGdiText()三个函数用于三种不同的文本绘制方法。增加DrawScene()函数,它被OnDraw函数调用,用于绘制场景。在DrawScene()函数中,当m_iWhichText为0、1、2时,分别调用 DrawGdiText()、DrawListText()和Draw3DText()显示文本。具体函数实现如下: 2 l0 z4 |! \6 m% Y& ~; I
0 D& c/ p5 E6 T) R2 B. ~/ w+ N( h
void CTextView::OnDraw(CDC* pDC) ) e3 K/ d+ y& |' t
{ * j0 k P8 X$ X: o. F, K/ Y
CTextDoc* pDoc = GetDocument(); ) ^' Q6 e( k j! a$ U. a: s/ `
ASSERT_VALID(pDoc); 1 r7 {+ U) T8 P, t- Y* v& t
: i5 [* c/ H& s8 c& c# u// TODO: add draw code for native data here
& s2 l# ?4 ]5 E$ y! ODrawScene(); $ e6 j' Z5 E! J! J' ]5 A7 W
//Invalidate(); # e; B0 ]9 _" A' P) j
}
" u Z* {# n, P) z8 U0 l* t. N; T8 \: _* _2 U3 L) b! w
void CTextView::DrawScene() / P2 N* k8 d. T; [ m( y
{
$ f+ ?7 t8 M/ F) F# F4 m7 M bglClear & s, b# P/ V9 k9 D R, J2 q
(GL_COLOR_BUFFERBIT|GL_DEPTH_BUFFER_BIT); 5 n0 Y4 |) W" E1 F
" g! _( u5 d( T
glPushMatrix(); $ x1 T* q: V; h% b) ~
glTranslatef(0.0f,0.0f,-FARPLANE); * a) o' p$ ~+ w+ ~3 U- U
//TextureMap();
' ^* K# q& T& w" S q4 @" e9 y/ O& SglPopMatrix();
; s0 Q$ m5 Z0 ^- D( _' w* b7 B5 ZglPushMatrix();
4 l Q0 c5 M7 N% E; m* }$ WglTranslatef 7 B. S& n8 R. h6 O) Q/ u: ~7 _
(0.0f,0.0f,-(FARPLANE+NEARPLANE)/2);
" y; ^3 W: A+ m1 K3 {) L
& r6 X O% v G9 ], I, G. z; m! ]if(m_iWhichText==1) DrawListText();
5 m: O+ s) H. A ^, _if(m_iWhichText==2) Draw3DText(); o) `- N8 h2 w; A( W# F
glPopMatrix(); ( S; V* w5 D3 X) H
glFinish();
! ?) w' B/ H6 t( \2 ?SwapBuffers(wglGetCurrentDC());
: ?/ @% [, \. w2 x( ?- ^7 G- c1 E: T: \$ {2 O: B
if(m_iWhichText==0) DrawGdiText(); ! r$ r% o$ T7 U/ E9 {; |# C
} , y9 t$ L' i$ B9 P' b5 Z
" J: t$ S+ P" W+ O+ N3 N6 U
GDI 显示文本
: o% n! D8 E# D, C6 m1 ^* |---- 调用wglGetCurrentDC()函数取得当前的设备上下文,使用TextOut函数显示文本,不过要注意在DoubleBuffer模式下,绘制函数要在glFinish()和 SwapBuffers(wglGetCurrentDC())函数之后调用,否则会产生闪烁,在绘制OpenGL结束之前使用GDI函数,要除去闪烁则只能使用SingleBuffer模式,具体函数如下: & e- s6 D O' ^5 N0 a5 c
void CTextView::DrawGdiText()
* s* g; c1 B2 G1 P8 F' H- s# ^{
% K. Q+ v. f# ~% ?7 |8 s' h# QHDC hdc=wglGetCurrentDC();
2 u/ D- O! u/ W6 \8 S5 p* `$ y( c::SetBkMode( hdc, TRANSPARENT );
9 |/ R A* m7 J::SetTextColor( hdc, RGB(250,0,0) ); # v9 x; J t2 Y
7 j( ?/ j, \7 |' A, fCString sState(“显示GDI文本。"); 6 s0 N6 ?8 q7 [5 a: H
::TextOut(hdc,5,5,sState,sState.GetLength()); & s/ A* A, G0 Q) L/ E8 G
} * F! y9 n! @( w
+ b4 G2 F T& q5 D# j, D8 ewglUseFontBitmaps
2 K5 M* K# `( ^2 I( i( `函数显示文字 & p$ F$ i u2 K$ E; c( {, |- n
----使用wglUseFontBitmaps()将ASCII字符装入显示列表,然后使用glCallLists()函数利用显示列表序列显示文本。wglUseFontBitmaps有四个参数,分别是当前使用的DC、从第几个ASCII字符起始装入列表、装入列表的ASCII字符数和起始的列表序号。glListBase()指定glCallLists执行的起始列表序列号。glCallLists()含有三个参数:执行列表序列的个数、列表值的类型和所要显示的文本。注意如果所要显示的文本是字符串,它所提供的信息是相对于起始装入ASCII字符的偏移量,因此最终所显示的ASCII字符是从glListBase()所指定的列表起始号在经过glCallLists()中偏移后的列表,因此wglUseFontBitmaps的从第几个ASCII字符起始装入列表参数、glListBase()指定的 glCallLists执行的起始列表序列号和glCallLists()中的所要显示的文本参数都可以影响最终显示结果。由于显示的是ASCII 字符,因此不能显示汉字。glRasterPos3f函数决定在 OpenGL视景体坐标系下的偏移。具体函数实现如下: ; j4 I8 Z1 O6 j: R- D" T) R% N! @
void CTextView::DrawListText()
& B" E l! A- f+ n. |7 V{ # t9 b# ]& v) k6 ?7 ]
wglUseFontBitmaps(wglGetCurrentDC(),0,256,1000); x) a8 x/ h8 v) w3 N
glListBase(1000);
: h+ L, y) i' o: l. h7 L. @glRasterPos3f(-5.0f,0.0f,0.0f); ' j! n( F* w' [% \$ D, s! ?
glCallLists(20,GL_UNSIGNED 2 \' }& e' M2 l. f
_BYTE,“Draw with List Text."); # ?+ w1 {; L7 s
}
# H& y: H- N' t! S2 `5 Q' e6 M4 j4 h* H' h& l
wglUseFontOutlines
@' D. F/ Y. \9 }5 O2 O$ E函数显示三维文字 . q. z2 w a9 U; o# ~
----wglUseFontOutlines使得OpenGL可以显示三维文字。它的用法与wglUseFontBitmaps函数大致相同,但是多了内插计算参数、字体深度、显示方式和装载字模的缓存四个参数,且只能显示TrueType字体,显示前应该先选择字体类型。具体函数实现如下: 2 a7 @, P3 C! e8 p" t
void CTextView::Draw3DText()
0 E" J% N9 v( C$ e- R1 q: b% u- E{
/ O% ?, n2 q# {# JGLYPHMETRICSFLOAT agmf[256]; ; ^& O8 o* o2 r, Z$ N
// create display lists for glyphs 0 through 255
% H* s) p2 m6 s8 f; M5 V* ]- A) F// with 0.1 extrusion and default deviation.
; {9 R1 P+ C, ?7 a0 j2 k//The display list numbering starts at 1000
7 \) k1 h; x0 G2 R, q9 e+ w+ h(it could be any number)
1 [' h9 O g3 W2 `4 BwglUseFontOutlines(wglGetCurrentDC(), . Q/ K! D9 ~ D, N5 q9 Q! B
0,255,1000,0.3f,0.8f, WGL_FONT_LINES ,agmf);
6 ]7 y* _" l* P4 f6 H0 i" Z% }. a1 H+ ]% w- }( x7 x) }9 X
// Set up transformation to draw the string
# J. f4 {5 L/ O/ f1 o' Z: [glTranslatef(-15.0f,0.0f,0.0f); ; o, L. z, _) a. J' I
glScalef(4.0f, 4.0f, 4.0f); % v6 E- c. D% `' j' F
// Display a string 3 M+ x8 d1 p# ?2 J+ Q# Y9 a5 B6 w
glListBase(1000);
; r! x* _8 `& z g5 d- R6 {0 ^" H// Indicates the start of display lists for the glyphs
/ T8 V7 B- `! g6 R$ V5 e# Q2 K% n& s// Draw the characters in a string # F' l! V B$ P" m
8 |( R9 n9 b6 N: e# g- h# |3 A& }6 l
glCallLists(26, GL_UNSIGNED_BYTE,
" A8 Q" r- K/ |# o4 b“Draw outline list 3D text.");
/ Z+ v) f0 u2 W2 g5 U4 j& k( p} |