忍者ブログ
場所取り そのうち引っ越すかも http://maglog.jp/gltest/
[15] [14] [13] [12] [11] [10] [9] [8] [7] [6]
×

[PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。

billiardss04.jpg

 
今まで特に不便を感じていなかったのでトライしていなかったが、 OpenGL を扱う上で大きなハードルになっている多バイト文字の描画について、今回は実験の結果をまとめる。 (尚、 DirectX の言語対応についてはよく知らないが、世の中には右手系の座標空間で日本語を使いたい人もいるはずだ!)

長いので全文は「続きを読む」で。


そもそも、 OpenGL はフォントの描画に関しては何の仕組みも持っていない。フォントは線分やポリゴンと同じようなプリミティブとして描画する必要がある。
ここで最初の選択肢となるのが、ビットマップやテクスチャといった、ラスタ画像で文字を描画するのか、それとも輪郭をなぞるようにプリミティブを描画する のか、ということである。前者は既存のフォントを使用する上では楽だが、後者は拡大縮小してもピクセルの粗が見えないという利点を持つ。

実はこのような文字描画を行う関数は Windows 環境では用意されていて、それぞれ wglUseFontBitmaps 、 wglUseFontOutlines という。だがこれらの関数は決定的な欠点を持っている。多バイト文字には対応していないのだ。よしんば、対応していたところで、数千と存在する漢字のグリ フを一つ一つディスプレイ・リストにコンパイルするのは、効率面で問題がある。

もう一つの安直な解決法は、通常の GDI 関数を使って OpenGL が描画した領域の上から文字を描画するという方法だ。 (Windows における) ウィンドウ座標と (OpenGL における) 正規化座標の間で適切な変換を施すという若干の手間はあるが、 Win32 API に慣れていれば実に簡単に日本語が表示できる。例えば:

WM_PAINTなどのメッセージへの応答:

/* ... OpenGL drawing ... */
wglSwapLayerBuffers(hdc, WGL_SWAP_MAIN_PLANE);
SetBkMode(hdc, TRANSPARENT);
SetTextColor(hdc, RGB(255,255,255));
TextOut(hdc, 0, 0, "あいうえおあお", 7*2);

尚、上記コード断片は wgl* を使ってウィンドウの生成・管理を行う場合を想定している。 GLUT を使っている場合は、ウィンドウのメッセージプロシージャを記述することはできないため、デバイスコンテキストの取得方法が少々異なる。 wglGetCurrentDC という関数でたぶんいける(テストはしていないが)。

この方法の欠点は、バックバッファリングが行われないため、アニメーションにすると文字がちらついて見えるということである。これが気にならないのであれば十分実用には足りると思われるが、やはり完全なバックバッファリングを行いたいという欲求が出てくるのは当然だといえる。

そこで、今回はビットマップに文字を描画して、それを glBitmap でバックバッファに描画する方法を試してみた。

まず、実行の始めに、文字を描画する先のビットマップ(DIB)を作っておく。

/* hbm, hcdcはグローバル変数 */
HDC hwdc = GetDC(GetDesktopWindow());
hcdc = CreateCompatibleDC(hwdc);
struct BITMAPINFO2{
BITMAPINFOHEADER biHeader;
RGBQUAD biColors[2];
} bi = {{
sizeof (BITMAPINFO2), //DWORD biSize;
32, //LONG biWidth; この値は32の倍数(4バイト境界)にしておいたほうが無難
32, //LONG biHeight;
1, //WORD biPlanes;
1, //WORD biBitCount;
BI_RGB, //DWORD biCompression;
0, //DWORD biSizeImage;
1000, //LONG biXPelsPerMeter;
1000, //LONG biYPelsPerMeter;
2, //DWORD biClrUsed;
2, //DWORD biClrImportant;
}, {0,0,0,0,255,255,255,0}};
hbm = CreateDIBSection(hcdc, (BITMAPINFO*)&bi, DIB_RGB_COLORS, (void**)&chbuf, NULL, 0);
ReleaseDC(GetDesktopWindow(), hwdc);


そして、描画するタイミングになったら DIB に書き込み、 glBitmap を呼び出す。次のような関数を定義しておくと便利だ。


void draw_text(const wchar_t *s){
SelectObject(hcdc, hbm);
SelectObject(hcdc, GetStockObject(DEFAULT_GUI_FONT));
SetBkColor(hcdc, RGB(255,255,255));
SetTextColor(hcdc, RGB(0,0,0));

while(*s != L'\0'){
int w;
GCP_RESULTSW gr = {sizeof(GCP_RESULTSW)};
gr.nGlyphs = 1;
gr.nMaxFit = 1;
w = GetCharacterPlacementW(hcdc, s, 1, 1000, &gr, 0);
w &= 0xffff;
{
RECT r = {0, 0, 32, 32};
ExtTextOutW(hcdc, 0, 0, ETO_OPAQUE, &r, s, 1, NULL);
glBitmap(32, 32, 0, 0, w, 0, chbuf);
}
s++;
}
}


この関数はwchar_t型の配列を引数に取るので、呼び出し時には文字列定数に L を付加してやるか、 mbtowc などの関数で変換してやる必要がある(テストはしていない)。

draw_text(L"ようやく日本語を表示することができました");


ポイントは GetCharacterPlacementW によって文字幅を取得しているところ。文字幅が可変なフォントは多いが、これまた多バイト文字への対応が困ったもので、 GetCharacterWidth32 という、より尤もらしい関数は Windows 98 以前では使えないし、日本語は使えない。

1文字ずつ文字幅を取得しているのでカーニングは考慮されていないが、まあこの程度の用途なら問題ないだろう。

最後の注意になるが、この方法による描画は毎回 glBitmap を使っているため、非常に遅い。特に高速なグラフィック・ハードウェアを装備している環境では遅さが目立つ。ボトルネックは、メインメモリからビデオメモリへのビットマップの転送にある。ただ、膨大な数の漢字を全てビデオメモリにキャッシュするというのも美しくない。使用されているグリフのみをキャッシュするような仕組みを作れば高速化が期待できる。


[追記]
フレームごとに上記の draw_text の最初の出力に ASCII 文字を指定しないと、文字列を描画する領域全体が塗りつぶされてしまうことがあるようだ。なぜだろう??
PR

コメント


コメントフォーム
お名前
タイトル
文字色
メールアドレス
URL
コメント
パスワード
  Vodafone絵文字 i-mode絵文字 Ezweb絵文字


トラックバック
この記事にトラックバックする:


忍者ブログ [PR]
カレンダー
03 2024/04 05
S M T W T F S
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
フリーエリア
最新コメント
最新トラックバック
プロフィール
HN:
gltest
性別:
非公開
自己紹介:
バーコード
ブログ内検索
アクセス解析