2008/09/27

とりあえずできたが、

とりあえず、GetGlyphOutlineでグリフイメージを取得してテクスチャにして、四角いポリゴンに貼るところまではできた。

で、右の画像。
左側が、文字テクスチャで、
フォントサイズが100Point、
5階調のアンチエイリアス、
サイズは128*128pixel、
α値のみのテクスチャ。

比較として、右側は、
同じくGetGlyphOutlineで
アウトラインフォントをポリゴン化したもの。
曲線部分は3つの直線に分割して近似、
ポリゴン数106、頂点数104。
右下の小さいのは、これのワイヤーフレーム表示。

ここまで大きく表示すると、
さすがにフォントサイズ100でも、荒さが目立つ。
それに対して、ポリゴン化したやつは、近似が荒いわりには意外ときれいに見える。
メモリのサイズとしてはポリゴン化のほうが省メモリっぽいが、
速度的には、やっぱりテクスチャのほうが速いのだろうか。

んー、
128*128pixのαチャンネルのみだから、メモリサイズは約16.4kB、
半角文字ならサイズ半分なんだろうけど、そこはひとまずおいといて、
256文字だと合計で約4.2MB・・・。
まぁこのくらいなら、今時ならそれほど問題にならないサイズだろうか。

2008/09/21

GL_BITMAPなテクスチャは不可

先日の件の
GetGlyphOutlineで取得したグリフのビットマップ(アンチエイリアス無しのやつ)を
そのまま使ってテクスチャにしてみようと思って
いろいろいじってみていたのだが、

glTexImage2D( GL_TEXTURE2D, 0, GL_ALPHA, 0, width, height, GL_ALPHA, GL_BITMAP, texels );

ってかんじで、ビットマップなテクスチャを作成しようとしたら、
エラー(GL_INVALID_ENUM)になった。
どうやら、最後から2番目の引数"type"にGL_BITMAPを指定できないらしい。

公式ホームページの関数リファレンスのページをみると、
引数"type"に指定可能な定数のリストに、GL_BITMAPが含まれているのだが、
関数リファレンスのページの最後のほうの「ERROR」項目に、
"GL_INVALID_ENUM is generated if type is GL_BITMAP and format is not GL_COLOR_INDEX."
と書いてある。

・・・つまり、
引数"type"にGL_BITMAPとGL_COLOR_INDEXは渡せるけど、
"format"がGL_COLOR_INDEXでないとエラーになるぞ、と。

ビットマップなテクスチャならメモリサイズも小さくて済むかなー、
とか目論んでいたのだが、無理なようだ。

2008/09/12

OpenGLでアンチエイリアスが掛かった文字を描く

Windows環境において
OpenGLでテキスト描画をしようと思うなら
"wglUseFontBitmaps" でビットマップの文字を描くという方法があるわけだが、
この方法だと、描画される文字はビットマップ(文字通りの0か1のみのbitのマップ)ゆえに、
アンチエイリアスが掛からない。

そこで、
アンチエイリアスが掛かった文字を描くにはどうしたらいいのか? 
と。

以前、文字をポリゴン化したメッシュを作成しようとしたときに、
アウトラインフォントの輪郭線を取得するのに
"GetGlyphOutline"
というwin32 API を使ったのだが、
たしか、この関数って、アンチエイリアスの掛かったグリフのビットマップなんかも
これで取得できるんだったよなー、
というのを思い出したので、
その辺りをググって調べつつ、やってみた。

おおざっぱな方針としては、
まず、アンチエイリアスの掛かったグリフのイメージを取得して、
それをDrawPixelsで描画、をディスプレイリスト化。
という感じ。

・・・これだと、
glBitmapみたいにglColor*で
テキストの色を変えることができないけど、
まぁ、しかたがないか。


<手順その1>
まずは、バッファを渡すところにnullを渡してやると、
必要なバッファのサイズを返してくれるので、
そうして取得したサイズぶんだけbyte配列(バッファ)を作成。

