スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

Direct3Dのテクスチャフィルタリング

大した記事ではないですが、Direct3Dでテクスチャマッピングされたポリゴンを描画するときに用いるフィルタ(補間手法)について忘備録を兼ねてちょっとだけ。

2Dのコンテンツを作るにしても3Dのコンテンツを作るにしても、画像をくるくる回したり、拡大縮小して表示する機会はきっとたくさんあるはず。ガチガチの3Dで描画するならまだしも、例えばノベルゲームのように絵の綺麗さや演出がウェイトを占めているようなコンテンツだと、綺麗に描画できないと死活問題になりかねないと思います。
なぜ今更こんな記事を書いたかというと、実は私自身がこれで随分悩んだからです。探したページが悪かったのか、キーワードが悪かったのか、はたまた参考書が悪かったのか運が悪かったのか……。

さて、本題。
フィルタリングといっても、ごく普通にDirect3Dのインタフェースとデバイスを初期化して描画に取り掛かるだけなのですが、デバイスにサンプリングステートをセットしてやるのを忘れるとラスタライズされた結果は以下の図のようになります。
いかにもニアレストネイバーっぽいジャギジャギ具合です。
等倍かつ回転無しの場合はPixeltoPixelできれいに表示されていますが、それ以外の場合は使えません。
これを直すためのコードはとても簡単で、SetSamplerStateというデバイスメソッドで下のように対応するステージとフィルタにフィルタリングモードを渡してやるだけ。列挙型によると、ちゃんと異方性フィルタリングなど色々な手法が準備されているようですが、完全な2Dなら平行投影なので拡大縮小ともにバイリニアで十分でしょう。もちろんシェーダでも記述できます。
// ホスト側でかくとこんなかんじ
pD3DDevice->SetSamplerState(0,D3DSAMP_MAGFILTER,D3DTEXF_LINEAR);
pD3DDevice->SetSamplerState(0,D3DSAMP_MINFILTER,D3DTEXF_LINEAR);

// シェーダ側でかくとこんなかんじ
sampler_state
{
MinFilter=LINEAR;
MagFilter=LINEAR;
};
というわけで、バイリニアで上の画像を描画したものが左図です。
ちゃんと拡大・回転したものはあらまほしい描画になっていることがわかります。ただ、当然ながら等倍の画像に対してもバイリニアでフィルタリングするので、微妙にボケが生じます。であるから、等倍表示の時はフィルタを切っておくのが無難だと思います。縮小したものについてはモアレが生じていますが、これはそうなってもしようがない元画像なので気にしません。ポリゴンにはアンチエイリアスがかからないので、袿の部分だけジャギーが出ているのも見て取れます。
結論。
2Dで何か作る&きれいに表示したいときは、動的に画像を弄るのではなくてできるだけ静的な画像を用意する。ヌルヌル動く回転などは……まあ、仕方がない。

以下、余談。
このメソッド、プログラマブルシェーダに手を付けるまで恥ずかしながら私は存在を知りませんでした。既に書きましたが、それのおかげでサンプリング手法をどのフラグで弄れるのかなど無駄に悩んだ感があります。2Dでゲームを作っている手前、ノベルゲームではないにしろきれいな補間はぜひ欲しいところです。
これにまつわるうれしいような悲しいようなニヤニヤしちゃうような四方山話が一つあって、ある市販のノベルゲームをやった時、キャラクタが主人公ににじり寄ってくる(立ち絵が拡大される)演出がありました。で、サンプラがちゃんと設定されていなかったらしく、キャラ絵が見事にドット絵と化したわけです。
今なら解決の手段を知っているし、これぐらいならテストプレイの時に潰せるでしょう。ですが、やっぱり開発者として知らないということは恐ろしいなと感じてゾゾーッとした一瞬でした。
スポンサーサイト

文字の縁取り&袋文字アルゴリズム

