Nibiru经测试,用了OpenGL的wglUseFontBitmaps,glCallLists函数来显示文字。
/ D( Y, i# n' [2 S也许是一个汉化的突破口?
1 _9 w2 @ q( s. k
/ w0 P9 x& L7 L% Z---- 本文详细讨论了在OpenGL中显示文本的几种方法。 " _3 k* B0 _% J7 R/ K+ ~
. C6 b9 c; m! k) L( v9 l0 \
----也许大多数程序员使用OpenGL更多的是将精力集中于动态三维图形应用,因此,OpenGL中的文本显示往往被忽视,使人有不见积薪之感。本文介绍了几种文本显示的方法,希望能对使用OpenGL的编程者有所帮助。 ; x% ]4 y9 O% h8 @; D
, Y! {' Z7 ^9 h3 d8 t
建立并修改程序
7 F0 _1 L% t" t----建立一个MFC SDI Windows应用工程Text,除单文档属性外,使用其他的所有默认选择。在菜单Project打开Settings对话框,在Link属性页的 object/library modules编辑框中加入opengl32.lib glu32.lib glaux.lib三个GL库。我们利用这些库函数完成图形编辑工作。
1 ]) k# D' D; p) K- x, c----为使VC++的AppWizard产生的SDI应用程序能使用 OpenGL绘图,还需要作一些修改,说明如下。 3 A9 t' j; p$ d
8 |& |. j& j* t----1.介绍PreCreateWindow函数
9 }/ A9 f& J0 S
; u# c/ A/ y( l L---- OpenGL窗口必须具有WS_CLIPCHILDREN(创建父窗口使用的Windows风格,用于重绘时剪裁子窗口所覆盖的区域)和WS_CLIPIBLINGS(创建子窗口使用的Windows风格,用于重绘时剪裁其他子窗口所覆盖的区域)两种风格。此外,窗口类属性不能包括CS_PARENTDC风格。具体程序实现如下:
2 \ O A/ A4 J2 h4 r. {0 G P: F9 M/ A( n- k. V" }
BOOL CTextView::PreCreateWindow 4 W" v. m0 m0 U* d
(CREATESTRUCT& cs)
& c7 G$ u: [: b Z1 F* N+ x# P$ m{
" w& o1 ~8 i: m// TODO: Modify the Window class or styles here by modifying
2 R% ? ^4 ?0 s2 O, j: U7 u+ e! h// the CREATESTRUCT cs
- N+ T0 F/ O0 ^" Q, Q* |; p! l C ]6 G$ P) G
//An OpenGL window must be created with the following flag : b4 g6 N" {4 {* B
// and must not include CS_PARENTIDC for the class style.
* J& g, Y8 ?$ E/ ]# w( l) }. {; Hcs.style|=WS_CLIPSIBLINGS|WS_CLIPCHILDREN; - I3 K' A: G& q( I
& ~6 e: V- L5 I
return CView::PreCreateWindow(cs);
/ T/ J5 C1 B* o# b; } u}
}; m% u& d! ^) e3 }# W: t9 q2 `3 X) m: M- K
----2.OnCreate函数中定义像素格式PIXELFORMAT和创建 RC 4 H* h7 o. J$ L- `+ w7 `6 y
! O# E5 [$ d; |8 S! c9 e8 P: A
----要使窗口支持OpenGL绘图,必须对窗口进行初始化。其中包括定义像素格式PIXELFORMAT和创建RC,为OpenGL指定一个合适的像素格式,创建着色上下文并将它和窗口的设备上下文关联起来。着色上下文保存着当前着色环境的信息。可在OnCreate中调用一个自建视口成员函数SetupPixelFormat(),具体函数如下:
$ t$ j% @0 Q( v4 Y2 k
0 v o9 {1 W7 P$ Y9 BBOOL CTextView::SetupPixelFormat() & a2 d' m" g6 d6 K
{ 7 c) e0 _" @( M: M, ~
//Create a rendering context 4 b' } E0 Q& q0 \6 n; m0 Y
CDC* m_pDC=GetDC();
, g0 D- S7 F' E' j5 Xif(m_pDC==NULL) //failure to get DC 8 Z# Z7 E8 Q0 d6 A8 e' p
{ 2 X" F( ?- d/ f# |
MessageBox(“Could't get a valid DC.");
6 d4 b9 N# _1 J5 F: D* Sreturn FALSE; 7 i& e2 W4 z5 m& d A, J) S/ H
}
, z* L1 K% P5 C0 ~! V: w* j; b
- `& V7 P6 [) F8 a//Default pixel format is a single-buffered,
9 O Y& f$ U7 S. a1 y, S, D+ Z//OpenGL support hardware-accelerated,RGBA mode format 3 B) s7 n0 r E, @: E9 w
PIXELFORMATDESCRIPTOR pfd =
; n4 `% H/ [$ \+ k' d0 R. v{ ( y6 H5 r" K- W" K o$ E
sizeof(PIXELFORMATDESCRIPTOR),//Structure size. 2 W) q- L3 V; Q5 n% u
1, + U# z" o6 X, N2 _) m- X
// Structure version number.Property flags(特性标志):
) i1 I2 Y: h. b' r" JPFD_DRAW_TO_WINDOW | // support window & n: [5 g5 }8 s. Y0 J# Z" v
PFD_SUPPORT_OPENGL | // support OpenGL
9 w. r; { p( k8 _PFD_DOUBLEBUFFER, , Z9 ?: g7 `* v4 X1 Z% S/ r
PFD_TYPE_RGBA, // RGBA type ! S' X4 z: F0 k5 b
24, // 32-bit color. 5 J! u: q2 {( B! [' @
0, 0, 0, 0, 0, 0, // Not concerned with these:不涉及的属性
+ g5 t% }9 w* v2 ~7 p9 m0, // No alpha :无alpha缓存 f$ o$ m! S5 q
0, // Shift bit ignored:忽略转换位 / X- [! I. m9 b9 R
0, 0, 0, 0, 0,// No accum buffer:没有累积缓存
' f, m& w! Z3 M0 @: G32, // 32-bit depth buffer. ! C) S+ |3 Z, ]7 a' g N
0, // No stencil:无模板缓存 * q& c! T! t# [* ~$ B
0, // No auxliliary buffers:无辅助缓存 + h K7 T& D6 F
PFD_MAIN_PLANE, // Main layer type.:主层类型
0 V- d, c! C" Q0, // Reserved.:保留结构数
# r6 n* Y/ |6 p ]9 V# A# J9 t0, 0, 0 // Unsupported.:不支持结构数 & J) ^ _( ?" S" Q
}; , ^1 n: b( R+ c0 e
int nPixelFormat= + c; x+ w+ g- D' V" @1 @
ChoosePixelFormat(m_pDC->GetSafeHdc(),&pfd); K) d: u4 m8 ]' C
if( nPixelFormat ==0)
% @) Y$ C. A9 @5 m8 Y- A0 P; t{
1 a. K" \0 z5 |MessageBox(“ChoosePixelFormat failed."); * ?3 j7 x/ r/ w0 v4 a& @, D
return FALSE;
/ {0 U) \. A$ f7 v} / O+ h8 P3 [: m* K9 w
1 v* y: w& A6 Y7 f$ [: |) N
if(SetPixelFormat(m_pDC->GetSafeHdc(), $ e, l8 r6 q* [( a
nPixelFormat,&pfd)==0) $ P0 k. j# ~9 h# ]
{
. C- x) Z0 C2 j! [) }- T/ KMessageBox(“SetPixelFormat failed.");
3 b3 R. B9 r8 z! q5 |& q' s4 freturn FALSE; " ]* ^+ A2 D2 C/ C
}
* n: g; |7 J' m' {' K
0 Z" Z, T( {# U2 L* L' }if( (m_hRC=wglCreateContext(m_pDC-> * s; I+ @, C: Y: T* r5 ^ K# F
GetSafeHdc())) ==0) 5 y+ C3 l7 ?$ _, t% b7 M, n x
{ ( X) v! h$ o8 o
MessageBox(“wglCreateContext failed.");
9 W; P+ y1 U O& S6 Yreturn FALSE; , e' @' A; ~2 y) M
}
, P! Y& \; R" y+ C vif( (wglMakeCurrent(m_pDC->GetSafeHdc(),m_hRC)) ==0) ; C; ~" M V, U, R. |
{
. y5 d+ C* v' l! B2 _* P7 S, Q. ]MessageBox(“wglMakeCurrent failed."); / i; |% p v0 s
return FALSE; ' Y% x7 T5 X ~) S' y
}
* m6 B! x* S4 h0 E* U, ~ x6 k3 c' T, B+ @/ G% }) G
if(m_pDC) ReleaseDC(m_pDC);
4 T6 b$ S4 r3 v& p2 E" Dreturn TRUE; + ]2 e% r) [+ ?! `' R& o
} ( n4 L3 U5 d1 z
8 Y& Q! {* z* ~% T, I- u( }
----3.在OnCreate()函数中调用初始化背景函数 InitializeOpenGL()
) y) o* D6 P$ ^6 [5 G1 T" [7 o
: K* r" Y i3 lvoid CTextView::InitializeOpenGL() - `( m7 W$ ]# h8 x4 Z* D
{ + [( `3 l3 {% m
glClearColor(0.2f,0.2f,0.2f,0.0f);
6 `2 j" v# A4 p5 [7 ?) PglClearDepth(1.0); ; C! e$ q" B. Z% i2 J
glDepthFunc(GL_LESS); : |4 J: f7 R V+ i5 ]% ~
glEnable(GL_DEPTH_TEST);
2 X& y# }' f3 `2 g% a0 z# \+ D& ?. SglShadeModel(GL_SMOOTH);
8 J( _2 F( F& H}
: e" u) d, n P6 e" W/ M+ G. d, S) _3 P
----4.在OnCreate()中启动动画定时器 ' Z. f' C+ |; X8 a
2 N ?$ h6 ]: L1 }7 L& ]/ j! o+ XSetTimer(0,40,NULL);
) M, w) x- |+ [
3 F0 W+ t5 C- H" ?----5.在收到WM_SIZE消息时要重新计算场景尺寸,用OnSize 设置图形显示模式
1 x! h& d; p. C$ }4 u, c Y% h1 u9 v( x
----为了使物体能合适的显示,必须要经过投影和确定视口的工作。 * j4 }! N6 l3 y4 C+ \6 y9 v W
' b5 b/ I: I2 o0 @: {! a
void CTextView::OnSize(UINT nType, int cx, int cy) + {& l/ w- k) b$ Q/ S X* A
{ * a7 y" p; X9 q. V% ]- M" O! F
CView::OnSize(nType, cx, cy);
3 p+ p: C. z/ h- U/ w3 h9 x4 i) @
// TODO: Add your message handler code here
6 f" P0 t# ]7 U" o- K, [//Save the wide and height of the current window Client ; h5 L/ c6 U9 w8 t
GLsizei nWidth=(GLsizei)cx; - i" T. a: c7 L4 ?8 E. C& X
GLsizei nHeight=(GLsizei)cy; 3 n$ _/ R% ^' Y8 O3 y
ratio=(double)cx/(double)cy; 1 u) n7 Q' k7 S! x& _5 j
- r) Y8 _! r6 F/ @; Q/ d# [
//Coupute the aspect ratio 9 d9 H! X$ j5 @% {! b4 u. b
GLdouble dAspect=(GLdouble)nWidth/(GLdouble)nHeight;
; }) B4 w+ n ~6 ^( g" `! S. i' v3 _& l! {
glViewport(0,0,nWidth,nHeight);
6 t3 i% W" r, l1 MglMatrixMode(GL_PROJECTION);
: ?' a0 m( `& s8 E# B! CglLoadIdentity();
5 u5 A X8 \. J0 ~8 tgluPerspective
( v% e* x+ F+ ~8 C(FOV,dAspect,NEARPLANE,FARPLANE); 4 ?4 ~0 H% e" T% v0 {
3 l8 g8 N# o9 ~# A1 ]3 w+ @, j. ]
glMatrixMode(GL_MODELVIEW);
) F" A' S% C/ W& N4 k0 R" E+ z! AglLoadIdentity(); 2 C" S/ R: |* r' ~# | h1 u
}
0 K n' y- h! z3 F8 j* S+ R+ q4 b% _ V* J( x! s
----6.在OnDraw()中简单调用DrawScene()以执行OpenGL函数 $ P- O0 k( A' Z& _# z4 t
) e: T, U9 P- u+ j, J# j
void CTextView::OnDraw(CDC* pDC) * \8 o* ^9 Q5 H( i3 W
{ . m6 t, ^2 a+ G5 W
CTextDoc* pDoc = GetDocument();
/ M, b$ t% Y' e7 x/ w0 jASSERT_VALID(pDoc);
5 n4 c9 G8 f: \! |$ z5 |' _8 T% n' ~* d0 g G/ L
// TODO: add draw code for native data here + z' I% p/ @5 A" B9 b
DrawScene(); / W. ~( p, _! V# Y- p7 Q
//Invalidate();
}/ y. l" W. X- N" l}
) P7 }: X- ~( Z) R R/ N2 J
0 b* a& A4 {2 o" g* \----7.撤销视窗时删除上下文并撤销定时器 - v& d3 `/ }. R" e
0 D: n* T) U/ D
void CTextView::OnDestroy() 8 G) r, D B. w) }" ?
{
+ i, M6 m1 x3 [CView::OnDestroy(); % n7 c" Q! C) t8 K
: A3 ]( I, m: q6 c8 X, `8 F2 }
// TODO: Add your message handler code here
' h" [+ n; b2 o8 {0 `, n//This call makes the current RC not current 7 a2 Y5 i+ z, w( c2 T% H% S/ \+ ?
if(wglMakeCurrent(0,0)==FALSE)
8 |+ F# u$ q/ ?, o1 GMessageBox(“wglMakeCurrent failed."); , d: v/ g+ N F* V
D' V6 F! D: n0 C5 z8 I
//delete the RC
5 Y) `7 g: w) \# G( iif(m_hRC && (wglDeleteContext(m_hRC)==FALSE))
6 _4 t& y! G: I7 ?& UMessageBox(“wglDeleteContext fail.");
! q; x0 Y6 ?! P! L, ZKillTimer(1);
, d) o) W0 U! w; [& C} 1 j9 [8 m* l& y+ |7 u3 x/ l
3 u7 W5 ^6 R# ]----8.当40ms定时器时间到时,简单地将整个场景的客户区置无效,使之重画
1 l$ W* Z! ?" H) r% v* `: [
& a' c% W& N. L# J9 C! jvoid CTextView::OnTimer(UINT nIDEvent)
( U# j# w9 ]- H6 t1 S O8 K{ 4 s0 b$ k6 y+ ?1 |0 K+ W
// TODO: Add your message handler code here and/or call default
B& W$ }! u9 U+ ~- MInvalidate(); " ~9 `9 Y) m& A7 g, Q2 p! N% D
CView::OnTimer(nIDEvent);
1 D3 L) ?6 Z8 s* ]% m- K} $ {5 w. C( c5 `" G
3 r, d" J. e: ^$ n3 B; \$ l修改界面
! C: U2 A4 Y4 }9 t8 f5 E1 c& b---- 删除菜单IDR_MAINFRAME中File下除去Exit菜单项外的所有选项,添加“显示GDI文字” “列表制作文字”“列表三维文字”三个菜单项,并给他们分配适当的ID。使用ClassWizard为这三个菜单项在视类中添加命令处理函数和界面更新函数,其中显示GDI文字的对应的函数如下: 1 B* H8 O# ~+ l* V9 T
void CTextView::OnGdiText()
8 S7 ~& E$ y3 v, z{
6 B- T# u( L \# U3 p$ B// TODO: Add your command handler code here
9 w B6 v4 l6 Pm_iWhichText=0;
- F: h$ B0 D3 p$ [+ [ T0 jInvalidate(); - f& t% X& }+ h3 o
} 6 P4 Q0 E4 z/ Y
. h% b6 o! h0 A+ K7 hvoid CTextView::OnUpdateGdiText(CCmdUI* pCmdUI)
& U# \/ m- G+ s- ^{ * B( y9 G6 |9 I! i
// TODO: Add your command update UI handler code here ; E1 e* d P5 E+ t" n* c! O
if(m_iWhichText==0) pCmdUI->SetCheck(); " W2 [9 A! O+ E+ W# b) ~: E1 l2 o( E9 K3 e3 S
else pCmdUI->SetCheck(0);
- c' _5 _8 I. P' N+ a}
" D9 Y, Q6 g: s$ c1 P, ?) y: Z* O
----增加Draw3DText()、DrawListText()和DrawGdiText()三个函数用于三种不同的文本绘制方法。增加DrawScene()函数,它被OnDraw函数调用,用于绘制场景。在DrawScene()函数中,当m_iWhichText为0、1、2时,分别调用 DrawGdiText()、DrawListText()和Draw3DText()显示文本。具体函数实现如下: ( _6 s( T, b* ~3 r
) o5 c8 o, ^/ B
void CTextView::OnDraw(CDC* pDC) S% C' J1 h. u3 S. H! N- t
{
v/ \, f* F+ o8 ^; WCTextDoc* pDoc = GetDocument(); ' U2 t5 c6 z9 l) ~1 g
ASSERT_VALID(pDoc); 4 [# ~' [; ~( X$ ]
8 q' [# C1 t+ c" x/ u
// TODO: add draw code for native data here
) L! a( S* ^ ADrawScene();
: K: B. ~/ g: [: X5 D" P//Invalidate(); 4 S* D9 x! L# n7 `
}
. H+ X6 t% d6 s
5 w! E( e5 k1 F7 Nvoid CTextView::DrawScene() ) X9 v( T6 G3 y2 L2 L
{
/ j3 h1 @! [2 `1 b5 s. x% h+ ZglClear # E# e6 Y% m% K; R
(GL_COLOR_BUFFERBIT|GL_DEPTH_BUFFER_BIT); 9 _4 C) j; P) [' u
: r2 F5 i$ _6 ^# {6 U
glPushMatrix();
% E: z! g% U2 k0 |glTranslatef(0.0f,0.0f,-FARPLANE);
; m% K! e% Q( h/ b5 {. {' r$ z//TextureMap();
. e1 ^ K% {, K4 L% J. X8 H0 \# WglPopMatrix();
1 g% `* `( w& z9 |7 w! k3 O! FglPushMatrix(); 4 V/ x1 A: Q6 p' U% ?7 `" r6 Z
glTranslatef
9 y# Y5 r! c& n2 J- e(0.0f,0.0f,-(FARPLANE+NEARPLANE)/2); % A2 j7 W0 @3 d0 _
! ?# j& @* r+ E5 b
if(m_iWhichText==1) DrawListText();
! a" I8 f+ I" e7 B5 O; fif(m_iWhichText==2) Draw3DText(); $ R& G/ @ a# m8 R& A6 T
glPopMatrix();
) A; I) h# E/ u; oglFinish();
! Y1 d6 \- \3 \; @& U, J$ `SwapBuffers(wglGetCurrentDC()); 2 N: d0 M/ G* G& Y8 |4 }1 e4 d
6 M* g$ m& S3 b" t8 Y. A. \if(m_iWhichText==0) DrawGdiText();
) ^8 S6 ?' ]7 ?2 @0 Y$ G- X( u}
6 h& |8 ~' c( D& J8 W A: q
# i+ w7 `, u% u- J' d* Z3 YGDI 显示文本 8 E& H( v4 Z1 {% l$ q& @2 A
---- 调用wglGetCurrentDC()函数取得当前的设备上下文,使用TextOut函数显示文本,不过要注意在DoubleBuffer模式下,绘制函数要在glFinish()和 SwapBuffers(wglGetCurrentDC())函数之后调用,否则会产生闪烁,在绘制OpenGL结束之前使用GDI函数,要除去闪烁则只能使用SingleBuffer模式,具体函数如下:
+ G/ m% z: x' T6 e6 V9 g( lvoid CTextView::DrawGdiText()
. j ~) Y3 y$ e7 ]. ^{
1 K) h- }8 C$ nHDC hdc=wglGetCurrentDC();
) K+ a- }) ^/ l7 _8 X::SetBkMode( hdc, TRANSPARENT );
4 \' I+ N4 U" Q' K T% _::SetTextColor( hdc, RGB(250,0,0) ); 9 `' S3 D2 ~4 C/ P: y* g
L0 O5 I+ W y! ?
CString sState(“显示GDI文本。"); 1 D( O) a. }6 l" N1 Y. a# r
::TextOut(hdc,5,5,sState,sState.GetLength());
1 w- r, a( J. [9 @ u}
* R. v+ b Z! D' W5 q
1 Q& I2 o8 @. X+ Y3 t, s9 ^6 w4 cwglUseFontBitmaps
$ d! W1 T2 ?& p8 W H7 z7 @3 A: i函数显示文字 ; `7 k0 q0 p; y+ R, m! e
----使用wglUseFontBitmaps()将ASCII字符装入显示列表,然后使用glCallLists()函数利用显示列表序列显示文本。wglUseFontBitmaps有四个参数,分别是当前使用的DC、从第几个ASCII字符起始装入列表、装入列表的ASCII字符数和起始的列表序号。glListBase()指定glCallLists执行的起始列表序列号。glCallLists()含有三个参数:执行列表序列的个数、列表值的类型和所要显示的文本。注意如果所要显示的文本是字符串,它所提供的信息是相对于起始装入ASCII字符的偏移量,因此最终所显示的ASCII字符是从glListBase()所指定的列表起始号在经过glCallLists()中偏移后的列表,因此wglUseFontBitmaps的从第几个ASCII字符起始装入列表参数、glListBase()指定的 glCallLists执行的起始列表序列号和glCallLists()中的所要显示的文本参数都可以影响最终显示结果。由于显示的是ASCII 字符,因此不能显示汉字。glRasterPos3f函数决定在 OpenGL视景体坐标系下的偏移。具体函数实现如下:
4 F$ B7 d( G* b) h2 t2 _. A( _ qvoid CTextView::DrawListText() 6 p, s' ]) U' D( g( T) D( K3 @
{ / Y, _& Q+ L. x
wglUseFontBitmaps(wglGetCurrentDC(),0,256,1000);
+ j( U1 b0 z" y* v# n( A- w- E7 }glListBase(1000);
$ p3 w* w2 S* s1 wglRasterPos3f(-5.0f,0.0f,0.0f); % |! `2 J8 W2 c \' v' x
glCallLists(20,GL_UNSIGNED & J) u3 X+ e5 k7 |4 C5 F- y
_BYTE,“Draw with List Text."); : W- t; K# E2 W0 f& [+ g
}
! \8 N7 B: L( j4 v/ N# C# r2 x: `1 @% A9 K$ ]( j* d! h$ i& i
wglUseFontOutlines . i+ @: ]% Y7 R6 u$ z
函数显示三维文字
7 y+ U1 L, X" ~; q5 I. T! l8 h----wglUseFontOutlines使得OpenGL可以显示三维文字。它的用法与wglUseFontBitmaps函数大致相同,但是多了内插计算参数、字体深度、显示方式和装载字模的缓存四个参数,且只能显示TrueType字体,显示前应该先选择字体类型。具体函数实现如下: % Z" x1 v0 H) t
void CTextView::Draw3DText() & ~/ B$ Q9 p3 k* D/ ~4 k1 K
{
, I+ `. g$ R* L" p3 XGLYPHMETRICSFLOAT agmf[256]; ( ]* [. d3 [4 I; i4 {* t
// create display lists for glyphs 0 through 255 - z0 d5 p$ T: O) l1 a* Q4 V' W
// with 0.1 extrusion and default deviation. 8 z- R5 c. x# `) G/ i
//The display list numbering starts at 1000
8 P- i+ b3 v4 Z# B5 ?! f(it could be any number) " P4 h1 d! P/ N
wglUseFontOutlines(wglGetCurrentDC(), ) {4 X s: r. F6 Q
0,255,1000,0.3f,0.8f, WGL_FONT_LINES ,agmf); 8 q! t$ A* W- J9 R& f
( z$ W; q0 H+ v// Set up transformation to draw the string
% c0 [) c4 h5 O$ `glTranslatef(-15.0f,0.0f,0.0f); # R3 E& A. ]( A0 e j5 [7 H% d: r
glScalef(4.0f, 4.0f, 4.0f);
1 [* c% T- @! d& }3 F// Display a string 1 ? Q h( m' w* q0 ]2 F
glListBase(1000); + p8 v0 ]7 G/ |8 d" A$ T* c8 q6 T
// Indicates the start of display lists for the glyphs ( D' ]0 {6 d0 G+ R! l m
// Draw the characters in a string ! P9 m% w% |2 o' ^
0 e0 y. R ^& R0 i" n
glCallLists(26, GL_UNSIGNED_BYTE, ) `! Y( ]0 t. P) c1 f: _1 R. ~
“Draw outline list 3D text.");
. Z+ l! r8 J2 ?( H} |