IntPtr prevObj = win32.SelectObject( hDC, font.ToHfont() );

uint bufferSize = win32.GetGlyphOutline( hDC, (uint)character, format, out metrics, 0, IntPtr.Zero, ref matrix );

byte[] buffer = new byte[bufferSize];


<その2>
さっきのバッファを渡してもう一度GetGlyphOutlineを呼び出せば、
バッファにアンチエイリアスの掛かったグリフのイメージが書き込まれる。


GCHandle gchBuffer = GCHandle.Alloc( buffer, System.Runtime.InteropServices.GCHandleType.Pinned );

uint ret = win32.GetGlyphOutline( hDC, (uint)character, format, out metrics, bufferSize, gchBuffer.AddrOfPinnedObject(), ref matrix );

gchBuffer.Free();


<その3>
バッファに書き込まれた値は0~255にはなっていなくて、
formatフラグでい指定した階調の値そのままが入ってる。
つまり、17階調なら0~16の値が入ってるので、
255/16とかでスケーリングする必要がある。
(注:MSDNには、GGO_GRAY8_BITMAP(65階調)を指定すると
0~255の値が入るって書いてあるけど、それは嘘。
実際にやってみると、0~64の値が入ってた。)

さらに、グリフイメージの左上から、という並びで書き込まれているので、
このままでは上下反転した文字になってしまうから、
これもまた、ひっくり返しておかないといけない。

あと、グリフイメージの幅は「4バイト境界」になってて、
metrics.blackBoxXと一致してないので、そこも要注意。
(これ、MSDNのリファレンスに書いてなくて、結構悩まされた・・・。)
つまり、バッファに入ってるイメージの幅は、
metrics.blackBoxXが収まるのに必要十分な4の整数倍、ということ。

<その4>
さて、グリフイメージが取得できたので、
ここからはOpenGLの部分。

glDrawPixelを使うわけだが、
文字が描かれていない部分はα値を0にして透明にするのが賢明であろうから、
フォーマットとして使用できそうなのは、
GL_RGBA、GL_LUMINANCE_ALPHA、GL_ALPA
の3つだろう。
GL_RGBAなら色を指定できるが1pixel辺り32bitのメモリが必要、
GL_LUMINANCE_ALPHAだと、1pixelあたり16bitだが色が白のみになる。
GL_ALPHAだと1pixelあたり8bitだけど色は黒になる。
どれにするかは、用途によりけり、といったところだろうか。
普通はRGBAだけど、白か黒の時はLUMINANCE_ALPHAやALPHAにする、
とかが、いいのだろうか。

後は、glDrawPixelをディスプレイリストにしてしまえばできあがり。

くれぐれも、毎フレームごとにglDrawPixelを呼び出したりしないこと。
これをやってしまうと、
いちいちプロセッサメモリからビデオメモリにピクセルデータを転送することになり、
非常に遅くなる。
それに対して、ディスプレイリスト化すると、
イメージデータはビデオメモリにコピーされるので、転送する手間が無くなり、
割と高速に描画されるようになる。

ちなみに、
"glPixelTransfer"関数と"glPixelZoom"関数を
glDrawPixelの手前で使えば、
<その3>でやる値のスケーリングや上下反転の操作を
ピクセル転送処理として行うことができる。
が、そのぶん、パフォーマンスが悪くなる。

2008/09/04

ほぼ日手帳

ここ3~4年、毎年買ってるのだが、
今年もいよいよ販売が開始されたので、
早速注文してきた。
今年は、ファブリックカバーの
マキノ・ヘリンボーン。

しかしまぁ、すごいね。ほぼ日手帳。
毎年、ユーザーの意見を取り入れて着実に改良を加えて、
やたら細かい部分までこだわって作ってるのがいいね。
あと、なぜか「マンガ」とか
「この手帳についていないもの」のページがあるところが、
いかにも、ほぼ日らしい。