自作ゲームエンジンの機能の一部として、今回は文字の縁取り機能を実装しました。
文章表示が売り物のADVゲームを作っているわけではないので、最低限普通に文字描画できるだけで十分なのですが、一応汎用ゲームエンジンということでやってみました。あと、機能として実装されているなら絶対どこかで使えるはずだろうということで。
実は仮実装のほうでは上下左右+中央に5回描画するおバカなやり方をとっていましたが、さすがにスマートではなかったのでちゃんと画像処理らしく再実装したという次第です。

以下、本題。
文字の縁取りで当初考えたのは、せっかくDirect3Dを使っていることだし、プログラマブルシェーダでポストエフェクトチックに扱おうかというものでした。が、文字(あるいは文章)テクスチャ自体にエフェクトをかけたいとなると処理が面倒になるなぁということで断念。プログラムで画像膨張処理用のフィルタらしきものを書いて処理することにしました。
文字をいかにしてテクスチャ化するかということは他のサイトに詳しいので省略しますが、基本的にはGetGlyphOutline関数を使った完全なCPU処理です。ですから、今回はDirect3Dは関係ありません。
文字の色を白、縁取り色を青として、以下にポイントを列挙します。
  1. GetGlyphOutline関数を使って、指定したフォントのアルファデータを得る
  2. 取得したアルファデータについて、左上から順番に値を取得する(この時周辺8近傍の値もストアする)

  3. 以下、3・4・5は場合分け
  4. 注目する画素について、注目する画素のα値が0(完全に透明)かつ周辺8近傍のα値が0でないなら、注目画素を縁取り色で塗りつぶす
  5. 注目画素のα値が0<α<255のとき、文字色と縁取り色をブレンドする(このとき注目する画素のα値は255とする。つまり今回の場合だと完全に不透明な水色)
  6. 注目する画素について、α=255のとき文字色で完全に塗りつぶす
それでは順番に詳細を見ていきます。


[1] 省略する部分です。

[2] 今回は注目する画素を含めて近傍8ピクセルしか見ないので、左の図のようなイメージで配列にその都度対応するα値を格納します。図で分かるとおり、ここでいう注目画素のインデクスは4です。また、以下では0~8のα値から決定した最終的な注目画素のα値をAとします。
GetGlyphOutline関数で取得できるフォントのデータは文字を収めるぴったりの矩形であるため、例えば今回のように1ピクセルの縁取りを実装したいならば、上下左右に1ピクセル大きな枠でサンプリングしないと縁の部分が切れてしまうことがありますので、注意してください。

[3] ここはは一寸実装する人による自由度があるところで、画像を周辺に1ピクセル膨張させているようなイメージです。私の実装では微妙に重み付けしてα値を決定しましたが、面倒ならば周辺8ピクセルで最もα値が高いものをAにしても良いし、0でないα値の平均をとってAにしても良いと思います。

[4] 縁取りをしないならば、文字色で半透明になる部分です。縁取りをする時はA=255とし、RGBは文字色と縁取り色をブレンドします。この時の比率は、注目する画素のα値から以下のように求めます。Cが最終的な画素値、Oが縁取り色、Sが文字色です。ちなみに下の式ではαは正規化しています。
$C=O\cdot (1-\alpha )+S\cdot \alpha$
[5] この部分は縁取りに関係ない、いわば”海岸線のない内陸国”のような扱いなので完全に文字の色で塗りつぶします。

これで縁取り文字が実装できました。以下に上記の3・4・5を順番に実装していった画像を掲載します。
なお、一番右側の画像はオマケで袋文字を実装したものです。袋文字は5番のステップを完全にオミットし、4番の処理を次の式に置き換えるだけで出来上がり。
$C=O, A=(1-\alpha )$

sidetitlePROFILEsidetitle
AUTHOR: かいのしずく


詳しい自己紹介ページ
本棚(ブクログ)
平成20年以降にチェックしたのすべての本
本棚(読書メーター)
各月の読了ページ数管理用
チームカチューシャ
同人ゲームサークル MATRICES

sidetitleINFORMATIONsidetitle
VISITORS
CALENDAR
09 | 2017/10 | 11
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 31 - - - -
SEARCH
RECENT ENTRIES
CATEGORIES
ARCHIVES
sidetitleLINKsidetitle
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。