FC2ブログ

Alouette-Cielのメンバーブログ

 
--
 
上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。
    --:-- | Top
 
22
 
じゃ、ジャッジメントですの!

……かくたす です。
きゆね先生がやってくれるまで続けるそうです。
でも、アニメ放送終わっちゃったよっ!


とりあえず、近況報告をば。
昨日から開催されました「コみケッとスペシャル5 in 水戸」との連動企画である同人ゲームクリエーターを応援する部屋に「ARCANUM ECHO」も参加させて頂いております。
※「同人ゲームクリエーターを応援する部屋」は、プレビさんが運営する水戸のアミューズメント施設の一角に設置された、同人ゲームをプレーできるスペースです
当サークルだけではなく、その他のサークルさんの様々なハイクオリティ作品も設置されているようなので、足を運ぶ予定のある方は、ARCANUM ECHO もついでにプレイして頂ければ幸いです。


これだけだと短いので、たまにはプログラムについて書いてみます。
えせプログラマ故浅い内容(&長文)ですが、そこはご容赦を。
と言う訳で、本日のお題は、DirectX を用いる際の文字列の描画についてです。

よく知られている事ですが、ID3DXFont インターフェイスを用いた文字列の描画は、内部で GDI 呼び出しが行われている為動作速度が遅く、あまりゲーム向きではありません。
表示内容が固定されている STG の得点表示用文字などは、グラフィッカーさんに頼んで予めテクスチャを作成しておけば、ID3DXFont インターフェイスを用いずに済みますが、会話シーンのような文字数が膨大なシーンが組み込まれているゲームの場合はそうもいきません。
しかし、「アクション等の FPS 維持が重要なゲーム」や「RPG のような画面内にデータを多く表示するようなゲーム」では少しでも文字表示を軽くしたいという要求があります。
※FPS の維持があまり必要ないノベルゲームでは問題ありませんが、解像度の高い立ち絵で60FPS 演出をしたい!という要求はある場合は微妙かもしれません

上記の問題への対応策として、よく用いられる手法が「文字をテクスチャ化して描画してしまおう」というものです。
これについては web に具体例が色々落ちているので、あえてここで書く必要は無いかと思います。
マルペケつくろーどっとコムさんの解説が有名&分かり易いと思います

しかし、もう一歩踏み込んだ、実際にゲーム中でテクスチャ化文字をどう管理し、表示しているのか、という話は少ない気がします。
そこで今回は、テクスチャ化文字に関するアプローチ例をいくつか挙げてみる事にしました。

【1. 静的生成】
厳密には静的生成では無い気がしますが、便宜上という事で。
ロード画面時等で予め使用したい文字のテクスチャを作成しておき、ゲーム中にはそのテクスチャを用いて通常のテクスチャと同様の描画を行うパターンです。
テクスチャ化した文字をそのまんま保持し、描画時にもそれを IDirect3DDevice9::SetTexture() すれば良いだけなので実装は簡単です。
しかし、表示したい文字数分テクスチャを保持しなくてはいけないので、文字数が増えれば増える程その管理はめんどくさくなり、メモリも圧迫されます。
※この実装でノベルパート用の文字テクスチャを作成すると軽く 10M を超えるのであまりお勧めできません


【2. 静的生成その2】
1 の静的生成を実装した場合、文字毎にテクスチャが分割されてるので 1 文字描画する毎に IDirect3DDevice9::SetTexture() を行わなければなりません。
しかし、IDirect3DDevice9::SetTexture() はとてもとても重い関数なので、文字毎に呼び出していてはえらい事になってしまいます。
更に、文字毎に IDirect3DTexture9 インターフェースを保有する必要があるのでその数も膨大なものになり、実装としてはあまり美しくありません。
そこで、じゃあ使用する文字テクスチャを文字「列」テクスチャとして保持しちゃえば良いじゃん、というのがこのパターンです。

「文字テクスチャ作成時に該当文字テクスチャを繋ぎ合わせて文字列テクスチャを作成する」という至って簡単な実装で実現可能な処理なので、コンフィグのメニュー等、使用する文字が固定されている場合はこのパターンを用いるのが最も単純でお手軽です。
しかし、この実装を用いた場合、D3DUSAGE_RENDERTARGET を指定してテクスチャを作成すると 強制的に 2 の累乗テクスチャが作成されてしまうグラフィックボード(少し前のグラボでも結構あります)への対応が異常にめんどくさいという欠点があります。

↓2 の累乗テクスチャ固定のグラフィックボードへの対応策
・作成したい文字列テクスチャの縦横長い方に合わせた 2 の累乗テクスチャを作成する
「アルエットシエル」

「しっぽ
 XXX
 XXX」
※X は空白
として作成する。
テクスチャサイズが無駄に増加し、メモリを圧迫します。
あと、長文になればなるほど無駄な領域が発生するのであまりお勧めできません。

・無理矢理 2 の累乗テクスチャにする
「アルエットシエル」

「アルエ
 ットシ
 エル 」
としてテクスチャを作成します。
描画の際の座標指定やら実装が酷くめんどくさいです。

・使用する文字列を 1 枚のでかいテクスチャにまとめる
1024 × 1024 のようなでかい空のテクスチャを作成し、そこに使用する文字列を書き込んでいく方式です。
書き込みの際に UV 座標を保存し、描画の際は座標指定をします。
※これならパターン 3 を使用する方が賢いです


