DirectX12が発表されてはや10年のこの時代に、
ようやくDirectX7で作っていたゲームの一つのグラフィック部分を、DirectX9へ移行してみた。
ここではその際に躓いた点をまとめる。
あらまし
テクスチャ画像のピクセルサイズの制約
スプライトと板ポリゴン
テクスチャのズレ
DrawPrimitive呼出回数
DrawPrimitiveUp
「d3dx9d_43.dllが見つかりません」
自己転送時の異常
GDIが使えない(未解決)
文字のちらつき
DirectX7とDirectX9の大きな違いは、2Dを描画する方法にある。
DirectX7では、2DはDirectDraw、3DはDirect3Dという機能を使うのだが、
DirectX8でDirectDrawが廃止、Direct3Dに統合され、大きく勝手が変わった。
それゆえ長らく移行をサボっていたのだが、DirectDrawには大きな制約がある。
それは、回転も半透明も反転もサポートしていないということだ!
スーパーファミコンにも追いついていないのである。
GDIという、ゲーム以外の一般アプリ向けのグラフィック機能を使えば実現はできるのだが
(DirectXを使っていないゲームはこれで作ってます)、これは遅い。
昨今のPCのパワーなら、余程のことをしない限りはそれでも目立った遅さは見られないかもしれないが、
過去には画面のフェードインフェードアウトをしようとしただけでスローモーションになってしまった環境もあり、
あまり気分的に使いたくない所である。
DirectX8の場合、pngを簡単に読み込む機能もある(GDIでも可能であるが)。
そこで、いつかはDirectX8に乗り換えようと思いつつ、その勝手の違いゆえに何年もサボっていたのだが、
ようやく重い腰をあげ、結局は触ったことのあるDirectX9への移行となった。
板ポリゴンにテクスチャを張り付けて表示してみたのだが、
なんか画像の上と下がループしていて、下端の色が上にはみ出る現象が発生……。
どうやら、テクスチャ画像のサイズは2の累乗でないと、このような異常が起こるらしい。
他にも、画像がぼやけるなどの現象が起こる。
Direct3Dで二次元描画と言えば、大抵はスプライトが真っ先に挙げられるが、
三角形2枚から成るポリゴンを用いる方法もある。
以前、スプライトは描画速度が遅い的な話を聞いた事があり、応用の観点からも、
今回は板ポリゴンを用いてみた。
ただ、後で改めて試してみた所、どうも自分の環境ではスプライトの方が速いようだ。
また、板ポリゴンの場合、後述の「テクスチャのズレ」が起こるので注意が要る。
あくまで2Dゲームを作るなら、素直にスプライトを使った方が良いかもしれない…。
板ポリゴンにテクスチャを張り付ける場合、
テクスチャと板ポリゴンのサイズが微妙にズレることがある。
例えば、512×512のサイズの画像をテクスチャとして読み込み、
その画像の全体を張り付けたい場合には、512×512のサイズの板ポリゴンに、
テクスチャ座標uとvの値を0〜1の範囲で指定してやれば良いのだが、
ただ、その中の(0,0)〜(300,300)の範囲を張り付けたいとする場合に問題が起こる。
この場合、uとvの値を0〜300/512と指定すると張り付けられそうであるが、
これでは画像に歪みが生じてしまう。
これを解決するためには、uとvの値を0.5/512〜300.5/512とする。
どうやら、float型の精度の問題から来るものらしい。
ただ、この方法でも未だ問題がある。板ポリゴンを回転させたい場合である。
この場合、テクスチャ画像の0〜32の範囲を切り出そうとした場合にすら問題が起こる。
回転角が0°の場合なら正常に表示されても、180°回転させると、
見えるべき0ピクセル目が見えず、見えてはいけない32ピクセル目が見えてしまう。
先の例の場合、180°回転した場合は、uとvの値を-0.5/512〜299.5/512とする必要がある。
結局、試した限りでは、以下のような形が精度が高めであったが、これも完璧というわけではない。
float tr = 1.0f / 0x200000;
float us = sxs / Width*(1.0f + tr * ((icos + isin) < 0 ? -1 : 1));
float ue = sxe / Width*(1.0f + tr * ((icos + isin) <= 0 ? -1 : 1));
float vs = sys / Height*(1.0f + tr * ((icos - isin) < 0 ? -1 : 1));
float ve = sye / Height*(1.0f + tr * ((icos - isin) <= 0 ? -1 : 1));
DirectDrawの場合、大きな画像から小さな画像を切り出して張り付ける際の速度は、
元々小さな画像を張り付ける速度よりも目立って遅く、
恐らく元の大きな画像を張り付ける速度と同じであった。
それゆえ、画像は読み込んだ後、サーフェス毎に小分けして持った方が良かった。
しかし、Direct3Dの場合は、切り出し元の画像サイズによる速度の変化は特に見られない。
その一方で、DrawPrimitiveにより同時に描画できるテクスチャは1枚のみである。
そしてDrawPrimitiveの処理は遅く、呼出回数はなるべく減らした方が良いとされる。
そのため、DirectDrawの場合とは逆に、画像はなるべくまとめて持った方が有利となる。
ポリゴンの描画の際、DrawPrimitiveを用いる説明が多いが、こちらは板を描く度に、
頂点情報をロックして編集としなければならない分、遅いらしい。
代わりにDrawPrimitiveUpを使う。
開発環境とは別の環境で動作確認して見た所、「d3dx9d_43.dllが見つからないため、コードの実行を続行できません」
というエラーが発生して開けない。
「d3dx9_43.dll」の時の対処は検索で出て来ても、9の後ろにdが付いてるこちらの場合の対策は見つからず、同じ対処では解決しない。
結局、プロジェクトのリンク設定の入力で、d3dx9.libでなくd3dx9d.libを使ってたのが原因のようで、d3dx9.libに置き換えた所解決。
d3dx9d.libはデバッグ用か何からしい?
ちなみに「d3dx9_43.dllが見つかりません」の場合は、DirectX エンドユーザー ランタイムをインストールしてやれば良い。
ただ、わざわざ追加インストールを行う必要があるというのは、その分プレイ人口が減りかねないわけで、
これからやる人にはやはり最新のDirectX12を使う事がお勧めかもしれない。
DirectDrawの場合、バックサーフェスの内容を同じバックサーフェスに転送することによって、
ズームやモザイク、画面割れ効果を簡単に表現できたが、Direct3Dの場合、
バックサーフェスの内容を他のサーフェスに張り付ける手段は無いらしい。
そこで、空のテクスチャを作成し、そのサーフェスに対して描画処理を行い、
そのテクスチャを張り付けた板ポリゴンなどをバックサーフェスへ描画する、
という形で、表示されている内容の編集を行う。
ただし、テクスチャを張り付けた板パネルを同じテクスチャのサーフェスに描画しようとすると、
画像に異常が起こる場合があるため、もう一枚空のテクスチャを作り、一旦そこにコピーして、
それを元の空テクスチャに描画する必要がある。
結局、「空テクスチャ1→空テクスチャ2→空テクスチャ1→バックサーフェス→表示」という流れになる。
なお、UpdateSurfaceというサーフェス間転送の関数もあるが、どうもこれは、
転送元はD3DPOOL_SYSTEMMEM、転送先はD3DPOOL_DEFAULTで作成されたサーフェス同士でしか機能しない様子。
バックバッファに対してはGDIも使えるが、テクスチャのサーフェスに対してはGetDCが失敗してしまう。
先の空のテクスチャに描画する方式の場合の大きな問題である。
マニュアルを見ると、GetDCはミップマップとなっているサーフェスに対しては使えないらしい。
GDIが使えずとも、DrawPrimitive系は線や点の描画もできるし、円も線やポリゴンの組み合わせで何とかなるし、
文字はDrawTextで描画できるから然程問題は無さそうではあるが、
PolyPolygonにあたる描画を行いたい場合には厄介な問題となると思う。
D3DPOOL_SYSTEMMEMで定義したサーフェスとUpdateSurfaceを使うことでなんとかなるかもしれないが、
通常のメモリの中に定義したサーフェスを中核部に使用するのでは、折角DirectXを使う意義が半減してしまう気がする。
異なる環境の一つで動作させてみた所、DrawTextによって描画された文字が常に点滅する状態となってしまった。
結局の所、CreateDeviceでD3DCREATE_SOFTWARE_VERTEXPROCESSINGの代わりにD3DCREATE_HARDWARE_VERTEXPROCESSINGを用いたら解決した。
勿論、D3DCREATE_HARDWARE_VERTEXPROCESSINGが利用できない場合には、
元のD3DCREATE_SOFTWARE_VERTEXPROCESSINGを使うようにしておく。
なお、DrawTextにはSpriteを指定する箇所があるが、ここにスプライトを指定する場合、
DrawTextをSprite->Begin(0)とSprite->End()の間で行う必要がある。
文字を連続して描画する場合には、一組のSprite->Begin(0)とSprite->End()の間にまとめて記述することで、
若干の高速化を確認。
他、一部で文字が化ける不具合にも悩まされたが、こちらは元々存在したポインタ関連のミスだった。
環境の違いにより発覚したわけである。
2024/4/6