9
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

DxLibAdvent Calendar 2018

Day 23

画像前処理で描画速度とメンテナンス性を両立する

Last updated at Posted at 2018-12-22

はじめに

この記事はDxLib Advent Calendar 2018 23日目の記事です。

2018/12/23 : 分かりにくい部分の訂正
2020/わかりにくかった部分を修正

<< [19日目 | DXライブラリ+nuklearでGUIを実現する](https://qiita.com/ta_dragon/items/b30c190d2108fd915e92) || 24日目 | 五芒星つくった>>

DirectXとDerivationGraph()の仕様の復習

同人の弾幕STGは圧倒的にc/c++で作られたものが多いです。これは、DirectXでは、同じテクスチャにいろいろな弾幕の画像がまとめてあると、複数の弾の描画を一度のDrawPrimitiveUP()1でやってくれるため高速化するからです。DXライブラリでは、同じ画像(DerivationGraph()で同じ親ハンドルから得られるものも含む)を連続で描画すると、自動でこの最適化をやってくれるようになっています。(因みにDrawPrimitiveUP()を呼んだ回数はGetDrawCallCount()で取得可能)
ちなみに、5年くらい前にjavaで弾幕STGを作ろうとしたことがありますが、300発くらいで処理落ちを始めて断念しました。
一般に、弾幕STGを作るためには200~2000発の画像を60fpsで描画する必要があります。

今までは弾幕の話をしてましたが、RPGツクール/ウディタのようなRPG系の2Dゲームでも同じ問題が起こり得ます。
典型的な画面サイズで640x480、マップチップ32x32ピクセルが3レイヤ、とすると一度に表示するマップチップの最大数は
(640/32)×(480/32)×3=20×15×3でなんと900枚です。大往生の二周目(210発)より多いね!

要するに:一枚の画像ファイルにまとまっていると速く描画できる

高速化案:画像を一つにまとめる

作ってるゲームが1000発くらい弾が飛んでくるゲームなのですが、いくらc++&DXライブラリであっても適当に書くと上記の原因で処理落ちしてゲームになりません。そこで、使う全部の画像を2048x2048等の大きな.pngファイル2にまとめてしまえばいいのではないか?との結論にたどり着きます

画像を一つにまとめた場合の弊害

理論上は最速で描画できます。機械には優しいですが、メンテナンス性が極端に悪いので毛髪が瞬く間に禿げ上がります。
・ship.pngという画像が巨大画像のどの位置に格納されているかを手打ちでどこかに持っておく必要がある
・この画像ちょっと暗いな、色味補正しよう -> 色味補正が全体に適用されて死んだ!
・大き目の画像で背景が透明の部分に、未使用領域と勘違いして別の画像を配置してしまう
・敵の画像の種類を増やしたい -> 画像の隣接した領域が空いているとは限らない -> 敵画像の位置が散り散りになってしまう

前処理で解決

もう何がしたいか分かった聡明な方もいると思います。全画像を例えばdata/image直下等に置いて、**ゲームを起動するたびにプログラムから画像を一つにまとめるような仕組みを作ります3。**こうすれば、敵画像はdata/image/enemyにまとめるだとか、管理はすごく楽です。DXライブラリ的には、まずc++標準関数でdata/image直下の.pngファイルを全部列挙し、ソフトイメージ系の関数で画像を合成して書き出します4。個々の画像の埋め込み位置と元.pngの最終更新日時(FileRead_getInfo()や標準関数で取得する)をまとめて.jsonにでも書き出しておいておけば、ゲームコードからは

game.cpp
const auto healItem = graph_maker::getHandle("システム/回復アイテム");

みたいに簡潔な形に呼び出せます。グラフィックハンドルの分割とか破棄はゲームエンジンが開始/終了時にまとめてやるので、ハンドルの所有権の管理に手を煩わせることもありません。自分の環境で、この合成には5秒くらいかかるので、全.pngの最終更新日時を取ってきて更新が不要ならサボる(cmakeみたいに)機能をつけるとベターです。ちなみに、DirectXの仕様で、画像描画時に周囲の画像が1px未満だけにじみ出ることがあるので(DXライブラリ側ではどうにもできない)画像合成のときには1ピクセルずつ透明色のパディングを挟んでおくと安心です。

余談:この議論の例外

DXライブラリで(大規模な)ノベルゲームを作る人はどれだけいるか知りませんが、(ノベルゲームエンジンを作ってた方はいましたね)
例えばノベルゲームのイベントCGが200枚(800x600サイズ)とかだと**一度しか使わないイベントのCGが起動時から終了までメモリを使用し続けます。**こういう用途では、むしろ描画速度は一切必要ないので、合成せずに普通に持っておいた方がいいです。でもゲームコードではさっきの使用例と統一的なインターフェースになるようにしたいですね!

  1. DXライブラリの関数ではなくDirectXの関数

  2. だからといって16384x16384とかの巨大サイズだとグラボが対応してない可能性があるので、2048くらいで数枚の画像にわけるのが無難ではないでしょうか

  3. DXアーカイブとの兼ね合いがあるので、開発中のみ画像合成し、リリース前にアーカイブにしてから、完成品では画像合成によってできた大きい画像のみ同梱するようにする

  4. Dxlib 3.19f現在、ソフトイメージ系の関数で画像をただ読み込んで書き出すだけでも乗算済みαの影響を受けてしまう(読み込みと書き出しの画像が変化してしまう)罠があるので、ソフトイメージ使用前には必ず乗算済みαをオフにしておくこと

9
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?