【3. 動的生成】
1,2 の静的生成を用いると、ノベルパートで文字描画を行う際に数 M 単位のテクスチャを用意する必要があります。
文字描画だけにこれだけのメモリを食われてしまうのはあまり嬉しくありません。
そこで、シナリオを読み進める(ページを進める)度にそのページで使用する文字テクスチャを作成する、という手段を採る事により無駄なテクスチャを保持しないようにします。
※実際には実装にもっと工夫が必要です
ただし、テクスチャ作成時には処理負荷が上がる為、FPS 維持が必要なゲームには向いていません。
ゲーム中の文字描写は 1 or 2 で行い、ノベルパートのみ当実装を用いる、というのが現実的です。

とにかくメモリ使用量が小さいのがメリットですが、ゲームへの組み込みとメモリの管理はその分煩雑化します(といっても大した手間ではないですが……)。


【4. 文字テクスチャテーブルを作成する】
1024 × 1024 のようなでかい空のテクスチャ(文字一覧テクスチャ)を作成し、そこに文字テクスチャを書き込んでいくパターンです。
std::map などを用いて書き込んだ「文字」と「文字テクスチャを書き込んだ UV 座標」を関連付けたリストを作成しておきましょう。
文字一覧テクスチャが1枚に収まりきらない場合、テクスチャナンバーも同時に関連付けておく必要があります。
そして、描画時には「文字」を指定するだけで管理クラス等が「UV 座標」、「テクスチャナンバー」を検索し、自動的に書き込みを行ってくれるように実装します。

UNICODE(UTF-16) で実装しても SJIS で実装してもどちらでも良いと思いますが、個人的には SJIS の方が楽だと思います。
というか、使用するフォントとかツールに合わせるのが吉です。
両対応実装でも良いとは思いますが、個人で使用する分にはそこまでやる必要も無い気はします。

と、ここまで書くと当然
「JIS 対応の文字は 1 万以上もあるのに、それを全部テクスチャ化するとか正気か?」
という疑問が浮かぶと思います。
これについては以下のような実装で回避可能です。

まず、「シナリオファイル」や「設定ファイル」など、ゲーム中に使用する文字列がまとめてあるファイルについて、使用されている文字を検出します。
検出した文字は、
std::map(使用されている文字, 使用頻度を示す変数)
のような感じでプログラム内で保持します。
画面表示する文字をハードコーディングしている場合は、ついでにそのファイルも読み込ませてしまいましょう(使用頻度検出機能を付ける場合はこの処理は避けた方が良いです)。
そして、上記のリストを基にこの項の初めに述べた「リスト」と「文字一覧テクスチャ」を作成します。
「文字一覧テクスチャ」生成時は使用頻度順で文字を書き込んでいくとベターです。
こうすると、ゲーム中で必要とされる文字のみをテクスチャ化する事ができ、使用するメモリ量を格段に抑える事が可能です。
また、一枚目の文字一覧テクスチャに頻度が高い文字を集中的に書き込む事で IDirect3DDevice9::SetTexture() を呼び出す回数を抑える事ができます。

更に、描画の際に毎回マップをサーチするのは処理的に無駄が多いので、呼び出し側で「UV 座標 + テクスチャナンバー」をキャッシュ化する機構を組み込んでおくのが良いでしょう。

このパターンは、
・描画処理が高速
・管理が楽ちん(文字表示クラスで保持するのは「文字列」、「UV 座標(キャッシュ)」、「テクスチャナンバー(キャッシュ用)」だけで良い)
・アプリケーション起動時に一度作成してしまえば後はずっとこのテクスチャを利用可能
・ゲームへの組み込みが簡単なので仕様変更に柔軟に対応可能
等のメリットがありますが、以下のようなデメリットも存在します。

・動的生成に弱い
プレイヤーに文字を入力させるタイプのゲームや、フォントタイプが可変なゲームにはあまり向いていません。
※この例ですと、どちらも実装により簡単に回避は可能ですが……

・デバッグ時に微妙
デバッグ時のような頻繁にアプリケーションを ON/OFF する場合や、ノベルパート作成時のようなに使用する文字が決まっていない場合にもあまり向いていません。
この場合、第二水準漢字まで全て用いるモードを用意しておく(#ifdef 等できるのが楽ですねー)、というような実装で逃げるのが良いと思います。
更に、起動時に毎回リスト化処理が走るのもだるいので、デバッグ用の文字一覧テクスチャはファイル化しておき、起動時に読み込む実装にしておいても良いかもしれません。
ただしこの場合、そのままのこのテクスチャを頒布しないように注意が必要です。
※フォントの規約をよく把握しておかないと訴訟沙汰にもなってしまいます

・テクスチャに割くメモリ消費量が大きくなる
例えば、「32bit カラー」、「32 × 32」サイズの文字をテクスチャ化する場合、
1024 × 1024 のテクスチャには 1024 文字格納する事が可能で、この時のファイルサイズは 4M です。
ノベルゲームで使用される文字の種類は大体 2000 字程度と聞いた事がありますので、
その場合、文字テクスチャに使用するメモリ量は 8M と結構なサイズとなってしまいます。
※それでも、1,2 の静的生成でノベルパートを実装するよりはサイズが小さくなる場合もあります(会話シーンの長さ次第です)


【まとめ】
実装は多少手間ですが、描画速度と管理の楽さから個人的には 4 をお勧めします。
※実は ARCANUM ECHO では 1 の手法を採っていました。メモリ食いまくりの駄目実装の手本ですね!
スポンサーサイト
    01:09 | Trackback : 0 | Comment : 0 | Top
Comment







(編集・削除用)


管理者にだけ表示を許可
Trackback
http://alouetteciel.blog105.fc2.com/tb.php/95-4f7d3372
プロフィール

alouetteciel

Author:alouetteciel
HP:http://alouetteciel.web.fc2.com/

最新記事
最新コメント
最新トラックバック
月別アーカイブ
カテゴリ
FC2カウンター
検索フォーム
RSSリンクの表示
リンク
ブロとも申請フォーム
QRコード
QRコード
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。